mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-03-28 17:55:06 +00:00
Sketch route for retrieving original file
This commit is contained in:
parent
7e1678da98
commit
39809f9d05
@ -8,11 +8,10 @@ import doobie._
|
||||
import doobie.implicits._
|
||||
import docspell.store.{AddResult, Store}
|
||||
import docspell.store.queries.{QAttachment, QItem}
|
||||
import OItem.{AttachmentData, ItemData, ListItem, Query}
|
||||
import OItem.{AttachmentData, AttachmentSourceData, ItemData, ListItem, Query}
|
||||
import bitpeace.{FileMeta, RangeDef}
|
||||
import docspell.common.{Direction, Ident, ItemState, MetaProposalList, Timestamp}
|
||||
import docspell.store.records.{RAttachment, RAttachmentMeta, RItem, RTagItem}
|
||||
import docspell.store.records.RSource
|
||||
import docspell.store.records.{RAttachment, RAttachmentMeta, RAttachmentSource, RItem, RSource, RTagItem}
|
||||
|
||||
trait OItem[F[_]] {
|
||||
|
||||
@ -22,6 +21,8 @@ trait OItem[F[_]] {
|
||||
|
||||
def findAttachment(id: Ident, collective: Ident): F[Option[AttachmentData[F]]]
|
||||
|
||||
def findAttachmentSource(id: Ident, collective: Ident): F[Option[AttachmentSourceData[F]]]
|
||||
|
||||
def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult]
|
||||
|
||||
def setDirection(item: Ident, direction: Direction, collective: Ident): F[AddResult]
|
||||
@ -67,7 +68,20 @@ object OItem {
|
||||
type ItemData = QItem.ItemData
|
||||
val ItemData = QItem.ItemData
|
||||
|
||||
trait BinaryData[F[_]] {
|
||||
def data: Stream[F, Byte]
|
||||
def name: Option[String]
|
||||
def meta: FileMeta
|
||||
}
|
||||
case class AttachmentData[F[_]](ra: RAttachment, meta: FileMeta, data: Stream[F, Byte])
|
||||
extends BinaryData[F] {
|
||||
val name = ra.name
|
||||
}
|
||||
|
||||
case class AttachmentSourceData[F[_]](rs: RAttachmentSource, meta: FileMeta, data: Stream[F, Byte])
|
||||
extends BinaryData[F] {
|
||||
val name = rs.name
|
||||
}
|
||||
|
||||
def apply[F[_]: Effect](store: Store[F]): Resource[F, OItem[F]] =
|
||||
Resource.pure[F, OItem[F]](new OItem[F] {
|
||||
@ -101,6 +115,9 @@ object OItem {
|
||||
(None: Option[AttachmentData[F]]).pure[F]
|
||||
})
|
||||
|
||||
def findAttachmentSource(id: Ident, collective: Ident): F[Option[AttachmentSourceData[F]]] =
|
||||
???
|
||||
|
||||
def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult] = {
|
||||
val db = for {
|
||||
cid <- RItem.getCollective(item)
|
||||
|
@ -1172,7 +1172,8 @@ paths:
|
||||
tags: [ Attachment ]
|
||||
summary: Get an attachment file.
|
||||
description: |
|
||||
Get the binary file belonging to the attachment with the given id.
|
||||
Get information about the binary file belonging to the
|
||||
attachment with the given id.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
@ -1198,7 +1199,60 @@ paths:
|
||||
tags: [ Attachment ]
|
||||
summary: Get an attachment file.
|
||||
description: |
|
||||
Get the binary file belonging to the attachment with the given id.
|
||||
Get the binary file belonging to the attachment with the given
|
||||
id.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/id"
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
/sec/attachment/{id}/original:
|
||||
head:
|
||||
tags: [ Attachment ]
|
||||
summary: Get an attachment file.
|
||||
description: |
|
||||
Get information about the original binary file of the
|
||||
attachment with the given id.
|
||||
|
||||
If the attachment is a converted PDF file, this route gets the
|
||||
original file as it was uploaded.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/id"
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
headers:
|
||||
Content-Type:
|
||||
schema:
|
||||
type: string
|
||||
Content-Length:
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
ETag:
|
||||
schema:
|
||||
type: string
|
||||
Content-Disposition:
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
tags: [ Attachment ]
|
||||
summary: Get an attachment file.
|
||||
description: |
|
||||
Get the original binary file of the attachment with the given
|
||||
id.
|
||||
|
||||
If the attachment is a converted PDF file, this route gets the
|
||||
original file as it was uploaded.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
|
@ -1,5 +1,6 @@
|
||||
package docspell.restserver.routes
|
||||
|
||||
import bitpeace.FileMeta
|
||||
import cats.data.NonEmptyList
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
@ -22,13 +23,13 @@ object AttachmentRoutes {
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
def withResponseHeaders(resp: F[Response[F]])(data: OItem.AttachmentData[F]): F[Response[F]] = {
|
||||
def withResponseHeaders(resp: F[Response[F]])(data: OItem.BinaryData[F]): F[Response[F]] = {
|
||||
val mt = MediaType.unsafeParse(data.meta.mimetype.asString)
|
||||
val ctype = `Content-Type`(mt)
|
||||
val cntLen: Header = `Content-Length`.unsafeFromLong(data.meta.length)
|
||||
val eTag: Header = ETag(data.meta.checksum)
|
||||
val disp: Header =
|
||||
`Content-Disposition`("inline", Map("filename" -> data.ra.name.getOrElse("")))
|
||||
`Content-Disposition`("inline", Map("filename" -> data.name.getOrElse("")))
|
||||
|
||||
resp.map(r =>
|
||||
if (r.status == NotModified) r.withHeaders(ctype, eTag, disp)
|
||||
@ -36,7 +37,7 @@ object AttachmentRoutes {
|
||||
)
|
||||
}
|
||||
|
||||
def makeByteResp(data: OItem.AttachmentData[F]): F[Response[F]] =
|
||||
def makeByteResp(data: OItem.BinaryData[F]): F[Response[F]] =
|
||||
withResponseHeaders(Ok(data.data.take(data.meta.length)))(data)
|
||||
|
||||
HttpRoutes.of {
|
||||
@ -52,7 +53,7 @@ object AttachmentRoutes {
|
||||
for {
|
||||
fileData <- backend.item.findAttachment(id, user.account.collective)
|
||||
inm = req.headers.get(`If-None-Match`).flatMap(_.tags)
|
||||
matches = matchETag(fileData, inm)
|
||||
matches = matchETag(fileData.map(_.meta), inm)
|
||||
resp <- fileData
|
||||
.map({ data =>
|
||||
if (matches) withResponseHeaders(NotModified())(data)
|
||||
@ -61,6 +62,27 @@ object AttachmentRoutes {
|
||||
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
||||
} yield resp
|
||||
|
||||
case HEAD -> Root / Ident(id) / "original" =>
|
||||
for {
|
||||
fileData <- backend.item.findAttachmentSource(id, user.account.collective)
|
||||
resp <- fileData
|
||||
.map(data => withResponseHeaders(Ok())(data))
|
||||
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
||||
} yield resp
|
||||
|
||||
case req @ GET -> Root / Ident(id) / "original" =>
|
||||
for {
|
||||
fileData <- backend.item.findAttachmentSource(id, user.account.collective)
|
||||
inm = req.headers.get(`If-None-Match`).flatMap(_.tags)
|
||||
matches = matchETag(fileData.map(_.meta), inm)
|
||||
resp <- fileData
|
||||
.map({ data =>
|
||||
if (matches) withResponseHeaders(NotModified())(data)
|
||||
else makeByteResp(data)
|
||||
})
|
||||
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / Ident(id) / "view" =>
|
||||
// this route exists to provide a stable url
|
||||
// it redirects currently to viewerjs
|
||||
@ -78,12 +100,12 @@ object AttachmentRoutes {
|
||||
}
|
||||
|
||||
private def matchETag[F[_]](
|
||||
fileData: Option[OItem.AttachmentData[F]],
|
||||
noneMatch: Option[NonEmptyList[EntityTag]]
|
||||
fileData: Option[FileMeta],
|
||||
noneMatch: Option[NonEmptyList[EntityTag]]
|
||||
): Boolean =
|
||||
(fileData, noneMatch) match {
|
||||
case (Some(fd), Some(nm)) =>
|
||||
fd.meta.checksum == nm.head.tag
|
||||
case (Some(meta), Some(nm)) =>
|
||||
meta.checksum == nm.head.tag
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user