import itertools, random from nevow import livepage, loaders, tags, static, inevow, rend, url import nevow from zope import interface from nevow.livepage import ( js, document, append, get, eol, var, assign, this, set, server) null = js.null sessionCounter = itertools.count().next WORDS = open('/usr/share/dict/words').readlines() class IColors(interface.Interface): pass class Colors(object): def __init__(self): self.colors = [ (255, 155, 153), (255, 185, 133), (255, 250, 101), (135, 255, 149), (136, 255, 252), (153, 196, 255), (203, 173, 255), (255, 172, 246), (255, 164, 198), (187, 187, 187)] def getColor(self): return self.colors.pop( random.choice(range(len(self.colors)))) def putBackColor(self, color): self.colors.append(color) class User(object): def __init__(self, color): self.username = random.choice(WORDS).strip() self.color = color def formatColor(self): return "#%02X%02X%02X" % self.color class IUser(interface.Interface): pass def userFactory(ctx): sess = ctx.tag.getSession() user = sess.getComponent(IUser, None) if user is None: colors = IColors(ctx.parent) color = colors.getColor() user = User(color) sess.setComponent(IUser, user) sess.notifyOnExpire(lambda: colors.putBackColor(color)) return user nevow.load(""" pavel.userFactory nevow.context.RequestContext pavel.IUser """) class PavelPageServer(rend.Page): docFactory = loaders.stan(tags.html[ tags.head[tags.title["Pavel Server"]], tags.body[ "Choose a page", tags.ol(data=tags.directive('children'), render=tags.directive('sequence'))[ tags.li(pattern="item", render=tags.directive('child'))[ tags.a(href=tags.slot('url'))[tags.slot('label')] ]]]]) addSlash = True def __init__(self): import atexit, pickle self.children = {} try: self.data = pickle.load(file('pavel.data', 'rb')) except Exception, e: print "EXCEPTION!", e self.data = {"MAX-COUNTER": 0, ('untitled', ): {}} for (k, v) in self.data.items(): self.children[k] = Pavel(k, self, v) self.counter = itertools.count(self.data['MAX-COUNTER']).next global counter counter = self.next atexit.register(lambda: pickle.dump(self.data, file('pavel.data', 'wb'))) def next(self): nx = self.counter() self.data['MAX-COUNTER'] = nx return nx def data_children(self, ctx, _): return [x for x in self.children.keys() if x != 'MAX-COUNTER'] def render_child(self, ctx, segments): u = url.here.keep('sess') for seg in segments: u = u.child(seg) ctx.fillSlots('url', u) ctx.fillSlots('label', '/'.join(segments)) return ctx.tag def locateChild(self, ctx, segments): if hasattr(self, 'child_%s' % segments[0]): return super(PavelPageServer, self).locateChild(ctx, segments) ## HACK if segments[-1].startswith('nevow_'): remain = (segments[-1], ) segments = segments[:-1] else: remain = () return self.getPageData(segments), remain def getPageData(self, segments): if not self.children.has_key(segments): self.data[segments] = {} self.children[segments] = Pavel(segments, self, self.data[segments]) return self.children[segments] setattr(PavelPageServer, 'child_pavel.js', static.File('pavel.js')) setattr(PavelPageServer, 'child_pavel.css', static.File('pavel.css')) setattr(PavelPageServer, 'child_favicon.ico', static.File('favicon.ico')) setattr(PavelPageServer, 'child_fat.js', static.File('fat.js')) setattr(PavelPageServer, 'child_sticky.png', static.File('sticky.png')) setattr(PavelPageServer, 'child_link.png', static.File('link.png')) setattr(PavelPageServer, 'child_resize.png', static.File('resize.png')) def notifyAll(func): def closure(self, *args): self.notify(func, *args) return func(self, *args) return closure def notifyOthers(func): def closure(self, *args): self.notify(func, *args) return closure class Base(object): def __init__(self, counter, x, y): self.counter = counter self.x = x self.y = y def setPosition(self, x, y): self.x = x self.y = y class Sticky(Base): def __init__(self, counter, x, y): super(Sticky, self).__init__(counter, x, y) self.width = 0 self.height = 0 self.text = '' def setText(self, newText): self.text = newText def setSize(self, width, height): self.width = width self.height = height class Link(Base): def __init__(self, counter, x, y, pagename): super(Link, self).__init__(counter, x, y) self.link = pagename def setLink(self, newLink): self.link = newLink class Pavel(livepage.LivePage): def __init__(self, segments, allData, myData): self.segments = segments self.data = allData super(livepage.LivePage, self).__init__(myData) docFactory = loaders.xmlfile("pavel.html") pattern = inevow.IQ(docFactory).patternGenerator sticky = pattern('sticky') link = pattern('link') user = pattern('user') counter = itertools.count().next children = None def data_urls(self, ctx, _): cur = [] rv = [] for seg in self.segments: cur.append(seg) rv.append(tuple(cur)) return rv def render_url(self, ctx, ur): u = url.root for seg in ur: u = u.child(seg) ctx.fillSlots('url', u) ctx.fillSlots('label', ur[-1]) return ctx.tag def renderHTTP(self, ctx): req = inevow.IRequest(ctx) req.getSession() return super(Pavel, self).renderHTTP(ctx) def notify(self, tosend, ctx, *args): if self.clientFactory is None: return me = livepage.IClientHandle(ctx) for you in self.clientFactory.clientHandles.values(): if you is not me: you.send(tosend(self, ctx, *args)) def goingLive(self, ctx, handle): user = IUser(ctx) handle.notifyOnClose().addCallback(self.removeUser, ctx, user).addErrback(self.removeUser, ctx, user) handle.user = user for you in self.clientFactory.clientHandles.values(): if you is not handle: you = you.user r, g, b = you.color handle.send(self.formatUserlistNode(ctx, you)) handle.send(notifyAll(type(self).formatUserlistNode)(self, ctx, user)) for k, v in self.original.items(): if isinstance(v, Sticky): self.sendStickyUpdate(ctx, handle, k, v) elif isinstance(v, Link): self.sendLinkUpdate(ctx, handle, k, v) def sendStickyUpdate(self, ctx, handle, k, v): handle.send(self.formatCloneUpdate(ctx, v.x, v.y, v.counter)) if v.width != 0 or v.height != 0: handle.send(self.formatResizedUpdate(ctx, k, v.width, v.height)) if v.text: handle.send(set("%s-content" % (k, ), v.text)) def sendLinkUpdate(self, ctx, handle, k, v): handle.send(self.formatLinkCreate(ctx, v.link or 'untitled', v.x, v.y, "link-%s" % (v.counter, ))) def removeUser(self, _, ctx, user): self.reallyRemoveUser(ctx, user) @notifyOthers def reallyRemoveUser(self, ctx, user): yield var(js.theUserNode, get("user-%s" % (user.username, ))), eol yield js.theUserNode.parentNode.removeChild(js.theUserNode) def formatUserlistNode(self, ctx, user): r, g, b = user.color return append( 'userlist', self.user.fillSlots('username', user.username).fillSlots('red', r).fillSlots('green', g).fillSlots('blue', b)) def handle_clone(self, ctx, x, y): count = counter() new = Sticky(count, x, y) boxid = 'box-%s' % (count, ) self.original[boxid] = new return [ notifyAll(type(self).formatCloneUpdate)(self, ctx, x, y, count), notifyOthers(lambda s, c: [js.Fat.fade_element(boxid, null, null, IUser(ctx).formatColor()), eol])( self, ctx)] def formatCloneUpdate(self, ctx, x, y, counter): boxid = ['box-', counter] yield append( 'body', self.sticky.fillSlots('top', y).fillSlots('left', x).fillSlots('id', boxid)), eol yield js.setup(get(boxid)), eol def handle_moved(self, ctx, whichNode, x, y): self.original[whichNode].setPosition(x, y) notifyOthers(type(self).formatMovedUpdate)(self, ctx, whichNode, x, y) def formatMovedUpdate(self, ctx, whichNode, x, y): yield js.Fat.fade_element("%s-drag" % (whichNode, ), null, null, IUser(ctx).formatColor()), eol yield assign(get(whichNode).style.left, x), eol yield assign(get(whichNode).style.top, y), eol def handle_resized(self, ctx, whichNode, width, height): self.original[whichNode].setSize(width, height) notifyOthers(type(self).formatResizedUpdate)(self, ctx, whichNode, width, height) notifyOthers(lambda s, c: [ js.Fat.fade_element("%s-resize" % (whichNode, ), null, null, IUser(ctx).formatColor()), eol] )(self, ctx) def formatResizedUpdate(self, ctx, whichNode, width, height): yield assign(get(whichNode).style.width, width), eol yield assign(get(whichNode).style.height, height), eol @notifyAll def saveOrCancel(self, ctx, action, whichNode, content): yield assign(get(whichNode).previousSibling.innerHTML, ''), eol yield js.setup(get(whichNode)), eol if action == 'save': ## commitorate self.original['-'.join(whichNode.split('-')[:2])].setText(content) yield js.Fat.fade_element(whichNode, null, null, IUser(ctx).formatColor()), eol yield set(whichNode, content), eol def handle_edit(self, ctx, whichNode, content): ## Lock everyone else def lockOthers(self, ctx, whichNode): yield assign(get(whichNode).onclick, ''), eol yield assign(get(whichNode).previousSibling.innerHTML, "Locked by %s"% (IUser(ctx).username, )) self.notify(lockOthers, ctx, whichNode) saver = livepage.IClientHandle(ctx).transient(self.saveOrCancel) yield assign(get(whichNode).onclick, ''), eol yield set(whichNode, [ tags.textarea(id=[whichNode, '-textarea'])[content], tags.div(_class="buttons")[ tags.button( onclick=saver('save', whichNode, get("%s-textarea" % whichNode).value) )["Save"], tags.button( onclick=saver('cancel', whichNode, content) )["Cancel"]]]) def handle_link(self, ctx, pagename, x, y): count = counter() print "HANDLE LINKE!!!", x, y, count new = Link(count, x, y, pagename) newpage = self.data.getPageData((pagename,)) reverseLink = Link(counter(), x, y, self.segments[0]) newpage.original['link-%s' % reverseLink.counter] = reverseLink boxid = 'link-%s' % (count, ) self.original[boxid] = new doer = lambda sel, con, thePageName, theBoxId: [ notifyAll(type(sel).formatLinkCreate)(sel, con, thePageName, x, y, theBoxId), notifyOthers(lambda sel, oth: [js.Fat.fade_element(theBoxId, null, null, IUser(oth).formatColor()), eol])( sel, con)] newpage.notify(doer, ctx, self.segments[0], 'link-%s' % reverseLink.counter) return doer(self, ctx, pagename, boxid) def formatLinkCreate(self, ctx, pagename, x, y, boxid): yield append( 'body', self.link.fillSlots('top', y).fillSlots('left', x).fillSlots('id', boxid).fillSlots('name', pagename).fillSlots('url', url.here.up().up().child(pagename))) yield eol, js.setup(get(boxid)), eol def handle_consume(self, ctx, whoIsConsuming, whoIsConsumed): newPage = self.data.getPageData((self.original[whoIsConsuming].link, )) value = self.original.pop(whoIsConsumed) newPage.original[whoIsConsumed] = value newPage.notify( lambda sel, con: sel.formatCloneUpdate(con, value.x, value.y, value.counter), ctx) return notifyAll(lambda s, c: get(whoIsConsumed).parentNode.removeChild(get(whoIsConsumed)))(self, ctx)