Playback while silent
[watersofshiloah] / src / stream_index.py
1 import weakref
2 import logging
3
4 import util.misc as misc_utils
5 from util import go_utils
6 import backend
7
8
9 _moduleLogger = logging.getLogger(__name__)
10
11
12 SOURCE_RADIO = "radio"
13 SOURCE_CONFERENCES = "conferences"
14 SOURCE_MAGAZINES = "magazines"
15 SOURCE_SCRIPTURES = "scriptures"
16
17
18 class Connection(object):
19
20         def __init__(self):
21                 self._backend = backend.Backend()
22                 self._indexing = go_utils.AsyncPool()
23
24         def start(self):
25                 self._indexing.start()
26
27         def stop(self):
28                 self._indexing.stop()
29
30         def download(self, func, on_success, on_error, args = None, kwds = None):
31                 if args is None:
32                         args = ()
33                 if kwds is None:
34                         kwds = {}
35
36                 self._indexing.add_task(
37                         getattr(self._backend, func),
38                         args,
39                         kwds,
40                         on_success,
41                         on_error,
42                 )
43
44
45 class AudioIndex(object):
46
47         def __init__(self):
48                 self._connection = Connection()
49                 self._languages = None
50                 self._languagesRequest = None
51                 self._sources = {}
52
53         def start(self):
54                 self._connection.start()
55
56         def stop(self):
57                 self._connection.stop()
58
59         def get_languages(self, on_success, on_error):
60                 if self._languages is None:
61                         assert self._languagesRequest is None
62                         self._languagesRequest = on_success, on_error
63                         self._connection.download(
64                                 "get_languages",
65                                 self._on_get_languages,
66                                 self._on_languages_error
67                         )
68                 else:
69                         on_success(self._languages)
70
71         def get_source(self, source, langId = None):
72                 key = (source, langId)
73                 if key in self._sources:
74                         node = self._sources[key]
75                 else:
76                         if source == SOURCE_RADIO:
77                                 node = RadioNode(self._connection)
78                         elif source == SOURCE_CONFERENCES:
79                                 assert langId is not None
80                                 node = ConferencesNode(self._connection, langId)
81                         elif source == SOURCE_MAGAZINES:
82                                 assert langId is not None
83                                 node = MagazinesNode(self._connection, langId)
84                         elif source == SOURCE_SCRIPTURES:
85                                 assert langId is not None
86                                 node = ScripturesNode(self._connection, langId)
87                         else:
88                                 raise NotImplementedError(source)
89                         self._sources[key] = node
90
91                 return node
92
93         @misc_utils.log_exception(_moduleLogger)
94         def _on_get_languages(self, languages):
95                 assert self._languages is None
96                 assert self._languagesRequest is not None
97                 r = self._languagesRequest
98                 self._languagesRequest = None
99                 self._languages = languages
100                 r[0](self._languages)
101
102         @misc_utils.log_exception(_moduleLogger)
103         def _on_languages_error(self, e):
104                 assert self._languages is None
105                 assert self._languagesRequest is not None
106                 r = self._languagesRequest
107                 self._languagesRequest = None
108                 r[1](self._languages)
109
110
111 class Node(object):
112
113         def __init__(self, connection, parent, data, id):
114                 self._connection = connection
115                 self._parent = weakref.ref(parent) if parent is not None else None
116                 self._data = data
117                 self._children = None
118                 self._id = id
119
120         def get_children(self, on_success, on_error):
121                 if self._children is None:
122                         self._get_children(on_success, on_error)
123                 else:
124                         on_success(self._children)
125
126         def get_parent(self):
127                 if self._parent is None:
128                         raise RuntimeError("")
129                 parent = self._parent()
130                 return parent
131
132         def get_properties(self):
133                 return self._data
134
135         @property
136         def title(self):
137                 raise NotImplementedError("On %s" % type(self))
138
139         @property
140         def id(self):
141                 return self._id
142
143         def is_leaf(self):
144                 raise NotImplementedError("")
145
146         def _get_children(self, on_success, on_error):
147                 raise NotImplementedError("")
148
149
150 class ParentNode(Node):
151
152         def __init__(self, connection, parent, data, id):
153                 Node.__init__(self, connection, parent, data, id)
154
155         def is_leaf(self):
156                 return False
157
158         def _get_children(self, on_success, on_error):
159                 assert self._children is None
160
161                 func, args, kwds = self._get_func()
162
163                 self._connection.download(
164                         func,
165                         lambda data: self._on_success(data, on_success, on_error),
166                         on_error,
167                         args,
168                         kwds,
169                 )
170
171         def _get_func(self):
172                 raise NotImplementedError()
173
174         def _create_child(self, data, id):
175                 raise NotImplementedError()
176
177         @misc_utils.log_exception(_moduleLogger)
178         def _on_success(self, data, on_success, on_error):
179                 try:
180                         self._children = [
181                                 self._create_child(child, i)
182                                 for i, child in enumerate(data)
183                         ]
184                 except Exception, e:
185                         _moduleLogger.exception("Translating error")
186                         self._children = None
187                         on_error(e)
188                 else:
189                         on_success(self._children)
190
191
192 class LeafNode(Node):
193
194         def __init__(self, connection, parent, data, id):
195                 Node.__init__(self, connection, parent, data, id)
196
197         def is_leaf(self):
198                 return True
199
200         @property
201         def can_navigate(self):
202                 raise NotImplementedError("On %s" % type(self))
203
204         @property
205         def subtitle(self):
206                 raise NotImplementedError("On %s" % type(self))
207
208         @property
209         def uri(self):
210                 raise NotImplementedError("On %s" % type(self))
211
212         def _get_children(self, on_success, on_error):
213                 raise RuntimeError("Not is a leaf")
214
215
216 class RadioNode(ParentNode):
217
218         def __init__(self, connection):
219                 ParentNode.__init__(self, connection, None, {}, SOURCE_RADIO)
220
221         @property
222         def title(self):
223                 return "Radio"
224
225         def _get_func(self):
226                 return "get_radio_channels", (), {}
227
228         def _create_child(self, data, id):
229                 return RadioChannelNode(self._connection, self, data, id)
230
231
232 class RadioChannelNode(LeafNode):
233
234         def __init__(self, connection, parent, data, id):
235                 LeafNode.__init__(self, connection, parent, data, id)
236                 self._extendedData = {}
237                 self._request = None
238
239         @property
240         def can_navigate(self):
241                 return False
242
243         @property
244         def title(self):
245                 return "Radio"
246
247         @property
248         def subtitle(self):
249                 return ""
250
251         @property
252         def uri(self):
253                 return self._data["url"]
254
255         def get_programming(self, date, on_success, on_error):
256                 date = date.strftime("%Y-%m-%d")
257                 try:
258                         programming = self._extendedData[date]
259                 except KeyError:
260                         self._get_programming(date, on_success, on_error)
261                 else:
262                         on_success(programming)
263
264         def _get_programming(self, date, on_success, on_error):
265                 assert self._request is None
266                 assert date not in self._extendedData
267                 self._request = on_success, on_error, date
268
269                 self._connection.download(
270                         "get_radio_channel_programming",
271                         self._on_success,
272                         self._on_error,
273                         (self._data["id"], date),
274                         {},
275                 )
276
277         @misc_utils.log_exception(_moduleLogger)
278         def _on_success(self, data):
279                 r = self._request
280                 date = r[2]
281                 self._request = None
282                 try:
283                         self._extendedData[date] = [
284                                 child
285                                 for child in data
286                         ]
287                 except Exception, e:
288                         _moduleLogger.exception("Translating error")
289                         del self._extendedData[date]
290                         r[1](e)
291                 else:
292                         r[0](self._extendedData[date])
293
294         @misc_utils.log_exception(_moduleLogger)
295         def _on_error(self, error):
296                 r = self._request
297                 self._request = None
298                 r[1](error)
299
300
301 class ConferencesNode(ParentNode):
302
303         def __init__(self, connection, langId):
304                 ParentNode.__init__(self, connection, None, {}, SOURCE_CONFERENCES)
305                 self._langId = langId
306
307         @property
308         def title(self):
309                 return "Conferences"
310
311         def _get_func(self):
312                 return "get_conferences", (self._langId, ), {}
313
314         def _create_child(self, data, id):
315                 return ConferenceNode(self._connection, self, data, id)
316
317
318 class ConferenceNode(ParentNode):
319
320         def __init__(self, connection, parent, data, id):
321                 ParentNode.__init__(self, connection, parent, data, id)
322
323         @property
324         def title(self):
325                 return self._data["title"]
326
327         def _get_func(self):
328                 return "get_conference_sessions", (self._data["id"], ), {}
329
330         def _create_child(self, data, id):
331                 return SessionNode(self._connection, self, data, id)
332
333
334 class SessionNode(ParentNode):
335
336         def __init__(self, connection, parent, data, id):
337                 ParentNode.__init__(self, connection, parent, data, id)
338
339         @property
340         def title(self):
341                 return self._data["title"]
342
343         def _get_func(self):
344                 return "get_conference_talks", (self._data["id"], ), {}
345
346         def _create_child(self, data, id):
347                 return TalkNode(self._connection, self, data, id)
348
349
350 class TalkNode(LeafNode):
351
352         def __init__(self, connection, parent, data, id):
353                 LeafNode.__init__(self, connection, parent, data, id)
354
355         @property
356         def can_navigate(self):
357                 return True
358
359         @property
360         def title(self):
361                 return self._data["title"]
362
363         @property
364         def subtitle(self):
365                 speaker = self._data["speaker"]
366                 if speaker is not None:
367                         return speaker
368                 else:
369                         return ""
370
371         @property
372         def uri(self):
373                 return self._data["url"]
374
375
376 class MagazinesNode(ParentNode):
377
378         def __init__(self, connection, langId):
379                 ParentNode.__init__(self, connection, None, {}, SOURCE_MAGAZINES)
380                 self._langId = langId
381
382         @property
383         def title(self):
384                 return "Magazines"
385
386         def _get_func(self):
387                 return "get_magazines", (self._langId, ), {}
388
389         def _create_child(self, data, id):
390                 return MagazineNode(self._connection, self, data, id)
391
392
393 class MagazineNode(ParentNode):
394
395         def __init__(self, connection, parent, data, id):
396                 ParentNode.__init__(self, connection, parent, data, id)
397
398         @property
399         def title(self):
400                 return self._data["title"]
401
402         def _get_func(self):
403                 return "get_magazine_issues", (self._data["id"], ), {}
404
405         def _create_child(self, data, id):
406                 return IssueNode(self._connection, self, data, id)
407
408
409 class IssueNode(ParentNode):
410
411         def __init__(self, connection, parent, data, id):
412                 ParentNode.__init__(self, connection, parent, data, id)
413
414         @property
415         def title(self):
416                 return self._data["title"]
417
418         def _get_func(self):
419                 return "get_magazine_articles", (self._data["id"], ), {}
420
421         def _create_child(self, data, id):
422                 return ArticleNode(self._connection, self, data, id)
423
424
425 class ArticleNode(LeafNode):
426
427         def __init__(self, connection, parent, data, id):
428                 LeafNode.__init__(self, connection, parent, data, id)
429
430         @property
431         def can_navigate(self):
432                 return True
433
434         @property
435         def title(self):
436                 return self._data["title"]
437
438         @property
439         def subtitle(self):
440                 speaker = self._data["author"]
441                 if speaker is not None:
442                         return speaker
443                 else:
444                         return ""
445
446         @property
447         def uri(self):
448                 return self._data["url"]
449
450
451 class ScripturesNode(ParentNode):
452
453         def __init__(self, connection, langId):
454                 ParentNode.__init__(self, connection, None, {}, SOURCE_SCRIPTURES)
455                 self._langId = langId
456
457         @property
458         def title(self):
459                 return "Scriptures"
460
461         def _get_func(self):
462                 return "get_scriptures", (self._langId, ), {}
463
464         def _create_child(self, data, id):
465                 return ScriptureNode(self._connection, self, data, id)
466
467
468 class ScriptureNode(ParentNode):
469
470         def __init__(self, connection, parent, data, id):
471                 ParentNode.__init__(self, connection, parent, data, id)
472
473         @property
474         def title(self):
475                 return self._data["title"]
476
477         def _get_func(self):
478                 return "get_scripture_books", (self._data["id"], ), {}
479
480         def _create_child(self, data, id):
481                 return BookNode(self._connection, self, data, id)
482
483
484 class BookNode(ParentNode):
485
486         def __init__(self, connection, parent, data, id):
487                 ParentNode.__init__(self, connection, parent, data, id)
488
489         @property
490         def title(self):
491                 return self._data["title"]
492
493         def _get_func(self):
494                 return "get_scripture_chapters", (self._data["id"], ), {}
495
496         def _create_child(self, data, id):
497                 return ChapterNode(self._connection, self, data, id)
498
499
500 class ChapterNode(LeafNode):
501
502         def __init__(self, connection, parent, data, id):
503                 LeafNode.__init__(self, connection, parent, data, id)
504
505         @property
506         def can_navigate(self):
507                 return True
508
509         @property
510         def title(self):
511                 return self._data["title"]
512
513         @property
514         def subtitle(self):
515                 return ""
516
517         @property
518         def uri(self):
519                 return self._data["url"]
520
521
522 def walk_ancestors(node):
523         while True:
524                 yield node
525                 try:
526                         node = node.get_parent()
527                 except RuntimeError:
528                         return
529
530
531 def common_paths(targetNode, currentNode):
532         targetNodePath = list(walk_ancestors(targetNode))
533         targetNodePath.reverse()
534         currentNodePath = list(walk_ancestors(currentNode))
535         currentNodePath.reverse()
536
537         ancestors = []
538         descendants = []
539
540         for i, (t, c) in enumerate(zip(targetNodePath, currentNodePath)):
541                 if t is not c:
542                         return ancestors, None, descendants
543                 ancestors.append(t)
544
545         descendants.extend(
546                 child
547                 for child in targetNodePath[i+1:]
548         )
549
550         return ancestors, currentNode, descendants
551
552
553 class AsyncWalker(object):
554
555         def __init__(self, func):
556                 self._func = func
557                 self._run = None
558
559         def start(self, *args, **kwds):
560                 assert self._run is None
561                 self._run = self._func(*args, **kwds)
562                 node = self._run.send(None) # priming the function
563                 node.get_children(self.on_success, self.on_error)
564
565         @misc_utils.log_exception(_moduleLogger)
566         def on_success(self, children):
567                 _moduleLogger.debug("Processing success for: %r", self._func)
568                 try:
569                         node = self._run.send(children)
570                 except StopIteration, e:
571                         pass
572                 else:
573                         node.get_children(self.on_success, self.on_error)
574
575         @misc_utils.log_exception(_moduleLogger)
576         def on_error(self, error):
577                 _moduleLogger.debug("Processing error for: %r", self._func)
578                 try:
579                         node = self._run.throw(error)
580                 except StopIteration, e:
581                         pass
582                 else:
583                         node.get_children(self.on_success, self.on_error)
584
585
586 def get_next(node, on_success, on_error):
587         try:
588                 assert node.is_leaf(), node
589
590                 # Find next branch
591                 childNode = node
592                 while True:
593                         parent = childNode.get_parent()
594                         siblings = yield parent
595                         for i, sibling in enumerate(siblings):
596                                 if sibling is childNode:
597                                         break
598                         i += 1
599                         if i < len(siblings):
600                                 sibling = siblings[i]
601                                 break
602                         else:
603                                 childNode = parent
604
605                 # dig into that branch to find the first leaf
606                 nodes = [sibling]
607                 while nodes:
608                         child = nodes.pop(0)
609                         if child.is_leaf():
610                                 on_success(child)
611                                 return
612                         children = yield child
613                         nodes[0:0] = children
614                 raise RuntimeError("Ran out of nodes when hunting for first leaf of %s" % node)
615         except Exception, e:
616                 on_error(e)
617
618
619 def get_previous(node, on_success, on_error):
620         try:
621                 assert node.is_leaf(), node
622
623                 # Find next branch
624                 childNode = node
625                 while True:
626                         parent = childNode.get_parent()
627                         siblings = yield parent
628                         for i, sibling in enumerate(siblings):
629                                 if sibling is childNode:
630                                         break
631                         i -= 1
632                         if 0 <= i:
633                                 sibling = siblings[i]
634                                 break
635                         else:
636                                 childNode = parent
637
638                 # dig into that branch to find the first leaf
639                 nodes = [sibling]
640                 while nodes:
641                         child = nodes.pop(-1)
642                         if child.is_leaf():
643                                 on_success(child)
644                                 return
645                         children = yield child
646                         nodes[0:0] = children
647                 raise RuntimeError("Ran out of nodes when hunting for first leaf of %s" % node)
648         except Exception, e:
649                 on_error(e)