# -*- python -*- from __future__ import print_function """Monitor a Tahoe grid, by playing sounds in response to remote events. To install: 1: install Boodler, from http://www.eblong.com/zarf/boodler/ 2: run "boodler.py -l listen.Sounds". This will run a daemon that listens on a network socket (31863 by default) and accepts commands in the form of "sound bird/crow1.aiff\n" 3: copy this file into a new directory, which we'll call $BASEDIR 4: write one or more logport FURLs into files named *.furl or *.furls, one per line. All logports from all such files will be used. 5: launch this daemon with 'cd $BASEDIR && twistd -y boodlegrid.tac' """ import os, time from zope.interface import implements from twisted.application import service from twisted.internet import protocol, reactor, defer from foolscap import Tub, Referenceable from foolscap.logging.interfaces import RILogObserver from twisted.python import log class Listener: def __init__(self): self.boodler = None # filled in when we connect to boodler self.last = {} def sound(self, name, slot=None, max=0.100): if not self.boodler: return now = time.time() if slot is None: slot = name if now < self.last.get(slot, 0) + max: return # too soon self.last[slot] = now self.boodler.write("sound %s\n" % name) def msg(self, m, furl): #print "got it", m message = m.get("message", m.get("format", "")) format = m.get("format", "") facility = m.get("facility", "") # messages emitted by the Introducer: client join/leave if message.startswith("introducer: subscription[storage] request"): print("new client") self.sound("voice/hooray.aiff") if message.startswith("introducer: unsubscribing"): print("unsubscribe") self.sound("electro/zaptrill-fade.aiff") # messages from the helper if message == "file already found in grid": print("already found") self.sound("mech/ziplash-high.aiff") #if message == "upload done": if format == "plaintext_hash=%(plaintext_hash)s, SI=%(SI)s, size=%(size)d": size = m.get("size") print("upload done, size", size) self.sound("mech/ziplash-low.aiff") if "fetching " in message: # helper grabbing ciphertext from client self.sound("voice/phoneme/sh.aiff", max=0.5) # messages from storage servers if message.startswith("storage: slot_readv"): #self.sound("voice/phoneme/r.aiff") self.sound("percussion/wood-tap-hollow.aiff") # messages from webapi if message.startswith("Retrieve") and "starting" in message: self.sound("mech/metal-clack.aiff") if message.startswith("Publish") and "starting" in message: self.sound("mech/door-slam.aiff") #self.sound("mech/metal-clash.aiff") if ("web: %(clientip)s" in format and m.get("method") == "POST" and ("t=set_children" in m.get("uri", "") # FIXME: may give false-positives or "t=set-children" in m.get("uri", ""))): self.sound("mech/clock-clang.aiff") # generic messages #if m['level'] < 20: # self.sound("mech/keyboard-1.aiff") if "_check_for_done but we're not running" in message: pass elif format == "excessive reactor delay (%ss)": self.sound("animal/frog-cheep.aiff") print("excessive delay %s: %s" % (m['args'][0], furl)) elif format == "excessive reactor delay (%(delay)ss)": self.sound("animal/frog-cheep.aiff") print("excessive delay %s: %s" % (m['delay'], furl)) elif facility == "foolscap.negotiation": if (message == "got offer for an existing connection" or "master told us to use a new connection" in message): print("foolscap: got offer for an existing connection", message, furl) else: #print "foolscap:", message pass elif m['level'] > 30: # SCARY or BAD #self.sound("mech/alarm-bell.aiff") self.sound("environ/thunder-tense.aiff") print(m, furl) elif m['level'] == 30: # WEIRD self.sound("mech/glass-breaking.aiff") print(m, furl) elif m['level'] > 20: # UNUSUAL or INFREQUENT or CURIOUS self.sound("mech/telephone-ring-old.aiff") print(m, furl) class BoodleSender(protocol.Protocol): def connectionMade(self): print("connected to boodler") self.factory.listener.boodler = self.transport class Bridge(Referenceable): implements(RILogObserver) def __init__(self, furl, listener): self.furl = furl self.listener = listener def remote_msg(self, m): d = defer.maybeDeferred(self.listener.msg, m, self.furl) d.addErrback(log.err) # never send errors to the remote side class Monitor(service.MultiService): def __init__(self): service.MultiService.__init__(self) self.tub = Tub() self.tub.setServiceParent(self) self.listener = Listener() self.targets = [] for fn in os.listdir("."): if fn.endswith(".furl") or fn.endswith(".furls"): for i,line in enumerate(open(fn, "r").readlines()): target = line.strip() if target: self.tub.connectTo(target, self._got_logpublisher, fn, i, target) cf = protocol.ClientFactory() cf.listener = self.listener cf.protocol = BoodleSender reactor.connectTCP("localhost", 31863, cf) def _got_logpublisher(self, publisher, fn, i, target): print("connected to %s:%d, %s" % (fn, i, target)) b = Bridge(target, self.listener) publisher.callRemote("subscribe_to_all", b) m = Monitor() application = service.Application("boodlegrid") m.setServiceParent(application)