Add rename of the files with keeping the state

This commit is contained in:
Alexander
2026-06-26 20:54:01 +02:00
parent 68aba52362
commit 4f52549e46
+137 -23
View File
@@ -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<LocalOrigin, io::Error> {
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<i64, i64> = Entity::find()
let db_items: std::collections::HashMap<i64, Model> = 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<ActiveModel> = 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<i64> =
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,