Multiuser Chatroom with App Engine Channel API

UPDATE
Have a look at part 2 of the series, with focus on optimization.

Perhaps the most primitive use of Google App Engine Channel API is the use of chat between two people in real time. This concept can be extended to chatrooms – where many people chat simultaneously with each other, or gamerooms – where many people play some game simultaneously in real time amongst each other.

The docs don’t say much about the quota details of Channel API, so here it is

Free Quota
Channel API Calls 46,310,400
Channels Created 8,640
Channel Data Sent 1,046.00 GBytes

Paid Quota
Channel API Calls 91,995,495
Channels Created 95,040
Channel Data Sent 2,088.13 GBytes

The example application tic-tac-toe shows to a quite good level how to use Channel API for playing duo-player game. I find some scope for improvements in that app. Let’s review them :

  1. Everytime a player starts a new game, a new client id (for channel) is created. The cap provided on Channels Creates is few, so this should be used judiciously
  2. The game room generates depends on the User. So, if the user who initiated the play reloads the page, the game is gone. This works for the case of tic-tac-toe but does not go for chatrooms or gamerooms

Apart from above issues, there are somethings that a serious gameroom/ chatroom needs and they are as follows:

  1. Automatic room creation. The room is automatically created as soon as number of players exceed the MAX_PLAYERS limit
  2. Every player has some assets (in a gameroom), and this keeps on changing with actions performed. So there needs to be state persistent information stored at the backend
  3. If the player reloads the page or opens a couple of new tabs, consistency of information should be maintained.
  4. Usually in a chat, when someone joins late, they want to be updated of the chats that took place when they were not there

.

Using above points as my guiding light, I tried to create an app which does the above in a net and beautiful manner. The app’s source code is located at http://code.google.com/p/pranav/source/browse/chat-channel.

Structure

The concept of the gameroom/chatroom goes like this – When a player wants to play a game, he is taken to a room. The room could be a new room (if no room exists or all others are full), or an existing room.
There are two entity kinds – Game and PlayerGame. The Game Entity Kind contains information about the game room. This is where you can keep a track of all the events happening in a room like maybe chat between users, or collaborative drawing or the moves in a tic-tac-toe. The PlayerGame is a child entity of the Game entity, which contains the information about the game the user is playing. For those games which go in a sequential manner where player1 does something and then player2 does something, PlayerGame could have very well been inside the Game entity, but in a game where many players can do multiple actions and their actions affect their state in some way or the other datastore congestion might occur. To avoid all those, its made in a separate entity.

class Game(db.Model):
“””
The Model to store the details of a particular
game room.
“””
players = db.StringListProperty()
chat = db.TextProperty()
active = db.BooleanProperty(default = True)
created = db.DateTimeProperty(auto_now_add = True)
updated = db.DateTimeProperty(auto_now = True)

class PlayerGame(db.Model):
“””
Stores information about which player is added into which game
room. This of course assumes that a player can be in only one
game room at a time.
“””
assets = db.TextProperty(default = ”)
created = db.DateTimeProperty(auto_now_add = True)
updated = db.DateTimeProperty(auto_now = True)

Then we need two wrapper Classes so that we can abstract the functioning. Our classes can very well be Player and Tournament.

class Player(object):
“””
A Player class
“””
userid = None
assets = ”
name = ”
updates = {}
_player = None

def __init__(self, userid=None):
self.userid = userid

def _get_tournament(self):
if self.userid:
return Game.all(keys_only=True).filter(‘players = ‘, self.userid).get()

def get_player(self):
if self._player:
return self._player
if self.userid:
self._player = PlayerGame.get_by_key_name(self.userid,
parent = self._get_tournament())
return self._player

def get_gameroom(self):
pg = self.get_player()
if pg:
return pg.parent().key().name()

def die(self):
playergame_key = db.Key.from_path(‘PlayerGame’, self.userid)
game = Game.all().filter(‘user = ‘, self.userid).get()

def txn(userid, pg_key, g):
db.delete(pg_key)
g.users.pop(userid)
db.put(g)

db.run_in_transaction(txn, self.userid, playergame_key, game_key)

The Player class contains all the wrapper functions that a player can possible do inside a chatroom/gameroom. Similarly, there shall be a Tournament class which will do all the activities that can be done in a tournament

class Tournament(object):
“””
A class for tournament
“””
game = None
room = None
delta_chat = ”
updates = {}

def __init__(self, room=None):
self.updates = {}
self.room = room

def get_tournament(self):
if self.game:
return self.game
if self.room:
self.game = Game.get_by_key_name(self.room)
return self.game

def can_add_player(self):
if MAX_PARTICIPANTS == -1:
return True
return len(self.get_tournament().players) < MAX_PARTICIPANTS

def add_player(self, player):
if self.can_add_player():
game = self.get_tournament()
if player.userid not in game.players:
game.players.append(player.userid)
db.put(game)
PlayerGame.get_or_insert(key_name = player.userid,
parent = game)
self.updates.update({'new_player' : 1,
'name' : player.userid})
self.send_update()
else:
return self.new(player)

def new(self, player):
"A new room shall be created only if there is a player to go into"
if not player: return
self.room = str(time.time())
self.game = Game(key_name = self.room,
players = [player.userid])
playergame = PlayerGame(key_name = player.userid,
parent = self.game)
db.put([self.game, playergame])
memcache.set(LATEST_GAMEROOM, self.room)
return self.room

@classmethod
def join_new_or_latest(cls, player):
latest_roomkey = memcache.get(LATEST_GAMEROOM)
if latest_roomkey:
cls(latest_roomkey).add_player(player)
return latest_roomkey
return cls().new(player)

@classmethod
def continue_tournament(cls, player):
"When the player was already in a tournament continue from there itself"
channel.send_message(player.get_channel(), cls().get_all_updates())

def chat(self, player, message):
delta_chat = message
game = self.get_tournament()
if game.chat:
chat = game.chat + '
' + delta_chat
else:
chat = delta_chat
game.chat = chat
db.put(game)
self.updates.update({'delta_chat' : delta_chat})
self.send_update()

def get_player_channels(self):
game = self.get_tournament()
return [Player(x).get_channel() for x in game.players]

def get_game_message(self):
update = self.updates
return simplejson.dumps(update)

def get_all_updates(self):
update = self.updates
update.update({'all' : 'all'})
return simplejson.dumps(update)

def send_update(self):
message = self.get_game_message()
for channel_id in self.get_player_channels():
channel.send_message(channel_id, message)

Flow

Whenever a user comes to the app, first of all it is checked if he was in an existing room or not. If found, then his activities are continued from that stage. If not found then the user is taken to a room. The room could be a new room, if all others are full, or no room is present. The user can perform activities in that room. Filling of rooms goes in First Come, First Serve order. So, when the next user wishes to join a room, the latest room created is checked for vacancy and the user is dropped there. The user is then free to perform actions and have fun.

What is typically important here is that, initially a Channel is created between the client and the server, and then using that same channel all the future communication for rooms goes. One advantage of this is that we don’t need to create a lot of channels for every user. For this it is very important that the algo which is used to create channel client id has only one variable as userid. (What will happen if we have two variables like userID and timestamp to create the channel client id?).

def gen_channel(userid):
return md5.md5(userid).hexdigest()

After the channel is created, the client send requests to join the room. This is taken care by the server and accordingly the user is dropped into a new room or an existing room

class JoinGame(BaseHandler):
def post(self):
“””
Processes the req from a client/player to join a gameroom
“””
user = users.get_current_user()
if not user:
return self.redirect(users.create_login_url(‘/’))

userid = user.user_id()
logging.info(‘join chat call by %s’%userid)
player = Player(userid)
if not player.get_gameroom():
gameroom = Tournament.join_new_or_latest(player)
return
Tournament.continue_tournament(player)

This is in a nutshell, a proof of concept about building huge chat-rooms/ game-rooms using Channel API of Google App Engine.

Resources

Read more about optimizing this code in part 2 of the series.

  1. Source Code: http://code.google.com/p/pranav/source/browse/chat-channel
  2. Channel API: http://code.google.com/appengine/docs/python/channel
  3. Discuss: Google Groups Discussion
  4. Tic Tac Toe App: http://code.google.com/p/channel-tac-toe

There are many more things that needs to be taken care of to increase speed and performance, but that shall come in another blog post

PyCon India 2010 Videos – Building Scalable Apps using Google App Engine

The Video shooting for PyCon India 2010 held on 25th and 26th Sept at Bangalore, was done by the media partner Vodex. They send the DVDs and CDs to people who have opted for it. Here I am sharing the videos from my talk on “Building Scalable Apps Using Google App Engine”. The video processing was done excellently in complete synchronization with the presentation slides. As a side effect of this, the videos are available in parts as per the slides.

Getting rid of facebook group emails, beautifully


Like many others, after being irritated by facebook’s constant emails for every post on a group I am a member of, I made Group Email Digest (facebook page) application which sends me the daily summary of posts on a group. The applications sends out one email per day per group. The first version of the app launched was pretty simple and had some basic bugs which restricted the use of the application. The latest release of the app (the app is still in beta) has fixed those bugs and paved way for more interesting features in the applications.

Here is a brief summary of bugs fixed, bugs not fixed and things on my mind (to show up in later releases). For a complete and updated detail of issues, visit the bug tracker page.

UPDATE
The bug related to closed/secret group’s emails not being trigged has been fixed.

Resolved Bugs/ Issues

  1. Blank Email/Text: A blonde had three swimming pools. One had cold water, the other had warm water and there was no water in the third pool. When asked about the third pool, she said.. well, sometimes I don’t feel like swimming. 🙂 The Group Email Digest app used to send out blank emails, when there had been no recorded activity. This has been fixed, and now, no emails are send if there is no activity.
    Likewise, if there were only links shared, videos posted, images uploaded onto the group, there was a similar blank text coming up. This has been alternated by message, description and caption in that order.
  2. Digest Emails not generating: For few old group’s, the emails were not being triggered. I found this out to be a version change issue on facebok front, and maintaining the compatibility, this has been fixed. The emails for all groups shall be triggering starting Nov 06
  3. URLs for old groups not coming properly: The URLs for old facebook groups were not formatted appropriately. This resulted in the clicks taking user to facebook home page instead of taking to the group page. This issue has been fixed.

Unresolved Bugs/Issues
The following issues remain unresolved as of now.

  1. Time Zone: All the emails are triggered just after 00:15 hrs UTC. This should ideally be 00:15 hrs User Time Zone. I am looking for a resource efficient method for this, and hopefully should get this fixed soon.
  2. Closed/Private Group Emails not trigerring: I am experimenting with delivering digest emails from closed/private group to its members in a resource efficient manner, and hopefully should arrive at some conclusion soon.

Things in my mind
The following features you should hope to see in near future.

  1. Localization: You should see the mail in the language of your choice, right? So, very soon, you will receive emails and all the text will be localized based on the information we obtain about you from your facebook account.
  2. Activity Graphs: Wanna know how active is a group? Very soon, you will be able to see a graphical visual of the activity taking place in the groups you are a member of.
  3. Group Suggestions: Want to do more exiting talks to people of your interests? Group suggestions will be telling you about new active groups based on your taste.

Did I miss a feature? Suggest features for Group Digest App here.

Saw a bug/issue/concern? Report it here.

Follow @groupdigestapp on twitter.

Facebook Group's Daily Digest Emails


Hey folks. So, wondering about the hell of emails facebook send you for every activity that is done in a group you are member of? Well, there is one official way to get rid of it – revoke permissions about facebook sending you emails. But then, you also don;t want to be left out, right? What if you could have a daily digest sort of thing for facebook groups? Won’t that be great?

Time to welcome Group Email Digest app (facebook link, website). This is a real simple, sweet app that keeps you updated about the activities performed in your facvorite group on a daily basis. The app’s motto is “To keep your inbox updated and uncluttered“.

Let me tell you more about this app. When you visit the app’s home page (http://digest.myblive.com), it will ask you to login with your facebook account, and ask the required permissions. The need for various permissions has been very clearly mentioned there itself. After you login, you will find all the groups you are currently a member of. Just check those groups for which you want a daily digest update email and click on “Subscribe Digest Email” at the bottom. If you want to opt out from a particular group, just uncheck the group and again click on “Subscribe Digest Email”. That will opt you out.

The app is in beta phase. This means that there will be frequent changes in the application’s behavior. The changes will be for the good only. Also, there are server limitations on the number of emails per day, that can be send out (since the application is running in free quota now), so we can support only a limited number of users right now. The limit should be somewhere around 1,000 however exact count shall be available only after observation for couple of days.

Let me know your views, critism and praises, so I will keep improving upon it.

Facebook autologin with google account


So, are you tired of logging in everytime on facebook? Putting your email id/facebook username and password. Wish there was a secure way in which you would have been automatically logged into facebook? There is! And the way is by connecting facebook and google account (gmail).

It’s pretty simple, safe and secure. All you have to do is to connect your gmail/google account with facebook once. Then everytime you are logged into gmail and open facebook, you will automatically be logged in. What’s more. Even if you try to logout, facebook will relogin you 🙂

Here is how this goes.

  1. Go to find your friends on facebook. It is on the right hand side bar. You will notice something like this. Now enter your gmail ID when asked as shown below.
  2. This will open a pop up as shown below. If you are not already logged into gmail, it will ask you to log in to your gmail account.
  3. After you have logged in, or if you are already logged in, you will see the request from google on behalf of facebook to access your contact information. Below there is a check box about remembering this authorization. Make sure that option is checked for auto login. Don’t worry, I will tell you how to revoke this privilage, if you want to later at the end of this post.
  4. After authentication, you will see a screen which will be something like the one below. This is where facebook has authenticated your google account and is now, fetching your friends who are on facebook. Let facebook do their job.
  5. Now you will find a list of your contacts from your gmail, who are on facebook. If you wish, you can send them a friend request, or you may skip this step.
  6. Now that is all. Your facebook account is connected to your gmail account and you are ready for passwordless login on facebook. To test this, logout of your facebook account, but remain logged in your gmail account. After you have logged out, you will see facebook logging you again into your account. Like the one shown below.
  7. If you ever wish to revoke permissions of facebook from your google account, just go to google.com and click on Google Account Settings.
  8. Next, click on Change Authorized Websites to see the list of websites you have authorized. Find for Facebook there, and click on revoke access. That’s all you have now successfully disconnected facebook and google account.