diff --git a/config/urls_shared_space.py b/config/urls_shared_space.py index eb37732..5503a14 100644 --- a/config/urls_shared_space.py +++ b/config/urls_shared_space.py @@ -10,8 +10,6 @@ from portal.views_shared_space import ( SharedSpaceLogoutView, SharedSpaceUploadAPIView, SharedSpaceFileListView, - ChunkedUploadInitView, - ChunkedUploadChunkView, ) @@ -20,7 +18,5 @@ urlpatterns = [ path('login/', SharedSpaceLoginView.as_view(), name='shared_space_login'), path('logout/', SharedSpaceLogoutView.as_view(), name='shared_space_logout'), path('api/upload/', SharedSpaceUploadAPIView.as_view(), name='shared_space_upload'), - path('api/upload/init/', ChunkedUploadInitView.as_view(), name='chunked_upload_init'), - path('api/upload/chunk/', ChunkedUploadChunkView.as_view(), name='chunked_upload_chunk'), path('files/', SharedSpaceFileListView.as_view(), name='shared_space_files'), ] diff --git a/portal/views_shared_space.py b/portal/views_shared_space.py index 19d743e..c7bd7d7 100644 --- a/portal/views_shared_space.py +++ b/portal/views_shared_space.py @@ -5,11 +5,6 @@ Each topic is accessible via a subdomain (e.g., cofi.rfiles.online). Anyone can view files and upload new ones - no password required. """ -import os -import uuid - -from django.conf import settings -from django.core.files.base import File from django.shortcuts import render, redirect, get_object_or_404 from django.http import JsonResponse, Http404 from django.views import View @@ -18,8 +13,6 @@ from django.utils.decorators import method_decorator from files.models import SharedSpace, MediaFile, PublicShare -CHUNK_UPLOAD_DIR = os.path.join(settings.MEDIA_ROOT, 'chunks') - def get_topic_or_404(request): """Get the topic (shared space) from the request's subdomain slug.""" @@ -224,171 +217,6 @@ class DirectUploadAPIView(View): }) -@method_decorator(csrf_exempt, name='dispatch') -class ChunkedUploadInitView(View): - """Initialize a chunked upload session.""" - - def post(self, request): - space = get_topic_or_404(request) - filename = request.POST.get('filename', '') - total_size = int(request.POST.get('total_size', 0)) - total_chunks = int(request.POST.get('total_chunks', 0)) - mime_type = request.POST.get('mime_type', 'application/octet-stream') - action = request.POST.get('action', '') - - if not filename or total_chunks < 1: - return JsonResponse({'error': 'Invalid parameters'}, status=400) - - if space.max_file_size_mb > 0: - max_size_bytes = space.max_file_size_mb * 1024 * 1024 - if total_size > max_size_bytes: - return JsonResponse({ - 'error': f'File too large. Maximum size is {space.max_file_size_mb}MB' - }, status=400) - - # Check for duplicate - existing = MediaFile.objects.filter( - shared_space=space, - original_filename=filename, - ).first() - - if existing and action != 'overwrite': - existing_share = existing.public_shares.filter(is_active=True).first() - return JsonResponse({ - 'duplicate': True, - 'existing_file': { - 'id': str(existing.id), - 'title': existing.title, - 'filename': existing.original_filename, - 'size': existing.file_size, - 'share_url': existing_share.get_public_url() if existing_share else None, - }, - }, status=409) - - upload_id = str(uuid.uuid4()) - chunk_dir = os.path.join(CHUNK_UPLOAD_DIR, upload_id) - os.makedirs(chunk_dir, exist_ok=True) - - # Store metadata - import json - meta = { - 'filename': filename, - 'total_size': total_size, - 'total_chunks': total_chunks, - 'mime_type': mime_type, - 'space_id': str(space.id), - 'action': action, - } - with open(os.path.join(chunk_dir, 'meta.json'), 'w') as f: - json.dump(meta, f) - - return JsonResponse({'upload_id': upload_id}) - - -@method_decorator(csrf_exempt, name='dispatch') -class ChunkedUploadChunkView(View): - """Receive a single chunk and finalize when all chunks are received.""" - - def post(self, request): - space = get_topic_or_404(request) - upload_id = request.POST.get('upload_id', '') - chunk_index = int(request.POST.get('chunk_index', -1)) - chunk_file = request.FILES.get('chunk') - - if not upload_id or chunk_index < 0 or not chunk_file: - return JsonResponse({'error': 'Invalid parameters'}, status=400) - - chunk_dir = os.path.join(CHUNK_UPLOAD_DIR, upload_id) - meta_path = os.path.join(chunk_dir, 'meta.json') - if not os.path.isdir(chunk_dir) or not os.path.exists(meta_path): - return JsonResponse({'error': 'Invalid upload_id'}, status=400) - - import json - with open(meta_path) as f: - meta = json.load(f) - - # Verify this chunk belongs to this space - if meta['space_id'] != str(space.id): - return JsonResponse({'error': 'Space mismatch'}, status=403) - - # Write chunk to disk - chunk_path = os.path.join(chunk_dir, f'{chunk_index:06d}') - with open(chunk_path, 'wb') as f: - for part in chunk_file.chunks(): - f.write(part) - - # Check if all chunks received - received = len([ - n for n in os.listdir(chunk_dir) - if n != 'meta.json' - ]) - - if received < meta['total_chunks']: - return JsonResponse({ - 'received': received, - 'total': meta['total_chunks'], - }) - - # All chunks received — assemble the file - assembled_path = os.path.join(chunk_dir, 'assembled') - with open(assembled_path, 'wb') as out: - for i in range(meta['total_chunks']): - part_path = os.path.join(chunk_dir, f'{i:06d}') - with open(part_path, 'rb') as part: - while True: - buf = part.read(8192) - if not buf: - break - out.write(buf) - - # Handle overwrite - if meta['action'] == 'overwrite': - existing = MediaFile.objects.filter( - shared_space=space, - original_filename=meta['filename'], - ).first() - if existing: - existing.file.delete(save=False) - existing.delete() - - # Create MediaFile from assembled file - with open(assembled_path, 'rb') as f: - django_file = File(f, name=meta['filename']) - media_file = MediaFile.objects.create( - file=django_file, - original_filename=meta['filename'], - title=meta['filename'], - mime_type=meta['mime_type'], - uploaded_by=request.user if request.user.is_authenticated else None, - shared_space=space, - ) - - share = PublicShare.objects.create( - media_file=media_file, - created_by=request.user if request.user.is_authenticated else None, - note=f'Uploaded to topic: {space.slug}', - ) - - # Cleanup chunk directory - import shutil - shutil.rmtree(chunk_dir, ignore_errors=True) - - return JsonResponse({ - 'success': True, - 'file': { - 'id': str(media_file.id), - 'title': media_file.title, - 'filename': media_file.original_filename, - 'size': media_file.file_size, - 'mime_type': media_file.mime_type, - }, - 'share': { - 'token': share.token, - 'url': share.get_public_url(), - } - }) - - class SharedSpaceFileListView(View): """List all files in the topic."""