Sketch route for retrieving original file

This commit is contained in:
Eike Kettner 2020-02-20 22:12:27 +01:00
parent 7e1678da98
commit 39809f9d05
3 changed files with 106 additions and 13 deletions

View File

@ -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)

View File

@ -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:

View File

@ -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
}