Files
ImageApi/src/utils.rs
Cameron dc2a96162e fix(dates): prefer earliest of fs created/modified as fallback
On copied or restored files (e.g. a backup library), the OS stamps
created at copy time while modified is preserved from the source, so
the earlier of the two is a better proxy for when the content
originated. Adds utils::earliest_fs_time and threads it through the
three spots that fall back to filesystem dates: photos-list sort,
memories grouping, and insight-generation timestamp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:20:12 -04:00

100 lines
2.7 KiB
Rust

use std::time::SystemTime;
/// Normalize a file path to use forward slashes for cross-platform consistency
/// This ensures paths stored in the database always use `/` regardless of OS
///
/// # Examples
/// ```
/// use image_api::utils::normalize_path;
///
/// assert_eq!(normalize_path("foo\\bar\\baz.jpg"), "foo/bar/baz.jpg");
/// assert_eq!(normalize_path("foo/bar/baz.jpg"), "foo/bar/baz.jpg");
/// ```
pub fn normalize_path(path: &str) -> String {
path.replace('\\', "/")
}
/// Pick the earlier of a file's created and modified timestamps.
///
/// On copied/restored files (e.g., a backup library), `created` is stamped at
/// copy time while `modified` is preserved from the source — so the earlier
/// of the two is a better proxy for when the content originated. Falls back
/// to whichever timestamp is available if one platform lacks the other.
pub fn earliest_fs_time(md: &std::fs::Metadata) -> Option<SystemTime> {
match (md.created().ok(), md.modified().ok()) {
(Some(c), Some(m)) => Some(c.min(m)),
(Some(t), None) | (None, Some(t)) => Some(t),
(None, None) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_path_with_backslashes() {
assert_eq!(normalize_path("foo\\bar\\baz.jpg"), "foo/bar/baz.jpg");
}
#[test]
fn test_normalize_path_with_forward_slashes() {
assert_eq!(normalize_path("foo/bar/baz.jpg"), "foo/bar/baz.jpg");
}
#[test]
fn test_normalize_path_mixed() {
assert_eq!(
normalize_path("foo\\bar/baz\\qux.jpg"),
"foo/bar/baz/qux.jpg"
);
}
#[test]
fn test_normalize_path_empty() {
assert_eq!(normalize_path(""), "");
}
#[test]
fn test_normalize_path_absolute_windows() {
assert_eq!(
normalize_path("C:\\Users\\Photos\\image.jpg"),
"C:/Users/Photos/image.jpg"
);
}
#[test]
fn test_normalize_path_unc_path() {
assert_eq!(
normalize_path("\\\\server\\share\\folder\\file.jpg"),
"//server/share/folder/file.jpg"
);
}
#[test]
fn test_normalize_path_single_filename() {
assert_eq!(normalize_path("image.jpg"), "image.jpg");
}
#[test]
fn test_normalize_path_trailing_slash() {
assert_eq!(normalize_path("foo\\bar\\"), "foo/bar/");
}
#[test]
fn test_normalize_path_multiple_consecutive_backslashes() {
assert_eq!(
normalize_path("foo\\\\bar\\\\\\baz.jpg"),
"foo//bar///baz.jpg"
);
}
#[test]
fn test_normalize_path_deep_nesting() {
assert_eq!(
normalize_path("a\\b\\c\\d\\e\\f\\g\\file.jpg"),
"a/b/c/d/e/f/g/file.jpg"
);
}
}