Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | espaco | 1 | # -*- coding: utf-8 -*- |
| 2 | # |
||
| 3 | # jQuery File Upload Plugin GAE Python Example 2.2.0 |
||
| 4 | # https://github.com/blueimp/jQuery-File-Upload |
||
| 5 | # |
||
| 6 | # Copyright 2011, Sebastian Tschan |
||
| 7 | # https://blueimp.net |
||
| 8 | # |
||
| 9 | # Licensed under the MIT license: |
||
| 10 | # http://www.opensource.org/licenses/MIT |
||
| 11 | # |
||
| 12 | |||
| 13 | from __future__ import with_statement |
||
| 14 | from google.appengine.api import files, images |
||
| 15 | from google.appengine.ext import blobstore, deferred |
||
| 16 | from google.appengine.ext.webapp import blobstore_handlers |
||
| 17 | import json |
||
| 18 | import re |
||
| 19 | import urllib |
||
| 20 | import webapp2 |
||
| 21 | |||
| 22 | WEBSITE = 'https://blueimp.github.io/jQuery-File-Upload/' |
||
| 23 | MIN_FILE_SIZE = 1 # bytes |
||
| 24 | MAX_FILE_SIZE = 5000000 # bytes |
||
| 25 | IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)') |
||
| 26 | ACCEPT_FILE_TYPES = IMAGE_TYPES |
||
| 27 | THUMBNAIL_MODIFICATOR = '=s80' # max width / height |
||
| 28 | EXPIRATION_TIME = 300 # seconds |
||
| 29 | |||
| 30 | |||
| 31 | def cleanup(blob_keys): |
||
| 32 | blobstore.delete(blob_keys) |
||
| 33 | |||
| 34 | |||
| 35 | class UploadHandler(webapp2.RequestHandler): |
||
| 36 | |||
| 37 | def initialize(self, request, response): |
||
| 38 | super(UploadHandler, self).initialize(request, response) |
||
| 39 | self.response.headers['Access-Control-Allow-Origin'] = '*' |
||
| 40 | self.response.headers[ |
||
| 41 | 'Access-Control-Allow-Methods' |
||
| 42 | ] = 'OPTIONS, HEAD, GET, POST, PUT, DELETE' |
||
| 43 | self.response.headers[ |
||
| 44 | 'Access-Control-Allow-Headers' |
||
| 45 | ] = 'Content-Type, Content-Range, Content-Disposition' |
||
| 46 | |||
| 47 | def validate(self, file): |
||
| 48 | if file['size'] < MIN_FILE_SIZE: |
||
| 49 | file['error'] = 'File is too small' |
||
| 50 | elif file['size'] > MAX_FILE_SIZE: |
||
| 51 | file['error'] = 'File is too big' |
||
| 52 | elif not ACCEPT_FILE_TYPES.match(file['type']): |
||
| 53 | file['error'] = 'Filetype not allowed' |
||
| 54 | else: |
||
| 55 | return True |
||
| 56 | return False |
||
| 57 | |||
| 58 | def get_file_size(self, file): |
||
| 59 | file.seek(0, 2) # Seek to the end of the file |
||
| 60 | size = file.tell() # Get the position of EOF |
||
| 61 | file.seek(0) # Reset the file position to the beginning |
||
| 62 | return size |
||
| 63 | |||
| 64 | def write_blob(self, data, info): |
||
| 65 | blob = files.blobstore.create( |
||
| 66 | mime_type=info['type'], |
||
| 67 | _blobinfo_uploaded_filename=info['name'] |
||
| 68 | ) |
||
| 69 | with files.open(blob, 'a') as f: |
||
| 70 | f.write(data) |
||
| 71 | files.finalize(blob) |
||
| 72 | return files.blobstore.get_blob_key(blob) |
||
| 73 | |||
| 74 | def handle_upload(self): |
||
| 75 | results = [] |
||
| 76 | blob_keys = [] |
||
| 77 | for name, fieldStorage in self.request.POST.items(): |
||
| 78 | if type(fieldStorage) is unicode: |
||
| 79 | continue |
||
| 80 | result = {} |
||
| 81 | result['name'] = re.sub( |
||
| 82 | r'^.*\\', |
||
| 83 | '', |
||
| 84 | fieldStorage.filename |
||
| 85 | ) |
||
| 86 | result['type'] = fieldStorage.type |
||
| 87 | result['size'] = self.get_file_size(fieldStorage.file) |
||
| 88 | if self.validate(result): |
||
| 89 | blob_key = str( |
||
| 90 | self.write_blob(fieldStorage.value, result) |
||
| 91 | ) |
||
| 92 | blob_keys.append(blob_key) |
||
| 93 | result['deleteType'] = 'DELETE' |
||
| 94 | result['deleteUrl'] = self.request.host_url +\ |
||
| 95 | '/?key=' + urllib.quote(blob_key, '') |
||
| 96 | if (IMAGE_TYPES.match(result['type'])): |
||
| 97 | try: |
||
| 98 | result['url'] = images.get_serving_url( |
||
| 99 | blob_key, |
||
| 100 | secure_url=self.request.host_url.startswith( |
||
| 101 | 'https' |
||
| 102 | ) |
||
| 103 | ) |
||
| 104 | result['thumbnailUrl'] = result['url'] +\ |
||
| 105 | THUMBNAIL_MODIFICATOR |
||
| 106 | except: # Could not get an image serving url |
||
| 107 | pass |
||
| 108 | if not 'url' in result: |
||
| 109 | result['url'] = self.request.host_url +\ |
||
| 110 | '/' + blob_key + '/' + urllib.quote( |
||
| 111 | result['name'].encode('utf-8'), '') |
||
| 112 | results.append(result) |
||
| 113 | deferred.defer( |
||
| 114 | cleanup, |
||
| 115 | blob_keys, |
||
| 116 | _countdown=EXPIRATION_TIME |
||
| 117 | ) |
||
| 118 | return results |
||
| 119 | |||
| 120 | def options(self): |
||
| 121 | pass |
||
| 122 | |||
| 123 | def head(self): |
||
| 124 | pass |
||
| 125 | |||
| 126 | def get(self): |
||
| 127 | self.redirect(WEBSITE) |
||
| 128 | |||
| 129 | def post(self): |
||
| 130 | if (self.request.get('_method') == 'DELETE'): |
||
| 131 | return self.delete() |
||
| 132 | result = {'files': self.handle_upload()} |
||
| 133 | s = json.dumps(result, separators=(',', ':')) |
||
| 134 | redirect = self.request.get('redirect') |
||
| 135 | if redirect: |
||
| 136 | return self.redirect(str( |
||
| 137 | redirect.replace('%s', urllib.quote(s, ''), 1) |
||
| 138 | )) |
||
| 139 | if 'application/json' in self.request.headers.get('Accept'): |
||
| 140 | self.response.headers['Content-Type'] = 'application/json' |
||
| 141 | self.response.write(s) |
||
| 142 | |||
| 143 | def delete(self): |
||
| 144 | key = self.request.get('key') or '' |
||
| 145 | blobstore.delete(key) |
||
| 146 | s = json.dumps({key: True}, separators=(',', ':')) |
||
| 147 | if 'application/json' in self.request.headers.get('Accept'): |
||
| 148 | self.response.headers['Content-Type'] = 'application/json' |
||
| 149 | self.response.write(s) |
||
| 150 | |||
| 151 | |||
| 152 | class DownloadHandler(blobstore_handlers.BlobstoreDownloadHandler): |
||
| 153 | def get(self, key, filename): |
||
| 154 | if not blobstore.get(key): |
||
| 155 | self.error(404) |
||
| 156 | else: |
||
| 157 | # Prevent browsers from MIME-sniffing the content-type: |
||
| 158 | self.response.headers['X-Content-Type-Options'] = 'nosniff' |
||
| 159 | # Cache for the expiration time: |
||
| 160 | self.response.headers['Cache-Control'] = 'public,max-age=%d' % EXPIRATION_TIME |
||
| 161 | # Send the file forcing a download dialog: |
||
| 162 | self.send_blob(key, save_as=filename, content_type='application/octet-stream') |
||
| 163 | |||
| 164 | app = webapp2.WSGIApplication( |
||
| 165 | [ |
||
| 166 | ('/', UploadHandler), |
||
| 167 | ('/([^/]+)/([^/]+)', DownloadHandler) |
||
| 168 | ], |
||
| 169 | debug=True |
||
| 170 | ) |