Skip to content

Commit

Permalink
all kinds of client improvements, both cli and web
Browse files Browse the repository at this point in the history
  • Loading branch information
teepark committed Aug 12, 2011
1 parent 5b53477 commit fd33038
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 10 deletions.
4 changes: 3 additions & 1 deletion bin/yakity
Expand Up @@ -11,6 +11,8 @@ from yakity import commandline, configs
def main(environ, argv):
parser = optparse.OptionParser()
parser.add_option("-c", "--configfile", default="yakity.conf")
parser.add_option("-!", "--no-auto-join", action="store_false",
default=True, dest="auto_join")

options, args = parser.parse_args(argv[1:])

Expand All @@ -20,7 +22,7 @@ def main(environ, argv):
print >> sys.stderr, "missing command"
return 1

getattr(commandline, args[0])(conf, *args[1:])
getattr(commandline, args[0])(options, *args[1:])

return 0

Expand Down
238 changes: 238 additions & 0 deletions examples/chatserver.py
@@ -0,0 +1,238 @@
#!/usr/bin/env python
# vim: fileencoding=utf8:et:sta:ai:sw=4:ts=4:sts=4

from yakity import configs, client, wsgi_api
from feather import wsgi


CONF = configs.get_configs("examples/single.conf")

def app(environ, start_response):
if environ['PATH_INFO'] in ('/', '/index.html'):
start_response("200 OK", [
("Content-Type", "text/html"),
("Content-Length", str(len(homepage)))
])
return [homepage]

return wsgi_api.WSGIApp(CONF)(environ, start_response)


HOMEPAGE = """
<doctype html>
<html>
<head>
<style type="text/css">
body {
font-family: Lucida Console, Monaco, monospace;
}
div.screen {
margin-left: auto;
margin-right: auto;
width: 960px;
border: 1px solid black;
}
div#screen1 {
margin-top: 160px;
}
div#screen1 table {
margin-left: auto;
margin-right: auto;
}
input#screen1submit {
height: 4em;
}
#screen1errors {
color: red;
font-weight: bold;
}
div#screen2 {
font-size: 20px;
display: none;
}
div#chatwindow {
height: 480px;
margin-top: 40px;
overflow: auto;
}
div#chatinput {
margin-left: auto;
margin-right: auto;
margin-top: 20px;
width: 960px;
border: 1px solid black;
}
div#chatinput input {
width: 100%;
border: 0;
color: gray;
font-size: inherit;
font-family: inherit;
}
form#typingform {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div class="screen" id="screen1">
<form id="screen1form">
<table>
<tr>
<td>user name:</td>
<td><input type="text" id="username" tabindex=1></input></td>
<td rowspan=2><input type="submit" id="screen1submit" value="Join" tabindex=3></input></td>
</tr><tr>
<td>room name:</td>
<td><input type="text" id="roomname" tabindex=2></input></td>
</tr><tr>
<td colspan=3 id="screen1errors"></td>
</tr>
</table>
</form>
</div>
<div id="screen2">
<div class="screen" id="chatwindow"></div>
<div id="chatinput"><form id="typingform"><input type="text" value="type here"></input></form></div>
</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.js"></script>
<script type="text/javascript">
$(function() {
var latest = 0;
var inflight = null;
var screen1 = $("div#screen1");
var screen2 = $("div#screen2");
var usernamebox = $("input#username");
var roomnamebox = $("input#roomname");
var screen1form = $("form#screen1form");
var screen1errors = $("#screen1errors");
var chatwindow = $("div#chatwindow");
var chatinputdiv = $("div#chatinput");
var chatinput = $("div#chatinput input");
var typingform = $("form#typingform");
function insertRoomAction(action) {
var item = $("<div>");
switch(action.event) {
case 'join':
item.text("* " + action.username + " has joined");
item.css({'color': 'gray'});
break;
case 'depart':
item.text("* " + action.username + " has left");
item.css({'color': 'gray'});
break;
case 'msg':
item.text(action.username + ": " + action.msg);
}
chatwindow.append(item);
chatwindow.scrollTop(chatwindow[0].scrollHeight);
};
function startPull() {
inflight = $.ajax({
url: "/" + roomnamebox.val(),
data: {'last': latest},
username: usernamebox.val(),
password: '',
dataType: 'json',
type: 'GET',
success: function(data, status, jqXHR) {
if (data.length)
latest = Math.max(latest, data[data.length - 1].counter);
startPull();
for (var i = 0; i < data.length; ++i)
insertRoomAction(data[i]);
},
error: function(jqXHR, status, exception) {
if (status === "abort")
return;
if (status === "timeout")
startPull();
if (exception)
throw exception;
}
});
};
screen1form.live("submit", function(event) {
event.preventDefault();
startPull();
$.ajax({
url: "/" + roomnamebox.val() + "/join",
username: usernamebox.val(),
password: '',
dataType: 'json',
success: function(data, status, jqXHR) {
screen1.css({'display': 'none'});
screen2.css({'display': 'block'});
},
error: function(jqXHR, status, exception) {
screen1errors.text(status + ": " + exception);
if (exception !== undefined)
throw exception;
}
});
});
chatinput
.live("focus", function() {
if (chatinput.val() === 'type here')
chatinput.val('');
chatinput.css({'color': 'black'});
})
.live("blur", function() {
chatinput.css({'color': 'gray'});
if (chatinput.val() === '')
chatinput.val('type here');
});
typingform.live("submit", function(event) {
event.preventDefault();
$.ajax({
url: "/" + roomnamebox.val() + "/",
data: chatinput.val(),
processData: false,
dataType: 'json',
contentType: 'application/json',
type: 'POST'
});
chatinput.val('');
});
});
</script>
</body>
</html>
""".strip()


wsgi.serve(("localhost", 8989), app, worker_count=1)
35 changes: 30 additions & 5 deletions yakity/commandline.py
Expand Up @@ -10,7 +10,8 @@
signal = greenhouse.patched("signal")


def runservice(conf, instance_name):
def runservice(options, instance_name):
conf = configs.get_configs(options.configfile)
instance = [inst for inst in conf.instances if inst.name == instance_name]
if not instance:
print >> sys.stderr, "unknown shard %r" % instance_name
Expand All @@ -28,7 +29,8 @@ def runservice(conf, instance_name):
pass


def listen(conf, roomname, username=None):
def listen(options, roomname, username=None):
conf = configs.get_configs(options.configfile)
yak = client.Yakity(conf, client.prepare_client(
conf, room_hint=roomname, user_hint=username), None)

Expand Down Expand Up @@ -59,7 +61,8 @@ def listen(conf, roomname, username=None):
pass


def speak(conf, roomname, username):
def converse(options, roomname, username):
conf = configs.get_configs(options.configfile)
def int_handler(signum, frame):
greenhouse.end(speaker_glet)
signal.signal(signal.SIGINT, int_handler)
Expand All @@ -73,14 +76,36 @@ def int_handler(signum, frame):
@greenhouse.greenlet
def speaker_glet():
try:
yak.join(roomname)
if options.auto_join:
yak.join(roomname)
print "(ctrl-c to exit)"
while 1:
greenhouse.stdout.write("%s> " % roomname)
line = greenhouse.stdin.readline().rstrip()
if line: yak.say(roomname, line)
finally:
yak.depart(roomname)
if options.auto_join:
yak.depart(roomname)
finished.set()

finished.wait()


def join(options, roomname, username):
conf = configs.get_configs(options.configfile)
yak = client.Yakity(conf, client.prepare_client(
conf, room_hint=roomname, user_hint=username), username)
yak.join(roomname)


def depart(options, roomname, username):
conf = configs.get_configs(options.configfile)
yak = client.Yakity(conf, client.prepare_client(
conf, room_hint=roomname, user_hint=username), username)
yak.depart(roomname)

def say(options, roomname, username, msg):
conf = configs.get_configs(options.configfile)
yak = client.Yakity(conf, client.prepare_client(
conf, room_hint=roomname, user_hint=username), username)
yak.say(roomname, msg)
13 changes: 9 additions & 4 deletions yakity/wsgi_api.py
Expand Up @@ -5,9 +5,9 @@
import time
import traceback

import yajl

import greenhouse
from . import client
import yajl


class WSGIApp(object):
Expand Down Expand Up @@ -50,8 +50,7 @@ def _handle(self, environ, start_response):
yak.depart(roomname)
return '{"success":true}'

def __call__(self, environ, start_response):
# auth required
def _auth(self, environ, start_response):
if 'HTTP_AUTHORIZATION' not in environ:
start_response("401 Unauthorized", [
("Content-Type", "text/plain"),
Expand All @@ -70,6 +69,12 @@ def __call__(self, environ, start_response):
return ["basic authorization required."]
environ['auth'] = token.decode("base64").split(":", 1)

def __call__(self, environ, start_response):
# auth required
result = self._auth(environ, start_response)
if result is not None:
return result

try:
result = self._handle(environ, start_response)
rc = "200 OK"
Expand Down

0 comments on commit fd33038

Please sign in to comment.