X-Git-Url: http://mmka.chem.univ.gda.pl/gitweb/?a=blobdiff_plain;f=filex%2Fviews.py;h=108f7c3435e4d56001b55161490dc70818974b0e;hb=HEAD;hp=2bda5c8649de461eb3736b18e5f94347c9a32f2e;hpb=ef3a889b1d85327be1aa86af703d814991cf60ad;p=qcg-portal.git diff --git a/filex/views.py b/filex/views.py index 2bda5c8..108f7c3 100644 --- a/filex/views.py +++ b/filex/views.py @@ -1,80 +1,221 @@ -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})