Several incompatible changes to the experimental proxy API to make it simpler
[rox-lib.git] / python / rox / proxy.py
blobaf2bb57c40590257fffe276f6155a4266751c25c
1 """Given a pair of pipes with a python process at each end, this module
2 allows one end to make calls on the other. This is used by the su module
3 to allow control of a subprocess running as another user, but it may also
4 be useful in other situations. The caller end should use the master_proxy
5 module.
7 EXPERIMENTAL.
8 """
10 from __future__ import generators
11 # Note: do not import rox or gtk. Needs to work without DISPLAY.
12 import os, sys, pwd
13 import traceback
14 import fcntl
15 from select import select
16 import cPickle as pickle
18 class Proxy:
19 def __init__(self, to_peer, from_peer, slave_object = None):
20 if not hasattr(to_peer, 'fileno'):
21 to_peer = os.fdopen(to_peer, 'w')
22 if not hasattr(from_peer, 'fileno'):
23 from_peer = os.fdopen(from_peer, 'r')
24 self.to_peer = to_peer
25 self.from_peer = from_peer
26 self.out_buffer = ""
27 self.in_buffer = ""
29 self.enable_read_watch()
31 def enable_read_watch(self):
32 from rox import g
33 g.input_add(self.from_peer, g.gdk.INPUT_READ,
34 lambda src, cond: self.read_ready())
36 def enable_write_watch(self):
37 from rox import g
38 INPUT_WRITE = 0x14 # g.gdk.INPUT_WRITE sometimes wrong!!
39 g.input_add(self.to_peer.fileno(), INPUT_WRITE,
40 lambda src, cond: self.write_ready())
42 def write_object(self, object):
43 if self.to_peer is None:
44 raise Exception('Peer is defunct')
45 if not self.out_buffer:
46 self.enable_write_watch()
48 s = pickle.dumps(object)
49 s = str(len(s)) + ":" + s
50 self.out_buffer += s
52 def write_ready(self):
53 """Returns True if the buffer is not empty on exit."""
54 while self.out_buffer:
55 w = select([], [self.to_peer], [], 0)[1]
56 if not w:
57 print "Not ready for writing"
58 return True
59 n = os.write(self.to_peer.fileno(), self.out_buffer)
60 self.out_buffer = self.out_buffer[n:]
61 return False
63 def read_ready(self):
64 new = os.read(self.from_peer.fileno(), 1000)
65 if not new:
66 self.finish()
67 self.lost_connection()
68 self.in_buffer += new
69 while ':' in self.in_buffer:
70 l, rest = self.in_buffer.split(':', 1)
71 l = int(l)
72 if len(rest) < l:
73 return True # Haven't got everything yet
74 s = rest[:l]
75 self.in_buffer = rest[l:]
76 value = pickle.loads(s)
77 self._dispatch(value)
78 return True
80 def finish(self):
81 self.to_slave = self.from_slave = None
83 def lost_connection(self):
84 raise Exception("Lost connection to peer!")
86 class SlaveProxy(Proxy):
87 """Methods invoked on MasterProxy.root will be invoked on
88 slave_object. The result is a master_proxy.RequestBlocker."""
89 def __init__(self, to_master, from_master, slave_object):
90 Proxy.__init__(self, to_master, from_master)
91 self.slave_object = slave_object
93 def _dispatch(self, value):
94 serial, method, args = value
95 try:
96 result = getattr(self.slave_object, method)(*args)
97 except Exception, e:
98 result = e
99 self.write_object((serial, result))
101 def lost_connection(self):
102 sys.exit()