from __future__ import annotations import argparse import json import sys import time from pathlib import Path from .app import make_client from .auth import login as auth_login from .client import download, list_remote, upload from .config import DEFAULT_CONFIG from .sync import sync_once DEFAULT_NODE = 'picoshare' def _cmd_login(args) -> int: return auth_login(args) def _cmd_upload(args) -> int: path = Path(args.file).expanduser().resolve() if not path.is_file(): print(f'not a file: {path}', file=sys.stderr) return 1 client = make_client(args) try: result = upload(client, args.node, path, args.name or path.name, args.note or '', args.expires or '') except Exception as exc: print(f'error: {exc}', file=sys.stderr) return 1 finally: client.close() print(json.dumps(result, indent=2, ensure_ascii=False)) return 0 def _cmd_list(args) -> int: client = make_client(args) try: files = list_remote(client, args.node) except Exception as exc: print(f'error: {exc}', file=sys.stderr) return 1 finally: client.close() for item in files: print(f"{item.get('file_id')}\t{item.get('file_name')}\t{item.get('file_size', '')}") return 0 def _cmd_download(args) -> int: dest = Path(args.output).expanduser() if args.output else Path(args.file_id) client = make_client(args) try: download(client, args.node, args.file_id, dest) except Exception as exc: print(f'error: {exc}', file=sys.stderr) return 1 finally: client.close() print(f'downloaded {args.file_id} -> {dest}') return 0 def _cmd_sync(args) -> int: client = make_client(args) try: return sync_once(args, client) finally: client.close() def _cmd_watch(args) -> int: print(f'watching {args.folder} every {args.interval}s') while True: code = _cmd_sync(args) if code: return code time.sleep(args.interval) def _add_common(parser: argparse.ArgumentParser) -> None: parser.add_argument('--config', default=str(DEFAULT_CONFIG), help='Path to config.toml.') parser.add_argument('--url', action='append', metavar='NODE=URL', help='Override/add a node URL.') parser.add_argument('--token', help='Static bearer token.') parser.add_argument('--refresh-token', dest='refresh_token', help='Refresh token to auto-exchange for access tokens.') parser.add_argument('--token-url', dest='token_url', help='Token-refresh endpoint.') parser.add_argument('--source', default='nanoshare-cli', help='Source node name.') parser.add_argument('--timeout', type=float, default=30.0) parser.add_argument('--node', default=DEFAULT_NODE, help='NanoShare node name.') def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(prog='nanoshare', description='NanoShare CLI and folder sync client.') sub = parser.add_subparsers(dest='command', required=True) login = sub.add_parser('login', help='Login via browser and write CLI config.') login.add_argument('base_url', help='NanoShare base URL, e.g. https://nanoshare.example') login.add_argument('--config', type=Path, default=DEFAULT_CONFIG) login.add_argument('--node', default=DEFAULT_NODE) login.add_argument('--source', default='nanoshare-cli') login.add_argument('--scope', default='files') login.add_argument('--callback-host', default='127.0.0.1') login.add_argument('--callback-port', type=int, default=0) login.add_argument('--login-timeout', type=float, default=300.0) login.add_argument('--timeout', type=float, default=30.0) login.add_argument('--no-browser', action='store_true') login.set_defaults(func=_cmd_login) upload_cmd = sub.add_parser('upload', help='Upload one file.') _add_common(upload_cmd) upload_cmd.add_argument('file') upload_cmd.add_argument('--name', help='Remote file name.') upload_cmd.add_argument('--note', default='uploaded from nanoshare cli') upload_cmd.add_argument('--expires', default='') upload_cmd.set_defaults(func=_cmd_upload) list_cmd = sub.add_parser('list', help='List remote files.') _add_common(list_cmd) list_cmd.set_defaults(func=_cmd_list) download_cmd = sub.add_parser('download', help='Download one file via private API endpoint.') _add_common(download_cmd) download_cmd.add_argument('file_id') download_cmd.add_argument('-o', '--output') download_cmd.set_defaults(func=_cmd_download) sync_cmd = sub.add_parser('sync', help='Two-way folder sync MVP.') _add_common(sync_cmd) sync_cmd.add_argument('folder') 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.set_defaults(func=_cmd_sync) watch_cmd = sub.add_parser('watch', help='Run sync repeatedly.') _add_common(watch_cmd) watch_cmd.add_argument('folder') 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('--interval', type=float, default=10.0) watch_cmd.set_defaults(func=_cmd_watch) args = parser.parse_args(argv) return args.func(args) if __name__ == '__main__': sys.exit(main())