mirror of
https://github.com/agersant/polaris
synced 2024-11-10 02:04:13 +00:00
add api parameter for thumbnail size (#144)
* add api parameter for thumbnail size * make max_dimension optinal in case of native resolution * add tests for thumbnail size * fix typo * fix thumbnail size tests * make unwrap more explicit * remove print statement * update workflows * reduce thumbnail variations * add removed token * Update coverage.yml * fix typo * hopefully prevent coverage timeout - split up thumnail tests - reduce threadcount used for test execution * get thread count using github actions specific step * use fixed thread count of 4 * run coverage tests in release mode * ignore large and native thumbnail_size tests in coverage
This commit is contained in:
parent
f104355076
commit
d01583b406
11 changed files with 133 additions and 27 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
- name: Install libsqlite3-dev
|
||||
if: contains(matrix.os, 'ubuntu') && !contains(matrix.features, 'bundle-sqlite')
|
||||
run: sudo apt-get update && sudo apt-get install libsqlite3-dev
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
|
|
12
.github/workflows/coverage.yml
vendored
12
.github/workflows/coverage.yml
vendored
|
@ -15,14 +15,16 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Polaris
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
- name: Install Tarpaulin
|
||||
run: cargo install cargo-tarpaulin
|
||||
- name: Run Tests
|
||||
run: cargo tarpaulin --all-features --ignore-tests --out Xml
|
||||
- name: Run Tarpaulin
|
||||
uses: actions-rs/tarpaulin@v0.1
|
||||
with:
|
||||
args: '--all-features --ignore-tests'
|
||||
out-type: Xml
|
||||
timeout: 240
|
||||
- name: Upload Results
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
target_branch: release
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Checkout Release Branch
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: release
|
||||
- name: Update Polaris Version in Cargo.toml
|
||||
|
@ -70,7 +70,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Polaris
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: release
|
||||
- name: Install Rust Toolchain
|
||||
|
@ -106,7 +106,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Polaris
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: release
|
||||
- name: Make release
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,6 +10,7 @@ TestConfig.toml
|
|||
# Runtime artifacts
|
||||
*.sqlite
|
||||
polaris.log
|
||||
polaris.pid
|
||||
/thumbnails
|
||||
|
||||
# Release process artifacts (usually runs on CI)
|
||||
|
|
|
@ -791,6 +791,16 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"in": "query",
|
||||
"description": "The maximum size of the thumbnail, either small (400x400), large (1200x1200) or native",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["small", "large", "native"],
|
||||
"default": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pad",
|
||||
"in": "query",
|
||||
|
|
|
@ -9,7 +9,10 @@ pub fn generate_thumbnail(image_path: &Path, options: &Options) -> Result<Dynami
|
|||
let source_image = DynamicImage::ImageRgb8(read(image_path)?.into_rgb8());
|
||||
let (source_width, source_height) = source_image.dimensions();
|
||||
let largest_dimension = cmp::max(source_width, source_height);
|
||||
let out_dimension = cmp::min(options.max_dimension, largest_dimension);
|
||||
let out_dimension = cmp::min(
|
||||
options.max_dimension.unwrap_or(largest_dimension),
|
||||
largest_dimension,
|
||||
);
|
||||
|
||||
let source_aspect_ratio: f32 = source_width as f32 / source_height as f32;
|
||||
let is_almost_square = source_aspect_ratio > 0.8 && source_aspect_ratio < 1.2;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#[derive(Debug, Hash)]
|
||||
pub struct Options {
|
||||
pub max_dimension: u32,
|
||||
pub max_dimension: Option<u32>,
|
||||
pub resize_if_almost_square: bool,
|
||||
pub pad_to_square: bool,
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ pub struct Options {
|
|||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_dimension: 400,
|
||||
max_dimension: Some(400),
|
||||
resize_if_almost_square: true,
|
||||
pad_to_square: true,
|
||||
}
|
||||
|
|
|
@ -703,8 +703,7 @@ async fn get_thumbnail(
|
|||
path: web::Path<String>,
|
||||
options_input: web::Query<dto::ThumbnailOptions>,
|
||||
) -> Result<NamedFile, APIError> {
|
||||
let mut options = thumbnail::Options::default();
|
||||
options.pad_to_square = options_input.pad.unwrap_or(options.pad_to_square);
|
||||
let options = thumbnail::Options::from(options_input.0);
|
||||
|
||||
let thumbnail_path = block(move || {
|
||||
let vfs = vfs_manager.get_vfs()?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::app::{config, ddns, settings, user, vfs};
|
||||
use crate::app::{config, ddns, settings, thumbnail, user, vfs};
|
||||
|
||||
pub const API_MAJOR_VERSION: i32 = 6;
|
||||
pub const API_MINOR_VERSION: i32 = 0;
|
||||
|
@ -39,9 +39,37 @@ pub struct AuthQueryParameters {
|
|||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ThumbnailOptions {
|
||||
pub size: Option<ThumbnailSize>,
|
||||
pub pad: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<ThumbnailOptions> for thumbnail::Options {
|
||||
fn from(dto: ThumbnailOptions) -> Self {
|
||||
let mut options = thumbnail::Options::default();
|
||||
options.max_dimension = dto.size.map_or(options.max_dimension, Into::into);
|
||||
options.pad_to_square = dto.pad.unwrap_or(options.pad_to_square);
|
||||
options
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ThumbnailSize {
|
||||
Small,
|
||||
Large,
|
||||
Native,
|
||||
}
|
||||
|
||||
impl Into<Option<u32>> for ThumbnailSize {
|
||||
fn into(self) -> Option<u32> {
|
||||
match self {
|
||||
Self::Small => Some(400),
|
||||
Self::Large => Some(1200),
|
||||
Self::Native => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ListPlaylistsEntry {
|
||||
pub name: String,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use http::{header, HeaderValue, StatusCode};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::service::dto::ThumbnailSize;
|
||||
use crate::service::test::{constants::*, protocol, ServiceType, TestService};
|
||||
use crate::test_name;
|
||||
|
||||
|
@ -84,8 +85,9 @@ fn thumbnail_requires_auth() {
|
|||
.iter()
|
||||
.collect();
|
||||
|
||||
let size = None;
|
||||
let pad = None;
|
||||
let request = protocol::thumbnail(&path, pad);
|
||||
let request = protocol::thumbnail(&path, size, pad);
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
@ -102,8 +104,9 @@ fn thumbnail_golden_path() {
|
|||
.iter()
|
||||
.collect();
|
||||
|
||||
let size = None;
|
||||
let pad = None;
|
||||
let request = protocol::thumbnail(&path, pad);
|
||||
let request = protocol::thumbnail(&path, size, pad);
|
||||
let response = service.fetch_bytes(&request);
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
@ -116,8 +119,50 @@ fn thumbnail_bad_path_returns_not_found() {
|
|||
|
||||
let path: PathBuf = ["not_my_collection"].iter().collect();
|
||||
|
||||
let size = None;
|
||||
let pad = None;
|
||||
let request = protocol::thumbnail(&path, pad);
|
||||
let request = protocol::thumbnail(&path, size, pad);
|
||||
let response = service.fetch(&request);
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thumbnail_size_default() {
|
||||
thumbnail_size(&test_name!(), None, None, 400);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thumbnail_size_small() {
|
||||
thumbnail_size(&test_name!(), Some(ThumbnailSize::Small), None, 400);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(tarpaulin))]
|
||||
fn thumbnail_size_large() {
|
||||
thumbnail_size(&test_name!(), Some(ThumbnailSize::Large), None, 1200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(tarpaulin))]
|
||||
fn thumbnail_size_native() {
|
||||
thumbnail_size(&test_name!(), Some(ThumbnailSize::Native), None, 1423);
|
||||
}
|
||||
|
||||
fn thumbnail_size(name: &str, size: Option<ThumbnailSize>, pad: Option<bool>, expected: u32) {
|
||||
let mut service = ServiceType::new(name);
|
||||
service.complete_initial_setup();
|
||||
service.login_admin();
|
||||
service.index();
|
||||
service.login();
|
||||
|
||||
let path: PathBuf = [TEST_MOUNT_NAME, "Tobokegao", "Picnic", "Folder.png"]
|
||||
.iter()
|
||||
.collect();
|
||||
|
||||
let request = protocol::thumbnail(&path, size, pad);
|
||||
let response = service.fetch_bytes(&request);
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let thumbnail = image::load_from_memory(response.body()).unwrap().to_rgb8();
|
||||
assert_eq!(thumbnail.width(), expected);
|
||||
assert_eq!(thumbnail.height(), expected);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use http::{method::Method, Request};
|
||||
use http::{Method, Request};
|
||||
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::app::user;
|
||||
use crate::service::dto;
|
||||
use crate::{app::user, service::dto::ThumbnailSize};
|
||||
|
||||
pub fn web_index() -> Request<()> {
|
||||
Request::builder()
|
||||
|
@ -200,14 +200,32 @@ pub fn audio(path: &Path) -> Request<()> {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn thumbnail(path: &Path, pad: Option<bool>) -> Request<()> {
|
||||
pub fn thumbnail(path: &Path, size: Option<ThumbnailSize>, pad: Option<bool>) -> Request<()> {
|
||||
let path = path.to_string_lossy();
|
||||
let mut endpoint = format!("/api/thumbnail/{}", url_encode(path.as_ref()));
|
||||
match pad {
|
||||
Some(true) => endpoint.push_str("?pad=true"),
|
||||
Some(false) => endpoint.push_str("?pad=false"),
|
||||
None => (),
|
||||
let mut params = String::new();
|
||||
if let Some(s) = size {
|
||||
params.push('?');
|
||||
match s {
|
||||
ThumbnailSize::Small => params.push_str("size=small"),
|
||||
ThumbnailSize::Large => params.push_str("size=large"),
|
||||
ThumbnailSize::Native => params.push_str("size=native"),
|
||||
};
|
||||
}
|
||||
if let Some(p) = pad {
|
||||
if params.is_empty() {
|
||||
params.push('?');
|
||||
} else {
|
||||
params.push('&');
|
||||
}
|
||||
if p {
|
||||
params.push_str("pad=true");
|
||||
} else {
|
||||
params.push_str("pad=false");
|
||||
};
|
||||
}
|
||||
|
||||
let endpoint = format!("/api/thumbnail/{}{}", url_encode(path.as_ref()), params);
|
||||
|
||||
Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri(&endpoint)
|
||||
|
|
Loading…
Reference in a new issue