mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Refactoring for migrating to binny library
This commit is contained in:
@ -0,0 +1,29 @@
|
||||
ALTER TABLE "filemeta" DROP COLUMN IF EXISTS "chunksize";
|
||||
ALTER TABLE "filemeta" DROP COLUMN IF EXISTS "chunks";
|
||||
|
||||
ALTER TABLE "filemeta"
|
||||
RENAME COLUMN "id" TO "file_id";
|
||||
|
||||
ALTER TABLE "filechunk"
|
||||
RENAME COLUMN "fileid" TO "file_id";
|
||||
|
||||
ALTER TABLE "filechunk"
|
||||
RENAME COLUMN "chunknr" TO "chunk_nr";
|
||||
|
||||
ALTER TABLE "filechunk"
|
||||
RENAME COLUMN "chunklength" TO "chunk_len";
|
||||
|
||||
ALTER TABLE "filechunk"
|
||||
RENAME COLUMN "chunkdata" TO "chunk_data";
|
||||
|
||||
-- change timestamp format, bitpeace used a string
|
||||
ALTER TABLE "filemeta"
|
||||
ADD COLUMN "created" timestamp;
|
||||
|
||||
UPDATE "filemeta" SET "created" = TO_TIMESTAMP("timestamp", 'YYYY-MM-DD"T"HH24:MI:SS.MS');
|
||||
|
||||
ALTER TABLE "filemeta"
|
||||
ALTER COLUMN "created" SET NOT NULL;
|
||||
|
||||
ALTER TABLE "filemeta"
|
||||
DROP COLUMN "timestamp";
|
@ -0,0 +1,29 @@
|
||||
ALTER TABLE `filemeta` DROP COLUMN IF EXISTS `chunksize`;
|
||||
ALTER TABLE `filemeta` DROP COLUMN IF EXISTS `chunks`;
|
||||
|
||||
ALTER TABLE `filemeta`
|
||||
RENAME COLUMN `id` TO `file_id`;
|
||||
|
||||
ALTER TABLE `filechunk`
|
||||
RENAME COLUMN `fileid` TO `file_id`;
|
||||
|
||||
ALTER TABLE `filechunk`
|
||||
RENAME COLUMN `chunknr` TO `chunk_nr`;
|
||||
|
||||
ALTER TABLE `filechunk`
|
||||
RENAME COLUMN `chunklength` TO `chunk_len`;
|
||||
|
||||
ALTER TABLE `filechunk`
|
||||
RENAME COLUMN `chunkdata` TO `chunk_data`;
|
||||
|
||||
-- change timestamp format, bitpeace used a string
|
||||
ALTER TABLE `filemeta`
|
||||
ADD COLUMN `created` timestamp;
|
||||
|
||||
UPDATE `filemeta` SET `created` = STR_TO_DATE(`timestamp`, '%Y-%m-%dT%H:%i:%s.%fZ');
|
||||
|
||||
ALTER TABLE `filemeta`
|
||||
MODIFY `created` timestamp NOT NULL;
|
||||
|
||||
ALTER TABLE `filemeta`
|
||||
DROP COLUMN `timestamp`;
|
@ -0,0 +1,29 @@
|
||||
ALTER TABLE "filemeta" DROP COLUMN IF EXISTS "chunksize";
|
||||
ALTER TABLE "filemeta" DROP COLUMN IF EXISTS "chunks";
|
||||
|
||||
ALTER TABLE "filemeta"
|
||||
RENAME COLUMN "id" TO "file_id";
|
||||
|
||||
ALTER TABLE "filechunk"
|
||||
RENAME COLUMN "fileid" TO "file_id";
|
||||
|
||||
ALTER TABLE "filechunk"
|
||||
RENAME COLUMN "chunknr" TO "chunk_nr";
|
||||
|
||||
ALTER TABLE "filechunk"
|
||||
RENAME COLUMN "chunklength" TO "chunk_len";
|
||||
|
||||
ALTER TABLE "filechunk"
|
||||
RENAME COLUMN "chunkdata" TO "chunk_data";
|
||||
|
||||
-- change timestamp format, bitpeace used a string
|
||||
ALTER TABLE "filemeta"
|
||||
ADD COLUMN "created" timestamp;
|
||||
|
||||
UPDATE "filemeta" SET "created" = CAST("timestamp" as timestamp);
|
||||
|
||||
ALTER TABLE "filemeta"
|
||||
ALTER COLUMN "created" SET NOT NULL;
|
||||
|
||||
ALTER TABLE "filemeta"
|
||||
DROP COLUMN "timestamp";
|
@ -11,9 +11,10 @@ import scala.concurrent.ExecutionContext
|
||||
import cats.effect._
|
||||
import fs2._
|
||||
|
||||
import docspell.store.file.FileStore
|
||||
import docspell.store.impl.StoreImpl
|
||||
|
||||
import bitpeace.Bitpeace
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import doobie._
|
||||
import doobie.hikari.HikariTransactor
|
||||
|
||||
@ -23,7 +24,7 @@ trait Store[F[_]] {
|
||||
|
||||
def transact[A](prg: Stream[ConnectionIO, A]): Stream[F, A]
|
||||
|
||||
def bitpeace: Bitpeace[F]
|
||||
def fileStore: FileStore[F]
|
||||
|
||||
def add(insert: ConnectionIO[Int], exists: ConnectionIO[Boolean]): F[AddResult]
|
||||
}
|
||||
@ -32,20 +33,23 @@ object Store {
|
||||
|
||||
def create[F[_]: Async](
|
||||
jdbc: JdbcConfig,
|
||||
chunkSize: Int,
|
||||
connectEC: ExecutionContext
|
||||
): Resource[F, Store[F]] = {
|
||||
|
||||
val hxa = HikariTransactor.newHikariTransactor[F](
|
||||
jdbc.driverClass,
|
||||
jdbc.url.asString,
|
||||
jdbc.user,
|
||||
jdbc.password,
|
||||
connectEC
|
||||
)
|
||||
val acquire = Sync[F].delay(new HikariDataSource())
|
||||
val free: HikariDataSource => F[Unit] = ds => Sync[F].delay(ds.close())
|
||||
|
||||
for {
|
||||
xa <- hxa
|
||||
st = new StoreImpl[F](jdbc, xa)
|
||||
ds <- Resource.make(acquire)(free)
|
||||
_ = Resource.pure {
|
||||
ds.setJdbcUrl(jdbc.url.asString)
|
||||
ds.setUsername(jdbc.user)
|
||||
ds.setPassword(jdbc.password)
|
||||
ds.setDriverClassName(jdbc.driverClass)
|
||||
}
|
||||
xa = HikariTransactor(ds, connectEC)
|
||||
fs = FileStore[F](xa, ds, chunkSize)
|
||||
st = new StoreImpl[F](fs, jdbc, xa)
|
||||
_ <- Resource.eval(st.migrate)
|
||||
} yield st
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.file
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.records.RFileMeta
|
||||
|
||||
import binny._
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
final private[file] class AttributeStore[F[_]: Sync](xa: Transactor[F])
|
||||
extends BinaryAttributeStore[F] {
|
||||
def saveAttr(id: BinaryId, attrs: F[BinaryAttributes]): F[Unit] =
|
||||
for {
|
||||
now <- Timestamp.current[F]
|
||||
a <- attrs
|
||||
fm = RFileMeta(
|
||||
Ident.unsafe(id.id),
|
||||
now,
|
||||
MimeType.parse(a.contentType.contentType).getOrElse(MimeType.octetStream),
|
||||
ByteSize(a.length),
|
||||
a.sha256
|
||||
)
|
||||
_ <- RFileMeta.insert(fm).transact(xa)
|
||||
} yield ()
|
||||
|
||||
def deleteAttr(id: BinaryId): F[Boolean] =
|
||||
RFileMeta.delete(Ident.unsafe(id.id)).transact(xa).map(_ > 0)
|
||||
|
||||
def findAttr(id: BinaryId): OptionT[F, BinaryAttributes] =
|
||||
findMeta(id).map(fm =>
|
||||
BinaryAttributes(
|
||||
fm.checksum,
|
||||
SimpleContentType(fm.mimetype.asString),
|
||||
fm.length.bytes
|
||||
)
|
||||
)
|
||||
|
||||
def findMeta(id: BinaryId): OptionT[F, RFileMeta] =
|
||||
OptionT(RFileMeta.findById(Ident.unsafe(id.id)).transact(xa))
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.file
|
||||
|
||||
import javax.sql.DataSource
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.effect._
|
||||
import fs2.{Pipe, Stream}
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.records.RFileMeta
|
||||
|
||||
import binny._
|
||||
import binny.jdbc.{GenericJdbcStore, JdbcStoreConfig}
|
||||
import binny.tika.TikaContentTypeDetect
|
||||
import doobie._
|
||||
|
||||
trait FileStore[F[_]] {
|
||||
|
||||
def find(id: Ident): OptionT[F, Stream[F, Byte]]
|
||||
|
||||
def getBytes(id: Ident): Stream[F, Byte]
|
||||
|
||||
def findMeta(id: Ident): OptionT[F, RFileMeta]
|
||||
|
||||
def delete(id: Ident): F[Unit]
|
||||
|
||||
def save(hint: MimeTypeHint): Pipe[F, Byte, Ident]
|
||||
}
|
||||
|
||||
object FileStore {
|
||||
private[this] val logger = org.log4s.getLogger
|
||||
|
||||
def apply[F[_]: Sync](
|
||||
xa: Transactor[F],
|
||||
ds: DataSource,
|
||||
chunkSize: Int
|
||||
): FileStore[F] = {
|
||||
val attrStore = new AttributeStore[F](xa)
|
||||
val cfg = JdbcStoreConfig("filechunk", chunkSize, TikaContentTypeDetect.default)
|
||||
val binStore = GenericJdbcStore[F](ds, Log4sLogger[F](logger), cfg, attrStore)
|
||||
new Impl[F](binStore, attrStore)
|
||||
}
|
||||
|
||||
final private class Impl[F[_]](bs: BinaryStore[F], attrStore: AttributeStore[F])
|
||||
extends FileStore[F] {
|
||||
def find(id: Ident): OptionT[F, Stream[F, Byte]] =
|
||||
bs.findBinary(BinaryId(id.id), ByteRange.All)
|
||||
|
||||
def getBytes(id: Ident): Stream[F, Byte] =
|
||||
Stream.eval(find(id).value).unNoneTerminate.flatMap(identity)
|
||||
|
||||
def findMeta(id: Ident): OptionT[F, RFileMeta] =
|
||||
attrStore.findMeta(BinaryId(id.id))
|
||||
|
||||
def delete(id: Ident): F[Unit] =
|
||||
bs.delete(BinaryId(id.id))
|
||||
|
||||
def save(hint: MimeTypeHint): Pipe[F, Byte, Ident] =
|
||||
bs.insert(Hint(hint.filename, hint.advertised))
|
||||
.andThen(_.map(bid => Ident.unsafe(bid.id)))
|
||||
}
|
||||
|
||||
private object Log4sLogger {
|
||||
|
||||
def apply[F[_]: Sync](log: org.log4s.Logger): binny.util.Logger[F] =
|
||||
new binny.util.Logger[F] {
|
||||
override def trace(msg: => String): F[Unit] =
|
||||
Sync[F].delay(log.trace(msg))
|
||||
|
||||
override def debug(msg: => String): F[Unit] =
|
||||
Sync[F].delay(log.debug(msg))
|
||||
|
||||
override def info(msg: => String): F[Unit] =
|
||||
Sync[F].delay(log.info(msg))
|
||||
|
||||
override def warn(msg: => String): F[Unit] =
|
||||
Sync[F].delay(log.warn(msg))
|
||||
|
||||
override def error(msg: => String): F[Unit] =
|
||||
Sync[F].delay(log.error(msg))
|
||||
|
||||
override def error(ex: Throwable)(msg: => String): F[Unit] =
|
||||
Sync[F].delay(log.error(ex)(msg))
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import doobie.util.log.Success
|
||||
import emil.doobie.EmilDoobieMeta
|
||||
import io.circe.Json
|
||||
import io.circe.{Decoder, Encoder}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
trait DoobieMeta extends EmilDoobieMeta {
|
||||
|
||||
@ -132,6 +133,15 @@ trait DoobieMeta extends EmilDoobieMeta {
|
||||
|
||||
implicit val metaKey: Meta[Key] =
|
||||
Meta[String].timap(Key.unsafeFromString)(_.asString)
|
||||
|
||||
implicit val metaMimeType: Meta[MimeType] =
|
||||
Meta[String].timap(MimeType.unsafe)(_.asString)
|
||||
|
||||
implicit val metaByteVectorHex: Meta[ByteVector] =
|
||||
Meta[String].timap(s => ByteVector.fromValidHex(s))(_.toHex)
|
||||
|
||||
implicit val metaByteSize: Meta[ByteSize] =
|
||||
Meta[Long].timap(ByteSize.apply)(_.bytes)
|
||||
}
|
||||
|
||||
object DoobieMeta extends DoobieMeta {
|
||||
|
@ -9,22 +9,18 @@ package docspell.store.impl
|
||||
import cats.effect.Async
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common.Ident
|
||||
import docspell.store.file.FileStore
|
||||
import docspell.store.migrate.FlywayMigrate
|
||||
import docspell.store.{AddResult, JdbcConfig, Store}
|
||||
|
||||
import bitpeace.{Bitpeace, BitpeaceConfig, TikaMimetypeDetect}
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
final class StoreImpl[F[_]: Async](jdbc: JdbcConfig, xa: Transactor[F]) extends Store[F] {
|
||||
val bitpeaceCfg =
|
||||
BitpeaceConfig(
|
||||
"filemeta",
|
||||
"filechunk",
|
||||
TikaMimetypeDetect,
|
||||
Ident.randomId[F].map(_.id)
|
||||
)
|
||||
final class StoreImpl[F[_]: Async](
|
||||
val fileStore: FileStore[F],
|
||||
jdbc: JdbcConfig,
|
||||
xa: Transactor[F]
|
||||
) extends Store[F] {
|
||||
|
||||
def migrate: F[Int] =
|
||||
FlywayMigrate.run[F](jdbc).map(_.migrationsExecuted)
|
||||
@ -35,9 +31,6 @@ final class StoreImpl[F[_]: Async](jdbc: JdbcConfig, xa: Transactor[F]) extends
|
||||
def transact[A](prg: fs2.Stream[doobie.ConnectionIO, A]): fs2.Stream[F, A] =
|
||||
prg.transact(xa)
|
||||
|
||||
def bitpeace: Bitpeace[F] =
|
||||
Bitpeace(bitpeaceCfg, xa)
|
||||
|
||||
def add(insert: ConnectionIO[Int], exists: ConnectionIO[Boolean]): F[AddResult] =
|
||||
for {
|
||||
save <- transact(insert).attempt
|
||||
|
@ -9,8 +9,6 @@ package docspell.store.queries
|
||||
import docspell.common._
|
||||
import docspell.store.records._
|
||||
|
||||
import bitpeace.FileMeta
|
||||
|
||||
case class ItemData(
|
||||
item: RItem,
|
||||
corrOrg: Option[ROrganization],
|
||||
@ -20,9 +18,9 @@ case class ItemData(
|
||||
inReplyTo: Option[IdRef],
|
||||
folder: Option[IdRef],
|
||||
tags: Vector[RTag],
|
||||
attachments: Vector[(RAttachment, FileMeta)],
|
||||
sources: Vector[(RAttachmentSource, FileMeta)],
|
||||
archives: Vector[(RAttachmentArchive, FileMeta)],
|
||||
attachments: Vector[(RAttachment, RFileMeta)],
|
||||
sources: Vector[(RAttachmentSource, RFileMeta)],
|
||||
archives: Vector[(RAttachmentArchive, RFileMeta)],
|
||||
customFields: Vector[ItemFieldValue]
|
||||
) {
|
||||
|
||||
|
@ -38,10 +38,10 @@ object QAttachment {
|
||||
|
||||
Stream
|
||||
.evalSeq(store.transact(findPreview))
|
||||
.map(_.fileId.id)
|
||||
.map(_.fileId)
|
||||
.evalTap(_ => store.transact(RAttachmentPreview.delete(attachId)))
|
||||
.flatMap(store.bitpeace.delete)
|
||||
.map(flag => if (flag) 1 else 0)
|
||||
.evalMap(store.fileStore.delete)
|
||||
.map(_ => 1)
|
||||
.compile
|
||||
.foldMonoid
|
||||
}
|
||||
@ -68,9 +68,8 @@ object QAttachment {
|
||||
f <-
|
||||
Stream
|
||||
.emits(files._1)
|
||||
.map(_.id)
|
||||
.flatMap(store.bitpeace.delete)
|
||||
.map(flag => if (flag) 1 else 0)
|
||||
.evalMap(store.fileStore.delete)
|
||||
.map(_ => 1)
|
||||
.compile
|
||||
.foldMonoid
|
||||
} yield n + k + f
|
||||
@ -91,9 +90,9 @@ object QAttachment {
|
||||
)
|
||||
f <-
|
||||
Stream
|
||||
.emits(ra.fileId.id +: (s.map(_.fileId.id).toSeq ++ p.map(_.fileId.id).toSeq))
|
||||
.flatMap(store.bitpeace.delete)
|
||||
.map(flag => if (flag) 1 else 0)
|
||||
.emits(ra.fileId +: (s.map(_.fileId).toSeq ++ p.map(_.fileId).toSeq))
|
||||
.evalMap(store.fileStore.delete)
|
||||
.map(_ => 1)
|
||||
.compile
|
||||
.foldMonoid
|
||||
} yield n + f
|
||||
@ -104,8 +103,8 @@ object QAttachment {
|
||||
n <- OptionT.liftF(store.transact(RAttachmentArchive.deleteAll(aa.fileId)))
|
||||
_ <- OptionT.liftF(
|
||||
Stream
|
||||
.emit(aa.fileId.id)
|
||||
.flatMap(store.bitpeace.delete)
|
||||
.emit(aa.fileId)
|
||||
.evalMap(store.fileStore.delete)
|
||||
.compile
|
||||
.drain
|
||||
)
|
||||
|
@ -99,16 +99,16 @@ object QCollective {
|
||||
inner join item i on a.itemid = i.itemid
|
||||
where i.cid = $coll)
|
||||
select a.fid,m.length from attachs a
|
||||
inner join filemeta m on m.id = a.fid
|
||||
inner join filemeta m on m.file_id = a.fid
|
||||
union distinct
|
||||
select a.file_id,m.length from attachment_source a
|
||||
inner join filemeta m on m.id = a.file_id where a.id in (select aid from attachs)
|
||||
inner join filemeta m on m.file_id = a.file_id where a.id in (select aid from attachs)
|
||||
union distinct
|
||||
select p.file_id,m.length from attachment_preview p
|
||||
inner join filemeta m on m.id = p.file_id where p.id in (select aid from attachs)
|
||||
inner join filemeta m on m.file_id = p.file_id where p.id in (select aid from attachs)
|
||||
union distinct
|
||||
select a.file_id,m.length from attachment_archive a
|
||||
inner join filemeta m on m.id = a.file_id where a.id in (select aid from attachs)
|
||||
inner join filemeta m on m.file_id = a.file_id where a.id in (select aid from attachs)
|
||||
) as t""".query[Option[Long]].unique
|
||||
|
||||
for {
|
||||
|
@ -496,7 +496,7 @@ object QItem {
|
||||
where(
|
||||
i.cid === collective &&
|
||||
i.state.in(ItemState.validStates) &&
|
||||
Condition.Or(fms.map(m => m.checksum === checksum)) &&?
|
||||
Condition.Or(fms.map(m => m.checksum ==== checksum)) &&?
|
||||
Nel
|
||||
.fromList(excludeFileMeta.toList)
|
||||
.map(excl => Condition.And(fms.map(m => m.id.isNull || m.id.notIn(excl))))
|
||||
|
@ -14,7 +14,6 @@ import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import bitpeace.FileMeta
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
@ -113,9 +112,7 @@ object RAttachment {
|
||||
def findById(attachId: Ident): ConnectionIO[Option[RAttachment]] =
|
||||
run(select(T.all), from(T), T.id === attachId).query[RAttachment].option
|
||||
|
||||
def findMeta(attachId: Ident): ConnectionIO[Option[FileMeta]] = {
|
||||
import bitpeace.sql._
|
||||
|
||||
def findMeta(attachId: Ident): ConnectionIO[Option[RFileMeta]] = {
|
||||
val m = RFileMeta.as("m")
|
||||
val a = RAttachment.as("a")
|
||||
Select(
|
||||
@ -123,7 +120,7 @@ object RAttachment {
|
||||
from(a)
|
||||
.innerJoin(m, a.fileId === m.id),
|
||||
a.id === attachId
|
||||
).build.query[FileMeta].option
|
||||
).build.query[RFileMeta].option
|
||||
}
|
||||
|
||||
def updateName(
|
||||
@ -206,9 +203,7 @@ object RAttachment {
|
||||
def findByItemAndCollectiveWithMeta(
|
||||
id: Ident,
|
||||
coll: Ident
|
||||
): ConnectionIO[Vector[(RAttachment, FileMeta)]] = {
|
||||
import bitpeace.sql._
|
||||
|
||||
): ConnectionIO[Vector[(RAttachment, RFileMeta)]] = {
|
||||
val a = RAttachment.as("a")
|
||||
val m = RFileMeta.as("m")
|
||||
val i = RItem.as("i")
|
||||
@ -218,12 +213,10 @@ object RAttachment {
|
||||
.innerJoin(m, a.fileId === m.id)
|
||||
.innerJoin(i, a.itemId === i.id),
|
||||
a.itemId === id && i.cid === coll
|
||||
).build.query[(RAttachment, FileMeta)].to[Vector]
|
||||
).build.query[(RAttachment, RFileMeta)].to[Vector]
|
||||
}
|
||||
|
||||
def findByItemWithMeta(id: Ident): ConnectionIO[Vector[(RAttachment, FileMeta)]] = {
|
||||
import bitpeace.sql._
|
||||
|
||||
def findByItemWithMeta(id: Ident): ConnectionIO[Vector[(RAttachment, RFileMeta)]] = {
|
||||
val a = RAttachment.as("a")
|
||||
val m = RFileMeta.as("m")
|
||||
Select(
|
||||
@ -231,7 +224,7 @@ object RAttachment {
|
||||
from(a)
|
||||
.innerJoin(m, a.fileId === m.id),
|
||||
a.itemId === id
|
||||
).orderBy(a.position.asc).build.query[(RAttachment, FileMeta)].to[Vector]
|
||||
).orderBy(a.position.asc).build.query[(RAttachment, RFileMeta)].to[Vector]
|
||||
}
|
||||
|
||||
/** Deletes the attachment and its related source and meta records.
|
||||
|
@ -13,7 +13,6 @@ import docspell.store.qb.DSL._
|
||||
import docspell.store.qb.TableDef
|
||||
import docspell.store.qb._
|
||||
|
||||
import bitpeace.FileMeta
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
@ -98,9 +97,7 @@ object RAttachmentArchive {
|
||||
|
||||
def findByItemWithMeta(
|
||||
id: Ident
|
||||
): ConnectionIO[Vector[(RAttachmentArchive, FileMeta)]] = {
|
||||
import bitpeace.sql._
|
||||
|
||||
): ConnectionIO[Vector[(RAttachmentArchive, RFileMeta)]] = {
|
||||
val a = RAttachmentArchive.as("a")
|
||||
val b = RAttachment.as("b")
|
||||
val m = RFileMeta.as("m")
|
||||
@ -110,7 +107,7 @@ object RAttachmentArchive {
|
||||
.innerJoin(m, a.fileId === m.id)
|
||||
.innerJoin(b, a.id === b.id),
|
||||
b.itemId === id
|
||||
).orderBy(b.position.asc).build.query[(RAttachmentArchive, FileMeta)].to[Vector]
|
||||
).orderBy(b.position.asc).build.query[(RAttachmentArchive, RFileMeta)].to[Vector]
|
||||
}
|
||||
|
||||
/** If the given attachment id has an associated archive, this returns the number of all
|
||||
|
@ -12,7 +12,6 @@ import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import bitpeace.FileMeta
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
@ -101,9 +100,7 @@ object RAttachmentPreview {
|
||||
|
||||
def findByItemWithMeta(
|
||||
id: Ident
|
||||
): ConnectionIO[Vector[(RAttachmentPreview, FileMeta)]] = {
|
||||
import bitpeace.sql._
|
||||
|
||||
): ConnectionIO[Vector[(RAttachmentPreview, RFileMeta)]] = {
|
||||
val a = RAttachmentPreview.as("a")
|
||||
val b = RAttachment.as("b")
|
||||
val m = RFileMeta.as("m")
|
||||
@ -114,6 +111,6 @@ object RAttachmentPreview {
|
||||
.innerJoin(m, a.fileId === m.id)
|
||||
.innerJoin(b, b.id === a.id),
|
||||
b.itemId === id
|
||||
).orderBy(b.position.asc).build.query[(RAttachmentPreview, FileMeta)].to[Vector]
|
||||
).orderBy(b.position.asc).build.query[(RAttachmentPreview, RFileMeta)].to[Vector]
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import bitpeace.FileMeta
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
@ -97,9 +96,7 @@ object RAttachmentSource {
|
||||
|
||||
def findByItemWithMeta(
|
||||
id: Ident
|
||||
): ConnectionIO[Vector[(RAttachmentSource, FileMeta)]] = {
|
||||
import bitpeace.sql._
|
||||
|
||||
): ConnectionIO[Vector[(RAttachmentSource, RFileMeta)]] = {
|
||||
val a = RAttachmentSource.as("a")
|
||||
val b = RAttachment.as("b")
|
||||
val m = RFileMeta.as("m")
|
||||
@ -110,7 +107,7 @@ object RAttachmentSource {
|
||||
.innerJoin(m, a.fileId === m.id)
|
||||
.innerJoin(b, b.id === a.id),
|
||||
b.itemId === id
|
||||
).orderBy(b.position.asc).build.query[(RAttachmentSource, FileMeta)].to[Vector]
|
||||
).orderBy(b.position.asc).build.query[(RAttachmentSource, RFileMeta)].to[Vector]
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,35 +6,37 @@
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
import docspell.store.syntax.MimeTypes._
|
||||
|
||||
import bitpeace.FileMeta
|
||||
import bitpeace.Mimetype
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
final case class RFileMeta(
|
||||
id: Ident,
|
||||
created: Timestamp,
|
||||
mimetype: MimeType,
|
||||
length: ByteSize,
|
||||
checksum: ByteVector
|
||||
)
|
||||
|
||||
object RFileMeta {
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "filemeta"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val timestamp = Column[Instant]("timestamp", this)
|
||||
val mimetype = Column[Mimetype]("mimetype", this)
|
||||
val length = Column[Long]("length", this)
|
||||
val checksum = Column[String]("checksum", this)
|
||||
val chunks = Column[Int]("chunks", this)
|
||||
val chunksize = Column[Int]("chunksize", this)
|
||||
val id = Column[Ident]("file_id", this)
|
||||
val timestamp = Column[Timestamp]("created", this)
|
||||
val mimetype = Column[MimeType]("mimetype", this)
|
||||
val length = Column[ByteSize]("length", this)
|
||||
val checksum = Column[ByteVector]("checksum", this)
|
||||
|
||||
val all = NonEmptyList
|
||||
.of[Column[_]](id, timestamp, mimetype, length, checksum, chunks, chunksize)
|
||||
.of[Column[_]](id, timestamp, mimetype, length, checksum)
|
||||
|
||||
}
|
||||
|
||||
@ -42,29 +44,25 @@ object RFileMeta {
|
||||
def as(alias: String): Table =
|
||||
Table(Some(alias))
|
||||
|
||||
def findById(fid: Ident): ConnectionIO[Option[FileMeta]] = {
|
||||
import bitpeace.sql._
|
||||
def insert(r: RFileMeta): ConnectionIO[Int] =
|
||||
DML.insert(T, T.all, fr"${r.id},${r.created},${r.mimetype},${r.length},${r.checksum}")
|
||||
|
||||
run(select(T.all), from(T), T.id === fid).query[FileMeta].option
|
||||
}
|
||||
|
||||
def findByIds(ids: List[Ident]): ConnectionIO[Vector[FileMeta]] = {
|
||||
import bitpeace.sql._
|
||||
def findById(fid: Ident): ConnectionIO[Option[RFileMeta]] =
|
||||
run(select(T.all), from(T), T.id === fid).query[RFileMeta].option
|
||||
|
||||
def findByIds(ids: List[Ident]): ConnectionIO[Vector[RFileMeta]] =
|
||||
NonEmptyList.fromList(ids) match {
|
||||
case Some(nel) =>
|
||||
run(select(T.all), from(T), T.id.in(nel)).query[FileMeta].to[Vector]
|
||||
run(select(T.all), from(T), T.id.in(nel)).query[RFileMeta].to[Vector]
|
||||
case None =>
|
||||
Vector.empty[FileMeta].pure[ConnectionIO]
|
||||
Vector.empty[RFileMeta].pure[ConnectionIO]
|
||||
}
|
||||
}
|
||||
|
||||
def findMime(fid: Ident): ConnectionIO[Option[MimeType]] = {
|
||||
import bitpeace.sql._
|
||||
|
||||
def findMime(fid: Ident): ConnectionIO[Option[MimeType]] =
|
||||
run(select(T.mimetype), from(T), T.id === fid)
|
||||
.query[Mimetype]
|
||||
.query[MimeType]
|
||||
.option
|
||||
.map(_.map(_.toLocal))
|
||||
}
|
||||
|
||||
def delete(id: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.id === id)
|
||||
}
|
||||
|
@ -8,16 +8,8 @@ package docspell.store.syntax
|
||||
|
||||
import docspell.common._
|
||||
|
||||
import bitpeace.Mimetype
|
||||
|
||||
object MimeTypes {
|
||||
|
||||
implicit final class BitpeaceMimeTypeOps(bmt: Mimetype) {
|
||||
|
||||
def toLocal: MimeType =
|
||||
MimeType(bmt.primary, bmt.sub, bmt.params)
|
||||
}
|
||||
|
||||
implicit final class EmilMimeTypeOps(emt: emil.MimeType) {
|
||||
def toLocal: MimeType =
|
||||
MimeType(emt.primary, emt.sub, emt.params)
|
||||
@ -26,8 +18,5 @@ object MimeTypes {
|
||||
implicit final class DocspellMimeTypeOps(mt: MimeType) {
|
||||
def toEmil: emil.MimeType =
|
||||
emil.MimeType(mt.primary, mt.sub, mt.params)
|
||||
|
||||
def toBitpeace: Mimetype =
|
||||
Mimetype(mt.primary, mt.sub, mt.params)
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,14 @@
|
||||
|
||||
package docspell.store
|
||||
|
||||
import javax.sql.DataSource
|
||||
|
||||
import cats.effect._
|
||||
|
||||
import docspell.common.LenientUri
|
||||
import docspell.store.file.FileStore
|
||||
import docspell.store.impl.StoreImpl
|
||||
import docspell.store.migrate.FlywayMigrate
|
||||
|
||||
import doobie._
|
||||
import munit._
|
||||
@ -20,18 +24,17 @@ trait StoreFixture extends CatsEffectFunFixtures { self: CatsEffectSuite =>
|
||||
val xa = ResourceFixture {
|
||||
val cfg = StoreFixture.memoryDB("test")
|
||||
for {
|
||||
xa <- StoreFixture.makeXA(cfg)
|
||||
store = new StoreImpl[IO](cfg, xa)
|
||||
_ <- Resource.eval(store.migrate)
|
||||
ds <- StoreFixture.dataSource(cfg)
|
||||
xa <- StoreFixture.makeXA(ds)
|
||||
_ <- Resource.eval(FlywayMigrate.run[IO](cfg))
|
||||
} yield xa
|
||||
}
|
||||
|
||||
val store = ResourceFixture {
|
||||
val cfg = StoreFixture.memoryDB("test")
|
||||
for {
|
||||
xa <- StoreFixture.makeXA(cfg)
|
||||
store = new StoreImpl[IO](cfg, xa)
|
||||
_ <- Resource.eval(store.migrate)
|
||||
store <- StoreFixture.store(cfg)
|
||||
_ <- Resource.eval(store.migrate)
|
||||
} yield store
|
||||
}
|
||||
}
|
||||
@ -47,31 +50,24 @@ object StoreFixture {
|
||||
""
|
||||
)
|
||||
|
||||
def globalXA(jdbc: JdbcConfig): Transactor[IO] =
|
||||
Transactor.fromDriverManager(
|
||||
"org.h2.Driver",
|
||||
jdbc.url.asString,
|
||||
jdbc.user,
|
||||
jdbc.password
|
||||
)
|
||||
|
||||
def makeXA(jdbc: JdbcConfig): Resource[IO, Transactor[IO]] = {
|
||||
def dataSource(jdbc: JdbcConfig): Resource[IO, JdbcConnectionPool] = {
|
||||
def jdbcConnPool =
|
||||
JdbcConnectionPool.create(jdbc.url.asString, jdbc.user, jdbc.password)
|
||||
|
||||
val makePool = Resource.make(IO(jdbcConnPool))(cp => IO(cp.dispose()))
|
||||
|
||||
for {
|
||||
ec <- ExecutionContexts.cachedThreadPool[IO]
|
||||
pool <- makePool
|
||||
xa = Transactor.fromDataSource[IO].apply(pool, ec)
|
||||
} yield xa
|
||||
Resource.make(IO(jdbcConnPool))(cp => IO(cp.dispose()))
|
||||
}
|
||||
|
||||
def store(jdbc: JdbcConfig): Resource[IO, Store[IO]] =
|
||||
def makeXA(ds: DataSource): Resource[IO, Transactor[IO]] =
|
||||
for {
|
||||
xa <- makeXA(jdbc)
|
||||
store = new StoreImpl[IO](jdbc, xa)
|
||||
ec <- ExecutionContexts.cachedThreadPool[IO]
|
||||
xa = Transactor.fromDataSource[IO](ds, ec)
|
||||
} yield xa
|
||||
|
||||
def store(jdbc: JdbcConfig): Resource[IO, StoreImpl[IO]] =
|
||||
for {
|
||||
ds <- dataSource(jdbc)
|
||||
xa <- makeXA(ds)
|
||||
store = new StoreImpl[IO](FileStore[IO](xa, ds, 64 * 1024), jdbc, xa)
|
||||
_ <- Resource.eval(store.migrate)
|
||||
} yield store
|
||||
}
|
||||
|
Reference in New Issue
Block a user