Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(155)

Side by Side Diff: appengine/isolate/handlers_frontend.py

Issue 2693953006: Isolate: Download files as their filename instead of hash (Closed)
Patch Set: Isolate: Download files as their filename instead of hash Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2012 The LUCI Authors. All rights reserved. 1 # Copyright 2012 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 """This module defines Isolate Server frontend url handlers.""" 5 """This module defines Isolate Server frontend url handlers."""
6 6
7 import cgi 7 import cgi
8 from collections import OrderedDict
M-A Ruel 2017/03/23 21:23:42 just "import collections"
jonesmi 2017/03/23 22:15:29 Done.
8 import datetime 9 import datetime
9 import json 10 import json
10 import logging 11 import logging
12 import os
11 import re 13 import re
14 import urllib
12 15
13 import webapp2 16 import webapp2
14 17
15 from google.appengine.api import memcache 18 from google.appengine.api import memcache
16 from google.appengine.api import modules 19 from google.appengine.api import modules
17 from google.appengine.api import users 20 from google.appengine.api import users
18 21
19 import acl 22 import acl
20 import config 23 import config
21 import gcs 24 import gcs
(...skipping 29 matching lines...) Expand all
51 'other_requests', 54 'other_requests',
52 'failures', 55 'failures',
53 'uploads', 56 'uploads',
54 'downloads', 57 'downloads',
55 'contains_requests', 58 'contains_requests',
56 'uploads_bytes', 59 'uploads_bytes',
57 'downloads_bytes', 60 'downloads_bytes',
58 'contains_lookups', 61 'contains_lookups',
59 ) 62 )
60 63
64 _ISOLATED_ROOT_MEMBERS = (
65 'algo',
66 'command',
67 'files',
68 'includes',
69 'read_only',
70 'relative_cwd',
71 'version',
72 )
73
61 74
62 ### Restricted handlers 75 ### Restricted handlers
63 76
64 77
65 class RestrictedConfigHandler(auth.AuthenticatingHandler): 78 class RestrictedConfigHandler(auth.AuthenticatingHandler):
66 @auth.autologin 79 @auth.autologin
67 @auth.require(auth.is_admin) 80 @auth.require(auth.is_admin)
68 def get(self): 81 def get(self):
69 self.common(None) 82 self.common(None)
70 83
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
183 ### Non-restricted handlers 196 ### Non-restricted handlers
184 197
185 198
186 class BrowseHandler(auth.AuthenticatingHandler): 199 class BrowseHandler(auth.AuthenticatingHandler):
187 @auth.autologin 200 @auth.autologin
188 @auth.require(acl.isolate_readable) 201 @auth.require(acl.isolate_readable)
189 def get(self): 202 def get(self):
190 namespace = self.request.get('namespace', 'default-gzip') 203 namespace = self.request.get('namespace', 'default-gzip')
191 # Support 'hash' for compatibility with old links. To remove eventually. 204 # Support 'hash' for compatibility with old links. To remove eventually.
192 digest = self.request.get('digest', '') or self.request.get('hash', '') 205 digest = self.request.get('digest', '') or self.request.get('hash', '')
206 save_as = self.request.get('as', '')
193 params = { 207 params = {
208 u'as': unicode(save_as),
194 u'digest': unicode(digest), 209 u'digest': unicode(digest),
195 u'namespace': unicode(namespace), 210 u'namespace': unicode(namespace),
196 } 211 }
197 # Check for existence of element, so we can 400/404 212 # Check for existence of element, so we can 400/404
198 if digest and namespace: 213 if digest and namespace:
199 try: 214 try:
200 model.get_content(namespace, digest) 215 model.get_content(namespace, digest)
201 except ValueError: 216 except ValueError:
202 self.abort(400, 'Invalid key') 217 self.abort(400, 'Invalid key')
203 except LookupError: 218 except LookupError:
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
250 if len(content) > 33554000: 265 if len(content) > 33554000:
251 host = modules.get_hostname(module='default', version='default') 266 host = modules.get_hostname(module='default', version='default')
252 # host is something like default.default.myisolateserver.appspot.com 267 # host is something like default.default.myisolateserver.appspot.com
253 host = host.replace('default.default.','') 268 host = host.replace('default.default.','')
254 sizeInMib = len(content) / (1024.0 * 1024.0) 269 sizeInMib = len(content) / (1024.0 * 1024.0)
255 content = ('Sorry, your file is %1.1f MiB big, which exceeds the 32 MiB' 270 content = ('Sorry, your file is %1.1f MiB big, which exceeds the 32 MiB'
256 ' App Engine limit.\nTo work around this, run the following command:\n' 271 ' App Engine limit.\nTo work around this, run the following command:\n'
257 ' python isolateserver.py download -I %s --namespace %s -f %s %s' 272 ' python isolateserver.py download -I %s --namespace %s -f %s %s'
258 % (sizeInMib, host, namespace, digest, digest)) 273 % (sizeInMib, host, namespace, digest, digest))
259 else: 274 else:
260 self.response.headers['Content-Disposition'] = str('filename=%s' 275 self.response.headers['Content-Disposition'] = str(
261 % digest) 276 'filename=%s' % self.request.get('as') or digest)
262 if content.startswith('{'): 277 try:
263 # Try to format as JSON. 278 json_data = json.loads(content)
264 try: 279 if self._is_isolated_format(json_data):
265 content = json.dumps(
266 json.loads(content), sort_keys=True, indent=2,
267 separators=(',', ': '))
268 content = cgi.escape(content)
269 # If we don't wrap this in html, browsers will put content in a pre
270 # tag which is also styled with monospace/pre-wrap. We can't use
271 # anchor tags in <pre>, so we force it to be a <div>, which happily
272 # accepts links.
273 content = (
274 '<div style="font-family:monospace;white-space:pre-wrap;">%s'
275 '</div>' % content)
276 # Linkify things that look like hashes
277 content = re.sub(r'([0-9a-f]{40})',
278 r'<a target="_blank" href="/browse?namespace=%s' % namespace +
279 r'&digest=\1">\1</a>',
280 content)
281 self.response.headers['Content-Type'] = 'text/html; charset=utf-8' 280 self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
282 except ValueError: 281 json_data['files'] = OrderedDict(
M-A Ruel 2017/03/23 21:23:42 collections.OrderedDict
jonesmi 2017/03/23 22:15:30 Done.
283 pass 282 sorted(
283 json_data['files'].items(),
284 key=lambda (filepath, data): filepath))
285 params = {
286 'namespace': namespace,
287 'isolated': json_data,
288 }
289 content = template.render('isolate/isolated.html', params)
290 except ValueError:
291 pass
284 292
285 self.response.write(content) 293 self.response.write(content)
286 294
295 @staticmethod
296 def _is_isolated_format(json_data):
297 """Checks if json_data is a valid .isolated format."""
298 if not isinstance(json_data, dict):
299 return False
300 actual = set(json_data)
301 return actual.issubset(_ISOLATED_ROOT_MEMBERS) and 'files' in actual
302
287 303
288 class StatsHandler(webapp2.RequestHandler): 304 class StatsHandler(webapp2.RequestHandler):
289 """Returns the statistics web page.""" 305 """Returns the statistics web page."""
290 def get(self): 306 def get(self):
291 """Presents nice recent statistics. 307 """Presents nice recent statistics.
292 308
293 It fetches data from the 'JSON' API. 309 It fetches data from the 'JSON' API.
294 """ 310 """
295 # Preloads the data to save a complete request. 311 # Preloads the data to save a complete request.
296 resolution = self.request.params.get('resolution', 'hours') 312 resolution = self.request.params.get('resolution', 'hours')
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
411 def create_application(debug): 427 def create_application(debug):
412 """Creates the url router. 428 """Creates the url router.
413 429
414 The basic layouts is as follow: 430 The basic layouts is as follow:
415 - /restricted/.* requires being an instance administrator. 431 - /restricted/.* requires being an instance administrator.
416 - /stats/.* has statistics. 432 - /stats/.* has statistics.
417 """ 433 """
418 acl.bootstrap() 434 acl.bootstrap()
419 template.bootstrap() 435 template.bootstrap()
420 return webapp2.WSGIApplication(get_routes(), debug=debug) 436 return webapp2.WSGIApplication(get_routes(), debug=debug)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698