diff --git a/src/server.rs b/src/server.rs index 3a675dd6..12050330 100644 --- a/src/server.rs +++ b/src/server.rs @@ -172,6 +172,7 @@ impl Server { fingerprint: Fingerprint, item: usize, ty: MediaType, + thumbnail: bool, ) -> ServerResult { let metadata = self.package_metadata(fingerprint)?; @@ -197,11 +198,20 @@ impl Server { ty, })?; - let path = item.path(); + let (path, ty) = if thumbnail + && let Some(thumbnail) = metadata + .thumbnails + .as_ref() + .and_then(|thumbnails| thumbnails.get(item.path())) + { + (&thumbnail.path, thumbnail.ty.resource_type()) + } else { + (item.path(), item.resource_type()) + }; let hash = self.verified_package_file(fingerprint, path)?; - Ok(self.open_file(hash)?.ty(item.resource_type())) + Ok(self.open_file(hash)?.ty(ty)) } fn metadata(&self, fingerprint: Fingerprint) -> ServerResult> { diff --git a/src/subcommand/serve.rs b/src/subcommand/serve.rs index 61dbc354..9e732e0a 100644 --- a/src/subcommand/serve.rs +++ b/src/subcommand/serve.rs @@ -324,6 +324,10 @@ impl Serve { "/media/image/{fingerprint}/item/{item}", get(route::media_image_item), ) + .route( + "/media/image/{fingerprint}/item/{item}/thumbnail", + get(route::media_image_item_thumbnail), + ) .route( "/media/video/{fingerprint}/item/{item}", get(route::media_video_item), diff --git a/src/subcommand/serve/route.rs b/src/subcommand/serve/route.rs index c2c666de..2f9d8637 100644 --- a/src/subcommand/serve/route.rs +++ b/src/subcommand/serve/route.rs @@ -157,7 +157,7 @@ pub(crate) async fn media_audio_item( block_in_place(|| { Ok( server - .media_item(fingerprint, item, MediaType::Audio)? + .media_item(fingerprint, item, MediaType::Audio, false)? .range(range), ) }) @@ -171,7 +171,21 @@ pub(crate) async fn media_image_item( block_in_place(|| { Ok( server - .media_item(fingerprint, item, MediaType::Image)? + .media_item(fingerprint, item, MediaType::Image, false)? + .range(range), + ) + }) +} + +pub(crate) async fn media_image_item_thumbnail( + server: ServerExtension, + Path((fingerprint, Ordinal(item))): Path<(Fingerprint, Ordinal)>, + range: Option>, +) -> ServerResult { + block_in_place(|| { + Ok( + server + .media_item(fingerprint, item, MediaType::Image, true)? .range(range), ) }) @@ -185,7 +199,7 @@ pub(crate) async fn media_video_item( block_in_place(|| { Ok( server - .media_item(fingerprint, item, MediaType::Video)? + .media_item(fingerprint, item, MediaType::Video, false)? .range(range), ) }) diff --git a/src/subcommand/serve/tests.rs b/src/subcommand/serve/tests.rs index 7c4c48fd..0eedc47f 100644 --- a/src/subcommand/serve/tests.rs +++ b/src/subcommand/serve/tests.rs @@ -1335,6 +1335,59 @@ fn media_image_item_response() { .send(); } +#[test] +fn media_image_item_thumbnail_response() { + let server = TestServer::new(); + + let foo: &[u8] = b"foo"; + let bar: &[u8] = b"barbar"; + let thumbnail: &[u8] = b"bazbazbaz"; + + let fingerprint = PackageBuilder::new() + .metadata(&Metadata { + media: Some(Media::Image { + items: vec!["foo.png".parse().unwrap(), "bar.jpg".parse().unwrap()], + }), + thumbnails: Some( + [( + "foo.png".parse().unwrap(), + "thumbnails/foo.jpg".parse().unwrap(), + )] + .into(), + ), + ..default() + }) + .file("foo.png", foo) + .file("bar.jpg", bar) + .file("thumbnails/foo.jpg", thumbnail) + .upload(&server); + + server + .get(format!("/media/image/{fingerprint}/item/1/thumbnail")) + .assert_header(header::CACHE_CONTROL, "public, max-age=31536000, immutable") + .assert_header(header::CONTENT_LENGTH, "9") + .assert_header(header::CONTENT_TYPE, "image/jpeg") + .assert_header(header::ETAG, format!("\"{}\"", Hash::bytes(thumbnail))) + .assert_body(thumbnail) + .send(); + + server + .get(format!("/media/image/{fingerprint}/item/2/thumbnail")) + .assert_header(header::CONTENT_LENGTH, "6") + .assert_header(header::CONTENT_TYPE, "image/jpeg") + .assert_header(header::ETAG, format!("\"{}\"", Hash::bytes(bar))) + .assert_body(bar) + .send(); + + server + .get(format!("/media/image/{fingerprint}/item/3/thumbnail")) + .status(StatusCode::NOT_FOUND) + .assert_body(format!( + "image 3 does not exist, package {fingerprint} has 2 images" + )) + .send(); +} + #[test] fn media_type_mismatch() { #[track_caller] @@ -1376,6 +1429,12 @@ fn media_type_mismatch() { format!("expected media type image but package {audio} is audio"), ); + case( + &server, + format!("/media/image/{audio}/item/1/thumbnail"), + format!("expected media type image but package {audio} is audio"), + ); + case( &server, format!("/media/audio/{image}/item/1"), diff --git a/src/templates/package.rs b/src/templates/package.rs index a2f7340b..9ffc31d1 100644 --- a/src/templates/package.rs +++ b/src/templates/package.rs @@ -416,17 +416,17 @@ mod tests { diff --git a/templates/package.html b/templates/package.html index 8eaed581..a9f282af 100644 --- a/templates/package.html +++ b/templates/package.html @@ -152,7 +152,7 @@

disc {{ disc[0].disc }}

%% for (i, image) in items.iter().enumerate() {
  • - +
  • %% }