3 """Monitor a Tahoe grid, by playing sounds in response to remote events.
6 1: install Boodler, from http://www.eblong.com/zarf/boodler/
7 2: run "boodler.py -l listen.Sounds". This will run a daemon
8 that listens on a network socket (31863 by default) and
9 accepts commands in the form of "sound bird/crow1.aiff\n"
10 3: copy this file into a new directory, which we'll call $BASEDIR
11 4: write one or more logport FURLs into files named *.furl or *.furls, one
12 per line. All logports from all such files will be used.
13 5: launch this daemon with 'cd $BASEDIR && twistd -y boodlegrid.tac'
18 from zope.interface import implements
19 from twisted.application import service
20 from twisted.internet import protocol, reactor, defer
21 from foolscap import Tub, Referenceable
22 from foolscap.logging.interfaces import RILogObserver
23 from twisted.python import log
28 self.boodler = None # filled in when we connect to boodler
31 def sound(self, name, slot=None, max=0.100):
37 if now < self.last.get(slot, 0) + max:
40 self.boodler.write("sound %s\n" % name)
42 def msg(self, m, furl):
44 message = m.get("message", m.get("format", ""))
45 format = m.get("format", "")
46 facility = m.get("facility", "")
48 # messages emitted by the Introducer: client join/leave
49 if message.startswith("introducer: subscription[storage] request"):
51 self.sound("voice/hooray.aiff")
52 if message.startswith("introducer: unsubscribing"):
54 self.sound("electro/zaptrill-fade.aiff")
56 # messages from the helper
57 if message == "file already found in grid":
59 self.sound("mech/ziplash-high.aiff")
60 #if message == "upload done":
61 if format == "plaintext_hash=%(plaintext_hash)s, SI=%(SI)s, size=%(size)d":
63 print "upload done, size", size
64 self.sound("mech/ziplash-low.aiff")
65 if "fetching " in message:
66 # helper grabbing ciphertext from client
67 self.sound("voice/phoneme/sh.aiff", max=0.5)
69 # messages from storage servers
70 if message.startswith("storage: slot_readv"):
71 #self.sound("voice/phoneme/r.aiff")
72 self.sound("percussion/wood-tap-hollow.aiff")
74 # messages from webapi
75 if message.startswith("Retrieve") and "starting" in message:
76 self.sound("mech/metal-clack.aiff")
77 if message.startswith("Publish") and "starting" in message:
78 self.sound("mech/door-slam.aiff")
79 #self.sound("mech/metal-clash.aiff")
80 if ("web: %(clientip)s" in format
81 and m.get("method") == "POST"
82 and ("t=set_children" in m.get("uri", "") # FIXME: may give false-positives
83 or "t=set-children" in m.get("uri", ""))):
84 self.sound("mech/clock-clang.aiff")
88 # self.sound("mech/keyboard-1.aiff")
89 if "_check_for_done but we're not running" in message:
91 elif format == "excessive reactor delay (%ss)":
92 self.sound("animal/frog-cheep.aiff")
93 print "excessive delay %s: %s" % (m['args'][0], furl)
94 elif format == "excessive reactor delay (%(delay)ss)":
95 self.sound("animal/frog-cheep.aiff")
96 print "excessive delay %s: %s" % (m['delay'], furl)
97 elif facility == "foolscap.negotiation":
98 if (message == "got offer for an existing connection"
99 or "master told us to use a new connection" in message):
100 print "foolscap: got offer for an existing connection", message, furl
102 #print "foolscap:", message
104 elif m['level'] > 30: # SCARY or BAD
105 #self.sound("mech/alarm-bell.aiff")
106 self.sound("environ/thunder-tense.aiff")
108 elif m['level'] == 30: # WEIRD
109 self.sound("mech/glass-breaking.aiff")
111 elif m['level'] > 20: # UNUSUAL or INFREQUENT or CURIOUS
112 self.sound("mech/telephone-ring-old.aiff")
115 class BoodleSender(protocol.Protocol):
116 def connectionMade(self):
117 print "connected to boodler"
118 self.factory.listener.boodler = self.transport
120 class Bridge(Referenceable):
121 implements(RILogObserver)
123 def __init__(self, furl, listener):
125 self.listener = listener
127 def remote_msg(self, m):
128 d = defer.maybeDeferred(self.listener.msg, m, self.furl)
129 d.addErrback(log.err)
130 # never send errors to the remote side
132 class Monitor(service.MultiService):
134 service.MultiService.__init__(self)
136 self.tub.setServiceParent(self)
137 self.listener = Listener()
139 for fn in os.listdir("."):
140 if fn.endswith(".furl") or fn.endswith(".furls"):
141 for i,line in enumerate(open(fn, "r").readlines()):
142 target = line.strip()
144 self.tub.connectTo(target, self._got_logpublisher,
147 cf = protocol.ClientFactory()
148 cf.listener = self.listener
149 cf.protocol = BoodleSender
150 reactor.connectTCP("localhost", 31863, cf)
152 def _got_logpublisher(self, publisher, fn, i, target):
153 print "connected to %s:%d, %s" % (fn, i, target)
154 b = Bridge(target, self.listener)
155 publisher.callRemote("subscribe_to_all", b)
159 application = service.Application("boodlegrid")
160 m.setServiceParent(application)