''' flauxtext.UndoStack instances are picklable. one NameServer on the first collaborator's [Server's] box. all collaborators run Client remote objects named after themselves. only the Server knows about all the collaborator's remote objects. only the first collaborator can directly access the master TextBuffer; all other collaborators [Clients] must synchronously request the Server to make edits for them. all Remote Objects should have methods: def insert(self, filename, revision_number, text, index, color): """ insert text into filenme at character index, and color its background color. """ def delete(self, filename, revision_number, start_index, end_index): """ remove text from filename between start_index and end_index. """ def set_friend_color(self, friend_name, color): """ record friend_name's new color """ def get_color(self): """ get your color """ and attributes: friends[name] = color color = flauxtext.collabcolor buffer[filename] = buffer # where buffer.filename == filename deltas[filename] = fifo of RevisionDelta objects # only on the Server when done, abstract out to a module which synchronizes any subclass across a network REVISION DELTAS!!! rotating stack kept by server for each buffer is only ever as long as the largest difference between revision_numbers according to any collaborator so, first RevisionDelta in Server.deltas[filename] is the difference between Server.buffer[filename] and what it was a split second ago solves the problem of a client editing an 'old' buffer like UndoEntry, but not merged to \W*\w+; tracks a single entry/deletion by any one user attributes: offset str inserted/deleted bool True if inserted methods: def __add__(self, another_delta): """ return a new RevisionDelta encapsulating both self and another_delta """ def __call__(self, buffer): """ apply the difference encapsulated by self to buffer """ if too slow, "Pyro.config.PYRO_COMPRESSION = True" to save bandwidth ''' PORT = 891 MIN_VALUE = 50 # the value [as in HSV] of your color must be greater than this NAP_TIME = .1 try: import Pyro.core, Pyro.util, Pyro.naming, socket, time def synchronized(f): ''' function decorator which ensures that f is running in only ONE thread at a time ''' def newf(*a, **kw): while newf.locked: time.sleep(NAP_TIME) newf.locked = True f(*a, **kw) newf.locked = False newf.locked = False return newf def nick_color(): ''' ''' def start_nameserver(): ''' ''' flauxtext.Popen("python -O -tt -c 'import Pyro.naming, sys; Pyro.naming.main([])' &") def serve(): ''' nameserver is on socket.gethostname() ''' def join(): ''' ''' def share_this(): ''' ''' def prompt_for_server_ip(): ''' ''' ip = '' d = self.mk_dialog() def get_my_ip(): ''' returns the actual ip of the local machine. ''' csock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) csock.connect(('www.google.com', 80)) (addr, port) = csock.getsockname() csock.close() return addr class Server(Pyro.core.ObjBase): ''' ''' def __init__(self, *a, **kw): ''' ''' Pyro.core.ObjBase.__init__(self) Pyro.core.initServer(banner=0) self.views = [] def insert(self, which, offset, text): ''' ''' b = self.views[which].get_buffer() b.insert(b.get_iter_at_offset(offset), text) def delete(self, startitr, enditr): ''' ''' def connect(self, ns, name=None): ''' connect to the given name server with the given name ''' if name == None: name = 'remote_object' + str(id(self)) self.name=name # create the daemon addr = get_ip_addr() self.demon = Pyro.core.Daemon(host=addr) self.demon.useNameServer(ns.ns) # instantiate the object and advertise it try: print 'Connecting remote object', name self.uri = self.demon.connect(self, name) except Pyro.errors.NamingError: print 'Pyro NamingError: name already exists or is illegal' sys.exit(1) return self.name def requestLoop(self): ''' run the request loop until an exception occurs ''' try: self.demon.requestLoop() except: self.cleanup() if sys.exc_type != KeyboardInterrupt: raise sys.exc_type, sys.exc_value def cleanup(self): ''' remove this object from the name server ''' print 'Shutting down remote object', self.name try: self.demon.disconnect(self) except KeyError: print "tried to remove a name that wasn't on the name server" self.stopLoop() self.demon.shutdown() def threadLoop(self): ''' run the request loop in a separate thread ''' self.thread = threading.Thread(target=self.stoppableLoop) self.thread.start() def stoppableLoop(self): ''' run handleRequests until another thread clears self.running ''' self.running = 1 try: while self.running: self.demon.handleRequests(0.1) finally: self.cleanup() def stopLoop(self): ''' if threadLoop is running, stop it ''' self.running = 0 def join(self): ''' wait for the threadLoop to complete ''' if hasattr(self, 'thread'): self.thread.join() class NameServer: ''' The NameServer object represents the name server running on a remote host and provides methods for interacting with it. Used by both the "Server" and the "Client". ''' def __init__(self, ns_host=default_ns_host): '''locate the name server on the given host''' self.ns_host = ns_host self.ns = Pyro.naming.NameServerLocator().getNS(ns_host) def get_proxy(self, name): '''look up a remote object by name and create a proxy for it''' try: uri = self.ns.resolve(name) except Pyro.errors.NamingError: type, value, traceback = sys.exc_info() print 'Pyro NamingError:', value sys.exit(1) return Pyro.core.getProxyForURI(uri) def query(self, name, group=None): '''check whether the given name is registered in the given group. return 1 if the name is a remote object, 0 if it is a group, and -1 if it doesn't exist.''' t = self.ns.list(group) for k, v in t: if k == name: return v return -1 def create_group(self, name): '''create a group with the given name''' self.ns.createGroup(name) def get_remote_object_list(self, prefix='', group=None): '''return a list of the remote objects in the given group that start with the given prefix''' t = self.ns.list(group) u = [s for (s, n) in t if n==1 and s.startswith(prefix)] return u def clear(self, prefix='', group=None): '''unregister all objects in the given group that start with the given prefix''' t = self.ns.list(group) for (s, n) in t: if not s.startswith(prefix): continue if n==1: self.ns.unregister(n) except Exception, e: Pyro = None def collab(*a, **kw): ''' main entry point for the plugin. ''' if Pyro is None: self.flash_info("you can't collaborate without Pyro [pyro.sf.net]", 5e3, 'no_pyro') elif a[0] in vars(): eval(a[0])(*a[i:])