Status: | Merged |
---|---|
Approved by: | Simon C |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4 |
Proposed branch: | lp:~slimey/engage/stockwell |
Merge into: | lp:engage |
Diff against target: |
816 lines (+464/-162) 21 files modified
.bzrignore (+2/-0) bin/engage (+28/-0) debian/changelog (+6/-0) debian/control (+2/-3) debian/engage.install (+3/-0) debian/postinst (+25/-0) etc/init/engage.conf (+1/-1) lib-depends/get-egg-src (+40/-0) restart (+0/-2) setup.py (+3/-3) src/engage/media/__init__.py (+91/-0) src/engage/media/movie.py (+21/-0) src/engage/media/static.py (+36/-0) src/engage/monitor/percent_used.py (+3/-3) src/engage/ui/carousel.py (+37/-48) src/engage/ui/web.py (+2/-0) src/engage/util/atv.py (+94/-0) src/engage/util/twisted_plugin.py (+65/-0) src/twisted/plugins/engage_plugin.py (+5/-66) start (+0/-19) stop (+0/-17) |
To merge this branch: | bzr merge lp:~slimey/engage/stockwell |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Simon C | Approve | ||
Review via email: mp+126278@code.launchpad.net |
Commit message
Embedded Twisted 12 and further ATV support
Description of the change
- embedded twisted 12 for deployment on ubuntu
- further ATV support (video support still half-baked but harmless)
To post a comment you must log in.
Revision history for this message
Simon C (slimey) : | # |
review:
Approve
lp:~slimey/engage/stockwell
updated
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file '.bzrignore' |
2 | --- .bzrignore 1970-01-01 00:00:00 +0000 |
3 | +++ .bzrignore 2012-09-25 15:49:24 +0000 |
4 | @@ -0,0 +1,2 @@ |
5 | +dropin.cache |
6 | +twisted-engage.egg |
7 | |
8 | === added directory 'bin' |
9 | === added file 'bin/engage' |
10 | --- bin/engage 1970-01-01 00:00:00 +0000 |
11 | +++ bin/engage 2012-09-25 15:49:24 +0000 |
12 | @@ -0,0 +1,28 @@ |
13 | +#!/usr/bin/python |
14 | +# |
15 | +# This is just a crazy equivalent of |
16 | +# |
17 | +# /usr/bin/twistd --uid nobody --gid nogroup --logfile /var/log/engage.log --nodaemon engage |
18 | +# |
19 | +# but allowing Twisted to be embedded, for those ubutnu machines with |
20 | +# a stock (pre-12.0) version. |
21 | +# |
22 | +# Happy day when we can ditch this. |
23 | +# |
24 | +import os, platform, sys |
25 | +try: |
26 | + import _preamble |
27 | +except ImportError: |
28 | + sys.exc_clear() |
29 | + |
30 | +lib_dir = '/usr/lib/engage/lib-depends' |
31 | +sys.path.insert(0, lib_dir) |
32 | +sys.path.insert(0, os.path.join(lib_dir, 'twisted-engage.egg')) |
33 | + |
34 | +argv_head = sys.argv[0] |
35 | +argv_tail = sys.argv[1:] |
36 | +sys.argv = [argv_head, '--uid', 'nobody', '--gid', 'nogroup', '--logfile', |
37 | + '/var/log/engage.log', '--nodaemon'] + argv_tail + ['engage'] |
38 | + |
39 | +from twisted.scripts.twistd import run |
40 | +run() |
41 | |
42 | === modified file 'debian/changelog' |
43 | --- debian/changelog 2012-09-17 13:13:06 +0000 |
44 | +++ debian/changelog 2012-09-25 15:49:24 +0000 |
45 | @@ -1,3 +1,9 @@ |
46 | +engage (0.6.1) precise; urgency=low |
47 | + |
48 | + * Embedded twisted 12 for easy of installing on stock ubuntu |
49 | + |
50 | + -- Simon C <simonc@ensoft.co.uk> Mon, 24 Sep 2012 22:42:58 +0100 |
51 | + |
52 | engage (0.6) lucid; urgency=low |
53 | |
54 | * Improved structure and added basic ATV support |
55 | |
56 | === modified file 'debian/control' |
57 | --- debian/control 2012-09-17 13:13:06 +0000 |
58 | +++ debian/control 2012-09-25 15:49:24 +0000 |
59 | @@ -8,8 +8,7 @@ |
60 | Package: engage |
61 | Architecture: all |
62 | # snmp package is just for snmptranslate |
63 | -Depends: python,python-twisted,python-twisted-web (>= 12.0.0),python-rrdtool,libsnmp-python,python-pynetsnmp,snmp,snmp-mibs-downloader,${misc:Depends} |
64 | +Depends: python (>= ${python:Versions}), python-setuptools, python-zope.interface, python-rrdtool, libsnmp-python, python-pynetsnmp, snmp, snmp-mibs-downloader, ${python:Depends}, ${misc:Depends} |
65 | Description: Combined system/network statistics and Apple TV control framework |
66 | EnGage is a plugin-based framework for gathering, summarizing, and displaying |
67 | - system or network administration data. This base package provides basic |
68 | - monitoring of the linux host itself. |
69 | + system or network administration data. |
70 | |
71 | === modified file 'debian/engage.install' |
72 | --- debian/engage.install 2012-09-17 13:13:06 +0000 |
73 | +++ debian/engage.install 2012-09-25 15:49:24 +0000 |
74 | @@ -1,3 +1,6 @@ |
75 | etc/engage.conf etc |
76 | etc/init/engage.conf etc/init |
77 | static/ usr/share/engage |
78 | +bin/engage /usr/sbin |
79 | +lib-depends/twisted-engage-egg-src.tar.bz2 /usr/lib/engage/lib-depends |
80 | +lib-depends/twisted-engage.egg /usr/lib/engage/lib-depends |
81 | |
82 | === added file 'debian/postinst' |
83 | --- debian/postinst 1970-01-01 00:00:00 +0000 |
84 | +++ debian/postinst 2012-09-25 15:49:24 +0000 |
85 | @@ -0,0 +1,25 @@ |
86 | +#!/bin/sh |
87 | +# |
88 | +# Build the embedded Twisted 12.2 package into an egg for the target system |
89 | +# architecture. When it's reasonable to just put Twisted >= 12.0 as a |
90 | +# dependency and expect ubuntu to honour it, we can cheerfully flush this down |
91 | +# the can. |
92 | +# |
93 | +# We include a zero-byte twisted-engage.egg and mark it as a conffile, so it's |
94 | +# ok to overwrite it here with a real egg. |
95 | +# |
96 | +set -e |
97 | +egg_src=/usr/lib/engage/lib-depends/twisted-engage-egg-src.tar.bz2 |
98 | +egg=/usr/lib/engage/lib-depends/twisted-engage.egg |
99 | +tmp=/tmp/engage-$$ |
100 | +rm -rf $tmp |
101 | +mkdir $tmp |
102 | +cd $tmp |
103 | +tar jxf $egg_src |
104 | +cd Twisted |
105 | +python setup.py bdist_egg |
106 | +mv dist/*.egg $egg |
107 | +cd /tmp |
108 | +rm -rf $tmp |
109 | + |
110 | +#DEBHELPER# |
111 | |
112 | === modified file 'etc/init/engage.conf' |
113 | --- etc/init/engage.conf 2012-09-18 21:21:43 +0000 |
114 | +++ etc/init/engage.conf 2012-09-25 15:49:24 +0000 |
115 | @@ -5,5 +5,5 @@ |
116 | stop on stopping network |
117 | stop on starting shutdown |
118 | |
119 | -exec /usr/bin/twistd --uid nobody --gid nogroup --logfile /var/log/engage.log --nodaemon engage |
120 | +exec /usr/sbin/engage |
121 | respawn |
122 | |
123 | === added directory 'lib-depends' |
124 | === added file 'lib-depends/get-egg-src' |
125 | --- lib-depends/get-egg-src 1970-01-01 00:00:00 +0000 |
126 | +++ lib-depends/get-egg-src 2012-09-25 15:49:24 +0000 |
127 | @@ -0,0 +1,40 @@ |
128 | +#!/bin/sh |
129 | +# |
130 | +# Download twisted from pypi, add our own plugin, and then tar up so |
131 | +# it can be built on each target build. |
132 | +# |
133 | +# When we can just impose twisted >= 12.0 as a dependency and expect a |
134 | +# normal ubuntu to be able to honour that, we can trash this whole |
135 | +# sorry effort. That includes: |
136 | +# |
137 | +# - Killing this file, the twisted egg source file, and the zero-byte |
138 | +# twisted-engage.egg file in this directory that is present just for |
139 | +# dpkg to keep track of. |
140 | +# |
141 | +# - Killing the engage binary and just running "twistd engage" from |
142 | +# etc/init with appropriate options. |
143 | +# |
144 | +# - Remove python-setuptools from the dependencies in debian/control |
145 | +# and add python-twisted-core and python-twisted-web. |
146 | +# |
147 | +# - Moving engage/util/twisted_plugin.py back to twisted/plugins. |
148 | +# |
149 | +# - Adding twisted.plugins back to setup.py. |
150 | +# |
151 | +# Simon C, September 2012 |
152 | + |
153 | +set -e |
154 | +src_target=$PWD/twisted-engage-egg-src.tar.bz2 |
155 | +tmp=/tmp/engage-$$ |
156 | +plugin_file=twisted/plugins/engage_plugin.py |
157 | +plugin_src=$PWD/../src/$plugin_file |
158 | +rm -rf $tmp |
159 | +mkdir $tmp |
160 | +cd $tmp |
161 | +pip install --upgrade --no-install Twisted |
162 | +cd build/Twisted |
163 | +cp $plugin_src $plugin_file |
164 | +(cd ..; tar cf - Twisted | bzip2 -9 > $src_target) |
165 | +cd / |
166 | +rm -rf $tmp |
167 | +ls -l $src_target |
168 | |
169 | === added file 'lib-depends/twisted-engage-egg-src.tar.bz2' |
170 | Binary files lib-depends/twisted-engage-egg-src.tar.bz2 1970-01-01 00:00:00 +0000 and lib-depends/twisted-engage-egg-src.tar.bz2 2012-09-25 15:49:24 +0000 differ |
171 | === removed file 'restart' |
172 | --- restart 2012-09-14 12:24:16 +0000 |
173 | +++ restart 1970-01-01 00:00:00 +0000 |
174 | @@ -1,2 +0,0 @@ |
175 | -./stop $* |
176 | -./start $* |
177 | |
178 | === modified file 'setup.py' |
179 | --- setup.py 2012-09-18 21:21:43 +0000 |
180 | +++ setup.py 2012-09-25 15:49:24 +0000 |
181 | @@ -7,10 +7,10 @@ |
182 | from distutils.core import setup |
183 | |
184 | setup(name='engage', |
185 | - version="0.6", |
186 | + version="0.6.1", |
187 | description='EnGage: system/network statistics monitoring and Apple TV control', |
188 | url='http://open.ensoft.co.uk/EnGage', |
189 | - packages=['engage', 'engage.collector', 'engage.monitor', 'engage.util', |
190 | - 'engage.ui', 'twisted.plugins'], |
191 | + packages=['engage', 'engage.collector', 'engage.media', 'engage.monitor', |
192 | + 'engage.util', 'engage.ui'], |
193 | package_dir={'': 'src' }, |
194 | ) |
195 | |
196 | === added directory 'src/engage/media' |
197 | === added file 'src/engage/media/__init__.py' |
198 | --- src/engage/media/__init__.py 1970-01-01 00:00:00 +0000 |
199 | +++ src/engage/media/__init__.py 2012-09-25 15:49:24 +0000 |
200 | @@ -0,0 +1,91 @@ |
201 | +# Media base class |
202 | +# |
203 | +# Simon C, September 2012 |
204 | + |
205 | +# |
206 | +# Unofficial AirPlay specification: |
207 | +# |
208 | +# http://nto.github.com/AirPlay.html |
209 | +# |
210 | + |
211 | +from zope.interface import Interface, implements |
212 | +from twisted.internet import reactor |
213 | +from twisted.plugin import IPlugin, getPlugins |
214 | +from twisted.application import service |
215 | +from twisted.python import log |
216 | +import engage.media |
217 | + |
218 | +class MediaSource(object): |
219 | + """ |
220 | + Base classes for a source of media to be displayed on a carousel |
221 | + """ |
222 | + |
223 | + cfg_defaults = {} |
224 | + |
225 | + def __init__(self, name, cfg, atv, register_cb): |
226 | + """ |
227 | + Initialize with the configuration for this media source. Call |
228 | + back register_cb with the number of items this media source |
229 | + can render (and call again whenever this updates). |
230 | + """ |
231 | + self.name = '%s (%s)' % (name, self.__class__.__name__) |
232 | + self.cfg = cfg.copy() |
233 | + for k, v in self.cfg_defaults.items(): |
234 | + if k not in self.cfg: |
235 | + self.cfg[k] = v |
236 | + log.msg("Starting media source %s with config %s" % (self.name, self.cfg)) |
237 | + self.atv = atv |
238 | + self.register_cb = register_cb |
239 | + reactor.callLater(0, self.register_items) |
240 | + |
241 | + def register_items(self): |
242 | + """ |
243 | + For documentation only in the base class. Work out how many |
244 | + different items we could render, and tell the carousel that. |
245 | + """ |
246 | + |
247 | + def show(self): |
248 | + """ |
249 | + For documentation only in the base class. Show a random media |
250 | + object using the provided http_agent. Return a deferred that |
251 | + fires when done. |
252 | + """ |
253 | + |
254 | + @classmethod |
255 | + def config_start(cls, cfg, ip_addr, register_cb): |
256 | + """ |
257 | + Start all the media sources that are configured |
258 | + """ |
259 | + media_sources = {} |
260 | + for media_source in getPlugins(IMediaSource, engage.media): |
261 | + for source in media_source.config_start(cfg, ip_addr, register_cb): |
262 | + media_sources[source] = source.name |
263 | + return media_sources |
264 | + |
265 | +class IMediaSource(Interface): |
266 | + """ |
267 | + Twisted plugin specification for a config-started MediaSource |
268 | + """ |
269 | + def config_start(self, cfg, atv, register_cb): |
270 | + """ |
271 | + Create a MedaSource if configured |
272 | + """ |
273 | + |
274 | +class StartMediaSource(object): |
275 | + """ |
276 | + Twisted plugin for starting a MediaSource |
277 | + """ |
278 | + implements(IPlugin, IMediaSource) |
279 | + |
280 | + def __init__(self, stanza_name, media_source_cls): |
281 | + self.stanza_name = stanza_name |
282 | + self.media_source_cls = media_source_cls |
283 | + |
284 | + def config_start(self, cfg, atv, register_cb): |
285 | + started = [] |
286 | + for key in cfg: |
287 | + if key.startswith(self.stanza_name): |
288 | + log.msg('Starting media source %s from config stanza %s' % |
289 | + (self.media_source_cls.__name__, key)) |
290 | + started.append(self.media_source_cls(key, cfg[key], atv, register_cb)) |
291 | + return started |
292 | |
293 | === added file 'src/engage/media/movie.py' |
294 | --- src/engage/media/movie.py 1970-01-01 00:00:00 +0000 |
295 | +++ src/engage/media/movie.py 2012-09-25 15:49:24 +0000 |
296 | @@ -0,0 +1,21 @@ |
297 | +# Static file-drive media source |
298 | +# |
299 | +# Simon C, September 2012 |
300 | + |
301 | +from twisted.python import log |
302 | +from engage.media import MediaSource, StartMediaSource |
303 | + |
304 | +# |
305 | +# @@@ still working out structure here, this is probably all wrong |
306 | +# |
307 | + |
308 | +class MovieMediaSource(MediaSource): |
309 | + |
310 | + def register_items(self): |
311 | + self.register_cb(self, 1) |
312 | + |
313 | + def show(self): |
314 | + d = self.atv.show_movie('http://172.22.22.143:8421/mp4/amy-janet.mp4') |
315 | + return d |
316 | + |
317 | +start = StartMediaSource('movie', MovieMediaSource) |
318 | |
319 | === added file 'src/engage/media/static.py' |
320 | --- src/engage/media/static.py 1970-01-01 00:00:00 +0000 |
321 | +++ src/engage/media/static.py 2012-09-25 15:49:24 +0000 |
322 | @@ -0,0 +1,36 @@ |
323 | +# Static file-drive media source |
324 | +# |
325 | +# Simon C, September 2012 |
326 | + |
327 | +import os, random, re |
328 | +from twisted.internet import reactor |
329 | +from twisted.python import log |
330 | +from engage.media import MediaSource, StartMediaSource |
331 | + |
332 | +class StaticMediaSource(MediaSource): |
333 | + |
334 | + cfg_defaults = { |
335 | + 'refresh': '20', |
336 | + 'weight': '1.0', |
337 | + } |
338 | + |
339 | + def register_items(self): |
340 | + self.photos = [] |
341 | + for folder, _, files in os.walk(self.cfg['photo_dir'], followlinks=True): |
342 | + for filename in files: |
343 | + if re.search('(?i)\.(jpg|png)', filename): |
344 | + pathname = os.path.join(folder, filename) |
345 | + self.photos.append(pathname) |
346 | + log.msg("%s found %d photos to display with weight %s" % |
347 | + (self.name, len(self.photos), self.cfg['weight'])) |
348 | + self.register_cb(self, len(self.photos) * float(self.cfg['weight'])) |
349 | + # re-scan every 20 minutes by default |
350 | + reactor.callLater(60 * float(self.cfg['refresh']), self.register_items) |
351 | + |
352 | + def show(self): |
353 | + item = random.randint(0, len(self.photos) - 1) |
354 | + pathname = self.photos[item] |
355 | + d = self.atv.show_photo(pathname) |
356 | + return d |
357 | + |
358 | +start = StartMediaSource('static', StaticMediaSource) |
359 | |
360 | === modified file 'src/engage/monitor/percent_used.py' |
361 | --- src/engage/monitor/percent_used.py 2012-09-18 21:21:43 +0000 |
362 | +++ src/engage/monitor/percent_used.py 2012-09-25 15:49:24 +0000 |
363 | @@ -32,9 +32,9 @@ |
364 | |
365 | # Memory |
366 | with open('/proc/meminfo') as f: |
367 | - total, free, buffers = [int(f.readline().split()[1]) |
368 | - for i in xrange(0, 3)] |
369 | - mem_used = percent(total - free - buffers, total) |
370 | + total, free, buffers, cached = \ |
371 | + [int(f.readline().split()[1]) for i in xrange(0, 4)] |
372 | + mem_used = percent(total - free - buffers - cached, total) |
373 | mem_buff = percent(buffers, total) |
374 | |
375 | # Disk |
376 | |
377 | === modified file 'src/engage/ui/carousel.py' |
378 | --- src/engage/ui/carousel.py 2012-09-18 21:21:43 +0000 |
379 | +++ src/engage/ui/carousel.py 2012-09-25 15:49:24 +0000 |
380 | @@ -6,9 +6,9 @@ |
381 | from twisted.application import service |
382 | from twisted.internet import reactor |
383 | from twisted.python import log |
384 | -from twisted.web.client import Agent, FileBodyProducer, HTTPConnectionPool |
385 | -from twisted.web.http_headers import Headers |
386 | from engage.monitor.traffic import TrafficMonitor |
387 | +from engage.media import MediaSource |
388 | +from engage.util.atv import ATV |
389 | |
390 | class Carousel(object): |
391 | """ |
392 | @@ -36,13 +36,11 @@ |
393 | self.cfg = carousel_cfg |
394 | self.period = int(carousel_cfg.get('period', 7)) |
395 | self.threshold = int(carousel_cfg.get('threshold', 10000)) |
396 | - self.url = 'http://%s:7000/photo' % carousel_cfg['atv'] |
397 | - self.agent = Agent(reactor, pool=HTTPConnectionPool(reactor)) |
398 | - self.headers = Headers({ |
399 | - 'User-Agent': ['MediaControl/1.0'], |
400 | - 'X-Apple-Transition': ['Dissolve']}) |
401 | - self.scan_photo_dir() |
402 | - self.photo_idx = 0 |
403 | + self.atv = ATV(carousel_cfg['atv']) |
404 | + self.total_weight = 0 |
405 | + self.media_weights = {} |
406 | + self.media_source_names = MediaSource.config_start( |
407 | + carousel_cfg, self.atv, self.register_media_source) |
408 | |
409 | # Get initial SNMP stats |
410 | self.snmp_results = (0, 0, 0) # fallback for failure |
411 | @@ -54,6 +52,14 @@ |
412 | # Kick off periodic photo display |
413 | reactor.callLater(self.period, self.maybe_show_next_photo) |
414 | |
415 | + def register_media_source(self, media_source, num_items): |
416 | + self.media_weights[media_source] = num_items |
417 | + self.total_weight = 0 |
418 | + for weight in self.media_weights.values(): |
419 | + self.total_weight += weight |
420 | + log.msg("Registered %d out of %d items from %s" % |
421 | + (num_items, self.total_weight, self.media_source_names[media_source])) |
422 | + |
423 | def maybe_show_next_photo(self): |
424 | # |
425 | # Main workhorse of this class, that is called periodically. |
426 | @@ -76,8 +82,10 @@ |
427 | log.msg("Carousel failure: %s" % fail) |
428 | failure.printTraceback() |
429 | d.addErrback(handle_failure) |
430 | - d.addBoth(lambda r: |
431 | - reactor.callLater(self.period, self.maybe_show_next_photo)) |
432 | + def reset(result): |
433 | + log.msg("FINAL CALLBACK!!!") |
434 | + reactor.callLater(self.period, self.maybe_show_next_photo) |
435 | + d.addBoth(reset) |
436 | |
437 | def check_snmp_results(self, results): |
438 | # |
439 | @@ -93,18 +101,28 @@ |
440 | |
441 | def conditionally_show_next_photo(self, tx_bps): |
442 | # |
443 | - # If the tx bps is low enough, pick a photo and send it. |
444 | - # Currently very dumb: round robin |
445 | + # If the tx bps is low enough, pick a media source at random |
446 | + # and ask it to do something. |
447 | # |
448 | if tx_bps >= self.threshold: |
449 | log.msg("Skipping ATV photo, tx bps = %d" % tx_bps) |
450 | + self.atv.drop_connection() |
451 | else: |
452 | - pathname = self.photos[self.photo_idx] |
453 | - log.msg("Showing photo %s (tx bps = %d)" % (pathname, tx_bps)) |
454 | - body = FileBodyProducer(file(pathname)) |
455 | - d = self.agent.request('PUT', self.url, self.headers, body) |
456 | - d.addCallbacks(self.display_done, self.display_failed) |
457 | - return d |
458 | + r = random.randint(0, self.total_weight) |
459 | + media_source = None |
460 | + for possible_source in self.media_source_names: |
461 | + media_source = possible_source |
462 | + r -= self.media_weights[possible_source] |
463 | + if r < 0: |
464 | + break |
465 | + if media_source is None: |
466 | + log.msg("No media source") |
467 | + else: |
468 | + log.msg("Using media source %s" % (self.media_source_names[media_source])) |
469 | + d = media_source.show() |
470 | + d.addCallback(lambda r: "MEDIA SOURCE SHOW CALLBACK!!!") |
471 | + d.addErrback(self.display_failed) |
472 | + return d |
473 | |
474 | def get_second_snmp_results(self, _): |
475 | # |
476 | @@ -125,35 +143,6 @@ |
477 | log.msg("Display failed: %s" % failure) |
478 | failure.printTraceback() |
479 | |
480 | - def display_done(self, response): |
481 | - # |
482 | - # Something went wrong sending a photo to the ATV - log it |
483 | - # |
484 | - if response.code != 200: |
485 | - log.msg("Bad display response: key %s, code %s, phrase %s" % |
486 | - (response.version, response.code, response.phrase)) |
487 | - |
488 | - # Either way, pick a new photo at random |
489 | - new_photo_idx = self.photo_idx |
490 | - while new_photo_idx == self.photo_idx: |
491 | - new_photo_idx = random.randint(0, len(self.photos) - 1) |
492 | - self.photo_idx = new_photo_idx |
493 | - |
494 | - def scan_photo_dir(self): |
495 | - # |
496 | - # Scan the configured path for any plausible photos. Re-scan |
497 | - # sometimes in case new photos have arrived. |
498 | - # |
499 | - self.photos = [] |
500 | - for folder, _, files in os.walk(self.cfg['photo_dir']): |
501 | - for filename in files: |
502 | - if re.search('(?i)\.jpg', filename): |
503 | - pathname = os.path.join(folder, filename) |
504 | - self.photos.append(pathname) |
505 | - log.msg("Found %d photos to display after scan" % len(self.photos)) |
506 | - # re-scan every 20 minutes |
507 | - reactor.callLater(60 * 20, self.scan_photo_dir) |
508 | - |
509 | class CarouselService(service.Service): |
510 | """ |
511 | Twisted Service encapsulating one carousel |
512 | |
513 | === modified file 'src/engage/ui/web.py' |
514 | --- src/engage/ui/web.py 2012-09-18 21:21:43 +0000 |
515 | +++ src/engage/ui/web.py 2012-09-25 15:49:24 +0000 |
516 | @@ -159,6 +159,8 @@ |
517 | site.putChild("", Main(config)) |
518 | site.putChild("graph", Graph(config)) |
519 | site.putChild("png", File('%s/png' % web_config['web_static_dir'])) |
520 | + site.putChild("mp4", File('%s/mp4' % web_config['web_static_dir'])) |
521 | + |
522 | |
523 | web = Site(site) |
524 | tcp_service = internet.TCPServer(int(web_config['http_port']), web) |
525 | |
526 | === added file 'src/engage/util/atv.py' |
527 | --- src/engage/util/atv.py 1970-01-01 00:00:00 +0000 |
528 | +++ src/engage/util/atv.py 2012-09-25 15:49:24 +0000 |
529 | @@ -0,0 +1,94 @@ |
530 | +# Apple TV control |
531 | +# |
532 | +# Simon C, September 2012 |
533 | + |
534 | +import re |
535 | +from StringIO import StringIO |
536 | +from twisted.internet import defer, reactor |
537 | +from twisted.python import log |
538 | +from twisted.internet.protocol import Protocol |
539 | +from twisted.web.client import Agent, HTTPConnectionPool |
540 | +from twisted.web.client import FileBodyProducer |
541 | +from twisted.web.http_headers import Headers |
542 | + |
543 | +POLL_INTERVAL = 1 |
544 | + |
545 | +class ResponseReceiver(Protocol): |
546 | + |
547 | + def __init__(self, finished, fire_again): |
548 | + self.finished = finished |
549 | + self.fire_again = fire_again |
550 | + self.remaining = POLL_INTERVAL |
551 | + |
552 | + def dataReceived(self, data): |
553 | + position, duration = [float(re.search(key + ': ([\d.]+)', data).group(1)) |
554 | + for key in ('position', 'duration')] |
555 | + if duration > 0: |
556 | + remaining = duration - position |
557 | + if remaining < self.remaining: |
558 | + self.remaining = remaining |
559 | + |
560 | + log.msg("Video at %f/%f; timer reset to %f" % |
561 | + (position, duration, self.remaining)) |
562 | + |
563 | + def connectionLost(self, reason): |
564 | + if self.remaining < POLL_INTERVAL: |
565 | + log.msg("Last %f seconds, just waiting" % self.remaining) |
566 | + else: |
567 | + self.finished.addCallback(self.fire_again) |
568 | + reactor.callLater(self.remaining, self.finished.callback, None) |
569 | + |
570 | + |
571 | +class ATV(object): |
572 | + |
573 | + HEADERS = Headers({'User-Agent': ['MediaControl/1.0'], |
574 | + 'X-Apple-Transition': ['Dissolve']}) |
575 | + |
576 | + def __init__(self, ip_addr): |
577 | + self.http_agent = None |
578 | + self.base_url = 'http://%s:7000' % ip_addr |
579 | + |
580 | + def drop_connection(self): |
581 | + self.http_agent = None |
582 | + |
583 | + def _check_connection(self): |
584 | + if self.http_agent is None: |
585 | + self.http_agent = Agent(reactor, pool=HTTPConnectionPool(reactor)) |
586 | + |
587 | + def show_photo(self, pathname): |
588 | + self._check_connection() |
589 | + body = FileBodyProducer(file(pathname)) |
590 | + d = self.http_agent.request('PUT', self.base_url + '/photo', |
591 | + self.HEADERS, body) |
592 | + def display_done(response): |
593 | + if response.code != 200: |
594 | + log.msg("Bad response from photo %s: key %s, code %s, phrase %s" % |
595 | + (pathname, response.version, response.code, response.phrase)) |
596 | + d.addCallback(display_done) |
597 | + return d |
598 | + |
599 | + def show_movie(self, url): |
600 | + self._check_connection() |
601 | + body_string = 'Content-Location: %s\nStart-Position: 0\n' % url |
602 | + body = FileBodyProducer(StringIO(body_string)) |
603 | + url = self.base_url + '/play' |
604 | + d = self.http_agent.request('POST', url, self.HEADERS, body) |
605 | + d.addCallback(self.get_scrub_soon) |
606 | + return d |
607 | + |
608 | + def get_scrub_soon(self, result): |
609 | + d = defer.Deferred() |
610 | + d.addCallback(self.get_scrub) |
611 | + reactor.callLater(POLL_INTERVAL, lambda: d.callback(result)) |
612 | + return d |
613 | + |
614 | + def get_scrub(self, result): |
615 | + url = self.base_url + '/scrub' |
616 | + d = self.http_agent.request('GET', url) |
617 | + d.addCallback(self.display_scrub) |
618 | + return d |
619 | + |
620 | + def display_scrub(self, response): |
621 | + d = defer.Deferred() |
622 | + response.deliverBody(ResponseReceiver(d, self.get_scrub)) |
623 | + return d |
624 | |
625 | === added file 'src/engage/util/twisted_plugin.py' |
626 | --- src/engage/util/twisted_plugin.py 1970-01-01 00:00:00 +0000 |
627 | +++ src/engage/util/twisted_plugin.py 2012-09-25 15:49:24 +0000 |
628 | @@ -0,0 +1,65 @@ |
629 | +# EnGage twisted plugin |
630 | +# |
631 | +# Simon C, September 2012 |
632 | + |
633 | +from zope.interface import implements |
634 | +from twisted.python import usage |
635 | +from twisted.plugin import IPlugin |
636 | +from twisted.application import service |
637 | + |
638 | +from engage.collector.rrd import RRDCollector |
639 | +from engage.ui import web, carousel |
640 | +from engage.util import config |
641 | + |
642 | +class Options(usage.Options): |
643 | + |
644 | + optParameters = [ |
645 | + ('conf', 'c', '/etc/engage.conf', 'EnGage configuration file.'), |
646 | + ] |
647 | + |
648 | +# |
649 | +# Built-in configuration. We don't really want the admin messing with the first |
650 | +# two; the others are more just handy defaults. |
651 | +# |
652 | +collector_cfg = { |
653 | + 'interval': 10, |
654 | + 'rrd_dir': '/var/lib/engage/rrd', |
655 | +} |
656 | + |
657 | +web_cfg = { |
658 | + 'web_static_dir': '/usr/share/engage/static', |
659 | + 'http_prefix': '', |
660 | + 'http_port': '8421', |
661 | + 'title': 'EnGage', |
662 | +} |
663 | + |
664 | +class EngageServiceMaker(object): |
665 | + |
666 | + implements(service.IServiceMaker, IPlugin) |
667 | + |
668 | + tapname = "engage" |
669 | + description = "EnGage system/network monitoring service." |
670 | + options = Options |
671 | + |
672 | + def makeService(self, options): |
673 | + top_service = service.MultiService() |
674 | + |
675 | + # Merge the user and built-in configuration |
676 | + all_config = config.parse(options['conf']) |
677 | + for k, v in all_config['Main'].items(): |
678 | + web_cfg[k] = v |
679 | + collector_cfg[k] = v |
680 | + |
681 | + # Start monitors based on configuration, with data collected by RRD |
682 | + RRDCollector.config_start_monitors(collector_cfg, |
683 | + all_config, |
684 | + top_service) |
685 | + |
686 | + # Start carousel if configured |
687 | + if 'Carousel' in all_config: |
688 | + carousel.start(all_config, top_service) |
689 | + |
690 | + # Start the web server too |
691 | + web.start(web_cfg, top_service) |
692 | + |
693 | + return top_service |
694 | |
695 | === modified file 'src/twisted/plugins/engage_plugin.py' |
696 | --- src/twisted/plugins/engage_plugin.py 2012-09-18 21:21:43 +0000 |
697 | +++ src/twisted/plugins/engage_plugin.py 2012-09-25 15:49:24 +0000 |
698 | @@ -1,67 +1,6 @@ |
699 | -# EnGage twisted plugin |
700 | -# |
701 | -# Simon C, September 2012 |
702 | - |
703 | -from zope.interface import implements |
704 | -from twisted.python import usage |
705 | -from twisted.plugin import IPlugin |
706 | -from twisted.application import service |
707 | - |
708 | -from engage.collector.rrd import RRDCollector |
709 | -from engage.ui import web, carousel |
710 | -from engage.util import config |
711 | - |
712 | -class Options(usage.Options): |
713 | - |
714 | - optParameters = [ |
715 | - ('conf', 'c', '/etc/engage.conf', 'EnGage configuration file.'), |
716 | - ] |
717 | - |
718 | -# |
719 | -# Built-in configuration. We don't really want the admin messing with the first |
720 | -# two; the others are more just handy defaults. |
721 | -# |
722 | -collector_cfg = { |
723 | - 'interval': 10, |
724 | - 'rrd_dir': '/var/lib/engage/rrd', |
725 | -} |
726 | - |
727 | -web_cfg = { |
728 | - 'web_static_dir': '/usr/share/engage/static', |
729 | - 'http_prefix': '', |
730 | - 'http_port': '8421', |
731 | - 'title': 'EnGage', |
732 | -} |
733 | - |
734 | -class EngageServiceMaker(object): |
735 | - |
736 | - implements(service.IServiceMaker, IPlugin) |
737 | - |
738 | - tapname = "engage" |
739 | - description = "EnGage system/network monitoring service." |
740 | - options = Options |
741 | - |
742 | - def makeService(self, options): |
743 | - top_service = service.MultiService() |
744 | - |
745 | - # Merge the user and built-in configuration |
746 | - all_config = config.parse(options['conf']) |
747 | - for k, v in all_config['Main'].items(): |
748 | - web_cfg[k] = v |
749 | - collector_cfg[k] = v |
750 | - |
751 | - # Start monitors based on configuration, with data collected by RRD |
752 | - RRDCollector.config_start_monitors(collector_cfg, |
753 | - all_config, |
754 | - top_service) |
755 | - |
756 | - # Start carousel if configured |
757 | - if 'Carousel' in all_config: |
758 | - carousel.start(all_config, top_service) |
759 | - |
760 | - # Start the web server too |
761 | - web.start(web_cfg, top_service) |
762 | - |
763 | - return top_service |
764 | - |
765 | +# |
766 | +# Crazy file to allow embedding of Twisted as an egg, for stock Ubuntu |
767 | +# distributions with a pre-12.0.0 version |
768 | +# |
769 | +from engage.util.twisted_plugin import EngageServiceMaker |
770 | service_maker = EngageServiceMaker() |
771 | |
772 | === removed file 'start' |
773 | --- start 2012-09-14 12:24:16 +0000 |
774 | +++ start 1970-01-01 00:00:00 +0000 |
775 | @@ -1,19 +0,0 @@ |
776 | -hostname=$(hostname) |
777 | -if [ z$ENGAGE == z ]; then |
778 | - engage=engage |
779 | - instance=$hostname |
780 | -else |
781 | - engage=test_engage |
782 | - instance=test_$hostname |
783 | -fi |
784 | -CFGFILE=$PWD/etc/$instance.conf |
785 | -PIDFILE=$PWD/var/run/$engage.pid |
786 | -LOGFILE=$PWD/var/log/$instance.log |
787 | - |
788 | -if [ ! -f $CFGFILE ]; then |
789 | - echo No specific config file for host $hostname, using default |
790 | - CFGFILE=$PWD/etc/engage.conf |
791 | -fi |
792 | - |
793 | -cd src |
794 | -twistd $* --pidfile $PIDFILE --logfile $LOGFILE engage -c $CFGFILE |
795 | |
796 | === removed file 'stop' |
797 | --- stop 2012-09-14 12:24:16 +0000 |
798 | +++ stop 1970-01-01 00:00:00 +0000 |
799 | @@ -1,17 +0,0 @@ |
800 | -#!/bin/bash |
801 | - |
802 | -if [ z$ENGAGE == z ]; then |
803 | - engage=engage |
804 | - rrd_dir=var/rrd |
805 | -else |
806 | - engage=test_engage |
807 | - rrd_dir=var/test-rrd |
808 | -fi |
809 | -PIDFILE=$PWD/var/run/$engage.pid |
810 | - |
811 | -if [ -f $PIDFILE ]; then |
812 | - cd $rrd_dir |
813 | - # pause for most recent data collection, to avoid holes in data set |
814 | - perl -e '$| = 1; while (time - (stat((glob("*.rrd"))[0]))[9] != 1) { print "."; sleep 1; }; print "\n"' |
815 | - kill $(cat $PIDFILE) |
816 | -fi |