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