# Copyright (c) 2010, Kundan Singh, all rights reserved.
#
# This is a public-chat application built using the Flash VideoIO component on Adobe
# Stratus service and Google App Engine. This site is just a demonstration of how such
# services can be built using the generic Flash-VideoIO component.
#
# This version of the project uses the Channel API available in Google App Engine for
# asynchronous notifications of user list and chat history.
#
# Visit http://code.google.com/p/flash-videoio for more.
import os, datetime, time, logging
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.api import channel
from django.utils import simplejson as json
# The main page at / just returns index.html.
class MainPage(webapp.RequestHandler):
def get(self):
path = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(path, {}))
# POST /create/
# Request: {"senderId": "client-id-of-sender"}
# Response: {"token", "new-channel-token-for-clientId-as-stream"}
class Create(webapp.RequestHandler):
def post(self):
data = json.loads(self.request.body)
data = {'token': channel.create_channel(data['senderId'])}
self.response.out.write(json.dumps(data))
# Data model to store connected clients and their locations.
class User(db.Model):
location = db.StringProperty()
name = db.StringProperty()
clientId = db.StringProperty()
extra = db.BlobProperty()
lastmodified = db.DateTimeProperty(auto_now=True)
def get_object(self):
return {'name': self.name, 'clientId': self.clientId, 'extra': self.extra}
def __repr__(self):
return ''%(self.location, self.clientId, self.name, self.lastmodified, len(self.extra) if self.extra else 0)
# Data model to store chat message in a location.
class Chat(db.Model):
location = db.StringProperty()
senderId = db.StringProperty()
sender = db.StringProperty()
targetId = db.StringProperty()
timestamp = db.DateTimeProperty(auto_now_add=True)
text = db.TextProperty()
extra = db.BlobProperty()
def get_object(self):
return {'senderId': self.senderId, 'sender': self.sender, 'targetId': self.targetId,
'timestamp': time.mktime(self.timestamp.timetuple()),
'text': self.text, 'extra': self.extra}
def __repr__(self):
return ''%(self.location, self.sender, self.targetId, self.timestamp, len(self.text), len(self.extra) if self.extra else 0)
# GET /userlist/?location={location}
# Response: {"userlist": [... list of {"name":..., "clientId":..., "extra":...}]
# POST /userlist/?location={location}
# Request: {"clientId":..., "name":..., "extra":...}
class UserList(webapp.RequestHandler):
def get(self):
location = self.request.get('location')
userlist = [u.get_object() for u in db.GqlQuery('SELECT * FROM User WHERE location = :1', location)]
# logging.debug("userlist returns: " + json.dumps({'userlist': userlist}))
self.response.out.write(json.dumps({'userlist': userlist}))
def post(self):
expired_users = [u for u in db.GqlQuery('SELECT * FROM User WHERE lastmodified < :1', datetime.datetime.fromtimestamp(time.time()-90))]
added_users, removed_users = [], [u.get_object() for u in expired_users]
[u.delete() for u in expired_users]
location, body = self.request.get('location'), json.loads(self.request.body)
clientId, name, extra = body['clientId'], body['name'], body.get('extra', None)
if extra == 'null': extra = None
if name == 'null': name = None
if extra and isinstance(extra, unicode):
extra = extra.encode('utf-8')
user = db.GqlQuery('SELECT * FROM User WHERE clientId = :1', clientId).get()
if self.request.path.endswith('/delete/'):
if user:
removed_users.append(user.get_object())
user.delete()
else:
if not user:
user = User(location=location, name=name, clientId=clientId, extra=extra)
added_users.append(user.get_object())
else:
changed = (user.location != location or user.name != name or user.extra != extra)
if changed:
user.location, user.name, user.extra = location, name, extra
added_users.append(user.get_object())
user.put()
if added_users or removed_users:
data = json.dumps({'method': 'userlist', 'added': added_users, 'removed': removed_users})
for u in db.GqlQuery('SELECT * FROM User WHERE location = :1', location):
try:
channel.send_message(u.clientId, data)
except channel.InvalidChannelClientIdError:
pass # ignore the exception
# GET /chathistory/?location={location}&target={targetId}
# Response: {"chathistory": [... list of {"senderId":...,"sender":...,"targetId":...,"timestamp":...,"text":...,"extra":...}]
# POST /chathistory?location={location}[&target={targetId}]
# Request: {"senderId":..., "sender":..., "text":..., "extra":...}
class ChatHistory(webapp.RequestHandler):
def get(self):
location, targetId = self.request.get('location'), self.request.get('targetId')
chats = [u for u in db.GqlQuery('SELECT * FROM Chat WHERE location = :1 ORDER BY timestamp DESC LIMIT 30', location)]
# logging.debug('result=' + str(chats))
chathistory = [u.get_object() for u in chats if not u.targetId or u.targetId == targetId]
result = json.dumps({'chathistory': [r for r in reversed(chathistory)]})
self.response.out.write(result)
def post(self):
location, targetId, body = self.request.get('location'), self.request.get('targetId'), json.loads(self.request.body)
senderId, sender, text, extra = body['senderId'], body['sender'], body['text'], body['extra'] if 'extra' in body else None
if sender == "null": sender = "User " + str(senderId)
if text == "null": text = None
if extra == "null": extra = None
if targetId == "null" or targetId == "": targetId = None
if extra and isinstance(extra, unicode):
extra = extra.encode('utf-8')
if text:
chat = Chat(location=location, senderId=senderId, sender=sender, targetId=targetId, text=text, extra=extra)
chat.put()
data = json.dumps({'method': 'chathistory', 'added': [chat.get_object()]})
if not targetId:
for u in db.GqlQuery('SELECT * FROM User WHERE location = :1', location):
try:
channel.send_message(u.clientId, data)
except channel.InvalidChannelClientIdError:
pass # ignore the error
else:
target = db.GqlQuery('SELECT * FROM User WHERE clientId = :1', targetId).get()
if target:
try:
channel.send_message(target.clientId, data)
except channel.InvalidChannelClientIdError:
pass # ignore the error
else:
self.response.set_status(404)
def main():
logging.getLogger().setLevel(logging.DEBUG)
application = webapp.WSGIApplication([
('/', MainPage), ('/create/', Create),
('/userlist/delete/', UserList), ('/userlist/', UserList),
('/chathistory/', ChatHistory),
], debug=True)
run_wsgi_app(application)
if __name__ == "__main__":
main()