Breaking out the various windows into separate files
[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                         else:
83                                 raise NotImplementedError(source)
84                         self._sources[key] = node
85
86                 return node
87
88         @misc_utils.log_exception(_moduleLogger)
89         def _on_get_languages(self, languages):
90                 assert self._languages is None
91                 assert self._languagesRequest is not None
92                 r = self._languagesRequest
93                 self._languagesRequest = None
94                 self._languages = languages
95                 r[0](self._languages)
96
97         @misc_utils.log_exception(_moduleLogger)
98         def _on_languages_error(self, e):
99                 assert self._languages is None
100                 assert self._languagesRequest is not None
101                 r = self._languagesRequest
102                 self._languagesRequest = None
103                 r[1](self._languages)
104
105
106 class Node(object):
107
108         def __init__(self, connection, parent, data, id):
109                 self._connection = connection
110                 self._parent = weakref.ref(parent) if parent is not None else None
111                 self._data = data
112                 self._children = None
113                 self._id = id
114
115         def get_children(self, on_success, on_error):
116                 if self._children is None:
117                         self._get_children(on_success, on_error)
118                 else:
119                         on_success(self._children)
120
121         def get_parent(self):
122                 if self._parent is None:
123                         raise RuntimeError("")
124                 parent = self._parent()
125                 return parent
126
127         def get_properties(self):
128                 return self._data
129
130         @property
131         def title(self):
132                 raise NotImplementedError("On %s" % type(self))
133
134         @property
135         def id(self):
136                 return self._id
137
138         def is_leaf(self):
139                 raise NotImplementedError("")
140
141         def _get_children(self, on_success, on_error):
142                 raise NotImplementedError("")
143
144
145 class ParentNode(Node):
146
147         def __init__(self, connection, parent, data, id):
148                 Node.__init__(self, connection, parent, data, id)
149                 self._request = None
150
151         def is_leaf(self):
152                 return False
153
154         def _get_children(self, on_success, on_error):
155                 assert self._request is None
156                 assert self._children is None
157                 self._request = on_success, on_error
158
159                 func, args, kwds = self._get_func()
160
161                 self._connection.download(
162                         func,
163                         self._on_success,
164                         self._on_error,
165                         args,
166                         kwds,
167                 )
168
169         def _get_func(self):
170                 raise NotImplementedError()
171
172         def _create_child(self, data, id):
173                 raise NotImplementedError()
174
175         @misc_utils.log_exception(_moduleLogger)
176         def _on_success(self, data):
177                 r = self._request
178                 self._request = None
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                         r[1](e)
188                 else:
189                         r[0](self._children)
190
191         @misc_utils.log_exception(_moduleLogger)
192         def _on_error(self, error):
193                 r = self._request
194                 self._request = None
195                 r[1](error)
196
197
198 class LeafNode(Node):
199
200         def __init__(self, connection, parent, data, id):
201                 Node.__init__(self, connection, parent, data, id)
202
203         def is_leaf(self):
204                 return True
205
206         @property
207         def can_navigate(self):
208                 raise NotImplementedError("On %s" % type(self))
209
210         @property
211         def subtitle(self):
212                 raise NotImplementedError("On %s" % type(self))
213
214         @property
215         def uri(self):
216                 raise NotImplementedError("On %s" % type(self))
217
218         def _get_children(self, on_success, on_error):
219                 raise RuntimeError("Not is a leaf")
220
221
222 class RadioNode(ParentNode):
223
224         def __init__(self, connection):
225                 ParentNode.__init__(self, connection, None, {}, SOURCE_RADIO)
226
227         @property
228         def title(self):
229                 return "Radio"
230
231         def _get_func(self):
232                 return "get_radio_channels", (), {}
233
234         def _create_child(self, data, id):
235                 return RadioChannelNode(self._connection, self, data, id)
236
237
238 class RadioChannelNode(LeafNode):
239
240         def __init__(self, connection, parent, data, id):
241                 LeafNode.__init__(self, connection, parent, data, id)
242                 self._extendedData = {}
243                 self._request = None
244
245         @property
246         def can_navigate(self):
247                 return False
248
249         @property
250         def title(self):
251                 return "Radio"
252
253         @property
254         def subtitle(self):
255                 return ""
256
257         @property
258         def uri(self):
259                 return self._data["url"]
260
261         def get_programming(self, date, on_success, on_error):
262                 date = date.strftime("%Y-%m-%d")
263                 try:
264                         programming = self._extendedData[date]
265                 except KeyError:
266                         self._get_programming(date, on_success, on_error)
267                 else:
268                         on_success(programming)
269
270         def _get_programming(self, date, on_success, on_error):
271                 assert self._request is None
272                 assert date not in self._extendedData
273                 self._request = on_success, on_error, date
274
275                 self._connection.download(
276                         "get_radio_channel_programming",
277                         self._on_success,
278                         self._on_error,
279                         (self._data["id"], date),
280                         {},
281                 )
282
283         @misc_utils.log_exception(_moduleLogger)
284         def _on_success(self, data):
285                 r = self._request
286                 date = r[2]
287                 self._request = None
288                 try:
289                         self._extendedData[date] = [
290                                 child
291                                 for child in data
292                         ]
293                 except Exception, e:
294                         _moduleLogger.exception("Translating error")
295                         del self._extendedData[date]
296                         r[1](e)
297                 else:
298                         r[0](self._extendedData[date])
299
300         @misc_utils.log_exception(_moduleLogger)
301         def _on_error(self, error):
302                 r = self._request
303                 self._request = None
304                 r[1](error)
305
306
307 class ConferencesNode(ParentNode):
308
309         def __init__(self, connection, langId):
310                 ParentNode.__init__(self, connection, None, {}, SOURCE_CONFERENCES)
311                 self._langId = langId
312
313         @property
314         def title(self):
315                 return "Conferences"
316
317         def _get_func(self):
318                 return "get_conferences", (self._langId, ), {}
319
320         def _create_child(self, data, id):
321                 return ConferenceNode(self._connection, self, data, id)
322
323
324 class ConferenceNode(ParentNode):
325
326         def __init__(self, connection, parent, data, id):
327                 ParentNode.__init__(self, connection, parent, data, id)
328
329         @property
330         def title(self):
331                 return self._data["title"]
332
333         def _get_func(self):
334                 return "get_conference_sessions", (self._data["id"], ), {}
335
336         def _create_child(self, data, id):
337                 return SessionNode(self._connection, self, data, id)
338
339
340 class SessionNode(ParentNode):
341
342         def __init__(self, connection, parent, data, id):
343                 ParentNode.__init__(self, connection, parent, data, id)
344
345         @property
346         def title(self):
347                 return self._data["title"]
348
349         def _get_func(self):
350                 return "get_conference_talks", (self._data["id"], ), {}
351
352         def _create_child(self, data, id):
353                 return TalkNode(self._connection, self, data, id)
354
355
356 class TalkNode(LeafNode):
357
358         def __init__(self, connection, parent, data, id):
359                 LeafNode.__init__(self, connection, parent, data, id)
360
361         @property
362         def can_navigate(self):
363                 return True
364
365         @property
366         def title(self):
367                 return self._data["title"]
368
369         @property
370         def subtitle(self):
371                 speaker = self._data["speaker"]
372                 if speaker is not None:
373                         return speaker
374                 else:
375                         return ""
376
377         @property
378         def uri(self):
379                 return self._data["url"]
380
381
382 def walk_ancestors(node):
383         while True:
384                 yield node
385                 try:
386                         node = node.get_parent()
387                 except RuntimeError:
388                         return
389
390
391 def common_paths(targetNode, currentNode):
392         targetNodePath = list(walk_ancestors(targetNode))
393         targetNodePath.reverse()
394         currentNodePath = list(walk_ancestors(currentNode))
395         currentNodePath.reverse()
396
397         ancestors = []
398         descendants = []
399
400         for i, (t, c) in enumerate(zip(targetNodePath, currentNodePath)):
401                 if t is not c:
402                         return ancestors, None, descendants
403                 ancestors.append(t)
404
405         descendants.extend(
406                 child
407                 for child in targetNodePath[i+1:]
408         )
409
410         return ancestors, currentNode, descendants
411
412
413 class AsyncWalker(object):
414
415         def __init__(self, func):
416                 self._func = func
417                 self._run = None
418
419         def start(self, *args, **kwds):
420                 assert self._run is None
421                 self._run = self._func(*args, **kwds)
422                 node = self._run.send(None) # priming the function
423                 node.get_children(self.on_success, self.on_error)
424
425         @misc_utils.log_exception(_moduleLogger)
426         def on_success(self, children):
427                 _moduleLogger.debug("Processing success for: %r", self._func)
428                 try:
429                         node = self._run.send(children)
430                 except StopIteration, e:
431                         pass
432                 else:
433                         node.get_children(self.on_success, self.on_error)
434
435         @misc_utils.log_exception(_moduleLogger)
436         def on_error(self, error):
437                 _moduleLogger.debug("Processing error for: %r", self._func)
438                 try:
439                         node = self._run.throw(error)
440                 except StopIteration, e:
441                         pass
442                 else:
443                         node.get_children(self.on_success, self.on_error)
444
445
446 def get_next(node, on_success, on_error):
447         try:
448                 assert node.is_leaf(), node
449
450                 # Find next branch
451                 childNode = node
452                 while True:
453                         parent = childNode.get_parent()
454                         siblings = yield parent
455                         for i, sibling in enumerate(siblings):
456                                 if sibling is childNode:
457                                         break
458                         i += 1
459                         if i < len(siblings):
460                                 sibling = siblings[i]
461                                 break
462                         else:
463                                 childNode = parent
464
465                 # dig into that branch to find the first leaf
466                 nodes = [sibling]
467                 while nodes:
468                         child = nodes.pop(0)
469                         if child.is_leaf():
470                                 on_success(child)
471                                 return
472                         children = yield child
473                         nodes[0:0] = children
474                 raise RuntimeError("Ran out of nodes when hunting for first leaf of %s" % node)
475         except Exception, e:
476                 on_error(e)
477
478
479 def get_previous(node, on_success, on_error):
480         try:
481                 assert node.is_leaf(), node
482
483                 # Find next branch
484                 childNode = node
485                 while True:
486                         parent = childNode.get_parent()
487                         siblings = yield parent
488                         for i, sibling in enumerate(siblings):
489                                 if sibling is childNode:
490                                         break
491                         i -= 1
492                         if 0 <= i:
493                                 sibling = siblings[i]
494                                 break
495                         else:
496                                 childNode = parent
497
498                 # dig into that branch to find the first leaf
499                 nodes = [sibling]
500                 while nodes:
501                         child = nodes.pop(-1)
502                         if child.is_leaf():
503                                 on_success(child)
504                                 return
505                         children = yield child
506                         nodes[0:0] = children
507                 raise RuntimeError("Ran out of nodes when hunting for first leaf of %s" % node)
508         except Exception, e:
509                 on_error(e)