feature/shuffle-sort #30
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1354,6 +1354,7 @@ dependencies = [
|
|||||||
"notify",
|
"notify",
|
||||||
"path-absolutize",
|
"path-absolutize",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -37,3 +37,4 @@ actix-web-prom = "0.9.0"
|
|||||||
prometheus = "0.13"
|
prometheus = "0.13"
|
||||||
lazy_static = "1.5"
|
lazy_static = "1.5"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ pub struct PhotosResponse {
|
|||||||
pub dirs: Vec<String>,
|
pub dirs: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Deserialize, PartialEq, Debug)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum SortType {
|
||||||
|
Shuffle,
|
||||||
|
NameAsc,
|
||||||
|
NameDesc,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct FilesRequest {
|
pub struct FilesRequest {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@@ -107,6 +115,7 @@ pub struct FilesRequest {
|
|||||||
pub tag_ids: Option<String>,
|
pub tag_ids: Option<String>,
|
||||||
pub tag_filter_mode: Option<FilterMode>,
|
pub tag_filter_mode: Option<FilterMode>,
|
||||||
pub recursive: Option<bool>,
|
pub recursive: Option<bool>,
|
||||||
|
pub sort: Option<SortType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Deserialize, PartialEq, Debug)]
|
#[derive(Copy, Clone, Deserialize, PartialEq, Debug)]
|
||||||
|
|||||||
35
src/files.rs
35
src/files.rs
@@ -16,13 +16,15 @@ use actix_web::{
|
|||||||
};
|
};
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
|
|
||||||
use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse};
|
use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse, SortType};
|
||||||
use crate::{create_thumbnails, AppState};
|
use crate::{create_thumbnails, AppState};
|
||||||
|
|
||||||
use crate::error::IntoHttpError;
|
use crate::error::IntoHttpError;
|
||||||
use crate::tags::TagDao;
|
use crate::tags::TagDao;
|
||||||
use crate::video::StreamActor;
|
use crate::video::StreamActor;
|
||||||
use path_absolutize::*;
|
use path_absolutize::*;
|
||||||
|
use rand::prelude::SliceRandom;
|
||||||
|
use rand::{random, thread_rng};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||||
@@ -52,6 +54,14 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
return dao
|
return dao
|
||||||
.get_files_with_any_tag_ids(tag_ids.clone())
|
.get_files_with_any_tag_ids(tag_ids.clone())
|
||||||
.context(format!("Failed to get files with tag_ids: {:?}", tag_ids))
|
.context(format!("Failed to get files with tag_ids: {:?}", tag_ids))
|
||||||
|
.map(|mut tagged_files| {
|
||||||
|
return if let Some(sort_type) = req.sort {
|
||||||
|
debug!("Sorting files: {:?}", sort_type);
|
||||||
|
sort(tagged_files, sort_type)
|
||||||
|
} else {
|
||||||
|
tagged_files
|
||||||
|
};
|
||||||
|
})
|
||||||
.map(|tagged_files| match filter_mode {
|
.map(|tagged_files| match filter_mode {
|
||||||
FilterMode::Any => tagged_files
|
FilterMode::Any => tagged_files
|
||||||
.iter()
|
.iter()
|
||||||
@@ -89,7 +99,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
if let Ok(files) = file_system.get_files_for_path(search_path) {
|
if let Ok(files) = file_system.get_files_for_path(search_path) {
|
||||||
debug!("Valid search path: {:?}", search_path);
|
debug!("Valid search path: {:?}", search_path);
|
||||||
|
|
||||||
let photos = files
|
let mut photos = files
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&f| {
|
.filter(|&f| {
|
||||||
f.metadata().map_or_else(
|
f.metadata().map_or_else(
|
||||||
@@ -127,6 +137,11 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
})
|
})
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
if let Some(sort_type) = req.sort {
|
||||||
|
debug!("Sorting files: {:?}", sort_type);
|
||||||
|
photos = sort(photos, sort_type)
|
||||||
|
}
|
||||||
|
|
||||||
let dirs = files
|
let dirs = files
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&f| f.metadata().map_or(false, |md| md.is_dir()))
|
.filter(|&f| f.metadata().map_or(false, |md| md.is_dir()))
|
||||||
@@ -144,6 +159,20 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sort(mut files: Vec<String>, sort_type: SortType) -> Vec<String> {
|
||||||
|
match sort_type {
|
||||||
|
SortType::Shuffle => files.shuffle(&mut thread_rng()),
|
||||||
|
SortType::NameAsc => {
|
||||||
|
files.sort_by(|l, r| l.cmp(&r));
|
||||||
|
}
|
||||||
|
SortType::NameDesc => {
|
||||||
|
files.sort_by(|l, r| r.cmp(&l));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_files(dir: &Path) -> io::Result<Vec<PathBuf>> {
|
pub fn list_files(dir: &Path) -> io::Result<Vec<PathBuf>> {
|
||||||
let files = read_dir(dir)?
|
let files = read_dir(dir)?
|
||||||
.filter_map(|res| res.ok())
|
.filter_map(|res| res.ok())
|
||||||
@@ -425,7 +454,7 @@ mod tests {
|
|||||||
|
|
||||||
let body: PhotosResponse = serde_json::from_str(&response.read_to_str()).unwrap();
|
let body: PhotosResponse = serde_json::from_str(&response.read_to_str()).unwrap();
|
||||||
debug!("{:?}", body);
|
debug!("{:?}", body);
|
||||||
|
|
||||||
assert!(body.photos.contains(&String::from("photo.jpg")));
|
assert!(body.photos.contains(&String::from("photo.jpg")));
|
||||||
assert!(body.dirs.contains(&String::from("test-dir")));
|
assert!(body.dirs.contains(&String::from("test-dir")));
|
||||||
assert!(body
|
assert!(body
|
||||||
|
|||||||
Reference in New Issue
Block a user