From 8c8788bc6953d76dc1025a9d8064f3e13572f8a3 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Mon, 9 Nov 2020 08:57:43 +0100 Subject: [PATCH] Provide fallback image for previews --- .../docspell/restserver/no-preview.svg | 128 ++++++++++++++++++ .../docspell/restserver/RestServer.scala | 9 +- .../restserver/http4s/BinaryUtil.scala | 13 ++ .../restserver/http4s/QueryParam.scala | 2 + .../restserver/routes/AttachmentRoutes.scala | 21 ++- .../restserver/routes/ItemRoutes.scala | 18 ++- modules/webapp/src/main/elm/Api.elm | 2 +- 7 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 modules/restserver/src/main/resources/docspell/restserver/no-preview.svg diff --git a/modules/restserver/src/main/resources/docspell/restserver/no-preview.svg b/modules/restserver/src/main/resources/docspell/restserver/no-preview.svg new file mode 100644 index 00000000..b7c093eb --- /dev/null +++ b/modules/restserver/src/main/resources/docspell/restserver/no-preview.svg @@ -0,0 +1,128 @@ + + + + + + + + + + image/svg+xml + + + + + + + + Previewnotavailable + + + + + + diff --git a/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala b/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala index de4dfbfb..9dbba2b0 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala @@ -33,7 +33,7 @@ object RestServer { "/api/info" -> routes.InfoRoutes(), "/api/v1/open/" -> openRoutes(cfg, restApp), "/api/v1/sec/" -> Authenticate(restApp.backend.login, cfg.auth) { token => - securedRoutes(cfg, restApp, token) + securedRoutes(cfg, pools, restApp, token) }, "/api/doc" -> templates.doc, "/app/assets" -> WebjarRoutes.appRoutes[F](pools.blocker), @@ -57,8 +57,9 @@ object RestServer { ) }.drain - def securedRoutes[F[_]: Effect]( + def securedRoutes[F[_]: Effect: ContextShift]( cfg: Config, + pools: Pools, restApp: RestApp[F], token: AuthToken ): HttpRoutes[F] = @@ -72,9 +73,9 @@ object RestServer { "user" -> UserRoutes(restApp.backend, token), "collective" -> CollectiveRoutes(restApp.backend, token), "queue" -> JobQueueRoutes(restApp.backend, token), - "item" -> ItemRoutes(cfg, restApp.backend, token), + "item" -> ItemRoutes(cfg, pools.blocker, restApp.backend, token), "items" -> ItemMultiRoutes(restApp.backend, token), - "attachment" -> AttachmentRoutes(restApp.backend, token), + "attachment" -> AttachmentRoutes(pools.blocker, restApp.backend, token), "upload" -> UploadRoutes.secured(restApp.backend, cfg, token), "checkfile" -> CheckFileRoutes.secured(restApp.backend, token), "email/send" -> MailSendRoutes(restApp.backend, token), diff --git a/modules/restserver/src/main/scala/docspell/restserver/http4s/BinaryUtil.scala b/modules/restserver/src/main/scala/docspell/restserver/http4s/BinaryUtil.scala index 065f07cd..152391b8 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/http4s/BinaryUtil.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/http4s/BinaryUtil.scala @@ -1,6 +1,7 @@ package docspell.restserver.http4s import cats.data.NonEmptyList +import cats.data.OptionT import cats.effect._ import cats.implicits._ @@ -51,4 +52,16 @@ object BinaryUtil { false } + def noPreview[F[_]: Sync: ContextShift]( + blocker: Blocker, + req: Option[Request[F]] + ): OptionT[F, Response[F]] = + StaticFile.fromResource( + name = "/docspell/restserver/no-preview.svg", + blocker = blocker, + req = req, + preferGzipped = true, + classloader = getClass.getClassLoader().some + ) + } diff --git a/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala b/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala index b83296a1..4d91d959 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala @@ -29,4 +29,6 @@ object QueryParam { object ContactKindOpt extends OptionalQueryParamDecoderMatcher[ContactKind]("kind") object QueryOpt extends OptionalQueryParamDecoderMatcher[QueryString]("q") + + object WithFallback extends OptionalQueryParamDecoderMatcher[Boolean]("withFallback") } 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 f168c400..34e09ba3 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala @@ -11,6 +11,7 @@ import docspell.common.MakePreviewArgs import docspell.restapi.model._ import docspell.restserver.conv.Conversions import docspell.restserver.http4s.BinaryUtil +import docspell.restserver.http4s.{QueryParam => QP} import docspell.restserver.webapp.Webjars import org.http4s._ @@ -21,7 +22,11 @@ import org.http4s.headers._ object AttachmentRoutes { - def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = { + def apply[F[_]: Effect: ContextShift]( + blocker: Blocker, + backend: BackendApp[F], + user: AuthToken + ): HttpRoutes[F] = { val dsl = new Http4sDsl[F] {} import dsl._ @@ -105,19 +110,25 @@ object AttachmentRoutes { .getOrElse(NotFound(BasicResult(false, "Not found"))) } yield resp - case req @ GET -> Root / Ident(id) / "preview" => + case req @ GET -> Root / Ident(id) / "preview" :? QP.WithFallback(flag) => + def notFound = + NotFound(BasicResult(false, "Not found")) for { fileData <- backend.itemSearch.findAttachmentPreview(id, user.account.collective) - inm = req.headers.get(`If-None-Match`).flatMap(_.tags) - matches = BinaryUtil.matchETag(fileData.map(_.meta), inm) + inm = req.headers.get(`If-None-Match`).flatMap(_.tags) + matches = BinaryUtil.matchETag(fileData.map(_.meta), inm) + fallback = flag.getOrElse(false) resp <- fileData .map { data => if (matches) withResponseHeaders(NotModified())(data) else makeByteResp(data) } - .getOrElse(NotFound(BasicResult(false, "Not found"))) + .getOrElse( + if (fallback) BinaryUtil.noPreview(blocker, req.some).getOrElseF(notFound) + else notFound + ) } yield resp case HEAD -> Root / Ident(id) / "preview" => diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala index 62e084be..ba0c8c08 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala @@ -15,6 +15,7 @@ import docspell.restserver.Config import docspell.restserver.conv.Conversions import docspell.restserver.http4s.BinaryUtil import docspell.restserver.http4s.Responses +import docspell.restserver.http4s.{QueryParam => QP} import org.http4s.HttpRoutes import org.http4s.circe.CirceEntityDecoder._ @@ -26,8 +27,9 @@ import org.log4s._ object ItemRoutes { private[this] val logger = getLogger - def apply[F[_]: Effect]( + def apply[F[_]: Effect: ContextShift]( cfg: Config, + blocker: Blocker, backend: BackendApp[F], user: AuthToken ): HttpRoutes[F] = { @@ -318,18 +320,24 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Attachment moved.")) } yield resp - case req @ GET -> Root / Ident(id) / "preview" => + case req @ GET -> Root / Ident(id) / "preview" :? QP.WithFallback(flag) => + def notFound = + NotFound(BasicResult(false, "Not found")) for { preview <- backend.itemSearch.findItemPreview(id, user.account.collective) - inm = req.headers.get(`If-None-Match`).flatMap(_.tags) - matches = BinaryUtil.matchETag(preview.map(_.meta), inm) + inm = req.headers.get(`If-None-Match`).flatMap(_.tags) + matches = BinaryUtil.matchETag(preview.map(_.meta), inm) + fallback = flag.getOrElse(false) resp <- preview .map { data => if (matches) BinaryUtil.withResponseHeaders(dsl, NotModified())(data) else BinaryUtil.makeByteResp(dsl)(data).map(Responses.noCache) } - .getOrElse(NotFound(BasicResult(false, "Not found"))) + .getOrElse( + if (fallback) BinaryUtil.noPreview(blocker, req.some).getOrElseF(notFound) + else notFound + ) } yield resp case HEAD -> Root / Ident(id) / "preview" => diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index fc603713..7230be7e 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -1505,7 +1505,7 @@ deleteAllItems flags ids receive = itemPreviewURL : String -> String itemPreviewURL itemId = - "/api/v1/sec/item/" ++ itemId ++ "/preview" + "/api/v1/sec/item/" ++ itemId ++ "/preview?withFallback=true" fileURL : String -> String