filesystem.py 14.5 KB
Newer Older
Marcel's avatar
Marcel committed
1
# -*- coding: utf-8 -*-
2

Marcel's avatar
Marcel committed
3
# imports
4
5
from StringIO import StringIO
from distutils.spawn import find_executable
6
7
from mimetypes import guess_type
from zipfile import ZipFile
8
import json
9
10
11
12
import logging
import os
import re
import shutil
Gero Müller's avatar
Gero Müller committed
13
import stat
14
15
16
17
import subprocess

try:
    import Image
Gero Müller's avatar
Gero Müller committed
18
    import ImageFilter
19
20
21
22
23
24
25
26
27
28
    HAVE_PIL = True
except:
    HAVE_PIL = False

if not HAVE_PIL:
    convert_executable = find_executable('convert')
    if convert_executable and os.path.isfile(convert_executable):
        HAVE_CONVERT = True
    else:
        HAVE_CONVERT = False
29

30
31
logger = logging.getLogger(__name__)

32

33
34
class FileSystem(object):

35
36
    FILE_EXTENSIONS = ["png", "jpg", "jpeg", "bmp", "ps", "eps", "pdf",
                       "txt", "xml", "py", "c", "cpp", "root", "pxlio"]
Marcel's avatar
Marcel committed
37
38
39
    BROWSER_EXTENSIONS = ["png", "jpg", "jpeg", "bmp"]
    ADDITIONAL_MIMES = {
        "pxlio": "text/plain",
40
        "root": "text/plain"
Marcel's avatar
Marcel committed
41
    }
42
43
44
45

    def __init__(self):
        # allowed extensions
        self.allowed_extensions = FileSystem.FILE_EXTENSIONS
46
        self.browser_extensions = FileSystem.BROWSER_EXTENSIONS
47

48
    def setup(self, basedir=None):
Marcel's avatar
Marcel committed
49
50
        if basedir is None:
            basedir = self.expand('~/')
murban's avatar
murban committed
51
        if not os.path.isdir(basedir):
Marcel's avatar
Marcel committed
52
            raise Exception("Basedir '%s' does not exist!" % basedir)
murban's avatar
murban committed
53
54
        # the basedir
        self.basedir = os.path.join(basedir, ".vispa")
55
56
        if os.path.isdir(self.basedir):
            return "Basedir already exists"
57
        else:
Gero Müller's avatar
Gero Müller committed
58
            os.makedirs(self.basedir, 0o700)
59
            return "Basedir now exists"
60

murban's avatar
murban committed
61
    def get_mime_type(self, filepath):
Marcel's avatar
Marcel committed
62
        filepath = self.expand(filepath)
63
64
65
66
        mime, encoding = guess_type(filepath)
        if mime is not None:
            return mime
        ext = filepath.split(".")[-1]
Gero Müller's avatar
Gero Müller committed
67
68
        if ext is not None and ext != "" and ext.lower(
        ) in FileSystem.ADDITIONAL_MIMES.keys():
69
70
71
            return FileSystem.ADDITIONAL_MIMES[ext]
        return None

murban's avatar
murban committed
72
    def check_file_extension(self, path, extensions=[]):
Marcel's avatar
Marcel committed
73
        path = self.expand(path)
74
75
76
77
78
79
80
81
        if (len(extensions) == 0):
            return True
        for elem in extensions:
            elem = elem if elem.startswith(".") else "." + elem
            if path.lower().endswith(elem.lower()):
                return True
        return False

Marcel's avatar
Marcel committed
82
83
    def exists(self, path, type=None):
        # type may be 'f' or 'd'
Marcel's avatar
Marcel committed
84
        path = self.expand(path)
85
86
        # path exists physically?
        if not os.path.exists(path):
Marcel's avatar
Marcel committed
87
88
            return None
        # type correct?
89
90
91
92
93
94
95
        target_type = 'd' if os.path.isdir(path) else 'f'
        if not type:
            return target_type
        type = type.lower()
        if type not in ['f', 'd']:
            return None
        return target_type if target_type == type else None
96

Gero Müller's avatar
Gero Müller committed
97
98
99
    def get_file_list(self, path, deep=False,
                      filter=None, reverse=False,
                      hide_hidden=True, encode_json=True):
100
        filter = filter or []
101
        filelist = []
Marcel's avatar
Marcel committed
102
        path_expand = self.expand(path)
103
104
105
106
107
108
109
110
111
112
113
114
115
116
        try:
            for elem in os.listdir(path_expand):
                # hide hidden files?
                if elem.startswith('.') and hide_hidden:
                    continue

                # excluded by filters?
                match = False
                for filter_elem in filter:
                    if re.search(filter_elem, elem):
                        match = True
                if match != reverse:
                    continue

Gero Müller's avatar
Gero Müller committed
117
118
119
                info = {
                    'name': elem
                }
120

Gero Müller's avatar
Gero Müller committed
121
                fullpath = os.path.join(path_expand, elem)
Gero Müller's avatar
Gero Müller committed
122
                stats = os.lstat(fullpath)
Gero Müller's avatar
Gero Müller committed
123
                is_symlink = stat.S_ISLNK(stats.st_mode)
Gero Müller's avatar
Gero Müller committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
                if is_symlink:
                    realfullpath = os.path.realpath(fullpath)
                    info.update({'symlink': True, 'realpath': realfullpath})
                    if os.path.exists(realfullpath):
                        stats = os.stat(realfullpath)

                info.update({
                    'size': stats.st_size,
                    'mtime': stats.st_mtime,
                    'type': 'd' if stat.S_ISDIR(stats.st_mode) else 'f'
                })

                filelist.append(info)
                if deep:
                    filelist.extend(self.get_file_list(fullpath, deep,
                                                       filter, reverse))
        except:
            pass
142
143

        # Determine the parent
Gero Müller's avatar
Gero Müller committed
144
        # parentpath = path_expand[:-1] if path_expand.endswith(os.sep) and
Gero Müller's avatar
Gero Müller committed
145
        #    path_expand != os.sep else path_expand
146
        parentpath = os.path.dirname(path_expand)
147
148
149
        data = {'filelist': filelist, 'parentpath': parentpath}
        if encode_json:
            return json.dumps(data)
150
151
        else:
            return data
152

Gero Müller's avatar
Gero Müller committed
153
154
    def get_suggestions(
            self, path, length=1, append_hidden=True, encode_json=True):
Marcel's avatar
Marcel committed
155
156
157
        suggestions = []
        source, filter = None, None
        # does the path exist?
Marcel's avatar
Marcel committed
158
159
        path_expanded = self.expand(path)
        if os.path.exists(path_expanded):
Marcel's avatar
Marcel committed
160
            # dir case
Marcel's avatar
Marcel committed
161
            if os.path.isdir(path_expanded):
Marcel's avatar
Marcel committed
162
163
164
165
166
167
168
169
170
171
172
                if path.endswith('/'):
                    source = path
                else:
                    suggestions.append(path + os.sep)
                    return suggestions
            # file case
            else:
                return suggestions
        else:
            # try to estimate source and filter
            head, tail = os.path.split(path)
Marcel's avatar
Marcel committed
173
            if os.path.isdir(os.path.expanduser(os.path.expandvars(head))):
Marcel's avatar
Marcel committed
174
175
176
177
178
179
180
                source = head
                filter = tail

        # return empty suggestions when source is not set
        if not source:
            return suggestions

Marcel's avatar
Marcel committed
181
        files = os.listdir(os.path.expanduser(os.path.expandvars(source)))
Marcel's avatar
Marcel committed
182
183
        # resort?
        if append_hidden:
Gero Müller's avatar
Gero Müller committed
184
185
            files = sorted(
                map(lambda f: str(f), files), cmp=file_compare, key=str.lower)
186
        while (len(suggestions) < length or length == 0) and len(files):
Marcel's avatar
Marcel committed
187
188
189
190
            file = files.pop(0)
            if filter and not file.startswith(filter):
                continue
            suggestion = os.path.join(source, file)
Gero Müller's avatar
Gero Müller committed
191
192
            if not suggestion.endswith(
                    '/') and os.path.isdir(os.path.expanduser(os.path.expandvars(suggestion))):
Marcel's avatar
Marcel committed
193
194
195
                suggestion += '/'
            suggestions.append(suggestion)

196
        return suggestions if not encode_json else json.dumps(suggestions)
197

murban's avatar
murban committed
198
    def cut_slashs(self, path):
Marcel's avatar
Marcel committed
199
        path = self.expand(path)
murban's avatar
murban committed
200
        path = path[1:] if path.startswith(os.sep) else path
201
202
        if path == "":
            return path
murban's avatar
murban committed
203
        path = path[:-1] if path.endswith(os.sep) else path
204
205
        return path

murban's avatar
murban committed
206
    def create_folder(self, path, name):
Marcel's avatar
Marcel committed
207
        path = self.expand(path)
Gero Müller's avatar
Gero Müller committed
208
209
        name = self.expand(name)

210
        # folder with the same name existent?
211
        name = self.handle_file_name_collision(name, path)
Marcel's avatar
Marcel committed
212
        fullpath = os.path.join(path, name)
213
214
215
        try:
            os.mkdir(fullpath)
        except Exception as e:
216
            # raise Exception("You don't have the permission to create this folder!")
217
218
            raise Exception(str(e))

murban's avatar
murban committed
219
    def create_file(self, path, name):
Marcel's avatar
Marcel committed
220
        path = self.expand(path)
Gero Müller's avatar
Gero Müller committed
221
222
        name = self.expand(name)

223
        # file with the same name existent?
224
        name = self.handle_file_name_collision(name, path)
Marcel's avatar
Marcel committed
225
        fullpath = os.path.join(path, name)
226
227
228
229
230
231
        try:
            f = file(fullpath, "w")
            f.close()
        except Exception as e:
            raise Exception(str(e))

232
    def rename(self, path, name, new_name, force=False):
Gero Müller's avatar
Gero Müller committed
233
234
235
236
        path = self.expand(path)
        name = self.expand(name)
        new_name = self.expand(new_name)

237
238
239
240
241
242
243
        try:
            if force == False:
                new_name = self.handle_file_name_collision(new_name, path)
            name = os.path.join(path, name)
            new_name = os.path.join(path, new_name)
            os.renames(name, new_name)
            return
Gero Müller's avatar
Gero Müller committed
244
        except Exception as e:
245
            return str(e)
246

murban's avatar
murban committed
247
248
    def remove(self, path):
        if isinstance(path, list):
249
250
251
            for p in path:
                self.remove(p)
        else:
Marcel's avatar
Marcel committed
252
253
254
255
256
            path = self.expand(path)
            if os.path.isdir(path):
                shutil.rmtree(path)
            else:
                os.remove(path)
257
258

    def compress(self, path, paths, name):
Gero Müller's avatar
Gero Müller committed
259
260
        # paths has to be a list of strings
        paths = paths if isinstance(paths, (list, tuple)) else [paths]
Gero Müller's avatar
Gero Müller committed
261
        paths = [self.expand(p) for p in paths]
Gero Müller's avatar
Gero Müller committed
262
263
264

        path = path if not path.endswith(os.sep) else path[:-1]
        path = self.expand(path)
Martin Urban's avatar
Martin Urban committed
265

Gero Müller's avatar
Gero Müller committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
        name = name if name.endswith(".zip") else name + ".zip"
        name = self.handle_file_name_collision(name, path)

        fullpath = os.path.join(path, name)

        with ZipFile(fullpath, "w") as archive:
            i = 0
            while i < len(paths):
                if not paths[i]:
                    i += 1
                    continue
                p = self.expand(paths[i])
                i += 1

                if os.path.isdir(p):
                    for elem in os.listdir(p):
                        fullp = os.path.join(p, elem)
                        if os.path.isdir(fullp):
                            paths.append(fullp)
                        else:
Gero Müller's avatar
Gero Müller committed
286
287
                            ap = fullp[
                                len(path):] if fullp.startswith(path) else fullp
Gero Müller's avatar
Gero Müller committed
288
289
290
                            logger.debug(fullp)
                            archive.write(fullp, ap)
                else:
Martin Urban's avatar
Martin Urban committed
291
                    ap = p[len(path):] if p.startswith(path) else p
Gero Müller's avatar
Gero Müller committed
292
                    logger.debug(p)
Martin Urban's avatar
Martin Urban committed
293
                    archive.write(p, ap)
294

295
    def paste(self, path, fullsrc, cut):
Marcel's avatar
Marcel committed
296
        # TODO
297
298
299
300
        path = self.expand(path)
        if isinstance(fullsrc, (list, tuple)):
            for p in fullsrc:
                p = self.expand(p)
murban's avatar
murban committed
301
                self.paste(path, p, cut)
302
303
            return True

304
305
        # fulltarget = os.path.join(path, fullsrc.split(os.sep)[-1])
        logger.error("")
Gero Müller's avatar
Gero Müller committed
306
307
        target = self.handle_file_name_collision(
            fullsrc.split(os.sep)[-1], path)
308
        fulltarget = os.path.join(path, target)
309

310
311
        if os.path.isdir(fullsrc):
            shutil.copytree(fullsrc, fulltarget)
312
            if cut:
313
                shutil.rmtree(fullsrc)
314
        else:
315
            shutil.copy2(fullsrc, fulltarget)
316
            if cut:
317
                os.remove(fullsrc)
318

Marcel's avatar
Marcel committed
319
    def save_file(self, path, content, binary=False, force=True):
320
321
322
        logger.info(path)
        logger.info("\n\n")
        return
Marcel's avatar
Marcel committed
323
        path = self.expand(path)
Marcel's avatar
Marcel committed
324
        # check if file already exists
murban's avatar
murban committed
325
        if os.path.exists(path) and not force:
Marcel's avatar
Marcel committed
326
            return False
Marcel's avatar
Marcel committed
327
        if binary:
328
329
330
331
332
            out = file(path, "wb")
        else:
            out = file(path, "w")
        logger.info("saved")
        logger.info("\n\n")
Marcel's avatar
Marcel committed
333
        out.write(content)
334
        out.close()
Marcel's avatar
Marcel committed
335
        return True
336

Gero Müller's avatar
Gero Müller committed
337
    def save_file_content(self, filename, content,
Gero Müller's avatar
Gero Müller committed
338
                          path=None, force=True, append=False):
339
340
        # check write permissions
        # if not self.checkPermission(username, vPath, 'w'):
Gero Müller's avatar
Gero Müller committed
341
342
343
344
345
346
        #    return False, "Permission denied!"

        if path:
            filename = os.path.join(path, filename)
        filename = self.expand(filename)

347
        # check if file already exists
Gero Müller's avatar
Gero Müller committed
348
349
350
351
352
353
354
355
356
        if os.path.exists(filename) and not force:
            return False, "The file '%s' already exists!" % filename

        out = open(filename, "ab+" if append else "wb")
        out.write(content)
        out.close()

        return True, "File saved!"

murban's avatar
murban committed
357
    def get_file_content(self, path):
Marcel's avatar
Marcel committed
358
        path = self.expand(path)
Gero Müller's avatar
Gero Müller committed
359
        f = open(path, "rb")
360
361
        content = f.read()
        f.close()
Marcel's avatar
Marcel committed
362
        return content
363

Marcel's avatar
Marcel committed
364
    def get_mtime(self, path):
Marcel's avatar
Marcel committed
365
        path = self.expand(path)
Marcel's avatar
Marcel committed
366
367
        return os.path.getmtime(path)

murban's avatar
murban committed
368
    def is_browser_file(self, path):
Marcel's avatar
Marcel committed
369
        path = self.expand(path)
370
        extension = path.split(".")[-1]
371
        return extension.lower() in FileSystem.BROWSER_EXTENSIONS
372

murban's avatar
murban committed
373
    def handle_file_name_collision(self, name, path):
374
        # collision?
murban's avatar
murban committed
375
        files = os.listdir(path)
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
        if name not in files:
            return name

        # when this line is reached, there is a collision!

        # cut the file extension
        extension = name.split(".")[-1]
        prename = None
        if name == extension:
            extension = ""
            prename = name
        else:
            prename = name.split("." + extension)[0]

        # has the name already a counter at its end?
        hasCounter = False
        preprename = None
        counter = prename.split("_")[-1]

        if counter != prename:
            try:
                counter = int(counter)
                hasCounter = True
                preprename = "_".join(prename.split("_")[:-1])
            except:
                pass

        if hasCounter:
            # increment and try again
            counter += 1
Gero Müller's avatar
Gero Müller committed
406
407
408
            newname = "%s_%d%s" % (preprename,
                                   counter,
                                   "" if extension == "" else "." + extension)
409
        else:
Gero Müller's avatar
Gero Müller committed
410
411
            newname = "%s_1%s" % (
                prename, "" if extension == "" else "." + extension)
412
413

        # return
murban's avatar
murban committed
414
        return self.handle_file_name_collision(newname, path)
Marcel's avatar
Marcel committed
415

Marcel's avatar
Marcel committed
416
417
    def expand(self, path):
        return os.path.expanduser(os.path.expandvars(path))
Marcel's avatar
Marcel committed
418

Gero Müller's avatar
Gero Müller committed
419
    def thumbnail(self, path, width=100, height=100, sharpen=True):
420
421
422
        path = self.expand(path)
        if HAVE_PIL:
            output = StringIO()
Gero Müller's avatar
Gero Müller committed
423
            img = Image.open(path)
Gero Müller's avatar
Gero Müller committed
424
            img.thumbnail((width, height), Image.ANTIALIAS)
Gero Müller's avatar
Gero Müller committed
425
            if sharpen:
426
                img.filter(ImageFilter.SHARPEN)
Gero Müller's avatar
Gero Müller committed
427
            img.save(output, "JPEG")
428
429
430
431
432
433
434
435
436
437
438
439
440
            contents = output.getvalue()
            output.close()
            return contents
        elif HAVE_CONVERT:
            cmd = ['convert', path,
                   '-thumbnail', '%dx%d' % (width, height),
                   'jpeg:-']
            p = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE)
            return p.communicate()[0]
        else:
            return self.get_file_content(path)


Marcel's avatar
Marcel committed
441
442
443
444
445
446
447
448
def string_compare(a, b):
    if a == b:
        return 0
    elif a > b:
        return 1
    else:
        return -1

449

Marcel's avatar
Marcel committed
450
451
452
453
454
455
456
457
458
def file_compare(a, b):
    if not a.startswith('.') and not b.startswith('.'):
        return string_compare(a, b)
    elif a.startswith('.') and b.startswith('.'):
        return string_compare(a, b)
    elif a.startswith('.') and not b.startswith('.'):
        return 1
    elif not a.startswith('.') and b.startswith('.'):
        return -1