server.py revision f2a3ef46f75d2196a93d3ed27f4d1fcf22b54fbe
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