filesystem.py 14.4 KB
Newer Older
1
2
# -*- coding: utf-8 -*-

Marcel's avatar
Marcel committed
3
# imports
4
import StringIO
murban's avatar
murban committed
5
import json
6
7
import stat
import logging
Gero Müller's avatar
Gero Müller committed
8

9
from cherrypy.lib import file_generator, cptools, httputil
10
import cherrypy
Martin Urban's avatar
Martin Urban committed
11
import rpyc
12

13
from vispa import AjaxException
14
from vispa.controller import AbstractController
15
16
17


logger = logging.getLogger(__name__)
18

Marcel's avatar
Marcel committed
19

20
class FSController(AbstractController):
21

Gero Müller's avatar
Gero Müller committed
22
23
24
    def __init__(self):
        AbstractController.__init__(self, mount_static=False)

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
    @staticmethod
    def _stream_remote_file(fs, path):
        offset = 0
        buffer_size = 2 ** 20
        while True:
            try:
                data = fs.get_file_content(path, offset, buffer_size)
            except:
                logger.exception("get content")
                raise

            l = len(data)
            if l <= 0:
                break
            else:
                offset += l

            yield data

44
    @cherrypy.expose
Gero Müller's avatar
Gero Müller committed
45
    @cherrypy.tools.ajax(on=False)
46
    @cherrypy.tools.method(accept="GET")
Marcel's avatar
Marcel committed
47
    def getfile(self, path, download=None, **kwargs):
48
        fs = self.get('fs')
49
50
51
        stats = fs.stat(path)
        if not stat.S_ISREG(stats.st_mode):
            raise cherrypy.HTTPError(404, "Not a File!")
52
53
54

        # Set the Last-Modified response header, so that
        # modified-since validation code can work.
55
56
57
58
59
60
61
        headers = cherrypy.response.headers
        headers[
            'Cache-Control'] = 'max-age=1, private, must-revalidate, no-cache'
        headers['Pragma'] = 'no-cache'
        headers['Last-Modified'] = httputil.HTTPDate(stats.st_mtime)
        headers['Expires'] = httputil.HTTPDate(stats.st_mtime + 1)
        headers['Content-Length'] = stats.st_size
62
63
        cptools.validate_since()

64
65
        if download and download.lower() in ['true', '1', 'yes']:
            disposition = 'attachment; filename=%s' % path.split('/')[-1]
66
67
68
        else:
            disposition = 'inline; filename=%s' % path.split('/')[-1]
        headers['Content-Disposition'] = disposition
69
70
71
72

        mimetype = fs.get_mime_type(path)
        if mimetype is not None:
            headers['Content-Type'] = mimetype
73
74
        else:
            headers['Content-Type'] = "application/octet-stream"
75
76

        self.release()
Gero Müller's avatar
Gero Müller committed
77

78
79
80
        return FSController._stream_remote_file(fs, path)

    getfile._cp_config = {'response.stream': True}
81

82
83
    @cherrypy.expose
    @cherrypy.tools.ajax(on=False)
84
    @cherrypy.tools.method(accept="GET")
85
    def thumbnail(self, path, width=100, height=100, **kwargs):
Gero Müller's avatar
Gero Müller committed
86
        self.release_session()
87
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
88
        self.release_database()
89
90
91
92
93
        contents = fs.thumbnail(path, int(width), int(height))
        cherrypy.response.headers['Content-Type'] = "image/jpeg"
        contents = StringIO.StringIO(contents)
        return file_generator(contents)

Marcel's avatar
Marcel committed
94

95
class FSAjaxController(AbstractController):
96
97

    @cherrypy.expose
98
    @cherrypy.tools.method(accept="GET")
Gero Müller's avatar
Gero Müller committed
99
    def exists(self, path, filetype=None):
Gero Müller's avatar
Gero Müller committed
100
        self.release_session()
Gero Müller's avatar
Gero Müller committed
101
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
102
        self.release_database()
Gero Müller's avatar
Gero Müller committed
103
104
105
106

        # type = type if type else type
        target_type = fs.exists(path, type=filetype)
        if target_type:
107
            return target_type
Gero Müller's avatar
Gero Müller committed
108
        else:
109
            return "Failed"
Marcel's avatar
Marcel committed
110

111
    @cherrypy.expose
112
    @cherrypy.tools.method(accept="GET")
113
    def filecount(self, path, watch_id=None):
114
115
116
117
        self.release_session()
        fs = self.get('fs')
        self.release_database()

118
119
120
121
        count = fs.get_file_count(path,
                                  window_id=self.get('window_id'),
                                  view_id=self.get('view_id'),
                                  watch_id=watch_id)
122
        if count == -1:
123
            raise AjaxException("%s does not exist" % path)
124
125
126
        # instead of raising an exception we return -2 as count to account for
        # the fact that the user does not have the permission to read the path
        # in the GUI elif count == -2:
127
        #   raise AjaxException("You do not have rights to read %s." % path)
128
        elif not isinstance(count, int):
129
            raise AjaxException(count)
130
131
132
        else:
            return {"count": count}

133
    @cherrypy.expose
134
    @cherrypy.tools.ajax(encoded=True)
135
    @cherrypy.tools.method(accept="GET")
136
    def filelist(self, path, filefilter=None, reverse=False, watch_id=None):
Gero Müller's avatar
Gero Müller committed
137
        self.release_session()
138
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
139
        self.release_database()
140

141
        reverse = self.convert(reverse, bool)
142

143
        # get the files with the filter
144
        return fs.get_file_list(path, filter=filefilter,
145
146
147
148
                                reverse=reverse, encode_json=True,
                                window_id=self.get('window_id'),
                                view_id=self.get('view_id'),
                                watch_id=watch_id)
149

150
    @cherrypy.expose
151
    @cherrypy.tools.method(accept="POST")
Gero Müller's avatar
Gero Müller committed
152
    def createfolder(self, path, name):
Gero Müller's avatar
Gero Müller committed
153
        self.release_session()
154
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
155
156
        self.release_database()

157
        fs.create_folder(path, name)
158
159

    @cherrypy.expose
160
    @cherrypy.tools.method(accept="POST")
Gero Müller's avatar
Gero Müller committed
161
    def createfile(self, path, name):
Gero Müller's avatar
Gero Müller committed
162
        self.release_session()
163
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
164
165
        self.release_database()

166
        fs.create_file(path, name)
167
168

    @cherrypy.expose
169
    @cherrypy.tools.method(accept="POST")
170
    def rename(self, path, name, new_name):
Gero Müller's avatar
Gero Müller committed
171
        self.release_session()
172
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
173
        self.release_database()
174
175
176
177
        try:
            fs.rename(path, name, new_name)
        except Exception as e:
            raise AjaxException(str(e).split("\n")[0])
178
179

    @cherrypy.expose
180
    @cherrypy.tools.method(accept="POST")
Gero Müller's avatar
Gero Müller committed
181
    def remove(self, path):
Gero Müller's avatar
Gero Müller committed
182
        self.release_session()
183
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
184
        self.release_database()
185
        path = json.loads(path)
186
187
        # 'path' can be a unicode/string or list of unicodes/strings
        # so convert it with the convert function
ThorbenQuast's avatar
ThorbenQuast committed
188
        try:
Martin Urban's avatar
Martin Urban committed
189
190
            async_remove = rpyc.async(fs.remove)
            async_remove(path)
ThorbenQuast's avatar
ThorbenQuast committed
191
192
193
            return
        except:
            return
194

195
    @cherrypy.expose
196
    @cherrypy.tools.method(accept="POST")
197
198
199
200
201
202
203
204
    def move(self, source, destination):
        self.release_session()
        fs = self.get('fs')
        self.release_database()
        source = json.loads(source)
        destination = json.loads(destination)
        # 'source' and 'destination' can be a unicode/string or list of unicodes/strings
        # so convert it with the convert function
205
206
207
208
        try:
            fs.move(source, destination)
        except Exception as e:
            raise AjaxException(str(e).split("\n")[0])
209

210
    @cherrypy.expose
211
    @cherrypy.tools.method(accept="POST")
Gero Müller's avatar
Gero Müller committed
212
    def compress(self, path, paths, name):
Gero Müller's avatar
Gero Müller committed
213
        self.release_session()
214
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
215
        self.release_database()
216
217
218
        # 'paths' can be a unicode/string or list of unicodes/strings
        # so convert it with the convert function
        paths = json.loads(paths)
219
220
221
222
        try:
            fs.compress(path, paths, name)
        except Exception as e:
            raise AjaxException(str(e).split("\n")[0])
223

224
    @cherrypy.expose
225
    @cherrypy.tools.method(accept="POST")
226
227
228
229
    def decompress(self, file):
        self.release_session()
        fs = self.get('fs')
        self.release_database()
230
231
232
233
234
        try:
            fs.decompress(file)
        except Exception as e:
            raise AjaxException(str(e).split("\n")[0])

235

236
    @cherrypy.expose
237
    @cherrypy.tools.method(accept="POST")
Gero Müller's avatar
Gero Müller committed
238
    def paste(self, path, paths, cut):
Gero Müller's avatar
Gero Müller committed
239
        self.release_session()
Gero Müller's avatar
Gero Müller committed
240
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
241
        self.release_database()
Gero Müller's avatar
Gero Müller committed
242

243
244
245
246
        paths = json.loads(paths)
        # path = fs.expand(path.encode("utf-8"))
        # 'paths' can be a unicode/string or list of unicodes/strings
        # so convert it with the convert function
247
248
249
250
        try:
            fs.paste(path, paths, self.convert(cut, bool))
        except Exception as e:
            raise AjaxException(str(e).split("\n")[0])
251
252

    @cherrypy.expose
253
    @cherrypy.tools.method(accept="POST")
Gero Müller's avatar
Gero Müller committed
254
    def upload(self, *args, **kwargs):
Gero Müller's avatar
Gero Müller committed
255
        self.release_session()
Gero Müller's avatar
Gero Müller committed
256
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
257
        self.release_database()
258
259
260
261
262
263
264
265
266

        # the html5 uploader provides following kwargs:
        # index, type, name, size, files[]
        # Since "files[]" ends with "[]"-brackets
        # we have to use kwargs instead of args
        # extract the path

        # prepare the parts
        parts = kwargs['files[]']
267
268
269
        # force parts to be a list
        if not isinstance(parts, list):
            parts = [parts]
270

271
272
273
274
275
276
277
278
279
280
281
        # get the byterange if chunked
        chunked = False
        if 'Content-Range' in cherrypy.request.headers:
            chunked = True
            tmp = cherrypy.request.headers['Content-Range']
            tmp = tmp.split()[1].split('/')
            maxbytes = int(tmp[1])
            tmp = tmp[0].split('-')
            firstbyte = int(tmp[0])
            lastbyte = int(tmp[1])

Gero Müller's avatar
Gero Müller committed
282
        path = kwargs['path']
283

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
        try:
            for part in parts:
                filename = fs.handle_file_name_collision(part.filename, path)
                if chunked:
                    filename = '~' + filename
                if chunked and (firstbyte != 0):
                    # check correct size of previous chunks
                    for file in fs.get_file_list(path, encode_json=False)['filelist']:
                        if file['name'] == filename and file['size'] != firstbyte:
                            raise AjaxException("Chunked upload failed")
                    append = True
                else:
                    append   = False

                while True:
                    data = part.file.read(1024 ** 2)
                    if len(data) <= 0:
                        break
302

303
304
305
306
307
                    success, msg = fs.save_file_content(filename, data,
                                                        path=path, force=True,
                                                        append=append)
                    if not success:
                        raise AjaxException(msg)
308

309
310
                    if not append:
                        append = True
311

312
313
314
315
                if chunked and (lastbyte + 1 == maxbytes):
                    fs.rename(path, filename, filename.lstrip('~'))
        except Exception as e:
            raise AjaxException(str(e).split("\n")[0])
316

317
    @cherrypy.expose
318
    @cherrypy.tools.method(accept="GET")
Gero Müller's avatar
Gero Müller committed
319
    def isbrowserfile(self, path):
Gero Müller's avatar
Gero Müller committed
320
        self.release_session()
Gero Müller's avatar
Gero Müller committed
321
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
322
        self.release_database()
Marcel's avatar
Marcel committed
323
        try:
324
            # return fs.is_browser_file(str(path))
Gero Müller's avatar
Gero Müller committed
325
            if fs.is_browser_file(path):
Martin Urban's avatar
Martin Urban committed
326
                return
Marcel's avatar
Marcel committed
327
            else:
328
                return "File can not be opened in browser."
329
        except Exception as e:
330
            raise AjaxException(str(e).split("\n")[0])
Marcel's avatar
Marcel committed
331
332

    @cherrypy.expose
333
    @cherrypy.tools.method(accept="GET")
Gero Müller's avatar
Gero Müller committed
334
    def getsuggestions(self, path, length=10, append_hidden=True):
Gero Müller's avatar
Gero Müller committed
335
        self.release_session()
Gero Müller's avatar
Gero Müller committed
336
        fs = self.get('fs')
Gero Müller's avatar
Gero Müller committed
337
        self.release_database()
338
        try:
Marcel's avatar
Marcel committed
339
            length = length or 1
340
            suggestions = fs.get_suggestions(path, length=int(
Gero Müller's avatar
Gero Müller committed
341
                length), append_hidden=self.convert(append_hidden, bool),
342
                encode_json=True)
343
            return self.success(suggestions=suggestions, encode_json=True)
344
        except Exception as e:
345
            return self.fail(msg=str(e), encode_json=True)
346

347
348
    @cherrypy.expose
    @cherrypy.tools.ajax(encoded=True)
349
    @cherrypy.tools.method(accept="POST")
350
    def savefile(self, path, content, watch_id=None, utf8=False):
351
352
353
354
355
356
357
358
359
360
        self.release_session()
        fs = self.get('fs')
        self.release_database()
        return fs.save_file(path, content, utf8=utf8,
                            window_id=self.get('window_id'),
                            view_id=self.get('view_id'),
                            watch_id=watch_id)

    @cherrypy.expose
    @cherrypy.tools.ajax(encoded=True)
361
    @cherrypy.tools.method(accept="GET")
362
    def getfile(self, path, watch_id=None, utf8=False):
363
364
365
366
        self.release_session()
        fs = self.get('fs')
        self.release_database()
        return fs.get_file(path, utf8=utf8,
367
368
369
                           window_id=self.get('window_id'),
                           view_id=self.get('view_id'),
                           watch_id=watch_id)
370

371
    @cherrypy.expose
372
    @cherrypy.tools.method(accept="POST")
373
374
375
376
377
    def watch(self, path, watch_id):
        self.release_session()
        fs = self.get('fs')
        self.release_database()

378
379
380
381
        err = fs.watch(path,
                       window_id=self.get('window_id'),
                       view_id=self.get('view_id'),
                       watch_id=watch_id)
382
        if err:
383
            raise AjaxException(err)
384
385
386
        return {"success": not err}

    @cherrypy.expose
387
    @cherrypy.tools.method(accept="POST")
388
389
390
391
    def unwatch(self, watch_id=None):
        self.release_session()
        fs = self.get('fs')
        self.release_database()
392

393
        err = fs.unwatch(window_id=self.get('window_id'),
394
395
                         view_id=self.get('view_id'),
                         watch_id=watch_id)
396
        if err:
397
            raise AjaxException(err)
398
        return {"success": not err}
399
400
401

    @cherrypy.expose
    @cherrypy.tools.ajax(encoded=True)
402
    @cherrypy.tools.method(accept="GET")
403
    def getworkspaceini(self, request, fail_on_missing=False):
404
405
406
        self.release_session()
        fs = self.get('fs')
        self.release_database()
407
408
        fail_on_missing = self.convert(fail_on_missing, bool)
        return fs.get_workspaceini(request, fail_on_missing=fail_on_missing)
asseldonk's avatar
asseldonk committed
409

410
    @cherrypy.expose
411
    @cherrypy.tools.method(accept="POST")
412
    def setworkspaceini(self, request):
413
414
415
        self.release_session()
        fs = self.get('fs')
        self.release_database()
416

417
418
        err = fs.set_workspaceini(request)
        if err:
419
            raise AjaxException(err)
asseldonk's avatar
asseldonk committed
420
421
422
        return {"success": not err}

    @cherrypy.expose
423
    @cherrypy.tools.method(accept="GET")
asseldonk's avatar
asseldonk committed
424
425
426
427
428
429
430
431
    def expand(self, path):
        self.release_session()
        fs = self.get('fs')
        self.release_database()
        path = json.loads(path)
        # 'path' can be a unicode/string or list of unicodes/strings
        # so convert it with the convert function
        return fs.expand(path)
asseldonk's avatar
asseldonk committed
432
433

    @cherrypy.expose
434
    @cherrypy.tools.method(accept="GET")
435
    def checkpermissions(self, path):
asseldonk's avatar
asseldonk committed
436
437
438
439
440
441
        self.release_session()
        fs = self.get('fs')
        self.release_database()
        path = json.loads(path)
        path = fs.expand(path)
        return {"permission": fs.checkPermissions(path)}