From dbe437c3c6cb6c385ad58e4e646e4f140e9c464a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 8 May 2010 18:19:13 -0500 Subject: [PATCH] Generalizing the way we walk the audiobooks --- src/backend.py | 1 - src/index.py | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/windows.py | 123 ++++++++++++------------- 3 files changed, 327 insertions(+), 71 deletions(-) diff --git a/src/backend.py b/src/backend.py index b4d207f..00efca6 100755 --- a/src/backend.py +++ b/src/backend.py @@ -31,7 +31,6 @@ class Backend(object): def get_radio_channel_programming(self, chanId, date=None): if date is not None: - date = date.strftime("%Y-%m-%d") tree = self._get_page_with_validation( action="lds.radio.radiochannels.programming.query", channelID=chanId, diff --git a/src/index.py b/src/index.py index 318956f..7e9341c 100644 --- a/src/index.py +++ b/src/index.py @@ -1,5 +1,7 @@ +import weakref import logging +import util.misc as misc_utils from util import go_utils import backend @@ -7,7 +9,7 @@ import backend _moduleLogger = logging.getLogger(__name__) -class AudioIndex(object): +class Connection(object): def __init__(self): self._backend = backend.Backend() @@ -19,7 +21,12 @@ class AudioIndex(object): def stop(self): self._indexing.stop() - def download(self, func, on_success, on_error, *args, **kwds): + def download(self, func, on_success, on_error, args = None, kwds = None): + if args is None: + args = () + if kwds is None: + kwds = {} + self._indexing.clear_tasks() self._indexing.add_task( getattr(self._backend, func), @@ -28,3 +35,266 @@ class AudioIndex(object): on_success, on_error, ) + + +class AudioIndex(object): + + def __init__(self): + self._connection = Connection() + self._languages = None + self._languagesRequest = None + self._sources = {} + + def start(self): + self._connection.start() + + def stop(self): + self._connection.stop() + + def get_languages(self, on_success, on_error): + if self._languages is None: + assert self._languagesRequest is None + self._languagesRequest = on_success, on_error + self._connection.download( + "get_languages", + self._on_get_languages, + self._on_languages_error + ) + else: + on_success(self._languages) + + def get_source(self, source, langId = None): + key = (source, langId) + if key in self._sources: + node = self._sources[key] + else: + if source == "radio": + node = RadioNode(self._connection) + elif source == "conferences": + assert langId is not None + node = ConferencesNode(self._connection, langId) + else: + raise NotImplementedError(source) + self._sources[key] = node + + return node + + @misc_utils.log_exception(_moduleLogger) + def _on_get_languages(self, languages): + assert self._languages is None + assert self._languagesRequest is not None + r = self._languagesRequest + self._languagesRequest = None + self._languages = languages + r[0](self._languages) + + @misc_utils.log_exception(_moduleLogger) + def _on_languages_error(self, e): + assert self._languages is None + assert self._languagesRequest is not None + r = self._languagesRequest + self._languagesRequest = None + r[1](self._languages) + + +class Node(object): + + def __init__(self, connection, parent, data): + self._connection = connection + self._parent = weakref.ref(parent) if parent is not None else None + self._data = data + self._children = None + + def get_children(self, on_success, on_error): + if self._children is None: + self._get_children(on_success, on_error) + else: + on_success(self._children) + + def get_parent(self): + if self._parent is None: + raise RuntimeError("") + parent = self._parent() + return parent + + def get_properties(self): + return self._data + + def is_leaf(self): + raise NotImplementedError("") + + def _get_children(self, on_success, on_error): + raise NotImplementedError("") + + +class ParentNode(Node): + + def __init__(self, connection, parent, data): + Node.__init__(self, connection, parent, data) + self._request = None + + def is_leaf(self): + return False + + def _get_children(self, on_success, on_error): + assert self._request is None + assert self._children is None + self._request = on_success, on_error + + func, args, kwds = self._get_func() + + self._connection.download( + func, + self._on_success, + self._on_error, + args, + kwds, + ) + + def _get_func(self): + raise NotImplementedError() + + def _create_child(self, data): + raise NotImplementedError() + + @misc_utils.log_exception(_moduleLogger) + def _on_success(self, data): + r = self._request + self._request = None + try: + self._children = [ + self._create_child(child) + for child in data + ] + except Exception, e: + _moduleLogger.exception("Translating error") + self._children = None + r[1](e) + else: + r[0](self._children) + + @misc_utils.log_exception(_moduleLogger) + def _on_error(self, error): + r = self._request + self._request = None + r[1](error) + + +class LeafNode(Node): + + def __init__(self, connection, parent, data): + Node.__init__(self, connection, parent, data) + + def is_leaf(self): + return True + + def _get_children(self, on_success, on_error): + raise RuntimeError("Not is a leaf") + + +class RadioNode(ParentNode): + + def __init__(self, connection): + ParentNode.__init__(self, connection, None, {}) + + def _get_func(self): + return "get_radio_channels", (), {} + + def _create_child(self, data): + return RadioChannelNode(self._connection, self, data) + + +class RadioChannelNode(LeafNode): + + def __init__(self, connection, parent, data): + LeafNode.__init__(self, connection, parent, data) + self._extendedData = {} + self._request = None + + def get_programming(self, date, on_success, on_error): + date = date.strftime("%Y-%m-%d") + try: + programming = self._extendedData[date] + except KeyError: + self._get_programming(date, on_success, on_error) + else: + on_success(programming) + + def _get_programming(self, date, on_success, on_error): + assert self._request is None + assert date not in self._extendedData + self._request = on_success, on_error, date + + self._connection.download( + "get_radio_channel_programming", + self._on_success, + self._on_error, + (self._data["id"], date), + {}, + ) + + @misc_utils.log_exception(_moduleLogger) + def _on_success(self, data): + r = self._request + date = r[2] + self._request = None + try: + self._extendedData[date] = [ + child + for child in data + ] + except Exception, e: + _moduleLogger.exception("Translating error") + del self._extendedData[date] + r[1](e) + else: + r[0](self._extendedData[date]) + + @misc_utils.log_exception(_moduleLogger) + def _on_error(self, error): + r = self._request + self._request = None + r[1](error) + + +class ConferencesNode(ParentNode): + + def __init__(self, connection, langId): + ParentNode.__init__(self, connection, None, {}) + self._langId = langId + + def _get_func(self): + return "get_conferences", (self._langId, ), {} + + def _create_child(self, data): + return ConferenceNode(self._connection, self, data) + + +class ConferenceNode(ParentNode): + + def __init__(self, connection, parent, data): + ParentNode.__init__(self, connection, parent, data) + + def _get_func(self): + return "get_conference_sessions", (self._data["id"], ), {} + + def _create_child(self, data): + return SessionNode(self._connection, self, data) + + +class SessionNode(ParentNode): + + def __init__(self, connection, parent, data): + ParentNode.__init__(self, connection, parent, data) + + def _get_func(self): + return "get_conference_talks", (self._data["id"], ), {} + + def _create_child(self, data): + return TalkNode(self._connection, self, data) + + +class TalkNode(LeafNode): + + def __init__(self, connection, parent, data): + LeafNode.__init__(self, connection, parent, data) diff --git a/src/windows.py b/src/windows.py index dad198f..8015b87 100644 --- a/src/windows.py +++ b/src/windows.py @@ -44,13 +44,12 @@ class BasicWindow(gobject.GObject): ), } - def __init__(self, player, store, index): + def __init__(self, player, store): gobject.GObject.__init__(self) self._isDestroyed = False self._player = player self._store = store - self._index = index self._clipboard = gtk.clipboard_get() self._windowInFullscreen = False @@ -153,19 +152,19 @@ class BasicWindow(gobject.GObject): class SourceSelector(BasicWindow): def __init__(self, player, store, index): + BasicWindow.__init__(self, player, store) self._languages = [] - - BasicWindow.__init__(self, player, store, index) + self._index = index self._loadingBanner = banners.GenericBanner() self._radioButton = self._create_button("radio", "Radio") - self._radioButton.connect("clicked", self._on_source_selected, RadioWindow) + self._radioButton.connect("clicked", self._on_source_selected, RadioWindow, "radio") self._radioWrapper = gtk.VBox() self._radioWrapper.pack_start(self._radioButton, False, True) self._conferenceButton = self._create_button("conferences", "Conferences") - self._conferenceButton.connect("clicked", self._on_source_selected, ConferencesWindow) + self._conferenceButton.connect("clicked", self._on_source_selected, ConferencesWindow, "conferences") self._conferenceWrapper = gtk.VBox() self._conferenceWrapper.pack_start(self._conferenceButton, False, True) @@ -214,11 +213,7 @@ class SourceSelector(BasicWindow): def _refresh(self): self._show_loading() - self._index.download( - "get_languages", - self._on_languages, - self._on_error, - ) + self._index.get_languages(self._on_languages, self._on_error) def _create_button(self, icon, message): image = self._store.get_image_from_store(self._store.STORE_LOOKUP[icon]) @@ -242,11 +237,12 @@ class SourceSelector(BasicWindow): @misc_utils.log_exception(_moduleLogger) def _on_error(self, exception): self._hide_loading() - self._errorBanner.push_message(exception) + self._errorBanner.push_message(str(exception)) @misc_utils.log_exception(_moduleLogger) - def _on_source_selected(self, widget, Source): - sourceWindow = Source(self._player, self._store, self._index, self._languages[0]["id"]) + def _on_source_selected(self, widget, Source, nodeName): + node = self._index.get_source(nodeName, self._languages[0]["id"]) + sourceWindow = Source(self._player, self._store, node) sourceWindow.window.set_modal(True) sourceWindow.window.set_transient_for(self._window) sourceWindow.window.set_default_size(*self._window.get_size()) @@ -259,8 +255,10 @@ gobject.type_register(SourceSelector) class RadioWindow(BasicWindow): - def __init__(self, player, store, index, languageId): - BasicWindow.__init__(self, player, store, index) + def __init__(self, player, store, node): + BasicWindow.__init__(self, player, store) + self._node = node + self._childNode = None self._player.connect("state-change", self._on_player_state_change) self._player.connect("title-change", self._on_player_title_change) @@ -340,8 +338,7 @@ class RadioWindow(BasicWindow): def _refresh(self): self._show_loading() self._programmingModel.clear() - self._index.download( - "get_radio_channels", + self._node.get_children( self._on_channels, self._on_load_error, ) @@ -423,16 +420,14 @@ class RadioWindow(BasicWindow): _moduleLogger.info("Download complete but window destroyed") return - channels = list(channels) + channels = channels if 1 < len(channels): _moduleLogger.warning("More channels now available!") - channel = channels[0] - self._index.download( - "get_radio_channel_programming", + self._childNode = channels[0] + self._childNode.get_programming( + self._dateShown, self._on_channel, self._on_load_error, - channel["id"], - self._dateShown, ) @misc_utils.log_exception(_moduleLogger) @@ -453,7 +448,7 @@ class RadioWindow(BasicWindow): @misc_utils.log_exception(_moduleLogger) def _on_load_error(self, exception): self._hide_loading() - self._errorBanner.push_message(exception) + self._errorBanner.push_message(str(exception)) @misc_utils.log_exception(_moduleLogger) def _on_row_changed(self, selection): @@ -472,8 +467,9 @@ gobject.type_register(RadioWindow) class ListWindow(BasicWindow): - def __init__(self, player, store, index): - BasicWindow.__init__(self, player, store, index) + def __init__(self, player, store, node): + BasicWindow.__init__(self, player, store) + self._node = node self._loadingBanner = banners.GenericBanner() @@ -542,15 +538,13 @@ class ListWindow(BasicWindow): class ConferencesWindow(ListWindow): - def __init__(self, player, store, index, languageId): - self._languageId = languageId - - ListWindow.__init__(self, player, store, index) + def __init__(self, player, store, node): + ListWindow.__init__(self, player, store, node) self._window.set_title("Conferences") @classmethod def _get_columns(cls): - yield gobject.TYPE_STRING, None + yield gobject.TYPE_PYOBJECT, None textrenderer = gtk.CellRendererText() column = gtk.TreeViewColumn("Date") @@ -570,11 +564,9 @@ class ConferencesWindow(ListWindow): def _refresh(self): ListWindow._refresh(self) - self._index.download( - "get_conferences", + self._node.get_children( self._on_conferences, self._on_error, - self._languageId, ) @misc_utils.log_exception(_moduleLogger) @@ -584,8 +576,9 @@ class ConferencesWindow(ListWindow): return self._hide_loading() - for program in programs: - row = program["id"], program["title"], program["full_title"] + for programNode in programs: + program = programNode.get_properties() + row = programNode, program["title"], program["full_title"] self._model.append(row) path = (self._get_current_row(), ) @@ -595,14 +588,14 @@ class ConferencesWindow(ListWindow): @misc_utils.log_exception(_moduleLogger) def _on_error(self, exception): self._hide_loading() - self._errorBanner.push_message(exception) + self._errorBanner.push_message(str(exception)) @misc_utils.log_exception(_moduleLogger) def _on_row_activated(self, view, path, column): itr = self._model.get_iter(path) - conferenceId = self._model.get_value(itr, 0) + node = self._model.get_value(itr, 0) - sessionsWindow = ConferenceSessionsWindow(self._player, self._store, self._index, conferenceId) + sessionsWindow = ConferenceSessionsWindow(self._player, self._store, node) sessionsWindow.window.set_modal(True) sessionsWindow.window.set_transient_for(self._window) sessionsWindow.window.set_default_size(*self._window.get_size()) @@ -616,15 +609,13 @@ gobject.type_register(ConferencesWindow) class ConferenceSessionsWindow(ListWindow): - def __init__(self, player, store, index, conferenceId): - self._conferenceId = conferenceId - - ListWindow.__init__(self, player, store, index) + def __init__(self, player, store, node): + ListWindow.__init__(self, player, store, node) self._window.set_title("Sessions") @classmethod def _get_columns(cls): - yield gobject.TYPE_STRING, None + yield gobject.TYPE_PYOBJECT, None textrenderer = gtk.CellRendererText() column = gtk.TreeViewColumn("Session") @@ -638,11 +629,9 @@ class ConferenceSessionsWindow(ListWindow): def _refresh(self): ListWindow._refresh(self) - self._index.download( - "get_conference_sessions", + self._node.get_children( self._on_conference_sessions, self._on_error, - self._conferenceId, ) @misc_utils.log_exception(_moduleLogger) @@ -652,8 +641,9 @@ class ConferenceSessionsWindow(ListWindow): return self._hide_loading() - for program in programs: - row = program["id"], program["title"] + for programNode in programs: + program = programNode.get_properties() + row = programNode, program["title"] self._model.append(row) path = (self._get_current_row(), ) @@ -663,14 +653,14 @@ class ConferenceSessionsWindow(ListWindow): @misc_utils.log_exception(_moduleLogger) def _on_error(self, exception): self._hide_loading() - self._errorBanner.push_message(exception) + self._errorBanner.push_message(str(exception)) @misc_utils.log_exception(_moduleLogger) def _on_row_activated(self, view, path, column): itr = self._model.get_iter(path) - sessionId = self._model.get_value(itr, 0) + node = self._model.get_value(itr, 0) - sessionsWindow = ConferenceTalksWindow(self._player, self._store, self._index, sessionId) + sessionsWindow = ConferenceTalksWindow(self._player, self._store, node) sessionsWindow.window.set_modal(True) sessionsWindow.window.set_transient_for(self._window) sessionsWindow.window.set_default_size(*self._window.get_size()) @@ -684,15 +674,13 @@ gobject.type_register(ConferenceSessionsWindow) class ConferenceTalksWindow(ListWindow): - def __init__(self, player, store, index, sessionId): - self._sessionId = sessionId - - ListWindow.__init__(self, player, store, index) + def __init__(self, player, store, node): + ListWindow.__init__(self, player, store, node) self._window.set_title("Talks") @classmethod def _get_columns(cls): - yield gobject.TYPE_STRING, None + yield gobject.TYPE_PYOBJECT, None textrenderer = gtk.CellRendererText() column = gtk.TreeViewColumn("Talk") @@ -706,11 +694,9 @@ class ConferenceTalksWindow(ListWindow): def _refresh(self): ListWindow._refresh(self) - self._index.download( - "get_conference_talks", + self._node.get_children( self._on_conference_talks, self._on_error, - self._sessionId, ) @misc_utils.log_exception(_moduleLogger) @@ -720,8 +706,9 @@ class ConferenceTalksWindow(ListWindow): return self._hide_loading() - for program in programs: - row = program, "%s\n%s" % (program["title"], program["speaker"]) + for programNode in programs: + program = programNode.get_properties() + row = programNode, "%s\n%s" % (program["title"], program["speaker"]) self._model.append(row) path = (self._get_current_row(), ) @@ -731,14 +718,14 @@ class ConferenceTalksWindow(ListWindow): @misc_utils.log_exception(_moduleLogger) def _on_error(self, exception): self._hide_loading() - self._errorBanner.push_message(exception) + self._errorBanner.push_message(str(exception)) @misc_utils.log_exception(_moduleLogger) def _on_row_activated(self, view, path, column): itr = self._model.get_iter(path) - program = self._model.get_value(itr, 0) + node = self._model.get_value(itr, 0) - sessionsWindow = ConferenceTalkWindow(self._player, self._store, self._index, program) + sessionsWindow = ConferenceTalkWindow(self._player, self._store, node) sessionsWindow.window.set_modal(True) sessionsWindow.window.set_transient_for(self._window) sessionsWindow.window.set_default_size(*self._window.get_size()) @@ -752,8 +739,8 @@ gobject.type_register(ConferenceTalksWindow) class ConferenceTalkWindow(BasicWindow): - def __init__(self, player, store, index, talkData): - BasicWindow.__init__(self, player, store, index) + def __init__(self, player, store, node): + BasicWindow.__init__(self, player, store) self._player.connect("state-change", self._on_player_state_change) self._player.connect("title-change", self._on_player_title_change) -- 1.7.9.5