mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Store files in different binary stores
This commit is contained in:
@ -16,6 +16,7 @@ import docspell.common.Pools
|
|||||||
import docspell.joex.routes._
|
import docspell.joex.routes._
|
||||||
import docspell.pubsub.naive.NaivePubSub
|
import docspell.pubsub.naive.NaivePubSub
|
||||||
import docspell.store.Store
|
import docspell.store.Store
|
||||||
|
import docspell.store.file.FileRepositoryConfig
|
||||||
import docspell.store.records.RInternalSetting
|
import docspell.store.records.RInternalSetting
|
||||||
|
|
||||||
import org.http4s.HttpApp
|
import org.http4s.HttpApp
|
||||||
@ -41,7 +42,7 @@ object JoexServer {
|
|||||||
|
|
||||||
store <- Store.create[F](
|
store <- Store.create[F](
|
||||||
cfg.jdbc,
|
cfg.jdbc,
|
||||||
cfg.files.chunkSize,
|
FileRepositoryConfig.Database(cfg.files.chunkSize),
|
||||||
pools.connectEC
|
pools.connectEC
|
||||||
)
|
)
|
||||||
settings <- Resource.eval(store.transact(RInternalSetting.create))
|
settings <- Resource.eval(store.transact(RInternalSetting.create))
|
||||||
|
@ -18,6 +18,7 @@ import docspell.restserver.http4s.InternalHeader
|
|||||||
import docspell.restserver.ws.OutputEvent.KeepAlive
|
import docspell.restserver.ws.OutputEvent.KeepAlive
|
||||||
import docspell.restserver.ws.OutputEvent
|
import docspell.restserver.ws.OutputEvent
|
||||||
import docspell.store.Store
|
import docspell.store.Store
|
||||||
|
import docspell.store.file.FileRepositoryConfig
|
||||||
import docspell.store.records.RInternalSetting
|
import docspell.store.records.RInternalSetting
|
||||||
import org.http4s._
|
import org.http4s._
|
||||||
import org.http4s.blaze.client.BlazeClientBuilder
|
import org.http4s.blaze.client.BlazeClientBuilder
|
||||||
@ -73,7 +74,7 @@ object RestServer {
|
|||||||
httpClient <- BlazeClientBuilder[F].resource
|
httpClient <- BlazeClientBuilder[F].resource
|
||||||
store <- Store.create[F](
|
store <- Store.create[F](
|
||||||
cfg.backend.jdbc,
|
cfg.backend.jdbc,
|
||||||
cfg.backend.files.chunkSize,
|
FileRepositoryConfig.Database(cfg.backend.files.chunkSize),
|
||||||
pools.connectEC
|
pools.connectEC
|
||||||
)
|
)
|
||||||
setting <- Resource.eval(store.transact(RInternalSetting.create))
|
setting <- Resource.eval(store.transact(RInternalSetting.create))
|
||||||
|
@ -12,7 +12,7 @@ import cats.effect._
|
|||||||
import cats.~>
|
import cats.~>
|
||||||
import fs2._
|
import fs2._
|
||||||
|
|
||||||
import docspell.store.file.FileRepository
|
import docspell.store.file.{FileRepository, FileRepositoryConfig}
|
||||||
import docspell.store.impl.StoreImpl
|
import docspell.store.impl.StoreImpl
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
@ -35,7 +35,7 @@ object Store {
|
|||||||
|
|
||||||
def create[F[_]: Async](
|
def create[F[_]: Async](
|
||||||
jdbc: JdbcConfig,
|
jdbc: JdbcConfig,
|
||||||
chunkSize: Int,
|
fileRepoConfig: FileRepositoryConfig,
|
||||||
connectEC: ExecutionContext
|
connectEC: ExecutionContext
|
||||||
): Resource[F, Store[F]] = {
|
): Resource[F, Store[F]] = {
|
||||||
val acquire = Sync[F].delay(new HikariDataSource())
|
val acquire = Sync[F].delay(new HikariDataSource())
|
||||||
@ -50,7 +50,7 @@ object Store {
|
|||||||
ds.setDriverClassName(jdbc.driverClass)
|
ds.setDriverClassName(jdbc.driverClass)
|
||||||
}
|
}
|
||||||
xa = HikariTransactor(ds, connectEC)
|
xa = HikariTransactor(ds, connectEC)
|
||||||
fr = FileRepository.genericJDBC(xa, ds, chunkSize)
|
fr = FileRepository.apply(xa, ds, fileRepoConfig)
|
||||||
st = new StoreImpl[F](fr, jdbc, xa)
|
st = new StoreImpl[F](fr, jdbc, xa)
|
||||||
_ <- Resource.eval(st.migrate)
|
_ <- Resource.eval(st.migrate)
|
||||||
} yield st
|
} yield st
|
||||||
|
@ -6,12 +6,19 @@
|
|||||||
|
|
||||||
package docspell.store.file
|
package docspell.store.file
|
||||||
|
|
||||||
import docspell.common
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
import cats.effect._
|
||||||
|
import fs2.io.file.Path
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.files.TikaMimetype
|
import docspell.files.TikaMimetype
|
||||||
import docspell.logging.Logger
|
import docspell.logging.Logger
|
||||||
|
|
||||||
import binny._
|
import binny._
|
||||||
|
import binny.fs.{FsBinaryStore, FsStoreConfig, PathMapping}
|
||||||
|
import binny.jdbc.{GenericJdbcStore, JdbcStoreConfig}
|
||||||
|
import binny.minio.{MinioBinaryStore, MinioConfig, S3KeyMapping}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
private[store] object BinnyUtils {
|
private[store] object BinnyUtils {
|
||||||
@ -26,7 +33,7 @@ private[store] object BinnyUtils {
|
|||||||
coll <- Ident.fromString(cId)
|
coll <- Ident.fromString(cId)
|
||||||
cat <- FileCategory.fromString(catId)
|
cat <- FileCategory.fromString(catId)
|
||||||
file <- Ident.fromString(fId)
|
file <- Ident.fromString(fId)
|
||||||
} yield common.FileKey(coll, cat, file)
|
} yield FileKey(coll, cat, file)
|
||||||
case _ =>
|
case _ =>
|
||||||
Left(s"Invalid format for file-key: $bid")
|
Left(s"Invalid format for file-key: $bid")
|
||||||
}
|
}
|
||||||
@ -57,4 +64,55 @@ private[store] object BinnyUtils {
|
|||||||
.asString
|
.asString
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val pathMapping: PathMapping = {
|
||||||
|
import binny.fs.PathMapping.syntax._
|
||||||
|
|
||||||
|
def toPath(base: Path, binaryId: BinaryId): Path = {
|
||||||
|
val fkey = unsafeBinaryIdToFileKey(binaryId)
|
||||||
|
base / fkey.collective.id / fkey.category.id.id / fkey.id.id / "file"
|
||||||
|
}
|
||||||
|
|
||||||
|
def toId(file: Path): Option[BinaryId] =
|
||||||
|
for {
|
||||||
|
id <- file.parent
|
||||||
|
cat <- id.parent
|
||||||
|
fcat <- FileCategory.fromString(cat.asId.id).toOption
|
||||||
|
coll <- cat.parent
|
||||||
|
fkey = FileKey(Ident.unsafe(coll.asId.id), fcat, Ident.unsafe(id.asId.id))
|
||||||
|
} yield fileKeyToBinaryId(fkey)
|
||||||
|
|
||||||
|
PathMapping(toPath)(toId)
|
||||||
|
}
|
||||||
|
|
||||||
|
def binaryStore[F[_]: Async](
|
||||||
|
cfg: FileRepositoryConfig,
|
||||||
|
attrStore: AttributeStore[F],
|
||||||
|
ds: DataSource,
|
||||||
|
logger: Logger[F]
|
||||||
|
): BinaryStore[F] =
|
||||||
|
cfg match {
|
||||||
|
case FileRepositoryConfig.Database(chunkSize) =>
|
||||||
|
val jdbcConfig =
|
||||||
|
JdbcStoreConfig("filechunk", chunkSize, BinnyUtils.TikaContentTypeDetect)
|
||||||
|
GenericJdbcStore[F](ds, LoggerAdapter(logger), jdbcConfig, attrStore)
|
||||||
|
|
||||||
|
case FileRepositoryConfig.S3(endpoint, accessKey, secretKey, bucket, chunkSize) =>
|
||||||
|
val keyMapping = S3KeyMapping.constant(bucket)
|
||||||
|
val minioCfg = MinioConfig
|
||||||
|
.default(endpoint, accessKey, secretKey, keyMapping)
|
||||||
|
.copy(chunkSize = chunkSize, detect = BinnyUtils.TikaContentTypeDetect)
|
||||||
|
|
||||||
|
MinioBinaryStore[F](minioCfg, attrStore, LoggerAdapter(logger))
|
||||||
|
|
||||||
|
case FileRepositoryConfig.Directory(path, chunkSize) =>
|
||||||
|
val fsConfig = FsStoreConfig(
|
||||||
|
path,
|
||||||
|
BinnyUtils.TikaContentTypeDetect,
|
||||||
|
FsStoreConfig.OverwriteMode.Fail,
|
||||||
|
BinnyUtils.pathMapping,
|
||||||
|
chunkSize
|
||||||
|
)
|
||||||
|
FsBinaryStore[F](fsConfig, LoggerAdapter(logger), attrStore)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,7 @@ import fs2._
|
|||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
|
||||||
import binny.BinaryId
|
import binny.{BinaryId, BinaryStore}
|
||||||
import binny.jdbc.{GenericJdbcStore, JdbcStoreConfig}
|
|
||||||
import doobie.Transactor
|
import doobie.Transactor
|
||||||
|
|
||||||
trait FileRepository[F[_]] {
|
trait FileRepository[F[_]] {
|
||||||
@ -33,16 +32,15 @@ trait FileRepository[F[_]] {
|
|||||||
|
|
||||||
object FileRepository {
|
object FileRepository {
|
||||||
|
|
||||||
def genericJDBC[F[_]: Sync](
|
def apply[F[_]: Async](
|
||||||
xa: Transactor[F],
|
xa: Transactor[F],
|
||||||
ds: DataSource,
|
ds: DataSource,
|
||||||
chunkSize: Int
|
cfg: FileRepositoryConfig
|
||||||
): FileRepository[F] = {
|
): FileRepository[F] = {
|
||||||
val attrStore = new AttributeStore[F](xa)
|
val attrStore = new AttributeStore[F](xa)
|
||||||
val cfg = JdbcStoreConfig("filechunk", chunkSize, BinnyUtils.TikaContentTypeDetect)
|
|
||||||
val log = docspell.logging.getLogger[F]
|
val log = docspell.logging.getLogger[F]
|
||||||
val binStore = GenericJdbcStore[F](ds, BinnyUtils.LoggerAdapter(log), cfg, attrStore)
|
|
||||||
val keyFun: FileKey => BinaryId = BinnyUtils.fileKeyToBinaryId
|
val keyFun: FileKey => BinaryId = BinnyUtils.fileKeyToBinaryId
|
||||||
|
val binStore: BinaryStore[F] = BinnyUtils.binaryStore(cfg, attrStore, ds, log)
|
||||||
|
|
||||||
new FileRepositoryImpl[F](binStore, attrStore, keyFun)
|
new FileRepositoryImpl[F](binStore, attrStore, keyFun)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package docspell.store.file
|
||||||
|
|
||||||
|
import fs2.io.file.Path
|
||||||
|
|
||||||
|
sealed trait FileRepositoryConfig {}
|
||||||
|
|
||||||
|
object FileRepositoryConfig {
|
||||||
|
|
||||||
|
final case class Database(chunkSize: Int) extends FileRepositoryConfig
|
||||||
|
|
||||||
|
final case class S3(
|
||||||
|
endpoint: String,
|
||||||
|
accessKey: String,
|
||||||
|
secretKey: String,
|
||||||
|
bucketName: String,
|
||||||
|
chunkSize: Int
|
||||||
|
) extends FileRepositoryConfig
|
||||||
|
|
||||||
|
final case class Directory(path: Path, chunkSize: Int) extends FileRepositoryConfig
|
||||||
|
|
||||||
|
}
|
@ -11,7 +11,7 @@ import javax.sql.DataSource
|
|||||||
import cats.effect._
|
import cats.effect._
|
||||||
|
|
||||||
import docspell.common.LenientUri
|
import docspell.common.LenientUri
|
||||||
import docspell.store.file.FileRepository
|
import docspell.store.file.{FileRepository, FileRepositoryConfig}
|
||||||
import docspell.store.impl.StoreImpl
|
import docspell.store.impl.StoreImpl
|
||||||
import docspell.store.migrate.FlywayMigrate
|
import docspell.store.migrate.FlywayMigrate
|
||||||
|
|
||||||
@ -67,7 +67,8 @@ object StoreFixture {
|
|||||||
for {
|
for {
|
||||||
ds <- dataSource(jdbc)
|
ds <- dataSource(jdbc)
|
||||||
xa <- makeXA(ds)
|
xa <- makeXA(ds)
|
||||||
fr = FileRepository.genericJDBC[IO](xa, ds, 64 * 1024)
|
cfg = FileRepositoryConfig.Database(64 * 1024)
|
||||||
|
fr = FileRepository[IO](xa, ds, cfg)
|
||||||
store = new StoreImpl[IO](fr, jdbc, xa)
|
store = new StoreImpl[IO](fr, jdbc, xa)
|
||||||
_ <- Resource.eval(store.migrate)
|
_ <- Resource.eval(store.migrate)
|
||||||
} yield store
|
} yield store
|
||||||
|
@ -307,7 +307,8 @@ object Dependencies {
|
|||||||
val binny = Seq(
|
val binny = Seq(
|
||||||
"com.github.eikek" %% "binny-core" % BinnyVersion,
|
"com.github.eikek" %% "binny-core" % BinnyVersion,
|
||||||
"com.github.eikek" %% "binny-jdbc" % BinnyVersion,
|
"com.github.eikek" %% "binny-jdbc" % BinnyVersion,
|
||||||
"com.github.eikek" %% "binny-minio" % BinnyVersion
|
"com.github.eikek" %% "binny-minio" % BinnyVersion,
|
||||||
|
"com.github.eikek" %% "binny-fs" % BinnyVersion
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://github.com/flyway/flyway
|
// https://github.com/flyway/flyway
|
||||||
|
Reference in New Issue
Block a user