Merge branch 'devel'
[qcg-portal.git] / filex / views.py
index 2bda5c8..108f7c3 100644 (file)
-from datetime import datetime
+import logging
+import mimetypes
+import os
 
-from django.core.exceptions import PermissionDenied, SuspiciousOperation
+from django.contrib.auth.decorators import login_required
+from django.core.exceptions import PermissionDenied
 from django.http import JsonResponse, StreamingHttpResponse
+from django.shortcuts import get_object_or_404, render
 from django.template.defaultfilters import filesizeformat
 from django.utils.formats import date_format
-from django.utils.timezone import UTC, localtime
-import mimetypes
+from django.utils.http import urlquote
+from django.views.decorators.http import require_POST
+from django.views.generic import View
 
-from filex.ftp import FTPOperation, FTPException
+from filex.forms import HostPathNameForm, RenameForm, FavoriteForm, HostPathForm, ExtractForm, HostItemsForm, \
+    CompressForm
+from filex.ftp import FTPOperation, FTPError
+from filex.models import Favorite
+from filex.utils import with_ftp_upload_handler, parse_ftp_error
 
 
-def check_auth(request):
-    if not request.user.is_authenticated():
-        raise PermissionDenied("Login required!")
-    if not request.session['proxy']:
-        raise PermissionDenied("No proxy found!")
+class FTPView(View):
+    method = 'get'
+    form_class = HostPathForm
+    request = None
 
+    @classmethod
+    def as_view(cls, **initkwargs):
+        def process(self, request):
+            if not request.user.is_authenticated():
+                raise PermissionDenied("Login required!")
+            if not request.session['proxy']:
+                raise PermissionDenied("No proxy found!")
 
-def list_content(request):
-    check_auth(request)
+            form = self.form_class(request.POST if self.method == 'post' else request.GET)
 
-    # TODO data validation
-    host = request.GET.get('host')
-    path = request.GET.get('path')
-    if not host or not path:
-        raise SuspiciousOperation("No path or host given!")
+            if not form.is_valid():
+                return JsonResponse({'error': form.errors}, status=400)
 
-    url = 'gsiftp://' + host + path
+            self.request = request
+            try:
+                return self.handle(FTPOperation(request.session['proxy']), form.cleaned_data)
+            except FTPError as e:
+                msg, status = parse_ftp_error(e)
+
+                logger = logging.getLogger('gridftp')
+                logger.error(e.verbose, extra={'user': request.user, 'path': request.path, 'params': form.cleaned_data})
+
+                return JsonResponse({'error': msg}, status=status)
+
+        setattr(cls, cls.method, process)
+
+        return super(FTPView, cls).as_view(**initkwargs)
+
+    def handle(self, ftp, params):
+        raise NotImplementedError
+
+
+class ListView(FTPView):
+    def handle(self, ftp, params):
+        listing = ftp.listing(make_url(params, 'path'))
+
+        data = []
+        for item in listing:
+            if item['name'] not in ['.', '..']:
+                item['size'] = filesizeformat(item['size'])
+                item['date'] = date_format(item['date'], 'CUSTOM_DATETIME_FORMAT')
+
+                data.append(item)
+
+        return JsonResponse(data, safe=False)
+
+
+class DownloadView(FTPView):
+    def handle(self, ftp, params):
+        url = make_url(params, 'path')
+
+        try:
+            stats = ftp.info(url)
+        except FTPError as e:
+            msg, status = parse_ftp_error(e)
+
+            return render(self.request, 'qcg/download_error.html', {'msg': msg, 'url': url}, status=status)
+
+        data = ftp.get(url)
+
+        name = os.path.basename(params['path'])
+        mime_type, encoding = mimetypes.guess_type(name)
+
+        response = StreamingHttpResponse(data, content_type=mime_type or 'application/octet-stream')
+        response['Content-Disposition'] = 'attachment; filename*={}'.format(urlquote(name))
+        response['Content-Length'] = stats['size']
+
+        if encoding:
+            response['Content-Encoding'] = encoding
+
+        return response
+
+
+class InfoView(FTPView):
+    def handle(self, ftp, params):
+        return JsonResponse(ftp.info(make_url(params, 'path')))
 
-    try:
-        listing = FTPOperation(request.session['proxy']).listing(url)
-    except FTPException as e:
-        return JsonResponse({'msg': e.message}, status=400)
 
-    data = []
+class DeleteView(FTPView):
+    method = 'post'
+    form_class = HostItemsForm
 
-    # ignore first two: '.' and '..'
-    for item in listing.strip().splitlines()[2:]:
-        # we may receive empty string when there are multiple consecutive newlines in listing
-        if item:
-            attrs, name = item.split(' ', 1)
+    def handle(self, ftp, params):
+        url = make_url(params)
+        done, fail = [], {}
 
-            attrs = dict((attr.split('=') for attr in attrs.split(';') if attr))
+        for path in params['dirs']:
+            try:
+                ftp.rmdir(url + urlquote(path))
+            except FTPError as e:
+                fail[path] = e.message
+            else:
+                done.append(path)
 
-            date = localtime(datetime.strptime(attrs['Modify'], "%Y%m%d%H%M%S").replace(tzinfo=UTC()))
+        for path in params['files']:
+            try:
+                ftp.delete(url + urlquote(path))
+            except FTPError as e:
+                fail[path] = e.message
+            else:
+                done.append(path)
 
-            data.append({
-                'name': name,
-                'type': 'file' if attrs['Type'] == 'file' else 'directory',
-                'size': filesizeformat(attrs['Size']),
-                'date': date_format(date, 'DATETIME_FORMAT'),
-            })
+        return JsonResponse({'done': done, 'fail': fail})
 
-    return JsonResponse(data, safe=False)
 
+class MkdirView(FTPView):
+    method = 'post'
+    form_class = HostPathNameForm
 
-def download(request):
-    check_auth(request)
+    def handle(self, ftp, params):
+        ftp.mkdir(make_url(params, 'path', 'name'))
+
+        return JsonResponse({'success': True})
+
+
+class MoveView(FTPView):
+    method = 'post'
+    form_class = RenameForm
+
+    def handle(self, ftp, params):
+        ftp.move(make_url(params, 'src'), make_url(params, 'dst'))
+
+        return JsonResponse({'success': True})
+
+
+class CompressView(FTPView):
+    method = 'post'
+    form_class = CompressForm
+
+    def handle(self, ftp, params):
+        try:
+            # consume generator with command output
+            list(ftp.compress(make_url(params), params['path'], params['files'], params['archive']))
+        except ValueError as e:
+            return JsonResponse({'error': e.message}, status=400)
+
+        return JsonResponse({'success': True})
+
+
+class ExtractView(FTPView):
+    method = 'post'
+    form_class = ExtractForm
+
+    def handle(self, ftp, params):
+        try:
+            # consume generator with command output
+            list(ftp.extract(make_url(params), params['path'], params['dst']))
+        except ValueError as e:
+            return JsonResponse({'error': e.message}, status=400)
+
+        return JsonResponse({'success': True})
+
+
+def make_url(params, *parts):
+    return 'gsiftp://{}/{}'.format(params['host'],
+                                   urlquote(os.path.join(*[params[part] for part in parts]), safe='/~') if parts else '')
+
+
+@require_POST
+@with_ftp_upload_handler
+def upload(request):
+    return JsonResponse({'success': True})
+
+
+@require_POST
+@login_required
+def fav_add(request):
+    data = request.POST.copy()
+    data['owner'] = request.user.id
+
+    form = FavoriteForm(data)
+
+    if not form.is_valid():
+        return JsonResponse({'error': form.errors}, status=400)
+
+    try:
+        FTPOperation(request.session['proxy']).info(make_url(form.cleaned_data, 'path'))
+    except FTPError as e:
+        msg, status = parse_ftp_error(e)
 
-    # TODO data validation
-    host = request.GET.get('host')
-    path = request.GET.get('path')
-    name = request.GET.get('name')
-    if not host or not path or not name:
-        raise SuspiciousOperation("No path or host or name given!")
+        return JsonResponse({'error': msg}, status=status)
 
-    url = 'gsiftp://' + host + path + '/' + name
+    instance = form.save()
 
-    mime_type, encoding = mimetypes.guess_type(name)
+    return JsonResponse({'group': 'usr', 'host': instance.host, 'path': instance.path,
+                         'value': instance.host + '/' + instance.path})
 
-    response = StreamingHttpResponse(FTPOperation(request.session['proxy']).get(url),
-                                     content_type=mime_type or 'application/octet-stream')
-    response['Content-Disposition'] = 'attachment; filename={}'.format(name)
-    # TODO Content-Length (?)
 
-    if encoding:
-        response['Content-Encoding'] = encoding
+@require_POST
+@login_required
+def fav_delete(request):
+    fav = get_object_or_404(Favorite, owner=request.user, host=request.POST['host'], path=request.POST['path'])
+    fav.delete()
 
-    return response
+    return JsonResponse({'success': True})