feat(cli): sync folders with remote paths
Build and Push Docker Container / build-and-push (push) Successful in 1m36s
Build and Push Docker Container / build-and-push (push) Successful in 1m36s
- Store synced folder locations in file_path instead of embedding them in file names. - Add ignore rules from .nanoshareignore and repeatable sync --ignore flags. - Adopt existing remote files only after SHA-256 verification. - Move adopted remotes with metadata updates instead of uploading duplicates. - Bump NanoShare to 1.23.0 and the standalone CLI to 0.2.0.
This commit is contained in:
@@ -11,6 +11,7 @@ from .auth import login as auth_login
|
||||
from .client import download, list_remote, upload
|
||||
from .completion import script as completion_script
|
||||
from .config import DEFAULT_CONFIG
|
||||
from .remote_path import join_remote_path
|
||||
from .sync import sync_once
|
||||
from .table import format_table
|
||||
|
||||
@@ -48,24 +49,29 @@ def _cmd_list(args) -> int:
|
||||
print(json.dumps(files, indent=2, ensure_ascii=False))
|
||||
elif args.format == 'tsv':
|
||||
for item in files:
|
||||
print(f"{item.get('file_id', '')}\t{item.get('file_name', '')}\t{item.get('file_size', '')}")
|
||||
print(f"{item.get('file_id', '')}\t{item.get('file_path', '')}\t{item.get('file_name', '')}\t{item.get('file_size', '')}")
|
||||
else:
|
||||
rows = [
|
||||
[item.get('file_id'), item.get('file_name'), item.get('file_size', '')]
|
||||
[item.get('file_id'), item.get('file_path', ''), item.get('file_name'), item.get('file_size', '')]
|
||||
for item in files
|
||||
]
|
||||
print(format_table(['ID', 'Name', 'Size'], rows))
|
||||
print(format_table(['ID', 'Path', 'Name', 'Size'], rows))
|
||||
return 0
|
||||
|
||||
def _resolve_remote_file(files: list[dict], selector: str) -> dict:
|
||||
id_matches = [item for item in files if item.get('file_id') == selector]
|
||||
if id_matches:
|
||||
return id_matches[0]
|
||||
full_path_matches = [item for item in files if join_remote_path(str(item.get('file_name') or ''), str(item.get('file_path') or ''), str(item.get('file_id') or '')) == selector]
|
||||
if full_path_matches:
|
||||
if len(full_path_matches) > 1:
|
||||
raise RuntimeError(f'multiple remote files at {selector!r}; use the file ID')
|
||||
return full_path_matches[0]
|
||||
name_matches = [item for item in files if item.get('file_name') == selector]
|
||||
if not name_matches:
|
||||
raise FileNotFoundError(f'no remote file matches: {selector}')
|
||||
if len(name_matches) > 1:
|
||||
raise RuntimeError(f'multiple remote files named {selector!r}; use the file ID')
|
||||
raise RuntimeError(f'multiple remote files named {selector!r}; use the file ID or full path')
|
||||
return name_matches[0]
|
||||
|
||||
def _cmd_download(args) -> int:
|
||||
@@ -74,6 +80,7 @@ def _cmd_download(args) -> int:
|
||||
remote = _resolve_remote_file(list_remote(client, args.node), args.file)
|
||||
file_id = str(remote['file_id'])
|
||||
file_name = str(remote.get('file_name') or file_id)
|
||||
remote_name = join_remote_path(file_name, str(remote.get('file_path') or ''), file_id)
|
||||
dest = Path(args.output).expanduser() if args.output else Path(file_name)
|
||||
download(client, args.node, file_id, dest)
|
||||
except Exception as exc:
|
||||
@@ -81,7 +88,7 @@ def _cmd_download(args) -> int:
|
||||
return 1
|
||||
finally:
|
||||
client.close()
|
||||
print(f'downloaded {file_name} ({file_id}) -> {dest}')
|
||||
print(f'downloaded {remote_name} ({file_id}) -> {dest}')
|
||||
return 0
|
||||
|
||||
def _cmd_sync(args) -> int:
|
||||
@@ -159,6 +166,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
sync_cmd.add_argument('--note', default='synced from nanoshare cli')
|
||||
sync_cmd.add_argument('--expires', default='')
|
||||
sync_cmd.add_argument('--delete', action='store_true', help='Delete remote files that were deleted locally.')
|
||||
sync_cmd.add_argument('--ignore', action='append', default=[], help='Ignore glob pattern for sync; repeatable.')
|
||||
sync_cmd.set_defaults(func=_cmd_sync)
|
||||
|
||||
watch_cmd = sub.add_parser('watch', help='Run sync repeatedly.')
|
||||
@@ -167,6 +175,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
watch_cmd.add_argument('--note', default='synced from nanoshare cli')
|
||||
watch_cmd.add_argument('--expires', default='')
|
||||
watch_cmd.add_argument('--delete', action='store_true')
|
||||
watch_cmd.add_argument('--ignore', action='append', default=[], help='Ignore glob pattern for sync; repeatable.')
|
||||
watch_cmd.add_argument('--interval', type=float, default=10.0)
|
||||
watch_cmd.set_defaults(func=_cmd_watch)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user