gridftp: handle paths beginning with `~`
[qcg-portal.git] / filex / views.py
1 from itertools import islice
2 import mimetypes
3 import os
4
5 from django.contrib.auth.decorators import login_required
6 from django.core.exceptions import PermissionDenied
7 from django.http import JsonResponse, StreamingHttpResponse
8 from django.shortcuts import get_object_or_404
9 from django.template.defaultfilters import filesizeformat
10 from django.utils.formats import date_format
11 from django.views.decorators.http import require_POST
12 from django.views.generic import View
13
14 from filex.forms import HostPathNameForm, RenameForm, FavoriteForm, HostPathForm, ExtractForm, HostItemsForm, \
15     CompressForm
16 from filex.ftp import FTPOperation, FTPException
17 from filex.models import Favorite
18 from filex.uploadhandler import with_ftp_upload_handler
19
20
21 class FTPView(View):
22     method = 'get'
23     form_class = HostPathForm
24
25     @classmethod
26     def as_view(cls, **initkwargs):
27         def process(self, request):
28             if not request.user.is_authenticated():
29                 raise PermissionDenied("Login required!")
30             if not request.session['proxy']:
31                 raise PermissionDenied("No proxy found!")
32
33             form = self.form_class(request.POST if self.method == 'post' else request.GET)
34
35             if not form.is_valid():
36                 return JsonResponse({'error': form.errors}, status=400)
37
38             try:
39                 return self.handle(FTPOperation(request.session['proxy']), form.cleaned_data)
40             except FTPException as e:
41                 status = 400
42                 if 'No such file or directory' in e.message:
43                     status = 404
44                 elif 'Permission denied' in e.message:
45                     status = 403
46
47                 return JsonResponse({'error': e.message}, status=status)
48
49         setattr(cls, cls.method, process)
50
51         return super(FTPView, cls).as_view(**initkwargs)
52
53     def handle(self, ftp, params):
54         raise NotImplementedError
55
56
57 class ListView(FTPView):
58     def handle(self, ftp, params):
59         listing = ftp.listing(make_url(params, 'path'))
60
61         data = []
62         # ignore . and .. from beginning of the listing
63         for item in islice(listing, 2, None):
64             item['size'] = filesizeformat(item['size'])
65             item['date'] = date_format(item['date'], 'CUSTOM_DATETIME_FORMAT')
66
67             data.append(item)
68
69         return JsonResponse(data, safe=False)
70
71
72 class DownloadView(FTPView):
73     def handle(self, ftp, params):
74         data = ftp.get(make_url(params, 'path'))
75
76         name = os.path.basename(params['path'])
77         mime_type, encoding = mimetypes.guess_type(name)
78
79         response = StreamingHttpResponse(data, content_type=mime_type or 'application/octet-stream')
80         response['Content-Disposition'] = u'attachment; filename={}'.format(name)
81         # TODO Content-Length (?)
82
83         if encoding:
84             response['Content-Encoding'] = encoding
85
86         return response
87
88
89 class InfoView(FTPView):
90     def handle(self, ftp, params):
91         return JsonResponse(ftp.info(make_url(params, 'path')))
92
93
94 class DeleteView(FTPView):
95     method = 'post'
96     form_class = HostItemsForm
97
98     def handle(self, ftp, params):
99         url = make_url(params)
100         done, fail = [], {}
101
102         for path in params['dirs']:
103             try:
104                 ftp.rmdir(url + path)
105             except FTPException as e:
106                 fail[path] = e.message
107             else:
108                 done.append(path)
109
110         for path in params['files']:
111             try:
112                 ftp.delete(url + path)
113             except FTPException as e:
114                 fail[path] = e.message
115             else:
116                 done.append(path)
117
118         return JsonResponse({'done': done, 'fail': fail})
119
120
121 class MkdirView(FTPView):
122     method = 'post'
123     form_class = HostPathNameForm
124
125     def handle(self, ftp, params):
126         ftp.mkdir(make_url(params, 'path', 'name'))
127
128         return JsonResponse({'success': True})
129
130
131 class MoveView(FTPView):
132     method = 'post'
133     form_class = RenameForm
134
135     def handle(self, ftp, params):
136         print params
137         ftp.move(make_url(params, 'src'), make_url(params, 'dst'))
138
139         return JsonResponse({'success': True})
140
141
142 class CompressView(FTPView):
143     method = 'post'
144     form_class = CompressForm
145
146     def handle(self, ftp, params):
147         try:
148             # consume generator with command output
149             list(ftp.compress(make_url(params), params['path'], params['files'], params['archive']))
150         except ValueError as e:
151             return JsonResponse({'error': e.message}, status=400)
152
153         return JsonResponse({'success': True})
154
155
156 class ExtractView(FTPView):
157     method = 'post'
158     form_class = ExtractForm
159
160     def handle(self, ftp, params):
161         try:
162             # consume generator with command output
163             list(ftp.extract(make_url(params), params['path'], params['dst']))
164         except ValueError as e:
165             return JsonResponse({'error': e.message}, status=400)
166
167         return JsonResponse({'success': True})
168
169
170 def make_url(params, *parts):
171     return 'gsiftp://{}/{}'.format(params['host'], os.path.join(*[params[part] for part in parts]) if parts else '')
172
173
174 @require_POST
175 @with_ftp_upload_handler
176 def upload(request):
177     return JsonResponse({'success': True})
178
179
180 @require_POST
181 @login_required
182 def fav_add(request):
183     data = request.POST.copy()
184     data['owner'] = request.user.id
185
186     form = FavoriteForm(data)
187
188     # TODO check if path exists
189     if form.is_valid():
190         instance = form.save()
191
192         return JsonResponse({'group': 'usr', 'host': instance.host, 'path': instance.path,
193                              'value': instance.host + '/' + instance.path})
194
195     return JsonResponse({'error': form.errors}, status=400)
196
197
198 @require_POST
199 @login_required
200 def fav_delete(request):
201     fav = get_object_or_404(Favorite, owner=request.user, host=request.POST['host'], path=request.POST['path'])
202     fav.delete()
203
204     return JsonResponse({'success': True})