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