mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 10:28:27 +00:00
Provide fallback image for previews
This commit is contained in:
@ -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/info" -> routes.InfoRoutes(),
|
||||||
"/api/v1/open/" -> openRoutes(cfg, restApp),
|
"/api/v1/open/" -> openRoutes(cfg, restApp),
|
||||||
"/api/v1/sec/" -> Authenticate(restApp.backend.login, cfg.auth) { token =>
|
"/api/v1/sec/" -> Authenticate(restApp.backend.login, cfg.auth) { token =>
|
||||||
securedRoutes(cfg, restApp, token)
|
securedRoutes(cfg, pools, restApp, token)
|
||||||
},
|
},
|
||||||
"/api/doc" -> templates.doc,
|
"/api/doc" -> templates.doc,
|
||||||
"/app/assets" -> WebjarRoutes.appRoutes[F](pools.blocker),
|
"/app/assets" -> WebjarRoutes.appRoutes[F](pools.blocker),
|
||||||
@ -57,8 +57,9 @@ object RestServer {
|
|||||||
)
|
)
|
||||||
}.drain
|
}.drain
|
||||||
|
|
||||||
def securedRoutes[F[_]: Effect](
|
def securedRoutes[F[_]: Effect: ContextShift](
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
|
pools: Pools,
|
||||||
restApp: RestApp[F],
|
restApp: RestApp[F],
|
||||||
token: AuthToken
|
token: AuthToken
|
||||||
): HttpRoutes[F] =
|
): HttpRoutes[F] =
|
||||||
@ -72,9 +73,9 @@ object RestServer {
|
|||||||
"user" -> UserRoutes(restApp.backend, token),
|
"user" -> UserRoutes(restApp.backend, token),
|
||||||
"collective" -> CollectiveRoutes(restApp.backend, token),
|
"collective" -> CollectiveRoutes(restApp.backend, token),
|
||||||
"queue" -> JobQueueRoutes(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),
|
"items" -> ItemMultiRoutes(restApp.backend, token),
|
||||||
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
"attachment" -> AttachmentRoutes(pools.blocker, restApp.backend, token),
|
||||||
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token),
|
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token),
|
||||||
"checkfile" -> CheckFileRoutes.secured(restApp.backend, token),
|
"checkfile" -> CheckFileRoutes.secured(restApp.backend, token),
|
||||||
"email/send" -> MailSendRoutes(restApp.backend, token),
|
"email/send" -> MailSendRoutes(restApp.backend, token),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package docspell.restserver.http4s
|
package docspell.restserver.http4s
|
||||||
|
|
||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
|
import cats.data.OptionT
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
@ -51,4 +52,16 @@ object BinaryUtil {
|
|||||||
false
|
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 ContactKindOpt extends OptionalQueryParamDecoderMatcher[ContactKind]("kind")
|
||||||
|
|
||||||
object QueryOpt extends OptionalQueryParamDecoderMatcher[QueryString]("q")
|
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.restapi.model._
|
||||||
import docspell.restserver.conv.Conversions
|
import docspell.restserver.conv.Conversions
|
||||||
import docspell.restserver.http4s.BinaryUtil
|
import docspell.restserver.http4s.BinaryUtil
|
||||||
|
import docspell.restserver.http4s.{QueryParam => QP}
|
||||||
import docspell.restserver.webapp.Webjars
|
import docspell.restserver.webapp.Webjars
|
||||||
|
|
||||||
import org.http4s._
|
import org.http4s._
|
||||||
@ -21,7 +22,11 @@ import org.http4s.headers._
|
|||||||
|
|
||||||
object AttachmentRoutes {
|
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] {}
|
val dsl = new Http4sDsl[F] {}
|
||||||
import dsl._
|
import dsl._
|
||||||
|
|
||||||
@ -105,19 +110,25 @@ object AttachmentRoutes {
|
|||||||
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
||||||
} yield resp
|
} 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 {
|
for {
|
||||||
fileData <-
|
fileData <-
|
||||||
backend.itemSearch.findAttachmentPreview(id, user.account.collective)
|
backend.itemSearch.findAttachmentPreview(id, user.account.collective)
|
||||||
inm = req.headers.get(`If-None-Match`).flatMap(_.tags)
|
inm = req.headers.get(`If-None-Match`).flatMap(_.tags)
|
||||||
matches = BinaryUtil.matchETag(fileData.map(_.meta), inm)
|
matches = BinaryUtil.matchETag(fileData.map(_.meta), inm)
|
||||||
|
fallback = flag.getOrElse(false)
|
||||||
resp <-
|
resp <-
|
||||||
fileData
|
fileData
|
||||||
.map { data =>
|
.map { data =>
|
||||||
if (matches) withResponseHeaders(NotModified())(data)
|
if (matches) withResponseHeaders(NotModified())(data)
|
||||||
else makeByteResp(data)
|
else makeByteResp(data)
|
||||||
}
|
}
|
||||||
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
.getOrElse(
|
||||||
|
if (fallback) BinaryUtil.noPreview(blocker, req.some).getOrElseF(notFound)
|
||||||
|
else notFound
|
||||||
|
)
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
case HEAD -> Root / Ident(id) / "preview" =>
|
case HEAD -> Root / Ident(id) / "preview" =>
|
||||||
|
@ -15,6 +15,7 @@ import docspell.restserver.Config
|
|||||||
import docspell.restserver.conv.Conversions
|
import docspell.restserver.conv.Conversions
|
||||||
import docspell.restserver.http4s.BinaryUtil
|
import docspell.restserver.http4s.BinaryUtil
|
||||||
import docspell.restserver.http4s.Responses
|
import docspell.restserver.http4s.Responses
|
||||||
|
import docspell.restserver.http4s.{QueryParam => QP}
|
||||||
|
|
||||||
import org.http4s.HttpRoutes
|
import org.http4s.HttpRoutes
|
||||||
import org.http4s.circe.CirceEntityDecoder._
|
import org.http4s.circe.CirceEntityDecoder._
|
||||||
@ -26,8 +27,9 @@ import org.log4s._
|
|||||||
object ItemRoutes {
|
object ItemRoutes {
|
||||||
private[this] val logger = getLogger
|
private[this] val logger = getLogger
|
||||||
|
|
||||||
def apply[F[_]: Effect](
|
def apply[F[_]: Effect: ContextShift](
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
|
blocker: Blocker,
|
||||||
backend: BackendApp[F],
|
backend: BackendApp[F],
|
||||||
user: AuthToken
|
user: AuthToken
|
||||||
): HttpRoutes[F] = {
|
): HttpRoutes[F] = {
|
||||||
@ -318,18 +320,24 @@ object ItemRoutes {
|
|||||||
resp <- Ok(Conversions.basicResult(res, "Attachment moved."))
|
resp <- Ok(Conversions.basicResult(res, "Attachment moved."))
|
||||||
} yield resp
|
} 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 {
|
for {
|
||||||
preview <- backend.itemSearch.findItemPreview(id, user.account.collective)
|
preview <- backend.itemSearch.findItemPreview(id, user.account.collective)
|
||||||
inm = req.headers.get(`If-None-Match`).flatMap(_.tags)
|
inm = req.headers.get(`If-None-Match`).flatMap(_.tags)
|
||||||
matches = BinaryUtil.matchETag(preview.map(_.meta), inm)
|
matches = BinaryUtil.matchETag(preview.map(_.meta), inm)
|
||||||
|
fallback = flag.getOrElse(false)
|
||||||
resp <-
|
resp <-
|
||||||
preview
|
preview
|
||||||
.map { data =>
|
.map { data =>
|
||||||
if (matches) BinaryUtil.withResponseHeaders(dsl, NotModified())(data)
|
if (matches) BinaryUtil.withResponseHeaders(dsl, NotModified())(data)
|
||||||
else BinaryUtil.makeByteResp(dsl)(data).map(Responses.noCache)
|
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
|
} yield resp
|
||||||
|
|
||||||
case HEAD -> Root / Ident(id) / "preview" =>
|
case HEAD -> Root / Ident(id) / "preview" =>
|
||||||
|
@ -1505,7 +1505,7 @@ deleteAllItems flags ids receive =
|
|||||||
|
|
||||||
itemPreviewURL : String -> String
|
itemPreviewURL : String -> String
|
||||||
itemPreviewURL itemId =
|
itemPreviewURL itemId =
|
||||||
"/api/v1/sec/item/" ++ itemId ++ "/preview"
|
"/api/v1/sec/item/" ++ itemId ++ "/preview?withFallback=true"
|
||||||
|
|
||||||
|
|
||||||
fileURL : String -> String
|
fileURL : String -> String
|
||||||
|
Reference in New Issue
Block a user