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/
|
/dist/
|
||||||
/*.egg-info
|
/*.egg-info
|
||||||
build
|
build
|
||||||
|
/*.egg/
|
||||||
|
/*.egg
|
||||||
|
|
|
@ -38,7 +38,7 @@ def check_spec(spec):
|
||||||
yield "Missing field 'kind'"
|
yield "Missing field 'kind'"
|
||||||
|
|
||||||
|
|
||||||
def generate(spec):
|
def audiogenerate(spec):
|
||||||
gen = get_audiogenerator(spec['kind'])
|
gen = get_audiogenerator(spec['kind'])
|
||||||
return tuple(gen(spec))
|
return tuple(gen(spec))
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ def main():
|
||||||
for err in errors:
|
for err in errors:
|
||||||
print(err) # TODO: to stderr
|
print(err) # TODO: to stderr
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
for path in generate(spec):
|
for path in audiogenerate(spec):
|
||||||
print(path)
|
print(path)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from mpd import MPDClient
|
||||||
|
|
||||||
from eventutils import ParentedLet, Timer
|
from eventutils import ParentedLet, Timer
|
||||||
import rpc
|
import rpc
|
||||||
from audiogen import generate
|
from audiogen import audiogenerate
|
||||||
|
|
||||||
CONTINOUS_AUDIODESC = dict(kind='mpd', howmany=1)
|
CONTINOUS_AUDIODESC = dict(kind='mpd', howmany=1)
|
||||||
MPD_HOST = os.getenv('MPD_HOST', 'localhost')
|
MPD_HOST = os.getenv('MPD_HOST', 'localhost')
|
||||||
|
@ -27,6 +27,7 @@ MPD_PORT = int(os.getenv('MPD_PORT', '6600'))
|
||||||
class MpcWatcher(ParentedLet):
|
class MpcWatcher(ParentedLet):
|
||||||
def __init__(self, queue, client=None):
|
def __init__(self, queue, client=None):
|
||||||
ParentedLet.__init__(self, queue)
|
ParentedLet.__init__(self, queue)
|
||||||
|
self.log = logging.getLogger(self.__class__.__name__)
|
||||||
if client is None:
|
if client is None:
|
||||||
self.client = MPDClient()
|
self.client = MPDClient()
|
||||||
# TODO: use config values
|
# TODO: use config values
|
||||||
|
@ -37,7 +38,7 @@ class MpcWatcher(ParentedLet):
|
||||||
def do_business(self):
|
def do_business(self):
|
||||||
while True:
|
while True:
|
||||||
status = self.client.idle()[0]
|
status = self.client.idle()[0]
|
||||||
logging.info(status)
|
self.log.info(status)
|
||||||
yield ('mpc', status)
|
yield ('mpc', status)
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ class Player(gevent.Greenlet):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
gevent.Greenlet.__init__(self)
|
gevent.Greenlet.__init__(self)
|
||||||
self.min_playlist_length = 10
|
self.min_playlist_length = 10
|
||||||
|
self.log = logging.getLogger(self.__class__.__name__)
|
||||||
self.q = Queue()
|
self.q = Queue()
|
||||||
|
|
||||||
def check_playlist(self):
|
def check_playlist(self):
|
||||||
|
@ -54,30 +56,28 @@ class Player(gevent.Greenlet):
|
||||||
songs = mpd_client.playlist()
|
songs = mpd_client.playlist()
|
||||||
if(len(songs) >= self.min_playlist_length):
|
if(len(songs) >= self.min_playlist_length):
|
||||||
return
|
return
|
||||||
logging.info('need to add new songs')
|
self.log.info('need to add new songs')
|
||||||
picker = gevent.Greenlet(generate, CONTINOUS_AUDIODESC)
|
picker = gevent.Greenlet(audiogenerate, CONTINOUS_AUDIODESC)
|
||||||
picker.link_value(lambda g: mpd_client.add(next(g.value).strip()))
|
picker.link_value(lambda g: mpd_client.add(g.value[0].strip()))
|
||||||
picker.start()
|
picker.start()
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
MpcWatcher(self.q, client=None).start()
|
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 = WSGIServer(('', 5000), rpc.create_app(self.q))
|
||||||
http_server.start()
|
http_server.start()
|
||||||
while True:
|
while True:
|
||||||
value = self.q.get()
|
value = self.q.get()
|
||||||
|
self.log.debug('<- %s' % str(value))
|
||||||
# emitter = value['emitter']
|
# emitter = value['emitter']
|
||||||
kind = value['kind']
|
kind = value['kind']
|
||||||
args = value['args']
|
args = value['args']
|
||||||
if kind == 'timer':
|
if kind == 'timer':
|
||||||
logging.info('CLOCK')
|
self.log.info('CLOCK')
|
||||||
if kind == 'timer' or (kind == 'mpc' and args[0] == 'playlist'):
|
if kind == 'timer' or (kind == 'mpc' and args[0] == 'playlist'):
|
||||||
gevent.Greenlet.spawn(self.check_playlist)
|
gevent.Greenlet.spawn(self.check_playlist)
|
||||||
elif value['tracker'] == 'mpd_generate':
|
|
||||||
logging.info("generated! %s" % tuple(args[0]))
|
|
||||||
else:
|
else:
|
||||||
logging.warning("Unknown message: %s" % str(value))
|
self.log.warning("Unknown message: %s" % str(value))
|
||||||
logging.info(str(value))
|
|
||||||
|
|
||||||
|
|
||||||
def on_player_crash(*args, **kwargs):
|
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',
|
'flask',
|
||||||
'python-mpd2'
|
'python-mpd2'
|
||||||
],
|
],
|
||||||
tests_require=['pytest'],
|
tests_require=['pytest', 'pytest-timeout'],
|
||||||
cmdclass={'test': PyTest},
|
cmdclass={'test': PyTest},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': ['larigira=larigira.mpc:main',
|
'console_scripts': ['larigira=larigira.mpc:main',
|
||||||
|
'larigira-timegen=larigira.timegen:main',
|
||||||
'larigira-audiogen=larigira.audiogen:main'],
|
'larigira-audiogen=larigira.audiogen:main'],
|
||||||
'larigira.audiogenerators': [
|
'larigira.audiogenerators': [
|
||||||
'mpd = larigira.audiogen_mpdrandom:generate_by_artist',
|
'mpd = larigira.audiogen_mpdrandom:generate_by_artist',
|
||||||
'static = larigira.audiogen_static:generate',
|
'static = larigira.audiogen_static:generate',
|
||||||
'randomdir = larigira.audiogen_randomdir:generate'
|
'randomdir = larigira.audiogen_randomdir:generate'
|
||||||
|
],
|
||||||
|
'larigira.timegenerators': [
|
||||||
|
'frequency = larigira.timegen_every:FrequencyAlarm',
|
||||||
|
'single = larigira.timegen_every:SingleAlarm',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue