feat(cloud-files): Geteilte Ordner + Rechtsklick-Menue
Backend:
- /api/sync/tree liefert jetzt {tree, shared} - shared enthaelt alle
Dateien die MIT dem Benutzer geteilt wurden (FilePermission), nur
Top-Level-Shares, mit Owner-Name im Anzeigenamen
- updated_at zusaetzlich als modified_at im Response fuer Client-
Kompatibilitaet
Client:
- fetch_remote_entries merged Shared-Subtree unter virtuellem Ordner
"Geteilt mit mir" (synthetische ID -1) in den Mount-Point
- modified_at faellt auf updated_at zurueck, falls nicht vorhanden
Kontextmenue:
- Neue HKCU-Registry-Eintraege fuer "Immer offline verfuegbar" und
"Speicher freigeben", AppliesTo filtert auf Mount-Pfad, sodass die
Verben nur bei Dateien unterhalb des Sync-Ordners erscheinen
- Aufruf der eigenen .exe mit --pin / --unpin <file>
- handle_cli_shortcuts fuehrt die Aktion aus und beendet sofort,
ohne die UI/Tray/Single-Instance-Logik anzustossen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+63
-16
@@ -1254,32 +1254,79 @@ def list_locks():
|
||||
@api_bp.route('/sync/tree', methods=['GET'])
|
||||
@token_required
|
||||
def sync_tree():
|
||||
"""Returns complete file tree with checksums for sync clients."""
|
||||
"""Returns complete file tree with checksums for sync clients.
|
||||
|
||||
Includes both files owned by the user (under 'tree') and files
|
||||
shared WITH the user (as a virtual 'Geteilt mit mir' folder under
|
||||
'shared'). The client merges both.
|
||||
"""
|
||||
user = request.current_user
|
||||
|
||||
def _entry(f):
|
||||
entry = {
|
||||
'id': f.id,
|
||||
'name': f.name,
|
||||
'is_folder': f.is_folder,
|
||||
'size': f.size,
|
||||
'checksum': f.checksum,
|
||||
'updated_at': f.updated_at.isoformat() if f.updated_at else None,
|
||||
'modified_at': f.updated_at.isoformat() if f.updated_at else None,
|
||||
}
|
||||
lock = FileLock.get_lock(f.id)
|
||||
if lock:
|
||||
entry['locked'] = True
|
||||
entry['locked_by'] = lock.user.username
|
||||
return entry
|
||||
|
||||
def _build_tree(parent_id):
|
||||
files = File.query.filter_by(owner_id=user.id, parent_id=parent_id, is_trashed=False)\
|
||||
.order_by(File.is_folder.desc(), File.name).all()
|
||||
result = []
|
||||
for f in files:
|
||||
entry = {
|
||||
'id': f.id,
|
||||
'name': f.name,
|
||||
'is_folder': f.is_folder,
|
||||
'size': f.size,
|
||||
'checksum': f.checksum,
|
||||
'updated_at': f.updated_at.isoformat() if f.updated_at else None,
|
||||
}
|
||||
lock = FileLock.get_lock(f.id)
|
||||
if lock:
|
||||
entry['locked'] = True
|
||||
entry['locked_by'] = lock.user.username
|
||||
e = _entry(f)
|
||||
if f.is_folder:
|
||||
entry['children'] = _build_tree(f.id)
|
||||
result.append(entry)
|
||||
e['children'] = _build_tree(f.id)
|
||||
result.append(e)
|
||||
return result
|
||||
|
||||
return jsonify({'tree': _build_tree(None)}), 200
|
||||
def _build_shared_children(parent_id):
|
||||
files = File.query.filter_by(parent_id=parent_id, is_trashed=False)\
|
||||
.order_by(File.is_folder.desc(), File.name).all()
|
||||
out = []
|
||||
for f in files:
|
||||
e = _entry(f)
|
||||
if f.is_folder:
|
||||
e['children'] = _build_shared_children(f.id)
|
||||
out.append(e)
|
||||
return out
|
||||
|
||||
shared_perms = FilePermission.query.filter_by(user_id=user.id).all()
|
||||
shared_roots = []
|
||||
seen = set()
|
||||
for perm in shared_perms:
|
||||
f = perm.file
|
||||
if not f or f.is_trashed or f.id in seen:
|
||||
continue
|
||||
seen.add(f.id)
|
||||
# Nur "Top-Level"-Shares: wenn der Eltern-Ordner NICHT auch geteilt
|
||||
# ist, ist dieses Item die Wurzel des Shares beim Empfaenger.
|
||||
parent_shared = any(
|
||||
p.file_id == f.parent_id for p in shared_perms
|
||||
) if f.parent_id else False
|
||||
if parent_shared:
|
||||
continue
|
||||
e = _entry(f)
|
||||
owner = f.owner.display_name if hasattr(f, 'owner') and f.owner else None
|
||||
if owner:
|
||||
e['name'] = f'{f.name} (von {owner})'
|
||||
if f.is_folder:
|
||||
e['children'] = _build_shared_children(f.id)
|
||||
shared_roots.append(e)
|
||||
|
||||
return jsonify({
|
||||
'tree': _build_tree(None),
|
||||
'shared': shared_roots,
|
||||
}), 200
|
||||
|
||||
|
||||
@api_bp.route('/sync/events', methods=['GET'])
|
||||
|
||||
Reference in New Issue
Block a user