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 30 import cgi 31 import logging 32 import md5 33 import urllib 34 35 from google.appengine.api import users 36 from google.appengine.ext import db 37 from google.appengine.ext import webapp 38 from google.appengine.ext.webapp.util import run_wsgi_app 39 40 logging.getLogger().setLevel(logging.DEBUG) 41 42 43 class 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 51 class 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 71 class 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 87 class 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 98 class 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 115 class 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 127 def 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 139 def 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 152 if __name__ == '__main__': 153 main() 154