diff --git a/modules/restserver/src/main/scala/docspell/restserver/http4s/BinaryUtil.scala b/modules/restserver/src/main/scala/docspell/restserver/http4s/BinaryUtil.scala new file mode 100644 index 00000000..065f07cd --- /dev/null +++ b/modules/restserver/src/main/scala/docspell/restserver/http4s/BinaryUtil.scala @@ -0,0 +1,54 @@ +package docspell.restserver.http4s + +import cats.data.NonEmptyList +import cats.effect._ +import cats.implicits._ + +import docspell.backend.ops._ + +import bitpeace.FileMeta +import org.http4s._ +import org.http4s.circe.CirceEntityEncoder._ +import org.http4s.dsl.Http4sDsl +import org.http4s.headers.ETag.EntityTag +import org.http4s.headers._ + +object BinaryUtil { + + def withResponseHeaders[F[_]: Sync](dsl: Http4sDsl[F], resp: F[Response[F]])( + data: OItemSearch.BinaryData[F] + ): F[Response[F]] = { + import dsl._ + + 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.name.getOrElse(""))) + + resp.map(r => + if (r.status == NotModified) r.withHeaders(ctype, eTag, disp) + else r.withHeaders(ctype, cntLen, eTag, disp) + ) + } + + def makeByteResp[F[_]: Sync]( + dsl: Http4sDsl[F] + )(data: OItemSearch.BinaryData[F]): F[Response[F]] = { + import dsl._ + withResponseHeaders(dsl, Ok(data.data.take(data.meta.length)))(data) + } + + def matchETag[F[_]]( + fileData: Option[FileMeta], + noneMatch: Option[NonEmptyList[EntityTag]] + ): Boolean = + (fileData, noneMatch) match { + case (Some(meta), Some(nm)) => + meta.checksum == nm.head.tag + case _ => + false + } + +} diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala index bc500bdd..1d3ee301 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala @@ -1,6 +1,5 @@ package docspell.restserver.routes -import cats.data.NonEmptyList import cats.effect._ import cats.implicits._ @@ -10,14 +9,13 @@ import docspell.backend.ops._ import docspell.common.Ident import docspell.restapi.model._ import docspell.restserver.conv.Conversions +import docspell.restserver.http4s.BinaryUtil import docspell.restserver.webapp.Webjars -import bitpeace.FileMeta import org.http4s._ import org.http4s.circe.CirceEntityDecoder._ import org.http4s.circe.CirceEntityEncoder._ import org.http4s.dsl.Http4sDsl -import org.http4s.headers.ETag.EntityTag import org.http4s.headers._ object AttachmentRoutes { @@ -26,24 +24,13 @@ object AttachmentRoutes { val dsl = new Http4sDsl[F] {} import dsl._ - def withResponseHeaders( - resp: F[Response[F]] - )(data: OItemSearch.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.name.getOrElse(""))) - - resp.map(r => - if (r.status == NotModified) r.withHeaders(ctype, eTag, disp) - else r.withHeaders(ctype, cntLen, eTag, disp) - ) - } + def withResponseHeaders(resp: F[Response[F]])( + data: OItemSearch.BinaryData[F] + ): F[Response[F]] = + BinaryUtil.withResponseHeaders[F](dsl, resp)(data) def makeByteResp(data: OItemSearch.BinaryData[F]): F[Response[F]] = - withResponseHeaders(Ok(data.data.take(data.meta.length)))(data) + BinaryUtil.makeByteResp(dsl)(data) HttpRoutes.of { case HEAD -> Root / Ident(id) => @@ -59,7 +46,7 @@ object AttachmentRoutes { for { fileData <- backend.itemSearch.findAttachment(id, user.account.collective) inm = req.headers.get(`If-None-Match`).flatMap(_.tags) - matches = matchETag(fileData.map(_.meta), inm) + matches = BinaryUtil.matchETag(fileData.map(_.meta), inm) resp <- fileData .map { data => @@ -82,7 +69,7 @@ object AttachmentRoutes { for { fileData <- backend.itemSearch.findAttachmentSource(id, user.account.collective) inm = req.headers.get(`If-None-Match`).flatMap(_.tags) - matches = matchETag(fileData.map(_.meta), inm) + matches = BinaryUtil.matchETag(fileData.map(_.meta), inm) resp <- fileData .map { data => @@ -107,7 +94,7 @@ object AttachmentRoutes { fileData <- backend.itemSearch.findAttachmentArchive(id, user.account.collective) inm = req.headers.get(`If-None-Match`).flatMap(_.tags) - matches = matchETag(fileData.map(_.meta), inm) + matches = BinaryUtil.matchETag(fileData.map(_.meta), inm) resp <- fileData .map { data => @@ -122,7 +109,7 @@ object AttachmentRoutes { fileData <- backend.itemSearch.findAttachmentPreview(id, user.account.collective) inm = req.headers.get(`If-None-Match`).flatMap(_.tags) - matches = matchETag(fileData.map(_.meta), inm) + matches = BinaryUtil.matchETag(fileData.map(_.meta), inm) resp <- fileData .map { data => @@ -173,16 +160,4 @@ object AttachmentRoutes { } yield resp } } - - private def matchETag[F[_]]( - fileData: Option[FileMeta], - noneMatch: Option[NonEmptyList[EntityTag]] - ): Boolean = - (fileData, noneMatch) match { - case (Some(meta), Some(nm)) => - meta.checksum == nm.head.tag - case _ => - false - } - }