1#!/usr/bin/python 2# Copyright 2012 Google Inc. All Rights Reserved. 3# Author: mrdmnd@ (Matt Redmond) 4# Based off of code in //depot/google3/experimental/mobile_gwp 5"""Code to transport profile data between a user's machine and the CWP servers. 6 Pages: 7 "/": the main page for the app, left blank so that users cannot access 8 the file upload but left in the code for debugging purposes 9 "/upload": Updates the datastore with a new file. the upload depends on 10 the format which is templated on the main page ("/") 11 input includes: 12 profile_data: the zipped file containing profile data 13 board: the architecture we ran on 14 chromeos_version: the chromeos_version 15 "/serve": Lists all of the files in the datastore. Each line is a new entry 16 in the datastore. The format is key~date, where key is the entry's 17 key in the datastore and date is the file upload time and date. 18 (Authentication Required) 19 "/serve/([^/]+)?": For downloading a file of profile data, ([^/]+)? means 20 any character sequence so to download the file go to 21 '/serve/$key' where $key is the datastore key of the file 22 you want to download. 23 (Authentication Required) 24 "/del/([^/]+)?": For deleting an entry in the datastore. To use go to 25 '/del/$key' where $key is the datastore key of the entry 26 you want to be deleted form the datastore. 27 (Authentication Required) 28 TODO: Add more extensive logging""" 29 30import cgi 31import logging 32import md5 33import urllib 34 35from google.appengine.api import users 36from google.appengine.ext import db 37from google.appengine.ext import webapp 38from google.appengine.ext.webapp.util import run_wsgi_app 39 40logging.getLogger().setLevel(logging.DEBUG) 41 42 43class FileEntry(db.Model): 44 profile_data = db.BlobProperty() # The profile data 45 date = db.DateTimeProperty(auto_now_add=True) # Date it was uploaded 46 data_md5 = db.ByteStringProperty() # md5 of the profile data 47 board = db.StringProperty() # board arch 48 chromeos_version = db.StringProperty() # ChromeOS version 49 50 51class MainPage(webapp.RequestHandler): 52 """Main page only used as the form template, not actually displayed.""" 53 54 def get(self, response=''): # pylint: disable-msg=C6409 55 if response: 56 self.response.out.write('<html><body>') 57 self.response.out.write("""<br> 58 <form action="/upload" enctype="multipart/form-data" method="post"> 59 <div><label>Profile Data:</label></div> 60 <div><input type="file" name="profile_data"/></div> 61 <div><label>Board</label></div> 62 <div><input type="text" name="board"/></div> 63 <div><label>ChromeOS Version</label></div> 64 <div><input type="text" name="chromeos_version"></div> 65 <div><input type="submit" value="send" name="submit"></div> 66 </form> 67 </body> 68 </html>""") 69 70 71class Upload(webapp.RequestHandler): 72 """Handler for uploading data to the datastore, accessible by anyone.""" 73 74 def post(self): # pylint: disable-msg=C6409 75 """Takes input based on the main page's form.""" 76 getfile = FileEntry() 77 f1 = self.request.get('profile_data') 78 getfile.profile_data = db.Blob(f1) 79 getfile.data_md5 = md5.new(f1).hexdigest() 80 getfile.board = self.request.get('board') 81 getfile.chromeos_version = self.request.get('chromeos_version') 82 getfile.put() 83 self.response.out.write(getfile.key()) 84 #self.redirect('/') 85 86 87class ServeHandler(webapp.RequestHandler): 88 """Given the entry's key in the database, output the profile data file. Only 89 accessible from @google.com accounts.""" 90 91 def get(self, resource): # pylint: disable-msg=C6409 92 if Authenticate(self): 93 file_key = str(urllib.unquote(resource)) 94 request = db.get(file_key) 95 self.response.out.write(request.profile_data) 96 97 98class ListAll(webapp.RequestHandler): 99 """Displays all files uploaded. Only accessible by @google.com accounts.""" 100 101 def get(self): # pylint: disable-msg=C6409 102 """Displays all information in FileEntry, ~ delimited.""" 103 if Authenticate(self): 104 query_str = 'SELECT * FROM FileEntry ORDER BY date ASC' 105 query = db.GqlQuery(query_str) 106 delimiter = '~' 107 108 for item in query: 109 display_list = [item.key(), item.date, item.data_md5, item.board, 110 item.chromeos_version] 111 str_list = [cgi.escape(str(i)) for i in display_list] 112 self.response.out.write(delimiter.join(str_list) + '</br>') 113 114 115class DelEntries(webapp.RequestHandler): 116 """Deletes entries. Only accessible from @google.com accounts.""" 117 118 def get(self, resource): # pylint: disable-msg=C6409 119 """A specific entry is deleted, when the key is given.""" 120 if Authenticate(self): 121 fkey = str(urllib.unquote(resource)) 122 request = db.get(fkey) 123 if request: 124 db.delete(fkey) 125 126 127def Authenticate(webpage): 128 """Some urls are only accessible if logged in with a @google.com account.""" 129 user = users.get_current_user() 130 if user is None: 131 webpage.redirect(users.create_login_url(webpage.request.uri)) 132 elif user.email().endswith('@google.com'): 133 return True 134 else: 135 webpage.response.out.write('Not Authenticated') 136 return False 137 138 139def main(): 140 application = webapp.WSGIApplication( 141 [ 142 ('/', MainPage), 143 ('/upload', Upload), 144 ('/serve/([^/]+)?', ServeHandler), 145 ('/serve', ListAll), 146 ('/del/([^/]+)?', DelEntries), 147 ], 148 debug=False) 149 run_wsgi_app(application) 150 151 152if __name__ == '__main__': 153 main() 154