PaferaPy Async 0.1
ASGI framework focused on simplicity and efficiency
Loading...
Searching...
No Matches
file.py
Go to the documentation of this file.
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4import subprocess
5import re
6import os
7
8import pafera.db
10
11from pafera.utils import *
12from pafera.validators import *
13
14# File types
15FILE_DOCUMENT = 1;
16FILE_IMAGE = 2;
17FILE_AUDIO = 3;
18FILE_VIDEO = 4;
19FILE_ARCHIVE = 5;
20FILE_PROGRAM = 6;
21FILE_SPREADSHEET = 7;
22FILE_PRESENTATION = 8;
23FILE_BOOK = 9;
24FILE_TEXT = 10;
25FILE_DISK_IMAGE = 11;
26FILE_WEBPAGE = 12;
27
28FILE_DELETED = 126;
29FILE_OTHER = 127;
30
31# Flags
32FILE_PUBLIC = 0x01;
33FILE_PROTECTED = 0x02;
34FILE_PRIVATE = 0x04;
35
36# *********************************************************************
38 """Class for managing all uploaded files.
39
40 This is a system-wide repository for user uploaded files that allows
41 easy search and discovery while retaining security and ease of
42 management. Files can be made searchable, requires a direct link to
43 access, or locked down to the point where a timed access token is
44 needed to access the file.
45
46 For efficiency, all uploaded images are converted to the webp format,
47 audio to mp3, and video to h264 MP4s. If you don't want this behavior,
48 modify the system/upload handler in system/__init__.py
49 """
50
51 _dbfields = {
52 'rid': ('INTEGER PRIMARY KEY', 'NOT NULL',),
53 'filepath': ('TEXT', 'NOT NULL', BlankValidator()),
54 'filename': ('TEXT', 'NOT NULL', BlankValidator()),
55 'extension': ('TEXT', 'NOT NULL', BlankValidator()),
56 'descriptions': ('TRANSLATION', "NOT NULL DEFAULT 0",),
57 'modified': ('DATETIME', 'NOT NULL',),
58 'size': ('BIGINT', 'NOT NULL',),
59 'hash': ('TEXT', 'NOT NULL',),
60 'filetype': ('INT16', 'NOT NULL',),
61 'width': ('INT16', 'NOT NULL DEFAULT 0',),
62 'height': ('INT16', 'NOT NULL DEFAULT 0',),
63 'length': ('INT16', 'NOT NULL DEFAULT 0',),
64 'flags': ('INT', 'NOT NULL DEFAULT 0',),
65 'accesstokens': ('DICT', "NOT NULL DEFAULT ''",),
66 }
67 _dbindexes = ()
68 _dblinks = []
69 _dbdisplay = ['filename', 'description', 'modified', 'size']
70 _dbflags = pafera.db.DB_SECURE
71
72 # Generic file types for faster searches
73 TYPES = {
74 'document': 1,
75 'image': 2,
76 'sound': 3,
77 'video': 4,
78 'archive': 5,
79 'program': 6,
80 'spreadsheet': 7,
81 'presentation': 8,
82 'book': 9,
83 'text': 10,
84 'disk image': 11,
85 'webpage': 12,
86 'source code': 13,
87 'deleted': 126,
88 'other': 127,
89 }
90
91 # -------------------------------------------------------------------
92 def __init__(self):
93 super().__init__()
94
95 self.fullpath = ''
96
97 # -------------------------------------------------------------------
98 def FullPath(self):
99 """Returns the filesystem path for this file.
100 """
101
102 if self.fullpath:
103 return self.fullpath
104
105 dirpath = CodeDir(ToShortCode(self.rid))
106
107 if self.flags & FILE_PRIVATE:
108 return 'private/system/files/' + dirpath + '.' + self.extension
109
110 return 'static/system/files/' + dirpath + '.' + self.extension
111
112 # -------------------------------------------------------------------
113 def ThumbnailFile(self):
114 """Returns the filesystem path for an appropiate thumbnail.
115 """
116 path = self.MakeThumbnail()
117
118 if path:
119 return path
120
121 if self.filetype == FILE_DOCUMENT:
122 return "static/system/icons/document.webp"
123 elif self.filetype == FILE_SPREADSHEET:
124 return "static/system/icons/spreadsheet.webp"
125 elif self.filetype == FILE_PRESENTATION:
126 return "static/system/icons/presentation.webp"
127 elif self.filetype == FILE_IMAGE:
128 return "static/system/icons/image.webp"
129 elif self.filetype == FILE_AUDIO:
130 return "static/system/icons/audio.webp"
131 elif self.filetype == FILE_VIDEO:
132 return "static/system/icons/video.webp"
133 elif self.filetype == FILE_ARCHIVE:
134 return "static/system/icons/archive.webp"
135 elif self.filetype == FILE_PROGRAM:
136 return "static/system/icons/program.webp"
137 elif self.filetype == FILE_BOOK:
138 return "static/system/icons/book.webp"
139 elif self.filetype == FILE_TEXT:
140 return "static/system/icons/text.webp"
141 elif self.filetype == FILE_DISK_IMAGE:
142 return "static/system/icons/iso.webp"
143 elif self.filetype == FILE_WEBPAGE:
144 return "static/system/icons/webpage.webp"
145
146 return "static/system/icons/unknown.webp"
147
148 # -------------------------------------------------------------------
149 def ThumbnailURL(self):
150 """Returns a URL to display the thumbnail image.
151 """
152 if self.flags & FILE_PRIVATE:
153 return '/system/thumbnailer/' + ToShortCode(self.rid)
154
155 return self.ThumbnailFile()[6:]
156
157 # -------------------------------------------------------------------
158 def Process(self, g):
159 """Analyzes the file and inserts it into the system registry, or
160 update any changed files.
161 """
162 if not self.filename:
163 raise Exception('Cannot process empty templateclass.')
164
165 fullpath = self.FullPath()
166 stats = os.stat(fullpath)
167
168 size = stats.st_size
169 modified = stats.st_mtime;
170
171 # Only process updated or different sized files
172 if self.size == size and self.modified == modified:
173 return;
174
175 changed = 1
176
177 extension = os.path.splitext(this.filename)[1]
178
179 if extension in [
180 'jpg',
181 'jpeg',
182 'gif',
183 'png',
184 'tif',
185 'tiff',
186 'svg',
187 'psd',
188 'bmp',
189 'pcd',
190 'pcx',
191 'pct',
192 'pgm',
193 'ppm',
194 'tga',
195 'img',
196 'raw',
197 'webp',
198 'wbmp',
199 'eps',
200 'cdr',
201 'ai',
202 'dwg',
203 'indd',
204 'dss',
205 'fla',
206 'ss',
207 ]:
208 filetype =FILE_IMAGE
209 elif extension in [
210 'doc',
211 'docx',
212 'odt',
213 'rtf',
214 'pub',
215 'wpd',
216 'qxd',
217 'cdl',
218 'eml',
219 ]:
220 filetype = FILE_DOCUMENT
221 elif extension in [
222 'chm',
223 'pdf',
224 'epub',
225 'mobi',
226 'cbr',
227 'cbz',
228 'cb7',
229 'cbt',
230 'cba',
231 'ibooks',
232 'kf8',
233 'pdg',
234 'azw',
235 'prc',
236 ]:
237 filetype = FILE_BOOK
238 elif extension in [
239 'mp3',
240 'aac',
241 'wma',
242 'oga',
243 'ogg',
244 'm4a',
245 'wav',
246 'aif',
247 'aiff',
248 'dvf',
249 'm4b',
250 'm4p',
251 'mid',
252 'midi',
253 'ram',
254 'mp2',
255 ]:
256 filetype = FILE_AUDIO
257 elif extension in [
258 'mp4',
259 'mpg',
260 'avi',
261 'wmv',
262 'rm',
263 'rmvb',
264 'ogv',
265 'ogm',
266 'm4v',
267 'mov',
268 'flv',
269 'f4v',
270 '3gp',
271 '3gpp',
272 'vob',
273 'asf',
274 'divx',
275 'mswmm',
276 'asx',
277 'amr',
278 'mkv',
279 'vp8',
280 'webm',
281 ]:
282 filetype = FILE_VIDEO
283 elif extension in [
284 'zip',
285 'gz',
286 'bz2',
287 'rar',
288 'tar',
289 'lz',
290 'lzma',
291 'lzo',
292 'rz',
293 'sfark',
294 'xz',
295 'z',
296 '7z',
297 's7z',
298 'ace',
299 'cab',
300 'pak',
301 'rk',
302 'sfx',
303 'sit',
304 'sitx',
305 'qbb',
306 'jar',
307 'pst',
308 'hqx',
309 'sea',
310 ]:
311 filetype = FILE_ARCHIVE
312 elif extension in [
313 'exe',
314 'msi',
315 'bat',
316 'cmd',
317 ]:
318 filetype = FILE_PROGRAM
319 elif extension in [
320 'xls',
321 'xlsx',
322 'ods',
323 'qbw',
324 'wps',
325 ]:
326 filetype = FILE_SPREADSHEET
327 elif extension in [
328 'ppt',
329 'pptx',
330 'odp',
331 ]:
332 filetype = FILE_PRESENTATION
333 elif extension in [
334 'txt',
335 'ini',
336 'cfg',
337 'log',
338 ]:
339 filetype = FILE_TEXT
340 elif extension in [
341 'htm',
342 'html',
343 'mhtml',
344 ]:
345 filetype = FILE_WEBPAGE
346 elif extension in [
347 'deleted',
348 ]:
349 filetype = FILE_DELETED
350 else:
351 filetype = FILE_OTHER
352
353 self.Set(
354 size = size,
355 modified = modified,
356 extension = extension,
357 filetype = filetype,
358 );
359
360 if self.rid:
361 g.db.Update(self)
362 else:
363 g.db.Insert(self)
364
365 # -------------------------------------------------------------------
366 def MakeThumbnail(self, thumbsize = '256'):
367 """Creates or updates a thumbnail if needed.
368
369 The default thumbnail size is 256x256, but if you are working with
370 high resolution screens, feel free to use 512x512 or higher.
371
372 You'll need to install imagemagick, ffmpeg, and ffmpegthumbnailer
373 to use this functionality.
374 """
375 if self.flags & FILE_PRIVATE:
376 thumbfile = 'private/system/thumbs/' + CodeDir(ToShortCode(self.rid)) + '.webp'
377 else:
378 thumbfile = 'static/system/thumbs/' + CodeDir(ToShortCode(self.rid)) + '.webp'
379
380 os.makedirs(os.path.dirname(thumbfile), exist_ok = True)
381
382 if os.path.exists(thumbfile):
383 stats = os.stat(thumbfile)
384
385 if stats.st_mtime > self.modified.timestamp():
386 return thumbfile
387
388 fullpath = self.FullPath()
389
390 if self.filetype == FILE_IMAGE:
391 # No need to make thumbnails for small images
392 if self.size < (20 * 1024):
393 if self.flags & FILE_PRIVATE:
394 thumbfile = 'private/system/files/' + CodeDir(ToShortCode(self.rid)) + '.webp'
395 else:
396 thumbfile = 'static/system/files/' + CodeDir(ToShortCode(self.rid)) + '.webp'
397
398 return thumbfile
399
400 result = subprocess.run(['identify', fullpath], stdout=subprocess.PIPE).stdout.decode('utf-8')
401
402 if not self.width:
403 matches = re.findall('([0-9]+)x([0-9]+)', result, re.MULTILINE)
404
405 self.Set(
406 width = matches[0][0],
407 height = matches[0][1],
408 )
409
410 subprocess.run([
411 'convert',
412 fullpath,
413 '-thumbnail',
414 thumbsize + 'x' + thumbsize + '^',
415 '-gravity',
416 'center',
417 '-extent',
418 thumbsize + 'x' + thumbsize,
419 thumbfile
420 ])
421 elif self.filetype == FILE_VIDEO:
422 if not self.width:
423 result = subprocess.run(['ffmpeg', '-i', fullpath], stdout=subprocess.PIPE).stdout.decode('utf-8')
424
425 matches = re.findall('Duration: ([0-9]+):([0-9]+):([0-9]+)', result, re.MULTILINE)
426
427 self.Set(
428 length = (
429 (int(matches[0][0]) * 3600)
430 + (int(matches[0][1]) * 60)
431 + (int(matches[0][2]))
432 )
433 )
434
435 matches = re.findall('([0-9]+)x([0-9]+)', result, re.MULTILINE)
436
437 self.Set(
438 width = matches[0][0],
439 height = matches[0][1],
440 )
441
442 subprocess.run(['ffmpegthumbnailer', '-i', 'fullpath', '-o', thumbfile, '-s', '512', '-a'])
443
444 if os.path.exists(thumbfile):
445 return thumbfile
446
447 return ''
Base class for all database models.
Definition: modelbase.py:20
def Set(self, **kwargs)
We use this method instead of direct attribute access in order to keep track of what values have been...
Definition: modelbase.py:115
Throws an exception on blank values.
Definition: validators.py:43
Class for managing all uploaded files.
Definition: file.py:37
def Process(self, g)
Analyzes the file and inserts it into the system registry, or update any changed files.
Definition: file.py:158
def ThumbnailFile(self)
Returns the filesystem path for an appropiate thumbnail.
Definition: file.py:113
def MakeThumbnail(self, thumbsize='256')
Creates or updates a thumbnail if needed.
Definition: file.py:366
def FullPath(self)
Returns the filesystem path for this file.
Definition: file.py:98
def __init__(self)
Initialize all fields at creation like a good programmer should.
Definition: file.py:92
def ThumbnailURL(self)
Returns a URL to display the thumbnail image.
Definition: file.py:149
Definition: db.py:1
def ToShortCode(val, chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_')
Turns a 32-bit value into a six character alphanumeric code.
Definition: utils.py:36
def CodeDir(code)
Separates a filename into three character segments with the directory separator '/' in between.
Definition: utils.py:87