class List(object): def __init__(self, name, id=None): self.id = id if id else str(uuid4()) self.channel = Channel(self.id) self.channel.onMessage = self.onMessage self.channel.onJoin = self.onJoin self.name = name self.items = [] self.dirty = False def entitle(self, user): self.channel.entitle(user) def entitled(self, user): return user in self.channel.entitled def onJoin(self, socket): for i, item in enumerate(self.items): self.channel.sendTo(socket, {"type": "insert", "index": i, "attrs": item}) def onMessage(self, socket, msg): require(msg, "index") if msg.type == "insert": require(msg, "attrs") self.items.insert(msg.index, msg.attrs) self.channel.broadcast( {"type": "insert", "index": msg.index, "attrs": msg.attrs}) self.dirty = True elif msg.type == "delete": del self.items[msg.index] self.channel.broadcast( {"type": "delete", "index": msg.index}) self.dirty = True elif msg.type == "update": require(msg, "attrs") item = self.items[msg.index] item.update(msg.attrs) self.channel.broadcast( {"type": "update", "index": msg.index, "attrs": msg.attrs}) self.dirty = True
class Server(object): def __init__(self): Router = SockJSRouter(ChannelDispatcher, '/todo') self.app = web.Application(Router.urls) self.control = Channel("control") self.control.onMessage = self.controlMessageHandler ChannelDispatcher.addChannel(self.control) self.lists = [] self.byId = {} self.init() self.dirty = True def controlMessageHandler(self, socket, msg): if msg.type == "get-lists": for list in self.lists: if not list.entitled(socket.username): continue self.control.sendTo( socket, {"type": "list-added", "name": list.name, "id": list.id}) elif msg.type == "create": require(msg, "name") self.createList(msg.name, None, socket.username) self.dirty = True elif msg.type == "rename": require(msg, "id") require(msg, "name") self.byId[msg.id].name = msg.name self.control.broadcast({"type": "list-rename", "id": msg.id, "name": msg.name}) self.dirty = True elif msg.type == "delete": require(msg, "id") self.deleteList(msg.id) self.dirty = True def createList(self, name, id=None, username=None): l = List(name, id) self.lists.append(l) self.byId[l.id] = l ChannelDispatcher.addChannel(l.channel, username) self.control.broadcast( {"type": "list-added", "name": name, "id": l.id}) def deleteList(self, id): self.lists.remove(self.byId.pop(id)) ChannelDispatcher.destroyChannel(id) self.control.broadcast({"type": "list-delete", "id": id}) def flush(self): if self.isDirty(): print "syncdb" output = [] for l in self.lists: output.append({"name": l.name, "id": l.id, "items": l.items, "users": list(l.channel.entitled)}) json.dump(output, open(TEMP_PATH, "w")) rename(DATA_PATH, "%s/%d.txt" % (BACKUP_PATH, int(time.time()))) rename(TEMP_PATH, DATA_PATH) self.clearDirty() def clearDirty(self): for l in self.lists: l.dirty = False self.dirty = False def isDirty(self): return self.dirty or any((l.dirty for l in self.lists)) def init(self): creds = json.load(open(CREDENTIALS_PATH, "r")) for user in creds: ChannelDispatcher.addUser( user["username"], user["pwhash"]) self.control.entitle(user["username"]) if not os.path.exists(DATA_PATH): return data = json.load(open(DATA_PATH, "r")) for l in data: self.createList(l["name"], l["id"]) lst = self.byId[l["id"]] for item in l["items"]: lst.items.append(item) for user in l["users"]: lst.entitle(user) def run(self): self.app.listen(PORT) flusher = ioloop.PeriodicCallback(self.flush, FLUSH_INTERVAL) flusher.start() ioloop.IOLoop.instance().start()