feat(cli): sync folders with remote paths
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:
2026-07-27 20:53:00 +02:00
parent 77c76b16c4
commit 993337be9f
13 changed files with 369 additions and 41 deletions
+16 -3
View File
@@ -12,6 +12,8 @@ from __future__ import annotations
import base64
import os
from pathlib import PurePosixPath
from quart import current_app
from my_modules.app.setup import LIMITER
@@ -24,6 +26,14 @@ RPC_SCOPES = ('files', 'mesh')
router = Router('picoshare')
def _safe_file_path(value: str) -> str:
raw = (value or '').replace('\\', '/')
parts = [
part for part in PurePosixPath(raw).parts
if part not in ('', '.', '..', '/')
]
return '/'.join(parts)
def _user_id(ctx):
if ctx.principal is None:
raise Unauthorized('authentication required')
@@ -47,10 +57,12 @@ async def files_upload(params, ctx):
else:
raise InvalidParams('provide text or content_b64')
file_name = params.get('file_name') or iso_stamp_filename('mesh', default_ext)
file_name = PurePosixPath(str(params.get('file_name') or iso_stamp_filename('mesh', default_ext)).replace('\\', '/')).name
file_path = _safe_file_path(str(params.get('file_path') or ''))
storage_id = await current_app.convex.send_to_storage(data=data, content_type=content_type)
file_record = await current_app.convex.add_file(
file_name=file_name,
file_path=file_path,
file_size=format_size(len(data)),
note=params.get('note', ''),
content_type=content_type,
@@ -59,7 +71,7 @@ async def files_upload(params, ctx):
user_id=user_id,
)
file_id = file_record.get('file_id') if isinstance(file_record, dict) else None
return {'file_id': file_id, 'file_name': file_name, 'size': len(data), 'content_type': content_type}
return {'file_id': file_id, 'file_name': file_name, 'file_path': file_path, 'size': len(data), 'content_type': content_type}
@router.method('files.list')
async def files_list(params, ctx):
@@ -85,12 +97,13 @@ async def files_info(params, ctx):
@router.method('files.update')
async def files_update(params, ctx):
file_id = params.get('file_id')
file_name = params.get('file_name')
file_name = PurePosixPath(str(params.get('file_name') or '').replace('\\', '/')).name
if not file_id or not file_name:
raise InvalidParams('file_id and file_name are required')
await current_app.convex.update_file(
file_id=file_id,
file_name=file_name,
file_path=_safe_file_path(str(params.get('file_path') or '')),
note=params.get('note', ''),
expires_at=ensure_utc(parse_expires(params.get('expires', ''))),
user_id=_user_id(ctx),