+###--------------------------------------------------------------------------
+### Pinging peers.
+
+class pingstate (struct):
+ """
+ Information kept for each peer by the Pinger.
+
+ Important attributes:
+
+ * peer = the peer name
+ * command = PING or EPING
+ * n = how many pings we've sent so far
+ * ngood = how many returned
+ * nmiss = how many didn't return
+ * nmissrun = how many pings since the last good one
+ * tlast = round-trip time for the last (good) ping
+ * ttot = total roung trip time
+ """
+ pass
+
+class Pinger (T.Coroutine, HookClient):
+ """
+ Coroutine which pings known peers and collects statistics.
+
+ Interesting attributes:
+
+ * _map: dict mapping peer names to Peer objects
+ * _q: event queue for notifying pinger coroutine
+ * _timer: gobject timer for waking the coroutine
+ """
+
+ def __init__(me):
+ """
+ Initialize the pinger.
+
+ We watch the monitor's PeerList to track which peers we should ping. We
+ maintain an event queue and put all the events on that.
+
+ The statistics for a PEER are held in the Peer object, in PEER.ping[CMD],
+ where CMD is 'PING' or 'EPING'.
+ """
+ T.Coroutine.__init__(me)
+ HookClient.__init__(me)
+ me._map = {}
+ me._q = T.Queue()
+ me._timer = None
+ me.hook(conn.connecthook, me._connected)
+ me.hook(conn.disconnecthook, me._disconnected)
+ me.hook(monitor.peers.addhook,
+ lambda p: T.defer(me._q.put, (p, 'ADD', None)))
+ me.hook(monitor.peers.delhook,
+ lambda p: T.defer(me._q.put, (p, 'KILL', None)))
+ if conn.connectedp(): me.connected()
+
+ def _connected(me):
+ """Respond to connection: start pinging thngs."""
+ me._timer = GL.timeout_add(1000, me._timerfunc)
+
+ def _timerfunc(me):
+ """Timer function: put a timer event on the queue."""
+ me._q.put((None, 'TIMER', None))
+ return True
+
+ def _disconnected(me, reason):
+ """Respond to disconnection: stop pinging."""
+ GL.source_remove(me._timer)
+
+ def run(me):
+ """
+ Coroutine function: read events from the queue and process them.
+
+ Interesting events:
+
+ * (PEER, 'KILL', None): remove PEER from the interesting peers list
+ * (PEER, 'ADD', None): add PEER to the list
+ * (PEER, 'INFO', TOKENS): result from a PING command
+ * (None, 'TIMER', None): interval timer went off: send more pings
+ """
+ while True:
+ tag, code, stuff = me._q.get()
+ if code == 'KILL':
+ name = tag.name
+ if name in me._map:
+ del me._map[name]
+ elif not conn.connectedp():
+ pass
+ elif code == 'ADD':
+ p = tag
+ p.ping = {}
+ for cmd in 'PING', 'EPING':
+ ps = pingstate(command = cmd, peer = p,
+ n = 0, ngood = 0, nmiss = 0, nmissrun = 0,
+ tlast = 0, ttot = 0)
+ p.ping[cmd] = ps
+ me._map[p.name] = p
+ elif code == 'INFO':
+ ps = tag
+ if stuff[0] == 'ping-ok':
+ t = float(stuff[1])
+ ps.ngood += 1
+ ps.nmissrun = 0
+ ps.tlast = t
+ ps.ttot += t
+ else:
+ ps.nmiss += 1
+ ps.nmissrun += 1
+ ps.n += 1
+ ps.peer.pinghook.run(ps.peer, ps.command, ps)
+ elif code == 'TIMER':
+ for name, p in me._map.iteritems():
+ for cmd, ps in p.ping.iteritems():
+ conn.rawcommand(T.TripeAsynchronousCommand(me._q, ps, [
+ cmd, '-background', conn.bgtag(), '--', name]))
+
+###--------------------------------------------------------------------------
+### Random dialogue boxes.
+
+class AddPeerDialog (MyDialog):
+ """
+ Let the user create a new peer the low-level way.
+
+ Interesting attributes:
+
+ * e_name, e_addr, e_port, c_keepalive, l_tunnel: widgets in the dialog
+ """
+
+ def __init__(me):
+ """Initialize the dialogue."""
+ MyDialog.__init__(me, 'Add peer',
+ buttons = [(G.STOCK_CANCEL, me.destroy),
+ (G.STOCK_OK, me.ok)])
+ me._setup()
+
+ @incr
+ def _setup(me):
+ """Coroutine function: background setup for AddPeerDialog."""
+ table = GridPacker()
+ me.vbox.pack_start(table, True, True, 0)
+ me.e_name = table.labelled('Name',
+ ValidatingEntry(r'^[^\s.:]+$', '', 16),
+ width = 3)
+ me.e_addr = table.labelled('Address',
+ ValidatingEntry(r'^[a-zA-Z0-9.-]+$', '', 24),
+ newlinep = True)
+ me.e_port = table.labelled('Port',
+ ValidatingEntry(numericvalidate(0, 65535),
+ '4070',
+ 5))
+ me.l_tunnel = table.labelled('Tunnel', combo_box_text(),
+ newlinep = True, width = 3)
+ me.tuns = ['(Default)'] + conn.tunnels()
+ for t in me.tuns:
+ me.l_tunnel.append_text(t)
+ me.l_tunnel.set_active(0)
+
+ def tickybox_sensitivity(tickybox, target):
+ tickybox.connect('toggled',
+ lambda t: target.set_sensitive (t.get_active()))
+
+ def optional_entry(label, rx_valid, width):
+ c = G.CheckButton(label)
+ table.pack(c, newlinep = True, xopt = G.FILL)
+ e = ValidatingEntry(rx_valid, '', width)
+ e.set_sensitive(False)
+ tickybox_sensitivity(c, e)
+ table.pack(e, width = 3)
+ return c, e
+
+ me.c_keepalive, me.e_keepalive = \
+ optional_entry('Keepalives', r'^\d+[hms]?$', 5)
+
+ me.c_cork = G.CheckButton('Cork')
+ table.pack(me.c_cork, newlinep = True, width = 4, xopt = G.FILL)
+
+ me.c_mobile = G.CheckButton('Mobile')
+ table.pack(me.c_mobile, newlinep = True, width = 4, xopt = G.FILL)
+
+ me.c_peerkey, me.e_peerkey = \
+ optional_entry('Peer key tag', r'^[^.:\s]+$', 16)
+ me.c_privkey, me.e_privkey = \
+ optional_entry('Private key tag', r'^[^.:\s]+$', 16)
+
+ me.show_all()
+
+ def ok(me):
+ """Handle an OK press: create the peer."""
+ try:
+ t = me.l_tunnel.get_active()
+ me._addpeer(me.e_name.get_text(),
+ me.e_addr.get_text(),
+ me.e_port.get_text(),
+ keepalive = (me.c_keepalive.get_active() and
+ me.e_keepalive.get_text() or None),
+ tunnel = t and me.tuns[t] or None,
+ cork = me.c_cork.get_active() or None,
+ mobile = me.c_mobile.get_active() or None,
+ key = (me.c_peerkey.get_active() and
+ me.e_peerkey.get_text() or None),
+ priv = (me.c_privkey.get_active() and
+ me.e_privkey.get_text() or None))
+ except ValidationError:
+ GDK.beep()
+ return
+
+ @incr
+ def _addpeer(me, *args, **kw):
+ """Coroutine function: actually do the ADD command."""
+ try:
+ conn.add(*args, **kw)
+ me.destroy()
+ except T.TripeError, exc:
+ T.defer(moanbox, ' '.join(exc))
+
+class ServInfo (TrivialWindow):
+ """
+ Show information about the server and available services.
+
+ Interesting attributes:
+
+ * e: maps SERVINFO keys to entry widgets
+ * svcs: Gtk ListStore describing services (columns are name and version)
+ """
+
+ def __init__(me):
+ TrivialWindow.__init__(me)
+ me.set_title('TrIPE server info')
+ table = GridPacker()
+ me.add(table)
+ me.e = {}
+ def add(label, tag, text = None, **kw):
+ me.e[tag] = table.info(label, text, **kw)
+ add('Implementation', 'implementation')
+ add('Version', 'version', newlinep = True)
+ me.svcs = G.ListStore(*(GO.TYPE_STRING,) * 2)
+ me.svcs.set_sort_column_id(0, G.SORT_ASCENDING)
+ scr = MyScrolledWindow()
+ lb = MyTreeView(me.svcs)
+ i = 0
+ for title in 'Service', 'Version':
+ lb.append_column(G.TreeViewColumn(
+ title, G.CellRendererText(), text = i))
+ i += 1
+ for svc in monitor.services:
+ me.svcs.append([svc.name, svc.version])
+ scr.add(lb)
+ table.pack(scr, width = 2, newlinep = True,
+ yopt = G.EXPAND | G.FILL | G.SHRINK)
+ me.update()
+ me.hook(conn.connecthook, me.update)
+ me.hook(monitor.services.addhook, me.addsvc)
+ me.hook(monitor.services.delhook, me.delsvc)
+ me.show_all()
+
+ def addsvc(me, svc):
+ me.svcs.append([svc.name, svc.version])
+
+ def delsvc(me, svc):
+ for i in xrange(len(me.svcs)):
+ if me.svcs[i][0] == svc.name:
+ me.svcs.remove(me.svcs.get_iter(i))
+ break
+ @incr
+ def update(me):
+ info = conn.servinfo()
+ for i in me.e:
+ me.e[i].set_text(info[i])
+
+class TraceOptions (MyDialog):
+ """Tracing options window."""
+ def __init__(me):
+ MyDialog.__init__(me, title = 'Tracing options',
+ buttons = [(G.STOCK_CLOSE, me.destroy),
+ (G.STOCK_OK, cr(me.ok))])
+ me._setup()
+
+ @incr
+ def _setup(me):
+ me.opts = []
+ for ch, st, desc in conn.trace():
+ if ch.isupper(): continue
+ text = desc[0].upper() + desc[1:]
+ ticky = G.CheckButton(text)
+ ticky.set_active(st == '+')
+ me.vbox.pack_start(ticky, True, True, 0)
+ me.opts.append((ch, ticky))
+ me.show_all()
+ def ok(me):
+ on = []
+ off = []
+ for ch, ticky in me.opts:
+ if ticky.get_active():
+ on.append(ch)
+ else:
+ off.append(ch)
+ setting = ''.join(on) + '-' + ''.join(off)
+ conn.trace(setting)
+ me.destroy()
+
+###--------------------------------------------------------------------------
+### Peer window.