Merge branch 'devel'
[qcg-portal.git] / filex / views.py
index 8cb0f05..108f7c3 100644 (file)
@@ -1,26 +1,28 @@
-from itertools import islice
+import logging
 import mimetypes
 import os
 
 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
+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.http import urlquote
 from django.views.decorators.http import require_POST
 from django.views.generic import View
 
 from filex.forms import HostPathNameForm, RenameForm, FavoriteForm, HostPathForm, ExtractForm, HostItemsForm, \
     CompressForm
-from filex.ftp import FTPOperation, FTPException
+from filex.ftp import FTPOperation, FTPError
 from filex.models import Favorite
-from filex.uploadhandler import with_ftp_upload_handler
+from filex.utils import with_ftp_upload_handler, parse_ftp_error
 
 
 class FTPView(View):
     method = 'get'
     form_class = HostPathForm
+    request = None
 
     @classmethod
     def as_view(cls, **initkwargs):
@@ -35,16 +37,16 @@ class FTPView(View):
             if not form.is_valid():
                 return JsonResponse({'error': form.errors}, status=400)
 
+            self.request = request
             try:
                 return self.handle(FTPOperation(request.session['proxy']), form.cleaned_data)
-            except FTPException as e:
-                status = 400
-                if 'No such file or directory' in e.message:
-                    status = 404
-                elif 'Permission denied' in e.message:
-                    status = 403
+            except FTPError as e:
+                msg, status = parse_ftp_error(e)
 
-                return JsonResponse({'error': e.message}, status=status)
+                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)
 
@@ -59,26 +61,35 @@ class ListView(FTPView):
         listing = ftp.listing(make_url(params, 'path'))
 
         data = []
-        # ignore . and .. from beginning of the listing
-        for item in islice(listing, 2, None):
-            item['size'] = filesizeformat(item['size'])
-            item['date'] = date_format(item['date'], 'CUSTOM_DATETIME_FORMAT')
+        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)
+                data.append(item)
 
         return JsonResponse(data, safe=False)
 
 
 class DownloadView(FTPView):
     def handle(self, ftp, params):
-        data = ftp.get(make_url(params, 'path'))
+        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'] = u'attachment; filename={}'.format(name)
-        # TODO Content-Length (?)
+        response['Content-Disposition'] = 'attachment; filename*={}'.format(urlquote(name))
+        response['Content-Length'] = stats['size']
 
         if encoding:
             response['Content-Encoding'] = encoding
@@ -101,16 +112,16 @@ class DeleteView(FTPView):
 
         for path in params['dirs']:
             try:
-                ftp.rmdir(url + path)
-            except FTPException as e:
+                ftp.rmdir(url + urlquote(path))
+            except FTPError as e:
                 fail[path] = e.message
             else:
                 done.append(path)
 
         for path in params['files']:
             try:
-                ftp.delete(url + path)
-            except FTPException as e:
+                ftp.delete(url + urlquote(path))
+            except FTPError as e:
                 fail[path] = e.message
             else:
                 done.append(path)
@@ -167,7 +178,8 @@ class ExtractView(FTPView):
 
 
 def make_url(params, *parts):
-    return 'gsiftp://{}/{}'.format(params['host'], os.path.join(*[params[part] for part in parts]) if parts else '')
+    return 'gsiftp://{}/{}'.format(params['host'],
+                                   urlquote(os.path.join(*[params[part] for part in parts]), safe='/~') if parts else '')
 
 
 @require_POST
@@ -184,14 +196,20 @@ def fav_add(request):
 
     form = FavoriteForm(data)
 
-    # TODO check if path exists
-    if form.is_valid():
-        instance = form.save()
+    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)
+
+        return JsonResponse({'error': msg}, status=status)
 
-        return JsonResponse({'group': 'usr', 'host': instance.host, 'path': instance.path,
-                             'value': instance.host + '/' + instance.path})
+    instance = form.save()
 
-    return JsonResponse({'error': form.errors}, status=400)
+    return JsonResponse({'group': 'usr', 'host': instance.host, 'path': instance.path,
+                         'value': instance.host + '/' + instance.path})
 
 
 @require_POST