diff --git a/build.sbt b/build.sbt index cc0f229..32147fe 100644 --- a/build.sbt +++ b/build.sbt @@ -120,6 +120,15 @@ lazy val vfs = project.in(file("vfs")). "commons-io" % "commons-io" % "2.4" % Test) ).dependsOn(core % "test->test;compile->compile") +lazy val gridfs = project.in(file("gridfs")). + settings(Common.settings: _*).settings( + name := "benji-gridfs", + libraryDependencies ++= Seq( + Dependencies.slf4jApi, + "org.reactivemongo" %% "reactivemongo" % "0.17.0" + ) + ).dependsOn(core % "test->test;compile->compile") + lazy val play = project.in(file("play")). settings(Common.settings ++ Seq( name := "benji-play", @@ -178,7 +187,7 @@ lazy val benji = (project in file(".")). } ) ++ Publish.settings). dependsOn(s3, google, vfs, play). - aggregate(core, s3, google, vfs, play) + aggregate(core, s3, google, vfs, play, gridfs) publishTo in ThisBuild := Some { import Resolver.mavenStylePatterns diff --git a/gridfs/.gitignore b/gridfs/.gitignore new file mode 100644 index 0000000..7e003d7 --- /dev/null +++ b/gridfs/.gitignore @@ -0,0 +1,4 @@ +project/project +project/target +src/test/resources/local.conf +target diff --git a/gridfs/README.md b/gridfs/README.md new file mode 100644 index 0000000..e69de29 diff --git a/gridfs/src/main/resources/META-INF/services/com.zengularity.benji.spi.StorageScheme b/gridfs/src/main/resources/META-INF/services/com.zengularity.benji.spi.StorageScheme new file mode 100644 index 0000000..e712667 --- /dev/null +++ b/gridfs/src/main/resources/META-INF/services/com.zengularity.benji.spi.StorageScheme @@ -0,0 +1 @@ +com.zengularity.benji.gridfs.GridFSScheme \ No newline at end of file diff --git a/gridfs/src/main/scala/GridFSBucketRef.scala b/gridfs/src/main/scala/GridFSBucketRef.scala new file mode 100644 index 0000000..89fac94 --- /dev/null +++ b/gridfs/src/main/scala/GridFSBucketRef.scala @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018-2019 Zengularity SA (FaberNovel Technologies) + */ +package com.zengularity.benji.gridfs + +import scala.concurrent.{ ExecutionContext, Future } + +import com.zengularity.benji.{ BucketRef, BucketVersioning } +import com.zengularity.benji.exception.BucketAlreadyExistsException + +final class GridFSBucketRef private[gridfs] ( + storage: GridFSStorage, + val name: String) extends BucketRef with BucketVersioning { + + def exists(implicit ec: ExecutionContext): Future[Boolean] = { + val filesAndChunksStats = for { + gridfs <- storage.transport.gridfs(name) + filesStats <- gridfs.files.stats + chunksStats <- gridfs.chunks.stats + } yield (filesStats, chunksStats) + + filesAndChunksStats.transform { + case Success(_) => Success(true) + case Failure(_) => Success(false) + } + } + + def create(failsIfExists: Boolean = false)(implicit ec: ExecutionContext): Future[Unit] = { + val isCreated = for { + gridfs <- storage.transport.gridfs(name) + isCreated <- gridfs.ensureIndex() + } yield isCreated + + isCreated.transform { + case Success(false) if failsIfExists => Failure[Unit](BucketAlreadyExistsException("toto")) + case Success(_) => Success({}) + case failed => failed + } + } +} \ No newline at end of file diff --git a/gridfs/src/main/scala/GridFSFactory.scala b/gridfs/src/main/scala/GridFSFactory.scala new file mode 100644 index 0000000..2a56728 --- /dev/null +++ b/gridfs/src/main/scala/GridFSFactory.scala @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018-2019 Zengularity SA (FaberNovel Technologies) + */ + +package com.zengularity.benji.gridfs + +import java.net.URI + +import play.api.libs.ws.ahc.StandaloneAhcWSClient + +import com.zengularity.benji.ObjectStorage + +import com.zengularity.benji.spi.{ Injector, StorageFactory, StorageScheme } + +/** + * This factory is using `javax.inject` + * to resolve `play.api.libs.ws.ahc.StandaloneAhcWSClient`. + */ +final class GridFSFactory extends StorageFactory { + @SuppressWarnings(Array("org.wartremover.warts.TryPartial")) + def apply(injector: Injector, uri: URI): ObjectStorage = { + @inline implicit def ws: StandaloneAhcWSClient = + injector.instanceOf(classOf[StandaloneAhcWSClient]) + + GridFS[URI](uri).get + } +} + +/** Storage scheme for GridFS */ +final class GridFSScheme extends StorageScheme { + val scheme = "gridfs" + + @inline + def factoryClass: Class[_ <: StorageFactory] = classOf[GridFSFactory] +} diff --git a/gridfs/src/main/scala/GridFSTransport.scala b/gridfs/src/main/scala/GridFSTransport.scala new file mode 100644 index 0000000..f4a9639 --- /dev/null +++ b/gridfs/src/main/scala/GridFSTransport.scala @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018-2019 Zengularity SA (FaberNovel Technologies) + */ +package com.zengularity.benji.gridfs + +import scala.concurrent.{ ExecutionContext, Future } +import scala.util.{ Failure, Try } + +import reactivemongo.api.gridfs.GridFS +import reactivemongo.api.{ BSONSerializationPack, MongoConnection } + +import com.zengularity.benji.exception.BenjiUnknownError + +final class GridFSTransport(driver: reactivemongo.api.MongoDriver, connection: MongoConnection, mongoUri: MongoConnection.ParsedURI) { + def close(): Unit = { + driver.close() + } + + def gridfs(prefix: String)(implicit ec: ExecutionContext): Future[GridFS[BSONSerializationPack.type]] = { + mongoUri.db match { + case Some(name) => + connection.database(name).map(db => GridFS[BSONSerializationPack.type](db, prefix)) + + case None => + Future.failed(new BenjiUnknownError(s"Fails to get the db from $mongoUri")) + } + } +} + +object GridFSTransport { + def apply(uri: String): Try[GridFSTransport] = { + val driver = new reactivemongo.api.MongoDriver + + val res = for { + mongoUri <- MongoConnection.parseURI(uri) + con <- driver.connection(mongoUri, strictUri = true) + } yield new GridFSTransport(driver, con, mongoUri) + + res.recoverWith { + case error => Failure[GridFSTransport](new BenjiUnknownError(s"Fails to create the connection to $uri", Some(error))) + } + } +} + diff --git a/gridfs/src/main/scala/GridsFSStorage.scala b/gridfs/src/main/scala/GridsFSStorage.scala new file mode 100644 index 0000000..6822f0f --- /dev/null +++ b/gridfs/src/main/scala/GridsFSStorage.scala @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2019 Zengularity SA (FaberNovel Technologies) + */ + +package com.zengularity.benji.gridfs + +import com.zengularity.benji.{ Bucket, ObjectStorage } + +class GridFSStorage(val transport: GridFSTransport, val requestTimeout: Option[Long]) extends ObjectStorage { self => + + def withRequestTimeout(timeout: Long) = + new GridFSStorage(transport, Some(timeout)) + + def bucket(name: String) = new GridFSBucketRef(this, name) + + object buckets extends self.BucketsRequest { + def apply()(implicit m: Materializer): Source[Bucket, NotUsed] = { + implicit def ec: ExecutionContext = m.executionContext + val gridfs = transport.gridfsdb + } + } +} + +object GridFSStorage { + /** + * Returns a client for GridsFS Object Storage. + * + * @param transport the GridFS transport + * @param requestTimeout the optional timeout for the prepared requests (none by default) + */ + def apply(transport: GridFSTransport, requestTimeout: Option[Long] = None): GridFSStorage = new GridFSStorage(transport, requestTimeout) +} \ No newline at end of file diff --git a/gridfs/src/main/scala/package.scala b/gridfs/src/main/scala/package.scala new file mode 100644 index 0000000..b2e70c4 --- /dev/null +++ b/gridfs/src/main/scala/package.scala @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2018-2019 Zengularity SA (FaberNovel Technologies) + */ + +package com.zengularity.benji + +/** + * Requires to add the appropriate dependencies in your build + * (see documentation). + */ +package object gridfs {}