about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2021-09-27 15:35:05 +0300
committerVika <vika@fireburn.ru>2021-09-27 15:35:05 +0300
commitd5b4b99503466dda2999dd04c78ecc5a27bceb5b (patch)
treec1f178c8dc173501c664eeaa2f1a25cd9cb9275e
parent1e204011e0e99de939475d4ce3b23b7b578f3b20 (diff)
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.
-rw-r--r--src/database/file/mod.rs83
1 files 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<File> {
     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<PathBuf> {
+    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<Component> = 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?;
                 }
             }
         }