mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-03-25 16:45:05 +00:00
Provide fallback image for previews
This commit is contained in:
parent
d4bbb936b6
commit
8c8788bc69
@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
sodipodi:docname="no-preview.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="638.19656"
|
||||
inkscape:cy="138.48596"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1896"
|
||||
inkscape:window-height="2101"
|
||||
inkscape:window-x="3844"
|
||||
inkscape:window-y="39"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.50112426;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect4518"
|
||||
width="209.3988"
|
||||
height="297.08929"
|
||||
x="0.37797618"
|
||||
y="0.28869045" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23650599px;line-height:2;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26478162"
|
||||
x="101.91397"
|
||||
y="163.31726"
|
||||
id="text4554"
|
||||
transform="scale(0.99565662,1.0043623)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4552"
|
||||
x="101.91397"
|
||||
y="163.31726"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:22.59469986px;line-height:2;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold';text-align:center;text-anchor:middle;stroke-width:0.26478162">Preview</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="101.91397"
|
||||
y="208.50665"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:22.59469986px;line-height:2;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold';text-align:center;text-anchor:middle;stroke-width:0.26478162"
|
||||
id="tspan4556">not</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="101.91397"
|
||||
y="253.69606"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:22.59469986px;line-height:2;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold';text-align:center;text-anchor:middle;stroke-width:0.26478162"
|
||||
id="tspan4558">available</tspan></text>
|
||||
<path
|
||||
style="opacity:1;fill:#d7e3f4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.64033973;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path4581"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="103.00598"
|
||||
sodipodi:cy="78.360054"
|
||||
sodipodi:rx="34.460411"
|
||||
sodipodi:ry="34.761723"
|
||||
sodipodi:start="0.34567468"
|
||||
sodipodi:end="0.34365009"
|
||||
sodipodi:open="true"
|
||||
d="M 135.42796,90.138421 A 34.460411,34.761723 0 0 1 91.34612,111.07148 34.460411,34.761723 0 0 1 70.572202,66.6148 34.460411,34.761723 0 0 1 114.63301,45.636742 a 34.460411,34.761723 0 0 1 20.81852,44.43544" />
|
||||
<rect
|
||||
style="opacity:1;fill:#000055;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.78938425;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect4583"
|
||||
width="35.846756"
|
||||
height="4.1953807"
|
||||
x="84.785538"
|
||||
y="90.746422" />
|
||||
<path
|
||||
style="opacity:1;fill:#000055;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.78938425;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path4585"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="117.95863"
|
||||
sodipodi:cy="66.872711"
|
||||
sodipodi:rx="5.8424263"
|
||||
sodipodi:ry="5.8935103"
|
||||
sodipodi:start="0.34567468"
|
||||
sodipodi:end="0.34365009"
|
||||
sodipodi:open="true"
|
||||
d="m 123.45546,68.869618 a 5.8424263,5.8935103 0 0 1 -7.47364,3.548995 5.8424263,5.8935103 0 0 1 -3.52202,-7.537195 5.8424263,5.8935103 0 0 1 7.47008,-3.556625 5.8424263,5.8935103 0 0 1 3.52958,7.533595" />
|
||||
<path
|
||||
style="opacity:1;fill:#000055;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.78938425;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path4585-8"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="87.558212"
|
||||
sodipodi:cy="67.172394"
|
||||
sodipodi:rx="5.8424263"
|
||||
sodipodi:ry="5.8935103"
|
||||
sodipodi:start="0.34567468"
|
||||
sodipodi:end="0.34365009"
|
||||
sodipodi:open="true"
|
||||
d="m 93.055042,69.169301 a 5.8424263,5.8935103 0 0 1 -7.473645,3.548995 5.8424263,5.8935103 0 0 1 -3.522015,-7.537195 5.8424263,5.8935103 0 0 1 7.47008,-3.556625 5.8424263,5.8935103 0 0 1 3.529577,7.533595" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.3 KiB |
@ -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),
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -29,4 +29,6 @@ object QueryParam {
|
||||
object ContactKindOpt extends OptionalQueryParamDecoderMatcher[ContactKind]("kind")
|
||||
|
||||
object QueryOpt extends OptionalQueryParamDecoderMatcher[QueryString]("q")
|
||||
|
||||
object WithFallback extends OptionalQueryParamDecoderMatcher[Boolean]("withFallback")
|
||||
}
|
||||
|
@ -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" =>
|
||||
|
@ -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" =>
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user