Comment out the 'loadtest' backend in the 'counter' backend sample so that it does...
[gae-samples.git] / geochat / events.py
blobbbdf737f92f5a6c80a2cb67166ba8e9bb5f7e00c
2 # Copyright 2008 Google Inc.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 """Handlers for Geochat user events.
18 Contains several RequestHandler subclasses used to handle put and get
19 operations, along with any helper functions. This script is designed to be
20 run directly as a WSGI application, and within Geochat handles all URLs
21 under /event.
23 UpdateHandler: Handles user requests for updated lists of events.
24 ChatHandler: Handles user chat input events.
25 MoveHandler: Handles user movement events.
26 RefreshCache(): Checks the age of the cache, and updates if necessary.
27 """
29 # TODO Cache sync problems.
30 # TODO Problem with duplicate messages.
31 # TODO Spam controls.
33 import datetime
34 import settings
35 import logging
36 import os
37 import time
39 import datamodel
40 import json
41 import settings
43 from google.appengine.api import users
44 from google.appengine.ext import db
45 from google.appengine.ext import webapp
46 from google.appengine.ext.webapp.util import run_wsgi_app
48 # The time interval between syncs as a timedelta.
49 sync_interval = datetime.timedelta(0, 10)
51 # A datetime indicating the last time the chat cache was synced from the DB.
52 last_sync = datetime.datetime.now() - sync_interval
54 # A list storing the chat cache.
55 chat_cache = []
57 # A list storing the move cache.
58 move_cache = []
61 class UpdateHandler(webapp.RequestHandler):
63 """Handles user requests for updated lists of events.
65 UpdateHandler only accepts "get" events, sent via web forms. It expects each
66 request to include "min_latitude", "min_longitude", "max_latitude",
67 "max_longitude", "zoom", and "since" fields.
68 """
70 def get(self):
71 global sync_interval
72 global last_sync
73 global chat_cache
74 global move_cache
76 min_latitude = float(self.request.get('min_latitude'))
77 min_longitude = float(self.request.get('min_longitude'))
78 max_latitude = float(self.request.get('max_latitude'))
79 max_longitude = float(self.request.get('max_longitude'))
80 zoom = self.request.get('zoom')
81 if self.request.get('since') == '':
82 since = 0
83 else:
84 since = float(self.request.get('since'))
85 since_datetime = datetime.datetime.fromtimestamp(since)
87 # Restrict latitude/longitude to restrict bulk downloads.
88 if (max_latitude - min_latitude) > 1:
89 max_latitude = min_latitude + 1
90 if (max_longitude - min_longitude) > 1:
91 max_longitude = min_longitude + 1
94 chat_events = []
95 move_events = []
97 if since > 0:
98 RefreshCache()
99 for entry in chat_cache:
100 if (entry.timestamp > since_datetime and
101 entry.latitude > min_latitude and
102 entry.latitude < max_latitude and
103 entry.longitude > min_longitude and
104 entry.longitude < max_longitude):
105 chat_events.append(entry)
107 for entry in move_cache:
108 if (entry['timestamp'] > since_datetime and
109 entry['latitude'] > min_latitude and
110 entry['latitude'] < max_latitude and
111 entry['longitude'] > min_longitude and
112 entry['longitude'] < max_longitude):
113 move_events.append(entry)
115 output = {
116 'timestamp': time.time(),
117 'chats': chat_events,
118 'moves': move_events,
121 self.response.headers['Content-Type'] = 'text/plain'
122 self.response.out.write(json.encode(output));
125 class MoveHandler(webapp.RequestHandler):
127 """Handles user movement events.
129 MoveHandler only provides a post method for receiving new user co-ordinates,
130 and doesn't store any data to the datastore as ChatHandler does with
131 ChatEvents, instead just adding straight to the local cache.
134 def post(self):
135 global move_cache
137 # Get the current user and return if not logged in.
138 user = users.get_current_user()
139 if user == None:
140 return
142 # Construct an ad hoc event dictionary.
143 event_dict = {
144 'user': user,
145 'timestamp': datetime.datetime.now(),
146 'latitude': float(self.request.get('latitude')),
147 'longitude': float(self.request.get('longitude')),
148 'zoom': int(self.request.get('zoom')),
151 # Append to the move cache, so we don't need to wait for a refresh.
152 move_cache.append(event_dict)
155 class ChatHandler(webapp.RequestHandler):
157 """Handles user chat events.
159 Chathandler only provides a post method for receiving user chat contents
160 and related positioning. This data is immediately dumped to the datastore,
161 without batching, and appended to the local cache.
164 def post(self):
165 global chat_cache
167 # Get the current user and return if not logged in.
168 user = users.get_current_User()
169 if user == None:
170 return
172 # Create and insert the a new chat event.
173 event = datamodel.ChatEvent()
174 event.user = user
175 event.contents = self.request.get('contents')
176 event.latitude = float(self.request.get('latitude'))
177 event.longitude = float(self.request.get('longitude'))
178 event.zoom = int(self.request.get('zoom'))
179 event.put()
181 # Append to the chat cache, so we don't need to wait on a refresh.
182 chat_cache.append(event)
185 def RefreshCache():
187 """Check the freshness of chat and move caches, and refresh if necessary.
189 RefreshCache relies on the globals "sync_interval" and "last_sync" to
190 determine the age of the existing cache and whether or not it should be
191 updated. All output goes to "chat_cache" and "move_cache" globals.
194 global sync_interval
195 global last_sync
196 global chat_cache
197 global move_cache
199 now = datetime.datetime.now()
200 sync_frame = sync_interval * 2
202 if last_sync < now - sync_interval:
204 # Sync the chat cache.
205 query = db.Query(datamodel.ChatEvent)
206 query.filter('timestamp > ', now - sync_frame)
207 query.order('timestamp')
208 chat_cache = list(query.fetch(100))
209 last_sync = datetime.datetime.now()
210 logging.info('Chat cache refreshed.')
212 # Trim the move cache.
213 move_cache = move_cache[-100:]
217 def main():
219 """Main method called when the script is executed directly.
221 This method is called each time the script is launched, and also has the
222 effect of enabling caching for global variables.
225 application = webapp.WSGIApplication(
227 ('/event/update', UpdateHandler),
228 ('/event/chat', ChatHandler),
229 ('/event/move', MoveHandler),
231 debug = True)
232 run_wsgi_app(application)
234 if __name__ == '__main__':
235 main()