From d5b4b99503466dda2999dd04c78ecc5a27bceb5b Mon Sep 17 00:00:00 2001 From: Vika Date: Mon, 27 Sep 2021 15:35:05 +0300 Subject: Improved symlink creation Now symlink creation works on Windows and creates links relative to the posts, allowing for seamless migrations of the backing directory for true portability and no data lock-in. --- src/database/file/mod.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/src/database/file/mod.rs b/src/database/file/mod.rs index d641779..a059d22 100644 --- a/src/database/file/mod.rs +++ b/src/database/file/mod.rs @@ -27,9 +27,69 @@ async fn get_lockable_file(file: File) -> RwLock { spawn_blocking(move || RwLock::new(file)).await } +// Copied from https://stackoverflow.com/questions/39340924 +// This routine is adapted from the *old* Path's `path_relative_from` +// function, which works differently from the new `relative_from` function. +// In particular, this handles the case on unix where both paths are +// absolute but with only the root as the common directory. +fn path_relative_from(path: &Path, base: &Path) -> Option { + use std::path::Component; + + if path.is_absolute() != base.is_absolute() { + if path.is_absolute() { + Some(PathBuf::from(path)) + } else { + None + } + } else { + let mut ita = path.components(); + let mut itb = base.components(); + let mut comps: Vec = vec![]; + loop { + match (ita.next(), itb.next()) { + (None, None) => break, + (Some(a), None) => { + comps.push(a); + comps.extend(ita.by_ref()); + break; + } + (None, _) => comps.push(Component::ParentDir), + (Some(a), Some(b)) if comps.is_empty() && a == b => (), + (Some(a), Some(b)) if b == Component::CurDir => comps.push(a), + (Some(_), Some(b)) if b == Component::ParentDir => return None, + (Some(a), Some(_)) => { + comps.push(Component::ParentDir); + for _ in itb { + comps.push(Component::ParentDir); + } + comps.push(a); + comps.extend(ita.by_ref()); + break; + } + } + } + Some(comps.iter().map(|c| c.as_os_str()).collect()) + } +} + +mod tests { + #[test] + fn test_relative_path_resolving() { + let path1 = std::path::Path::new("/home/vika/Projects/kittybox"); + let path2 = std::path::Path::new("/home/vika/Projects/nixpkgs"); + let relative_path = super::path_relative_from(path2, path1).unwrap(); + + assert_eq!(relative_path, std::path::Path::new("../nixpkgs")) + } +} + fn url_to_path(root: &Path, url: &str) -> PathBuf { + url_to_relative_path(url).to_path(root).to_path_buf() +} + +fn url_to_relative_path(url: &str) -> relative_path::RelativePathBuf { let url = http_types::Url::parse(url).expect("Couldn't parse a URL"); - let mut path: PathBuf = root.to_owned(); + let mut path = relative_path::RelativePathBuf::new(); path.push(url.origin().ascii_serialization() + &url.path().to_string() + ".json"); path @@ -197,14 +257,27 @@ impl Storage for FileStorage { .iter() .map(|i| i.as_str().unwrap()) { - // TODO consider using the symlink crate - // to promote cross-platform compat on Windows - // do we even need to support Windows?... if url != key && url.starts_with(user) { let link = url_to_path(&self.root_dir, url); debug!("Creating a symlink at {:?}", link); let orig = path.clone(); - spawn_blocking(move || { std::os::unix::fs::symlink(orig, link) }).await?; + spawn_blocking::<_, Result<()>>(move || { + // We're supposed to have a parent here. + let basedir = link.parent().ok_or(StorageError::new(ErrorKind::Backend, "Failed to calculate parent directory when creating a symlink"))?; + let relative = path_relative_from(&orig, &basedir).unwrap(); + println!("{:?} - {:?} = {:?}", &orig, &basedir, &relative); + println!("Created a symlink at {:?}", &link); + let symlink_result; + #[cfg(unix)] + { symlink_result = std::os::unix::fs::symlink(relative, link); } + // Wow it even supports windows. Not sure if I need it to run on Windows but oh well + #[cfg(windows)] + { symlink_result = std::os::windows::fs::symlink_file(relative, link); } + match symlink_result { + Ok(()) => Ok(()), + Err(e) => Err(e.into()) + } + }).await?; } } } -- cgit 1.4.1