pinakes-server: relativize media paths against configured root directories
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I9f113e6402030c46ad97f636985b5d6c6a6a6964
This commit is contained in:
parent
5077e9f117
commit
9c67c81a79
11 changed files with 212 additions and 42 deletions
|
|
@ -1,9 +1,39 @@
|
|||
use std::{collections::HashMap, path::PathBuf};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Strip the longest matching root prefix from `full_path`, returning a
|
||||
/// forward-slash-separated relative path string. Falls back to the full path
|
||||
/// string when no root matches. If `roots` is empty, returns the full path as a
|
||||
/// string so internal callers that have not yet migrated still work.
|
||||
pub fn relativize_path(full_path: &Path, roots: &[PathBuf]) -> String {
|
||||
let mut best: Option<&PathBuf> = None;
|
||||
for root in roots {
|
||||
if full_path.starts_with(root) {
|
||||
let is_longer = best.map_or(true, |b| root.components().count() > b.components().count());
|
||||
if is_longer {
|
||||
best = Some(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(root) = best {
|
||||
if let Ok(rel) = full_path.strip_prefix(root) {
|
||||
// Normalise to forward slashes on all platforms.
|
||||
return rel
|
||||
.components()
|
||||
.map(|c| c.as_os_str().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
}
|
||||
}
|
||||
full_path.to_string_lossy().into_owned()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MediaResponse {
|
||||
pub id: String,
|
||||
|
|
@ -233,12 +263,18 @@ impl From<pinakes_core::model::ManagedStorageStats>
|
|||
}
|
||||
}
|
||||
|
||||
// Conversion helpers
|
||||
impl From<pinakes_core::model::MediaItem> for MediaResponse {
|
||||
fn from(item: pinakes_core::model::MediaItem) -> Self {
|
||||
impl MediaResponse {
|
||||
/// Build a `MediaResponse` from a `MediaItem`, stripping the longest
|
||||
/// matching root prefix from the path before serialization. Pass the
|
||||
/// configured root directories so that clients receive a relative path
|
||||
/// (e.g. `"Music/song.mp3"`) rather than a full server filesystem path.
|
||||
pub fn new(
|
||||
item: pinakes_core::model::MediaItem,
|
||||
roots: &[PathBuf],
|
||||
) -> Self {
|
||||
Self {
|
||||
id: item.id.0.to_string(),
|
||||
path: item.path.to_string_lossy().to_string(),
|
||||
path: relativize_path(&item.path, roots),
|
||||
file_name: item.file_name,
|
||||
media_type: serde_json::to_value(item.media_type)
|
||||
.ok()
|
||||
|
|
@ -282,6 +318,60 @@ impl From<pinakes_core::model::MediaItem> for MediaResponse {
|
|||
}
|
||||
}
|
||||
|
||||
// Conversion helpers
|
||||
impl From<pinakes_core::model::MediaItem> for MediaResponse {
|
||||
/// Convert using no root stripping. Prefer `MediaResponse::new(item, roots)`
|
||||
/// at route-handler call sites where roots are available.
|
||||
fn from(item: pinakes_core::model::MediaItem) -> Self {
|
||||
Self::new(item, &[])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn relativize_path_strips_matching_root() {
|
||||
let roots = vec![PathBuf::from("/home/user/music")];
|
||||
let path = Path::new("/home/user/music/artist/song.mp3");
|
||||
assert_eq!(relativize_path(path, &roots), "artist/song.mp3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relativize_path_picks_longest_root() {
|
||||
let roots = vec![
|
||||
PathBuf::from("/home/user"),
|
||||
PathBuf::from("/home/user/music"),
|
||||
];
|
||||
let path = Path::new("/home/user/music/song.mp3");
|
||||
assert_eq!(relativize_path(path, &roots), "song.mp3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relativize_path_no_match_returns_full() {
|
||||
let roots = vec![PathBuf::from("/home/user/music")];
|
||||
let path = Path::new("/srv/videos/movie.mkv");
|
||||
assert_eq!(relativize_path(path, &roots), "/srv/videos/movie.mkv");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relativize_path_empty_roots_returns_full() {
|
||||
let path = Path::new("/home/user/music/song.mp3");
|
||||
assert_eq!(
|
||||
relativize_path(path, &[]),
|
||||
"/home/user/music/song.mp3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relativize_path_exact_root_match() {
|
||||
let roots = vec![PathBuf::from("/media/library")];
|
||||
let path = Path::new("/media/library/file.mp3");
|
||||
assert_eq!(relativize_path(path, &roots), "file.mp3");
|
||||
}
|
||||
}
|
||||
|
||||
// Watch progress
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WatchProgressRequest {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue