Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Tools / GardeningServer / alerts.py
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import calendar
6 import datetime
7 import json
8 import logging
9 import webapp2
10 import zlib
11
12 from google.appengine.api import memcache
13 from google.appengine.api import users
14 from google.appengine.datastore import datastore_query
15 from google.appengine.ext import ndb
16
17 LOGGER = logging.getLogger(__name__)
18
19
20 class DateTimeEncoder(json.JSONEncoder):
21     def default(self, obj):
22         if isinstance(obj, datetime.datetime):
23             return calendar.timegm(obj.timetuple())
24         # Let the base class default method raise the TypeError.
25         return json.JSONEncoder.default(self, obj)
26
27
28 class AlertsJSON(ndb.Model):
29     type = ndb.StringProperty()
30     json = ndb.BlobProperty(compressed=True)
31     date = ndb.DateTimeProperty(auto_now_add=True)
32
33
34 class AlertsHandler(webapp2.RequestHandler):
35     ALERTS_TYPE = 'alerts'
36
37     # Has no 'response' member.
38     # pylint: disable=E1101
39     def send_json_headers(self):
40         self.response.headers.add_header('Access-Control-Allow-Origin', '*')
41         self.response.headers['Content-Type'] = 'application/json'
42
43     # Has no 'response' member.
44     # pylint: disable=E1101
45     def send_json_data(self, data):
46         self.send_json_headers()
47         self.response.write(data)
48
49     def generate_json_dump(self, alerts):
50         return json.dumps(alerts, cls=DateTimeEncoder, indent=1)
51
52     def get_from_memcache(self, memcache_key):
53         compressed = memcache.get(memcache_key)
54         if not compressed:
55             self.send_json_headers()
56             return
57         uncompressed = zlib.decompress(compressed)
58         self.send_json_data(uncompressed)
59
60     def get(self):
61         self.get_from_memcache(AlertsHandler.ALERTS_TYPE)
62
63     def post_to_history(self, alerts_type, alerts):
64         last_query = AlertsJSON.query().filter(AlertsJSON.type == alerts_type)
65         last_entry = last_query.order(-AlertsJSON.date).get()
66         last_alerts = json.loads(last_entry.json) if last_entry else {}
67
68         # Only changes to the fields with 'alerts' in the name should cause a
69         # new history entry to be saved.
70         def alert_fields(alerts_json):
71             filtered_json = {}
72             for key, value in alerts_json.iteritems():
73                 if 'alerts' in key:
74                     filtered_json[key] = value
75             return filtered_json
76
77         if alert_fields(last_alerts) != alert_fields(alerts):
78             new_entry = AlertsJSON(
79                 json=self.generate_json_dump(alerts),
80                 type=alerts_type)
81             new_entry.put()
82
83     # Has no 'response' member.
84     # pylint: disable=E1101
85     def post_to_memcache(self, memcache_key, alerts):
86         uncompressed = self.generate_json_dump(alerts)
87         compression_level = 1
88         compressed = zlib.compress(uncompressed, compression_level)
89         memcache.set(memcache_key, compressed)
90
91     def parse_alerts(self, alerts_json):
92         try:
93             alerts = json.loads(alerts_json)
94         except ValueError:
95             warning = 'content field was not JSON'
96             self.response.set_status(400, warning)
97             LOGGER.warn(warning)
98             return
99
100         alerts.update({'date': datetime.datetime.utcnow()})
101
102         return alerts
103
104     def update_alerts(self, alerts_type):
105         alerts = self.parse_alerts(self.request.get('content'))
106         if alerts:
107             self.post_to_memcache(alerts_type, alerts)
108             self.post_to_history(alerts_type, alerts)
109
110     def post(self):
111         self.update_alerts(AlertsHandler.ALERTS_TYPE)
112
113
114 class AlertsHistory(webapp2.RequestHandler):
115     MAX_LIMIT_PER_PAGE = 100
116
117     def get_entry(self, query, key):
118         try:
119             key = int(key)
120         except ValueError:
121             self.response.set_status(400, 'Invalid key format')
122             return {}
123
124         ndb_key = ndb.Key(AlertsJSON, key)
125         result = query.filter(AlertsJSON.key == ndb_key).get()
126         if result:
127             return json.loads(result.json)
128         else:
129             self.response.set_status(404, 'Failed to find key %s' % key)
130             return {}
131
132     def get_list(self, query):
133         cursor = self.request.get('cursor')
134         if cursor:
135             cursor = datastore_query.Cursor(urlsafe=cursor)
136
137         limit = int(self.request.get('limit', self.MAX_LIMIT_PER_PAGE))
138         limit = min(self.MAX_LIMIT_PER_PAGE, limit)
139
140         if cursor:
141             alerts, next_cursor, has_more = query.fetch_page(limit,
142                                                              start_cursor=cursor)
143         else:
144             alerts, next_cursor, has_more = query.fetch_page(limit)
145
146         return {
147             'has_more': has_more,
148             'cursor': next_cursor.urlsafe() if next_cursor else '',
149             'history': [alert.key.integer_id() for alert in alerts]
150         }
151
152     def get(self, key=None):
153         query = AlertsJSON.query().order(-AlertsJSON.date)
154         result_json = {}
155
156         user = users.get_current_user()
157         result_json['login-url'] = users.create_login_url(self.request.uri)
158
159         # Return only public alerts for non-internal users.
160         if not user or not user.email().endswith('@google.com'):
161             query = query.filter(AlertsJSON.type == AlertsHandler.ALERTS_TYPE)
162
163         if key:
164             result_json.update(self.get_entry(query, key))
165         else:
166             result_json.update(self.get_list(query))
167
168         self.response.headers['Content-Type'] = 'application/json'
169         self.response.out.write(json.dumps(result_json))
170
171
172 app = webapp2.WSGIApplication([
173     ('/alerts', AlertsHandler),
174     ('/alerts-history', AlertsHistory),
175     ('/alerts-history/(.*)', AlertsHistory),
176 ])