From 4f52549e46c2691c431837b814656282d9443b61 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 26 Jun 2026 20:54:01 +0200 Subject: [PATCH] Add rename of the files with keeping the state --- src/local.rs | 160 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 137 insertions(+), 23 deletions(-) diff --git a/src/local.rs b/src/local.rs index eaf4208..99fa44f 100644 --- a/src/local.rs +++ b/src/local.rs @@ -60,9 +60,7 @@ impl LocalItem { .unwrap_or(0); hasher.write_u64(self.inode.0); - hasher.write(self.name.as_bytes()); hasher.write(self.original_path.as_os_str().as_bytes()); - hasher.write(self.local_path.as_os_str().as_bytes()); hasher.write_u64(metadata.ctime() as u64); hasher.write_u64(mtime); hasher.write_u64(crtime); @@ -97,14 +95,15 @@ impl LocalOrigin { ) -> Result { println!("Initializing the LocalOrigin"); - let snapshot = LocalOrigin::build_snapshot(Path::new(&source), Path::new(&destination))?; + let mut snapshot = + LocalOrigin::build_snapshot(Path::new(&source), Path::new(&destination))?; - let db_map: std::collections::HashMap = Entity::find() + let db_items: std::collections::HashMap = Entity::find() .all(&client) .await .unwrap() .into_iter() - .map(|e| (e.inode, e.hash)) + .map(|e| (e.inode, e)) .collect(); let mut to_insert: Vec = vec![]; @@ -113,9 +112,9 @@ impl LocalOrigin { for (ino, item) in &snapshot { let ino_i64 = ino.0 as i64; - match db_map.get(&ino_i64) { + match db_items.get(&ino_i64) { None => to_insert.push(ActiveModel::from(item)), - Some(&db_hash) if db_hash != item.hash as i64 => { + Some(db_item) if db_item.hash != item.hash as i64 => { to_update.push(ActiveModel::from(item)) } _ => {} @@ -124,7 +123,7 @@ impl LocalOrigin { let fresh_inodes: std::collections::HashSet = snapshot.keys().map(|i| i.0 as i64).collect(); - for ino in db_map.keys().filter(|i| !fresh_inodes.contains(i)) { + for ino in db_items.keys().filter(|i| !fresh_inodes.contains(i)) { to_delete.push(*ino); } @@ -138,6 +137,36 @@ impl LocalOrigin { Entity::delete_by_id(ino).exec(&client).await.unwrap(); } + // For unchanged items (hash matches), restore virtual paths from DB + // so that renames performed in a previous session are preserved. + let restorations: Vec<(INodeNo, String, PathBuf)> = db_items + .values() + .filter_map(|db_item| { + let ino = INodeNo(db_item.inode as u64); + snapshot + .get(&ino) + .filter(|item| item.hash as i64 == db_item.hash) + .map(|_| { + ( + ino, + db_item.name.clone(), + PathBuf::from(&db_item.local_path), + ) + }) + }) + .collect(); + + for (_, _, local_path) in &restorations { + LocalOrigin::ensure_virtual_dirs(local_path, Path::new(&source), &mut snapshot); + } + for (ino, name, local_path) in restorations { + if let Some(item) = snapshot.get_mut(&ino) { + item.name = name; + item.parent_inode = LocalOrigin::parent_inode_from_path(&local_path); + item.local_path = local_path; + } + } + let local_origin = LocalOrigin { source: source.into(), destination: destination.into(), @@ -290,6 +319,21 @@ impl LocalOrigin { }; } + fn parent_inode_from_path(local_path: &Path) -> INodeNo { + let components: Vec<_> = local_path.components().collect(); + if components.len() <= 1 { + return INodeNo::ROOT; + } + let mut parent_path = String::new(); + for (i, comp) in components[..components.len() - 1].iter().enumerate() { + if i > 0 { + parent_path.push('/'); + } + parent_path.push_str(comp.as_os_str().to_str().unwrap_or_default()); + } + LocalOrigin::virtual_inode(&parent_path) + } + fn virtual_inode(path: &str) -> INodeNo { let mut hasher = XxHash64::with_seed(5678); hasher.write(path.as_bytes()); @@ -412,6 +456,90 @@ impl Filesystem for LocalOrigin { reply.ok(); } + fn rename( + &self, + _req: &Request, + parent: INodeNo, + name: &std::ffi::OsStr, + newparent: INodeNo, + newname: &std::ffi::OsStr, + _flags: fuser::RenameFlags, + reply: fuser::ReplyEmpty, + ) { + let name_str = match name.to_str() { + Some(s) => s, + None => { + reply.error(Errno::EINVAL); + return; + } + }; + let newname_str = match newname.to_str() { + Some(s) => s, + None => { + reply.error(Errno::EINVAL); + return; + } + }; + + let mut files = self.files.lock().unwrap(); + + let item_ino = match files + .iter() + .find(|(_, v)| v.parent_inode == parent && v.name == name_str) + { + Some((ino, _)) => *ino, + None => { + reply.error(Errno::ENOENT); + return; + } + }; + + let new_local_path = if newparent == INodeNo::ROOT { + PathBuf::from(newname_str) + } else { + match files.get(&newparent) { + Some(dir) => dir.local_path.join(newname_str), + None => { + reply.error(Errno::ENOENT); + return; + } + } + }; + + let new_parent_inode = LocalOrigin::parent_inode_from_path(&new_local_path); + + let item = files.get_mut(&item_ino).unwrap(); + item.name = newname_str.to_string(); + item.local_path = new_local_path.clone(); + item.parent_inode = new_parent_inode; + + let inode_i64 = item_ino.0 as i64; + let new_name_owned = newname_str.to_string(); + let new_local_path_str = new_local_path.to_string_lossy().into_owned(); + let client = self.client.clone(); + + drop(files); + + tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .unwrap() + .block_on(async move { + use sea_orm::ActiveValue::Set; + ActiveModel { + inode: Set(inode_i64), + name: Set(new_name_owned), + local_path: Set(new_local_path_str), + ..Default::default() + } + .update(&client) + .await + .unwrap(); + }); + + reply.ok(); + } + fn lookup( &self, _req: &Request, @@ -529,21 +657,7 @@ impl From<&LocalItem> for ActiveModel { impl From<(LocalItemEntity, fs::Metadata)> for LocalItem { fn from((entity, metadata): (LocalItemEntity, fs::Metadata)) -> Self { let local_path = PathBuf::from(&entity.local_path); - let parent_inode = { - let components: Vec<_> = local_path.components().collect(); - if components.len() <= 1 { - INodeNo::ROOT - } else { - let mut parent_path = String::new(); - for (i, comp) in components[..components.len() - 1].iter().enumerate() { - if i > 0 { - parent_path.push('/'); - } - parent_path.push_str(comp.as_os_str().to_str().unwrap_or_default()); - } - LocalOrigin::virtual_inode(&parent_path) - } - }; + let parent_inode = LocalOrigin::parent_inode_from_path(&local_path); let local_item = LocalItem { inode: INodeNo(entity.inode as u64), parent_inode,