"""Shared file-tree access and serialization helpers.""" import uuid from django.core.exceptions import ValidationError from django.http import Http404 from rest_framework.response import Response from .models import DriveItem, ItemGrant from .spaces import can_read_space, can_write_space from .permissions import has_space_right, item_rights def error(message, code=400): return Response({'detail':message}, status=code) def item_info(item, user=None): data = { 'id':str(item.id), 'name':item.name, 'is_folder':item.is_folder, 'size':item.size,'mime_type':item.mime_type,'owner_id':item.owner_id, 'owner':item.owner.username,'parent':str(item.parent_id) if item.parent_id else None, 'space_id':item.space_id, 'is_shared':item.user_shares.exists() or any(link.revoked_at is None for link in item.public_links.all()), 'trashed_at':item.trashed_at.isoformat() if item.trashed_at else None, 'created_at':item.created_at.isoformat(),'updated_at':item.updated_at.isoformat(), } if user is not None: data['can_write'] = writable(item,user) data['permissions'] = sorted(item_rights(user, item)) return data def get_item(pk): try: return DriveItem.objects.select_related('owner','parent','space').get(pk=pk) except (DriveItem.DoesNotExist,ValueError,ValidationError): raise Http404 def chain(item): ancestors=[] visited=set() while item is not None: if item.pk in visited: raise Http404 visited.add(item.pk) ancestors.append(item) item=item.parent return ancestors def readable(item, user, allow_trashed=False): if 'view' not in item_rights(user, item): return False return allow_trashed or not any(n.trashed_at is not None for n in chain(item)) def writable(item, user, action='modify'): return action in item_rights(user, item) def valid_name(name): if not isinstance(name,str) or not name.strip() or len(name)>255: raise ValueError('Nom invalide (1 à 255 caractères).') name=name.strip() if name in ('.','..', '.gooddrive-trash') or any(c in name for c in ('/','\\','\x00','\r','\n')): raise ValueError('Ce nom contient des caractères interdits.') return name def target_parent(parent_id, user, space): if not parent_id: return None try: parent=DriveItem.objects.select_related('parent','space').get(pk=uuid.UUID(str(parent_id))) except (ValueError,DriveItem.DoesNotExist,AttributeError): raise ValueError('Dossier de destination introuvable.') if parent.space_id != space.id: raise PermissionError('Impossible de déplacer un élément entre deux espaces.') if not parent.is_folder or not readable(parent,user) or not writable(parent,user,'add'): raise PermissionError('Dossier de destination inaccessible en écriture.') return parent def descendant_files(node): files=[] pending=[node.pk] while pending: batch=pending[:500];pending=pending[500:] children=list(DriveItem.objects.filter(parent_id__in=batch).values_list('pk','is_folder','storage_key','storage_location')) pending.extend(row[0] for row in children) for _,is_folder,key,location in children: if not is_folder and key: files.append(location or f'{key[:2]}/{key}') if not node.is_folder and node.storage_key: key=node.storage_key files.append(node.storage_location or f'{key[:2]}/{key}') return files def writable_destination(user, space_id, parent_id): """Uploads may target a folder shared with this user, without space membership.""" from .spaces import resolve_space from .permissions import has_space_right if parent_id: folder=get_item(parent_id) if space_id is not None and str(folder.space_id)!=str(space_id): raise PermissionError('Le dossier ne correspond pas à cet espace.') if not folder.is_folder or not readable(folder,user) or not writable(folder,user,'add'): raise PermissionError('Dossier inaccessible en ajout.') return folder.space, folder space=resolve_space(user,space_id) if not has_space_right(user,space,'add'): raise PermissionError('Ajout interdit dans cet espace.') return space,None