mod common;
use musefs_core::{CoreError, HeaderCache, Mode};
use musefs_db::Db;
use std::os::unix::fs::MetadataExt;
#[test]
fn shrinking_the_backing_file_after_scan_yields_backing_changed() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("a.flac");
let (audio_offset, audio_length) = common::write_flac(&src, &["TITLE=T"], &[0xAB; 4096]);
let db = Db::open_in_memory().unwrap();
let id = db
.upsert_track(&musefs_db::NewTrack {
backing_path: src.to_string_lossy().into_owned(),
format: musefs_db::Format::Flac,
audio_offset,
audio_length,
backing_size: std::fs::metadata(&src).unwrap().len(),
backing_mtime_ns: common::real_mtime_ns(&src),
backing_ctime_ns: common::real_ctime_ns(&src),
})
.unwrap();
db.replace_tags(id, &[musefs_db::Tag::new("title", "T", 0)])
.unwrap();
let cache = HeaderCache::new(Mode::Synthesis);
cache.resolve(&db, id).unwrap();
let f = std::fs::OpenOptions::new().write(true).open(&src).unwrap();
f.set_len(10).unwrap();
drop(f);
let err = HeaderCache::new(Mode::Synthesis)
.resolve(&db, id)
.unwrap_err();
match err {
CoreError::BackingChanged(path) => assert!(path.ends_with("a.flac")),
other => panic!("expected BackingChanged, got {other:?}"),
}
}
#[test]
fn same_size_subsecond_rewrite_yields_backing_changed() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("a.flac");
let (audio_offset, audio_length) = common::write_flac(&src, &["TITLE=T"], &[0xAB; 4096]);
let db = Db::open_in_memory().unwrap();
let meta = std::fs::metadata(&src).unwrap();
let id = db
.upsert_track(&musefs_db::NewTrack {
backing_path: src.to_string_lossy().into_owned(),
format: musefs_db::Format::Flac,
audio_offset,
audio_length,
backing_size: meta.len(),
backing_mtime_ns: common::real_mtime_ns(&src),
backing_ctime_ns: common::real_ctime_ns(&src),
})
.unwrap();
db.replace_tags(id, &[musefs_db::Tag::new("title", "T", 0)])
.unwrap();
HeaderCache::new(Mode::Synthesis).resolve(&db, id).unwrap();
std::fs::write(&src, {
let mut v = std::fs::read(&src).unwrap();
v[usize::try_from(audio_offset).unwrap()] ^= 0xFF;
v
})
.unwrap();
let err = HeaderCache::new(Mode::Synthesis)
.resolve(&db, id)
.unwrap_err();
assert!(matches!(err, CoreError::BackingChanged(_)), "got {err:?}");
}
#[test]
fn forged_mtime_is_caught_by_ctime() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("a.flac");
let (audio_offset, audio_length) = common::write_flac(&src, &["TITLE=T"], &[0xAB; 4096]);
let db = Db::open_in_memory().unwrap();
let meta = std::fs::metadata(&src).unwrap();
let original_modified = meta.modified().unwrap();
let id = db
.upsert_track(&musefs_db::NewTrack {
backing_path: src.to_string_lossy().into_owned(),
format: musefs_db::Format::Flac,
audio_offset,
audio_length,
backing_size: meta.len(),
backing_mtime_ns: common::real_mtime_ns(&src),
backing_ctime_ns: common::real_ctime_ns(&src),
})
.unwrap();
db.replace_tags(id, &[musefs_db::Tag::new("title", "T", 0)])
.unwrap();
HeaderCache::new(Mode::Synthesis).resolve(&db, id).unwrap();
let mut v = std::fs::read(&src).unwrap();
v[usize::try_from(audio_offset).unwrap()] ^= 0xFF;
std::fs::write(&src, v).unwrap();
let f = std::fs::OpenOptions::new().write(true).open(&src).unwrap();
f.set_times(std::fs::FileTimes::new().set_modified(original_modified))
.unwrap();
drop(f);
let err = HeaderCache::new(Mode::Synthesis)
.resolve(&db, id)
.unwrap_err();
assert!(matches!(err, CoreError::BackingChanged(_)), "got {err:?}");
}
#[test]
fn displayed_mtime_is_whole_seconds() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("a.flac");
let (audio_offset, audio_length) = common::write_flac(&src, &["TITLE=T"], &[0xAB; 4096]);
let db = Db::open_in_memory().unwrap();
let meta = std::fs::metadata(&src).unwrap();
let id = db
.upsert_track(&musefs_db::NewTrack {
backing_path: src.to_string_lossy().into_owned(),
format: musefs_db::Format::Flac,
audio_offset,
audio_length,
backing_size: meta.len(),
backing_mtime_ns: common::real_mtime_ns(&src),
backing_ctime_ns: common::real_ctime_ns(&src),
})
.unwrap();
db.replace_tags(id, &[musefs_db::Tag::new("title", "T", 0)])
.unwrap();
let resolved = HeaderCache::new(Mode::Synthesis).resolve(&db, id).unwrap();
assert!(resolved.mtime_secs >= meta.mtime() && resolved.mtime_secs < 32_503_680_000);
}