mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
Merge pull request #1639 from storytold/fix/relink-windows-paths
Footage: relinking a moved project keeps Windows separators and UNC shares
This commit is contained in:
@@ -126,17 +126,18 @@ pub(crate) fn find_moved(services: &dyn Services, project_dir: &str, f: &Footage
|
||||
})
|
||||
}
|
||||
|
||||
/// The folder a project file is in.
|
||||
/// The folder a project file is in, with the path's own separator and its leading `/` or UNC `\\`.
|
||||
pub(crate) fn project_dir(project_path: &str) -> Option<String> {
|
||||
let p = parts(project_path);
|
||||
if p.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
let mut dir = join(if project_path.starts_with('/') { "/" } else { "" }, p.get(..p.len() - 1)?);
|
||||
if !project_path.starts_with('/') {
|
||||
dir = dir.trim_start_matches(['/', '\\']).to_string();
|
||||
}
|
||||
Some(dir)
|
||||
let dir = p.get(..p.len().checked_sub(1)?).filter(|d| !d.is_empty())?;
|
||||
let root = if project_path.starts_with("\\\\") {
|
||||
"\\\\"
|
||||
} else if project_path.starts_with('/') {
|
||||
"/"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
Some(format!("{root}{}", dir.join(&sep_of(project_path).to_string())))
|
||||
}
|
||||
|
||||
/// Relink `found` in one undo step and say how many.
|
||||
@@ -262,6 +263,10 @@ mod tests {
|
||||
);
|
||||
assert_eq!(project_dir("/x/y/p.ecproj").as_deref(), Some("/x/y"));
|
||||
assert_eq!(project_dir("p.ecproj"), None);
|
||||
// Windows paths keep their separator, and a UNC share its `\\`.
|
||||
assert_eq!(project_dir("C:\\Work\\Job\\p.ecproj").as_deref(), Some("C:\\Work\\Job"));
|
||||
assert_eq!(project_dir("\\\\server\\share\\Job\\p.ecproj").as_deref(), Some("\\\\server\\share\\Job"));
|
||||
assert_eq!(moved_candidates("\\\\server\\share\\Job", "D:\\Old\\Job\\shots\\a.png")[2], "\\\\server\\share\\Job\\shots\\a.png");
|
||||
// Hostile input doesn't panic.
|
||||
for (a, b) in [("", ""), ("/", "/"), ("x", ""), ("\\\\", "//"), ("/a", "/a/b/c")] {
|
||||
let _ = moved_root(a, b);
|
||||
@@ -302,11 +307,11 @@ mod tests {
|
||||
let b = add(&mut s, "/gone/proj/shots/b.mov");
|
||||
let m = add(&mut s, "/gone/proj/audio/m.wav");
|
||||
let other = add(&mut s, "/elsewhere/c.mov");
|
||||
let target = new.join("shots/a.mov").to_string_lossy().to_string();
|
||||
let target = new.join("shots").join("a.mov").to_string_lossy().to_string();
|
||||
let r = s.execute("footage.relink", json!({"item": a.0, "path": target})).unwrap();
|
||||
assert_eq!(r["relinked"], json!([a.0, b.0, m.0]), "{r}");
|
||||
assert_eq!(r["stillMissing"], 1);
|
||||
assert_eq!(path_of(&s, m), new.join("audio/m.wav").to_string_lossy());
|
||||
assert_eq!(path_of(&s, m), new.join("audio").join("m.wav").to_string_lossy());
|
||||
assert_eq!(path_of(&s, other), "/elsewhere/c.mov");
|
||||
// One undo step brings every path back.
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
|
||||
@@ -254,7 +254,7 @@ mod tests {
|
||||
std::fs::write(d.join("Job/shots/a.png"), b"x").unwrap();
|
||||
// Saved on another machine: the footage sat next to the project in /Users/x/Job.
|
||||
let mut s = session_with(&["/Users/x/Job/shots/a.png", "/Users/x/elsewhere/b.png"]);
|
||||
let path = d.join("Job/p.ecproj");
|
||||
let path = d.join("Job").join("p.ecproj");
|
||||
s.execute("file.saveAs", json!({"path": path.to_string_lossy()})).unwrap();
|
||||
let mut o = Session::default();
|
||||
o.execute("file.open", json!({"path": path.to_string_lossy()})).unwrap();
|
||||
@@ -269,7 +269,7 @@ mod tests {
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert!(paths.contains(&d.join("Job/shots/a.png").to_string_lossy().to_string()), "{paths:?}");
|
||||
assert!(paths.contains(&d.join("Job").join("shots").join("a.png").to_string_lossy().to_string()), "{paths:?}");
|
||||
assert!(!o.is_dirty(), "finding the files is not an edit");
|
||||
let _ = std::fs::remove_dir_all(d);
|
||||
}
|
||||
|
||||
@@ -38,14 +38,14 @@ fn replacing_a_missing_file_relinks_the_others_that_moved_with_it() {
|
||||
let before = footage_paths(&s);
|
||||
assert_eq!(before.len(), 2, "{before:?}");
|
||||
// The job folder moves; both files go missing.
|
||||
let new = root.join("Archive/Job");
|
||||
let new = root.join("Archive").join("Job");
|
||||
std::fs::create_dir_all(root.join("Archive")).unwrap();
|
||||
std::fs::rename(&old, &new).unwrap();
|
||||
let r = s.execute("footage.check", json!({"wait": true})).unwrap();
|
||||
assert_eq!(r["missing"], 2, "{r}");
|
||||
// Locating one finds the other.
|
||||
let a = before.iter().find(|(_, p, _)| p.ends_with("a.png")).unwrap().0;
|
||||
let r = s.execute("file.replaceFootage", json!({"item": a, "path": new.join("stills/a.png").to_string_lossy()})).unwrap();
|
||||
let r = s.execute("file.replaceFootage", json!({"item": a, "path": new.join("stills").join("a.png").to_string_lossy()})).unwrap();
|
||||
assert_eq!(r["relinked"].as_array().map(Vec::len), Some(1), "{r}");
|
||||
let after = footage_paths(&s);
|
||||
assert!(after.iter().all(|(_, p, missing)| p.starts_with(&*new.to_string_lossy()) && !missing), "{after:?}");
|
||||
|
||||
Reference in New Issue
Block a user