fix(cli): preserve notes during sync moves
Build and Push Docker Container / build-and-push (push) Successful in 1m52s
Build and Push Docker Container / build-and-push (push) Successful in 1m52s
- Leave sync and watch notes empty by default instead of writing placeholder text. - Preserve existing remote notes when moving or adopting files without --note. - Detect tracked local moves by SHA-256 and update remote metadata safely. - Skip files that vanish during watch scans instead of aborting the sync cycle. - Bump NanoShare to 1.24.0 and the standalone CLI to 0.3.0.
This commit is contained in:
@@ -112,6 +112,15 @@ def parse_size(value: object) -> int | None:
|
||||
factor = factors.get(unit)
|
||||
return int(number * factor) if factor else None
|
||||
|
||||
def find_moved_entry(root: Path, known: dict, rel: str, local_hash: str, remote_ids: dict[str, dict]) -> tuple[str, dict] | None:
|
||||
for old_rel, entry in known.items():
|
||||
if old_rel == rel or not isinstance(entry, dict):
|
||||
continue
|
||||
file_id = entry.get('file_id')
|
||||
if entry.get('sha256') == local_hash and file_id in remote_ids and not (root / old_rel).is_file():
|
||||
return old_rel, entry
|
||||
return None
|
||||
|
||||
def find_adoptable_remote(client, node: str, path: Path, local_hash: str, remote_files: list[dict], known_ids: set[str]) -> dict | None:
|
||||
size = path.stat().st_size
|
||||
name = path.name
|
||||
@@ -152,10 +161,14 @@ def sync_once(args, client) -> int:
|
||||
remote_files = list_remote(client, args.node)
|
||||
remote_ids = remote_by_id(remote_files)
|
||||
remote_names = remote_by_name(remote_files)
|
||||
local_paths = {relative(root, path): path for path in iter_local_files(root, ignore_patterns)}
|
||||
|
||||
for rel, path in local_paths.items():
|
||||
current_hash = sha256(path)
|
||||
for path in iter_local_files(root, ignore_patterns):
|
||||
rel = relative(root, path)
|
||||
try:
|
||||
current_hash = sha256(path)
|
||||
except FileNotFoundError:
|
||||
print(f'skip vanished local file: {rel}')
|
||||
continue
|
||||
entry = known.get(rel)
|
||||
remote_id = entry.get('file_id') if isinstance(entry, dict) else None
|
||||
old_hash = entry.get('sha256') if isinstance(entry, dict) else None
|
||||
@@ -165,12 +178,31 @@ def sync_once(args, client) -> int:
|
||||
|
||||
remote_file_name, remote_file_path = split_remote_path(rel)
|
||||
if not entry:
|
||||
moved = find_moved_entry(root, known, rel, current_hash, remote_ids)
|
||||
if moved:
|
||||
old_rel, moved_entry = moved
|
||||
moved_id = moved_entry['file_id']
|
||||
update_remote(client, args.node, moved_id, remote_file_name, remote_file_path, args.note if args.note else None, args.expires or '')
|
||||
print(f'move remote: {old_rel} -> {rel}')
|
||||
known.pop(old_rel, None)
|
||||
known[rel] = {
|
||||
'file_id': moved_id,
|
||||
'sha256': current_hash,
|
||||
'local_mtime': path.stat().st_mtime,
|
||||
'file_name': remote_file_name,
|
||||
'file_path': remote_file_path,
|
||||
}
|
||||
remote_files = list_remote(client, args.node)
|
||||
remote_ids = remote_by_id(remote_files)
|
||||
remote_names = remote_by_name(remote_files)
|
||||
continue
|
||||
|
||||
adopted = find_adoptable_remote(client, args.node, path, current_hash, remote_files, {value.get('file_id') for value in known.values() if isinstance(value, dict)})
|
||||
if adopted:
|
||||
adopted_id = adopted['file_id']
|
||||
current_remote_name = join_remote_path(adopted.get('file_name') or '', adopted.get('file_path') or '', adopted_id)
|
||||
if current_remote_name != rel:
|
||||
update_remote(client, args.node, adopted_id, remote_file_name, remote_file_path, args.note or '', args.expires or '')
|
||||
update_remote(client, args.node, adopted_id, remote_file_name, remote_file_path, args.note if args.note else None, args.expires or '')
|
||||
print(f'move remote: {current_remote_name} -> {rel}')
|
||||
known[rel] = {
|
||||
'file_id': adopted_id,
|
||||
@@ -241,7 +273,7 @@ def sync_once(args, client) -> int:
|
||||
|
||||
if args.delete:
|
||||
for rel, entry in list(known.items()):
|
||||
if rel in local_paths:
|
||||
if (root / rel).is_file():
|
||||
continue
|
||||
file_id = entry.get('file_id') if isinstance(entry, dict) else None
|
||||
if file_id and file_id in remote_ids:
|
||||
|
||||
Reference in New Issue
Block a user