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