timegenerators introduced, and some cleanup
This commit is contained in:
parent
241eb30c7c
commit
2bd8f56aa3
7 changed files with 315 additions and 14 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@
|
|||
/dist/
|
||||
/*.egg-info
|
||||
build
|
||||
/*.egg/
|
||||
/*.egg
|
||||
|
|
|
@ -38,7 +38,7 @@ def check_spec(spec):
|
|||
yield "Missing field 'kind'"
|
||||
|
||||
|
||||
def generate(spec):
|
||||
def audiogenerate(spec):
|
||||
gen = get_audiogenerator(spec['kind'])
|
||||
return tuple(gen(spec))
|
||||
|
||||
|
@ -53,7 +53,7 @@ def main():
|
|||
for err in errors:
|
||||
print(err) # TODO: to stderr
|
||||
sys.exit(1)
|
||||
for path in generate(spec):
|
||||
for path in audiogenerate(spec):
|
||||
print(path)
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ from mpd import MPDClient
|
|||
|
||||
from eventutils import ParentedLet, Timer
|
||||
import rpc
|
||||
from audiogen import generate
|
||||
from audiogen import audiogenerate
|
||||
|
||||
CONTINOUS_AUDIODESC = dict(kind='mpd', howmany=1)
|
||||
MPD_HOST = os.getenv('MPD_HOST', 'localhost')
|
||||
|
@ -27,6 +27,7 @@ MPD_PORT = int(os.getenv('MPD_PORT', '6600'))
|
|||
class MpcWatcher(ParentedLet):
|
||||
def __init__(self, queue, client=None):
|
||||
ParentedLet.__init__(self, queue)
|
||||
self.log = logging.getLogger(self.__class__.__name__)
|
||||
if client is None:
|
||||
self.client = MPDClient()
|
||||
# TODO: use config values
|
||||
|
@ -37,7 +38,7 @@ class MpcWatcher(ParentedLet):
|
|||
def do_business(self):
|
||||
while True:
|
||||
status = self.client.idle()[0]
|
||||
logging.info(status)
|
||||
self.log.info(status)
|
||||
yield ('mpc', status)
|
||||
|
||||
|
||||
|
@ -45,6 +46,7 @@ class Player(gevent.Greenlet):
|
|||
def __init__(self):
|
||||
gevent.Greenlet.__init__(self)
|
||||
self.min_playlist_length = 10
|
||||
self.log = logging.getLogger(self.__class__.__name__)
|
||||
self.q = Queue()
|
||||
|
||||
def check_playlist(self):
|
||||
|
@ -54,30 +56,28 @@ class Player(gevent.Greenlet):
|
|||
songs = mpd_client.playlist()
|
||||
if(len(songs) >= self.min_playlist_length):
|
||||
return
|
||||
logging.info('need to add new songs')
|
||||
picker = gevent.Greenlet(generate, CONTINOUS_AUDIODESC)
|
||||
picker.link_value(lambda g: mpd_client.add(next(g.value).strip()))
|
||||
self.log.info('need to add new songs')
|
||||
picker = gevent.Greenlet(audiogenerate, CONTINOUS_AUDIODESC)
|
||||
picker.link_value(lambda g: mpd_client.add(g.value[0].strip()))
|
||||
picker.start()
|
||||
|
||||
def _run(self):
|
||||
MpcWatcher(self.q, client=None).start()
|
||||
Timer(6000, self.q).start()
|
||||
Timer(60 * 1000, self.q).start()
|
||||
http_server = WSGIServer(('', 5000), rpc.create_app(self.q))
|
||||
http_server.start()
|
||||
while True:
|
||||
value = self.q.get()
|
||||
self.log.debug('<- %s' % str(value))
|
||||
# emitter = value['emitter']
|
||||
kind = value['kind']
|
||||
args = value['args']
|
||||
if kind == 'timer':
|
||||
logging.info('CLOCK')
|
||||
self.log.info('CLOCK')
|
||||
if kind == 'timer' or (kind == 'mpc' and args[0] == 'playlist'):
|
||||
gevent.Greenlet.spawn(self.check_playlist)
|
||||
elif value['tracker'] == 'mpd_generate':
|
||||
logging.info("generated! %s" % tuple(args[0]))
|
||||
else:
|
||||
logging.warning("Unknown message: %s" % str(value))
|
||||
logging.info(str(value))
|
||||
self.log.warning("Unknown message: %s" % str(value))
|
||||
|
||||
|
||||
def on_player_crash(*args, **kwargs):
|
||||
|
|
114
larigira/tests/test_time_every.py
Normal file
114
larigira/tests/test_time_every.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
from datetime import timedelta
|
||||
from pprint import pprint
|
||||
|
||||
import pytest
|
||||
|
||||
from larigira.timegen_every import FrequencyAlarm, SingleAlarm
|
||||
from larigira.timegen import timegenerate
|
||||
|
||||
|
||||
def eq_(a, b, reason=None):
|
||||
'''migrating tests from nose'''
|
||||
if reason is not None:
|
||||
assert a == b, reason
|
||||
else:
|
||||
assert a == b
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def now():
|
||||
from datetime import datetime
|
||||
return datetime.now()
|
||||
|
||||
|
||||
def days(n):
|
||||
return timedelta(days=n)
|
||||
|
||||
|
||||
def test_single_creations(now):
|
||||
return SingleAlarm({
|
||||
'timestamp': now
|
||||
})
|
||||
|
||||
|
||||
def test_freq_creations(now):
|
||||
return FrequencyAlarm({
|
||||
'start': now - timedelta(days=1),
|
||||
'interval': 3600,
|
||||
'end': now})
|
||||
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
def test_single_ring(now):
|
||||
dt = now + days(1)
|
||||
s = SingleAlarm({'timestamp': dt})
|
||||
eq_(s.next_ring(), dt)
|
||||
eq_(s.next_ring(now), dt)
|
||||
assert s.next_ring(dt) is None, "%s - %s" % (str(s.next_ring(dt)), str(dt))
|
||||
assert s.next_ring(now + days(2)) is None
|
||||
assert s.has_ring(dt)
|
||||
assert not s.has_ring(now)
|
||||
assert not s.has_ring(now + days(2))
|
||||
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
def test_single_all(now):
|
||||
dt = now + timedelta(days=1)
|
||||
s = SingleAlarm({'timestamp': dt})
|
||||
eq_(list(s.all_rings()), [dt])
|
||||
eq_(list(s.all_rings(now)), [dt])
|
||||
eq_(list(s.all_rings(now + days(2))), [])
|
||||
|
||||
|
||||
def test_freq_short(now):
|
||||
f = FrequencyAlarm({
|
||||
'start': now - days(1),
|
||||
'interval': 10,
|
||||
'end': now + days(1)
|
||||
})
|
||||
assert now in f.all_rings(now - days(3))
|
||||
assert f.next_ring(now) is not None
|
||||
assert f.next_ring(now) != now
|
||||
assert f.next_ring(now) > now
|
||||
assert now not in f.all_rings(now)
|
||||
for r in f.all_rings(now):
|
||||
assert r > now
|
||||
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
def test_freq_ring(now):
|
||||
f = FrequencyAlarm({
|
||||
'start': now - days(1),
|
||||
'interval': 3600,
|
||||
'end': now + days(1)
|
||||
})
|
||||
assert now in f.all_rings(now - days(3))
|
||||
assert f.next_ring(now) is not None
|
||||
assert f.next_ring(now) != now
|
||||
assert f.next_ring(now) > now
|
||||
assert now not in f.all_rings(now)
|
||||
for r in f.all_rings(now):
|
||||
assert r > now
|
||||
allr = list(f.all_rings(now))
|
||||
eq_(len(allr), 24)
|
||||
|
||||
eq_(len(tuple(f.all_rings(now + days(2)))), 0)
|
||||
|
||||
allr = tuple(f.all_rings(now - days(20)))
|
||||
eq_(f.next_ring(now - days(20)), now - days(1))
|
||||
eq_(len(allr), 49, pprint(allr))
|
||||
|
||||
|
||||
def test_single_registered():
|
||||
timegenerate({
|
||||
'kind': 'single',
|
||||
'timestamp': 1234567890
|
||||
})
|
||||
|
||||
|
||||
def test_frequency_registered():
|
||||
timegenerate({
|
||||
'kind': 'frequency',
|
||||
'start': 1234567890,
|
||||
'interval': 60*15
|
||||
})
|
75
larigira/timegen.py
Normal file
75
larigira/timegen.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
'''
|
||||
main module to read and get informations about alarms
|
||||
'''
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import argparse
|
||||
from pkg_resources import iter_entry_points
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def get_timegenerator(kind):
|
||||
'''Messes with entrypoints to return an timegenerator function'''
|
||||
points = tuple(iter_entry_points(group='larigira.timegenerators',
|
||||
name=kind))
|
||||
if not points:
|
||||
raise ValueError('cant find a generator for ', kind)
|
||||
if len(points) > 1:
|
||||
logging.warning("Found more than one timegenerator for '%s'" % kind)
|
||||
gen = points[0]
|
||||
return gen.load()
|
||||
|
||||
|
||||
def get_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate "ring times" from a timespec')
|
||||
parser.add_argument('timespec', metavar='TIMESPEC', type=str, nargs=1,
|
||||
help='filename for timespec, formatted in json')
|
||||
parser.add_argument('--now', metavar='NOW', type=int, nargs=1,
|
||||
default=None,
|
||||
help='Set a different "time", in unix epoch')
|
||||
parser.add_argument('--howmany', metavar='N', type=int, nargs=1,
|
||||
default=[1],
|
||||
help='Set a different "time", in unix epoch')
|
||||
return parser
|
||||
|
||||
|
||||
def read_spec(fname):
|
||||
if fname == '-':
|
||||
return json.load(sys.stdin)
|
||||
with open(fname) as buf:
|
||||
return json.load(buf)
|
||||
|
||||
|
||||
def check_spec(spec):
|
||||
if 'kind' not in spec:
|
||||
yield "Missing field 'kind'"
|
||||
|
||||
|
||||
def timegenerate(spec, now=None, howmany=1):
|
||||
Alarm = get_timegenerator(spec['kind'])
|
||||
generator = Alarm(spec)
|
||||
if now is not None:
|
||||
if type(now) is not datetime:
|
||||
now = datetime.fromtimestamp(now)
|
||||
for _ in xrange(howmany):
|
||||
now = generator.next_ring(current_time=now)
|
||||
yield now
|
||||
|
||||
|
||||
def main():
|
||||
'''Main function for the "larigira-timegen" executable'''
|
||||
args = get_parser().parse_args()
|
||||
spec = read_spec(args.timespec[0])
|
||||
errors = tuple(check_spec(spec))
|
||||
if errors:
|
||||
logging.error("Errors in timespec")
|
||||
for err in errors:
|
||||
print(err) # TODO: to stderr
|
||||
sys.exit(1)
|
||||
now = None if args.now is None else args.now.pop()
|
||||
howmany = None if args.howmany is None else args.howmany.pop()
|
||||
for time in timegenerate(spec, now=now, howmany=howmany):
|
||||
print(time)
|
105
larigira/timegen_every.py
Normal file
105
larigira/timegen_every.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
from __future__ import print_function
|
||||
import logging
|
||||
log = logging.getLogger('time-every')
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def getdate(val):
|
||||
if type(val) is int:
|
||||
return datetime.fromtimestamp(val)
|
||||
return val
|
||||
|
||||
|
||||
class Alarm(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def next_ring(self, current_time=None):
|
||||
'''if current_time is None, it is now()
|
||||
|
||||
returns the next time it will ring; or None if it will not anymore
|
||||
'''
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_ring(self, time=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
def all_rings(self, current_time=None):
|
||||
'''
|
||||
all future rings
|
||||
this, of course, is an iterator (they could be infinite)
|
||||
'''
|
||||
ring = self.next_ring(current_time)
|
||||
while ring is not None:
|
||||
yield ring
|
||||
ring = self.next_ring(ring)
|
||||
|
||||
|
||||
class SingleAlarm(Alarm):
|
||||
'''
|
||||
rings a single time
|
||||
'''
|
||||
|
||||
def __init__(self, obj):
|
||||
self.dt = getdate(obj['timestamp'])
|
||||
|
||||
def next_ring(self, current_time=None):
|
||||
'''if current_time is None, it is now()'''
|
||||
if current_time is None:
|
||||
current_time = datetime.now()
|
||||
if current_time >= self.dt:
|
||||
return None
|
||||
return self.dt
|
||||
|
||||
def has_ring(self, current_time=None):
|
||||
if current_time is None:
|
||||
current_time = datetime.now()
|
||||
return current_time == self.dt
|
||||
|
||||
|
||||
class FrequencyAlarm(Alarm):
|
||||
'''
|
||||
rings on {t | exists a k integer >= 0 s.t. t = start+k*t, start<t<end}
|
||||
'''
|
||||
|
||||
def __init__(self, obj):
|
||||
self.start = getdate(obj['start'])
|
||||
self.interval = obj['interval']
|
||||
self.end = getdate(obj['end']) if 'end' in obj else None
|
||||
|
||||
def next_ring(self, current_time=None):
|
||||
'''if current_time is None, it is now()'''
|
||||
if current_time is None:
|
||||
current_time = datetime.now()
|
||||
if self.end is not None and current_time > self.end:
|
||||
return None
|
||||
if current_time < self.start:
|
||||
return self.start
|
||||
if self.end is not None:
|
||||
assert self.start <= current_time <= self.end
|
||||
else:
|
||||
assert self.start <= current_time
|
||||
n_interval = (
|
||||
(current_time - self.start).total_seconds() // self.interval
|
||||
) + 1
|
||||
ring = self.start + timedelta(seconds=self.interval * n_interval)
|
||||
if ring == current_time:
|
||||
ring += timedelta(seconds=self.interval)
|
||||
if self.end is not None and ring > self.end:
|
||||
return None
|
||||
return ring
|
||||
|
||||
def has_ring(self, current_time=None):
|
||||
if current_time is None:
|
||||
current_time = datetime.now()
|
||||
if not self.start >= current_time >= self.end:
|
||||
return False
|
||||
|
||||
n_interval = (current_time - self.start).total_seconds() // \
|
||||
self.interval
|
||||
expected_time = self.start + \
|
||||
timedelta(seconds=self.interval * n_interval)
|
||||
return expected_time == current_time
|
||||
|
||||
def __str__(self):
|
||||
return 'FrequencyAlarm(every %ds)' % self.interval
|
7
setup.py
7
setup.py
|
@ -41,16 +41,21 @@ setup(name='larigira',
|
|||
'flask',
|
||||
'python-mpd2'
|
||||
],
|
||||
tests_require=['pytest'],
|
||||
tests_require=['pytest', 'pytest-timeout'],
|
||||
cmdclass={'test': PyTest},
|
||||
zip_safe=False,
|
||||
entry_points={
|
||||
'console_scripts': ['larigira=larigira.mpc:main',
|
||||
'larigira-timegen=larigira.timegen:main',
|
||||
'larigira-audiogen=larigira.audiogen:main'],
|
||||
'larigira.audiogenerators': [
|
||||
'mpd = larigira.audiogen_mpdrandom:generate_by_artist',
|
||||
'static = larigira.audiogen_static:generate',
|
||||
'randomdir = larigira.audiogen_randomdir:generate'
|
||||
],
|
||||
'larigira.timegenerators': [
|
||||
'frequency = larigira.timegen_every:FrequencyAlarm',
|
||||
'single = larigira.timegen_every:SingleAlarm',
|
||||
]
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue