diff options
Diffstat (limited to 'GetIABooksActivity.py')
-rw-r--r-- | GetIABooksActivity.py | 1256 |
1 files changed, 1256 insertions, 0 deletions
diff --git a/GetIABooksActivity.py b/GetIABooksActivity.py new file mode 100644 index 0000000..a1488d0 --- /dev/null +++ b/GetIABooksActivity.py @@ -0,0 +1,1256 @@ +#! /usr/bin/env python + +# Copyright (C) 2009 James D. Simmons +# Copyright (C) 2009 Sayamindu Dasgupta <sayamindu@laptop.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import os +import logging +import time + +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GdkPixbuf +from gi.repository import GObject +from gi.repository import Pango + +from sugar3.graphics.toolbarbox import ToolbarBox +from sugar3.activity.widgets import StopButton +from sugar3.graphics import style +from sugar3.graphics.toolbutton import ToolButton +from sugar3.graphics.toggletoolbutton import ToggleToolButton +from sugar3.graphics.toolcombobox import ToolComboBox +from sugar3.graphics.combobox import ComboBox +from sugar3.graphics import iconentry +from sugar3 import profile +from sugar3.activity import activity +from sugar3.activity.widgets import ToolbarButton +from sugar3.bundle.activitybundle import ActivityBundle +from sugar3.datastore import datastore +from sugar3.graphics.alert import NotifyAlert +from sugar3.graphics.alert import Alert +from sugar3.graphics.icon import Icon +from gettext import gettext as _ + +import dbus +import ConfigParser +import base64 +import urllib2 +import socket + + +from listview import ListView +import opds +import languagenames +import devicemanager + +_MIMETYPES = {'PDF': u'application/pdf', 'PDF BW': u'application/pdf-bw', + 'EPUB': u'application/epub+zip', 'DJVU': u'image/x.djvu', 'MP3': u'audio/mpeg', 'OGG': u'audio/ogg'} +_SOURCES = {} +_SOURCES_CONFIG = {} + +READ_STREAM_SERVICE = 'read-activity-http' + +# directory exists if powerd is running. create a file here, +# named after our pid, to inhibit suspend. +POWERD_INHIBIT_DIR = '/var/run/powerd-inhibit-suspend' + + +class GetIABooksActivity(activity.Activity): + + def __init__(self, handle): + "The entry point to the Activity" + activity.Activity.__init__(self, handle, False) + self.max_participants = 1 + + self.selected_book = None + self.queryresults = None + self._getter = None + self.show_images = True + self.languages = {} + self._lang_code_handler = languagenames.LanguageNames() + self.catalogs_configuration = {} + self.catalog_history = [] + + if os.path.exists('/etc/get-books.cfg'): + self._read_configuration('/etc/get-books.cfg') + else: + self._read_configuration() + + toolbar_box = ToolbarBox() + activity_button = ToolButton() + color = profile.get_color() + bundle = ActivityBundle(activity.get_bundle_path()) + icon = Icon(file=bundle.get_icon(), xo_color=color) + activity_button.set_icon_widget(icon) + activity_button.show() + + toolbar_box.toolbar.insert(activity_button, 0) + self._add_search_controls(toolbar_box.toolbar) + + separator = Gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(True) + toolbar_box.toolbar.insert(separator, -1) + + toolbar_box.toolbar.insert(StopButton(self), -1) + + self.set_toolbar_box(toolbar_box) + toolbar_box.show_all() + self._books_toolbar = toolbar_box.toolbar + + self._create_controls() + + if not self.powerd_running(): + try: + bus = dbus.SystemBus() + proxy = bus.get_object('org.freedesktop.ohm', + '/org/freedesktop/ohm/Keystore') + self.ohm_keystore = dbus.Interface(proxy, + 'org.freedesktop.ohm.Keystore') + except dbus.DBusException, e: + logging.warning("Error setting OHM inhibit: %s", e) + self.ohm_keystore = None + + def powerd_running(self): + self.using_powerd = os.access(POWERD_INHIBIT_DIR, os.W_OK) + logging.error("using_powerd: %d", self.using_powerd) + return self.using_powerd + + def _inhibit_suspend(self): + if self.using_powerd: + fd = open(POWERD_INHIBIT_DIR + "/%u" % os.getpid(), 'w') + logging.error("inhibit_suspend file is %s", (POWERD_INHIBIT_DIR \ + + "/%u" % os.getpid())) + fd.close() + return True + + if self.ohm_keystore is not None: + try: + self.ohm_keystore.SetKey('suspend.inhibit', 1) + return self.ohm_keystore.GetKey('suspend.inhibit') + except dbus.exceptions.DBusException: + logging.debug("failed to inhibit suspend") + return False + else: + return False + + def _allow_suspend(self): + if self.using_powerd: + if os.path.exists(POWERD_INHIBIT_DIR + "/%u" % os.getpid()): + os.unlink(POWERD_INHIBIT_DIR + "/%u" % os.getpid()) + logging.error("allow_suspend unlinking %s", (POWERD_INHIBIT_DIR \ + + "/%u" % os.getpid())) + return True + + if self.ohm_keystore is not None: + try: + self.ohm_keystore.SetKey('suspend.inhibit', 0) + return self.ohm_keystore.GetKey('suspend.inhibit') + except dbus.exceptions.DBusException: + logging.error("failed to allow suspend") + return False + else: + return False + + def _read_configuration(self, file_name='get-books.cfg'): + logging.error('Reading configuration from file %s', file_name) + config = ConfigParser.ConfigParser() + config.readfp(open(file_name)) + if config.has_option('GetBooks', 'show_images'): + self.show_images = config.getboolean('GetBooks', 'show_images') + self.languages = {} + if config.has_option('GetBooks', 'languages'): + languages_param = config.get('GetBooks', 'languages') + for language in languages_param.split(','): + lang_code = language.strip() + if len(lang_code) > 0: + self.languages[lang_code] = \ + self._lang_code_handler.get_full_language_name(lang_code) + + for section in config.sections(): + if section != 'GetBooks' and not section.startswith('Catalogs'): + name = config.get(section, 'name') + _SOURCES[section] = name + repo_config = {} + repo_config['query_uri'] = config.get(section, 'query_uri') + repo_config['opds_cover'] = config.get(section, 'opds_cover') + if config.has_option(section, 'summary_field'): + repo_config['summary_field'] = \ + config.get(section, 'summary_field') + else: + repo_config['summary_field'] = None + if config.has_option(section, 'blacklist'): + blacklist = config.get(section, 'blacklist') + repo_config['blacklist'] = blacklist.split(',') + # TODO strip? + else: + repo_config['blacklist'] = [] + + _SOURCES_CONFIG[section] = repo_config + + logging.error('_SOURCES %s', _SOURCES) + logging.error('_SOURCES_CONFIG %s', _SOURCES_CONFIG) + + for section in config.sections(): + if section.startswith('Catalogs'): + catalog_source = section.split('_')[1] + if not catalog_source in _SOURCES_CONFIG: + logging.error('There are not a source for the catalog ' + + 'section %s', section) + break + source_config = _SOURCES_CONFIG[catalog_source] + opds_cover = source_config['opds_cover'] + for catalog in config.options(section): + catalog_config = {} + catalog_config['query_uri'] = config.get(section, catalog) + catalog_config['opds_cover'] = opds_cover + catalog_config['source'] = catalog_source + catalog_config['name'] = catalog + catalog_config['summary_field'] = \ + source_config['summary_field'] + self.catalogs_configuration[catalog] = catalog_config + + self.source = _SOURCES_CONFIG.keys()[0] + + self.filter_catalogs_by_source() + + logging.error('languages %s', self.languages) + logging.error('catalogs %s', self.catalogs) + + def _add_search_controls(self, toolbar): + book_search_item = Gtk.ToolItem() + toolbar.search_entry = iconentry.IconEntry() + toolbar.search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, + 'system-search') + toolbar.search_entry.add_clear_button() + toolbar.search_entry.connect('activate', + self.__search_entry_activate_cb) + width = int(Gdk.Screen.width() / 4) + toolbar.search_entry.set_size_request(width, -1) + book_search_item.add(toolbar.search_entry) + toolbar.search_entry.show() + toolbar.insert(book_search_item, -1) + book_search_item.show() + + toolbar.source_combo = ComboBox() + toolbar.source_combo.props.sensitive = True + toolbar.source_changed_cb_id = \ + toolbar.source_combo.connect('changed', self.__source_changed_cb) + combotool = ToolComboBox(toolbar.source_combo) + combotool.set_size_request(width, -1) + toolbar.insert(combotool, -1) + combotool.show() + + self.bt_catalogs = ToggleToolButton('books') + self.bt_catalogs.set_tooltip(_('Catalogs')) + toolbar.insert(self.bt_catalogs, -1) + self.bt_catalogs.connect('toggled', self.__toggle_cats_cb) + if len(self.catalogs) > 0: + self.bt_catalogs.show() + + if len(self.languages) > 0: + toolbar.config_toolbarbutton = ToolbarButton() + toolbar.config_toolbarbutton.props.icon_name = 'preferences-system' + toolbar.config_toolbarbox = Gtk.Toolbar() + toolbar.config_toolbarbutton.props.page = toolbar.config_toolbarbox + toolbar.language_combo = ComboBox() + toolbar.language_combo.props.sensitive = True + combotool = ToolComboBox(toolbar.language_combo) + toolbar.language_combo.append_item('all', _('Any language')) + for key in self.languages.keys(): + toolbar.language_combo.append_item(key, self.languages[key]) + toolbar.language_combo.set_active(0) + toolbar.config_toolbarbutton.props.page.insert(combotool, -1) + toolbar.insert(toolbar.config_toolbarbutton, -1) + toolbar.config_toolbarbutton.show() + combotool.show() + toolbar.language_changed_cb_id = \ + toolbar.language_combo.connect('changed', + self.__language_changed_cb) + + self.bt_catalogs = ToggleToolButton('books') + self.bt_catalogs.set_tooltip(_('Catalogs')) + toolbar.insert(self.bt_catalogs, -1) + self.bt_catalogs.connect('toggled', self.__toggle_cats_cb) + if len(self.catalogs) > 0: + self.bt_catalogs.show() + + self._device_manager = devicemanager.DeviceManager() + self._refresh_sources(toolbar) + self._device_manager.connect('device-changed', + self.__device_changed_cb) + + toolbar.search_entry.grab_focus() + return toolbar + + def __bt_catalogs_clicked_cb(self, button): + palette = button.get_palette() + palette.popup(immediate=True, state=palette.SECONDARY) + + def __switch_catalog_cb(self, catalog_name): + catalog_config = self.catalogs[catalog_name.decode('utf-8')] + self.__activate_catalog_cb(None, catalog_config) + + def __activate_catalog_cb(self, menu, catalog_config): + query_language = self.get_query_language() + + self.enable_button(False) + self.clear_downloaded_bytes() + self.book_selected = False + self.listview.clear() + logging.error('SOURCE %s', catalog_config['source']) + self._books_toolbar.search_entry.props.text = '' + self.source = catalog_config['source'] + position = _SOURCES_CONFIG[self.source]['position'] + self._books_toolbar.source_combo.set_active(position) + + if self.queryresults is not None: + self.queryresults.cancel() + self.queryresults = None + #arch = open("/home/olpc/Activities/BibliotecaCeibal.activity/prueba","w") + #arch.write(query_language) + #arch.close() + query_language = 'all' + self.queryresults = opds.RemoteQueryResult(catalog_config, + '', query_language) + self.show_message(_('Performing lookup, please wait...')) + # README: I think we should create some global variables for + # each cursor that we are using to avoid the creation of them + # every time that we want to change it + self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH)) + + self.queryresults.connect('updated', self.__query_updated_cb) + + def update_format_combo(self, links): + self.format_combo.handler_block(self.__format_changed_cb_id) + self.format_combo.remove_all() + for key in _MIMETYPES.keys(): + if _MIMETYPES[key] in links.keys(): + self.format_combo.append_item(_MIMETYPES[key], key) + self.format_combo.set_active(0) + self.format_combo.handler_unblock(self.__format_changed_cb_id) + + def get_search_terms(self): + return self._books_toolbar.search_entry.props.text + + def __device_changed_cb(self, mgr): + logging.debug('Device was added/removed') + self._refresh_sources(self._books_toolbar) + + def _refresh_sources(self, toolbar): + toolbar.source_combo.handler_block(toolbar.source_changed_cb_id) + + #TODO: Do not blindly clear this + toolbar.source_combo.remove_all() + + position = 0 + for key in _SOURCES.keys(): + toolbar.source_combo.append_item(_SOURCES[key], key, + icon_name='internet-icon') + _SOURCES_CONFIG[key]['position'] = position + position = position + 1 + + # Add menu for local books + if len(_SOURCES) > 0: + toolbar.source_combo.append_separator() + toolbar.source_combo.append_item('local_books', _('My books'), + icon_name='activity-journal') + + devices = self._device_manager.get_devices() + + first_device = True + for device_name in devices: + device = devices[device_name] + logging.debug('device %s', device) + if device['removable']: + mount_point = device['mount_path'] + label = device['label'] + if label == '' or label is None: + capacity = device['size'] + label = (_('%.2f GB Volume') % (capacity / (1024.0 ** 3))) + logging.debug('Adding device %s', (label)) + if first_device: + toolbar.source_combo.append_separator() + first_device = False + toolbar.source_combo.append_item(mount_point, label) + + toolbar.source_combo.set_active(0) + toolbar.source_combo.handler_unblock(toolbar.source_changed_cb_id) + + def __format_changed_cb(self, combo): + self.show_book_data(False) + + def __language_changed_cb(self, combo): + self.find_books(self.get_search_terms()) + + def __search_entry_activate_cb(self, entry): + self.find_books(self.get_search_terms()) + + def __get_book_cb(self, button): + self.get_book() + + def enable_button(self, state): + self._download.props.sensitive = state + self.format_combo.props.sensitive = state + + def move_up_catalog(self, treeview): + len_cat = len(self.catalog_history) + if len_cat == 1: + return + else: + # move a level up the tree + self.catalog_listview.handler_block(self._catalog_changed_id) + self.catalog_history.pop() + len_cat -= 1 + if(len_cat == 1): + title = self.catalog_history[0]['title'] + self.bt_move_up_catalog.set_label(title) + self.bt_move_up_catalog.hide_image() + else: + title = self.catalog_history[len_cat - 1]['title'] + self.bt_move_up_catalog.set_label(title) + self.bt_move_up_catalog.show_image() + self.catalogs = self.catalog_history[len_cat - 1]['catalogs'] + if len(self.catalogs) > 0: + self.path_iter = {} + self.categories = [] + for key in self.catalogs.keys(): + self.categories.append({'text': key, 'dentro': []}) + self.treemodel.clear() + for p in self.categories: + self.path_iter[p['text']] = \ + self.treemodel.append([p['text']]) + self.catalog_listview.handler_unblock(self._catalog_changed_id) + + def move_down_catalog(self, treeview): + treestore, coldex = \ + self.catalog_listview.get_selection().get_selected() + len_cat = len(self.catalog_history) + if len_cat > 0 and self.catalog_history[len_cat - 1]['catalogs'] == []: + self.catalog_history.pop() + len_cat = len(self.catalog_history) + + # README: when the Activity starts by default there is nothing + # selected and this signal is called, so we have to avoid this + # 'append' because it fails + if coldex is not None: + self.catalog_history.append( + {'title': treestore.get_value(coldex, 0), 'catalogs': []}) + self.__switch_catalog_cb(treestore.get_value(coldex, 0)) + + def _sort_logfile(self, treemodel, itera, iterb): + a = treemodel.get_value(itera, 0) + b = treemodel.get_value(iterb, 0) + if a == None or b == None: + return 0 + a = a.lower() + b = b.lower() + if a > b: + return 1 + if a < b: + return -1 + return 0 + + def __toggle_cats_cb(self, button): + if button.get_active(): + self.tree_scroller.show_all() + self.separa.show() + else: + self.tree_scroller.hide() + self.separa.hide() + + def _create_controls(self): + self._download_content_length = 0 + self._download_content_type = None + self.progressbox = Gtk.Box(spacing=20, + orientation=Gtk.Orientation.HORIZONTAL) + self.progressbar = Gtk.ProgressBar() + self.progressbar.set_fraction(0.0) + self.progressbox.pack_start(self.progressbar, expand=True, fill=True, + padding=0) + self.cancel_btn = Gtk.Button(stock=Gtk.STOCK_CANCEL) + self.cancel_btn.connect('clicked', self.__cancel_btn_clicked_cb) + self.progressbox.pack_start(self.cancel_btn, expand=False, + fill=False, padding=0) + + self.msg_label = Gtk.Label() + + self.list_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + + # Catalogs treeview + self.catalog_listview = Gtk.TreeView() + self.catalog_listview.headers_clickble = True + self.catalog_listview.hover_expand = True + self.catalog_listview.rules_hint = True + self._catalog_changed_id = self.catalog_listview.connect( + 'cursor-changed', self.move_down_catalog) + self.catalog_listview.set_enable_search(False) + + self.treemodel = Gtk.ListStore(str) + self.treemodel.set_sort_column_id(0, Gtk.SortType.ASCENDING) + self.catalog_listview.set_model(self.treemodel) + + renderer = Gtk.CellRendererText() + renderer.set_property('wrap-mode', Pango.WrapMode.WORD) + self.treecol = Gtk.TreeViewColumn(_('Catalogs'), renderer, text=0) + self.treecol.set_property('clickable', True) + self.treecol.connect('clicked', self.move_up_catalog) + self.catalog_listview.append_column(self.treecol) + self.bt_move_up_catalog = ButtonWithImage(_('Catalogs')) + self.bt_move_up_catalog.hide_image() + self.treecol.set_widget(self.bt_move_up_catalog) + + self.load_source_catalogs() + + self.tree_scroller = Gtk.ScrolledWindow(hadjustment=None, + vadjustment=None) + self.tree_scroller.set_policy(Gtk.PolicyType.NEVER, + Gtk.PolicyType.AUTOMATIC) + self.tree_scroller.add(self.catalog_listview) + self.list_box.pack_start(self.tree_scroller, expand=False, + fill=False, padding=0) + self.separa = Gtk.VSeparator() + self.list_box.pack_start(self.separa, expand=False, + fill=False, padding=0) + + # books listview + self.listview = ListView(self._lang_code_handler) + self.listview.connect('selection-changed', self.selection_cb) + self.listview.set_enable_search(False) + + self.list_scroller = Gtk.ScrolledWindow(hadjustment=None, + vadjustment=None) + self.list_scroller.set_policy(Gtk.PolicyType.AUTOMATIC, + Gtk.PolicyType.AUTOMATIC) + vadjustment = self.list_scroller.get_vadjustment() + vadjustment.connect('value-changed', + self.__vadjustment_value_changed_cb) + self.list_scroller.add(self.listview) + self.list_box.pack_start(self.list_scroller, expand=True, + fill=True, padding=0) + + self.scrolled = Gtk.ScrolledWindow() + self.scrolled.set_policy(Gtk.PolicyType.NEVER, + Gtk.PolicyType.AUTOMATIC) + self.scrolled.props.shadow_type = Gtk.ShadowType.NONE + self.textview = Gtk.TextView() + self.textview.set_editable(False) + self.textview.set_cursor_visible(False) + self.textview.set_wrap_mode(Gtk.WrapMode.WORD) + self.textview.set_justification(Gtk.Justification.LEFT) + self.textview.set_left_margin(20) + self.textview.set_right_margin(20) + self.scrolled.add(self.textview) + self.list_box.show_all() + self.separa.hide() + self.tree_scroller.hide() + + vbox_download = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + + hbox_format = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + format_label = Gtk.Label(label=_('Format:')) + self.format_combo = ComboBox() + for key in _MIMETYPES.keys(): + self.format_combo.append_item(_MIMETYPES[key], key) + self.format_combo.set_active(0) + self.format_combo.props.sensitive = False + self.__format_changed_cb_id = \ + self.format_combo.connect('changed', self.__format_changed_cb) + + hbox_format.pack_start(format_label, False, False, 10) + hbox_format.pack_start(self.format_combo, False, False, 10) + vbox_download.pack_start(hbox_format, False, False, 10) + + self._download = Gtk.Button(_('Get Book')) + self._download.props.sensitive = False + self._download.connect('clicked', self.__get_book_cb) + vbox_download.pack_start(self._download, False, False, 10) + + bottom_hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + + if self.show_images: + self.__image_downloader = None + self.image = Gtk.Image() + self.add_default_image() + bottom_hbox.pack_start(self.image, False, False, 10) + bottom_hbox.pack_start(self.scrolled, True, True, 10) + bottom_hbox.pack_start(vbox_download, False, False, 10) + bottom_hbox.show_all() + + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + vbox.pack_start(self.msg_label, False, False, 10) + vbox.pack_start(self.progressbox, False, False, 10) + vbox.pack_start(self.list_box, True, True, 0) + vbox.pack_start(bottom_hbox, False, False, 10) + self.set_canvas(vbox) + self.listview.show() + vbox.show() + self.list_scroller.show() + self.progressbox.hide() + self.show_message( + _('Enter words from the Author or Title to begin search.')) + + self._books_toolbar.search_entry.grab_focus() + if len(self.catalogs) > 0: + self.bt_catalogs.set_active(True) + + def filter_catalogs_by_source(self): + self.catalogs = {} + for catalog_key in self.catalogs_configuration: + catalog = self.catalogs_configuration[catalog_key] + if catalog['source'] == self.source: + self.catalogs[catalog_key] = catalog + + def load_source_catalogs(self): + self.filter_catalogs_by_source() + + if len(self.catalogs) > 0: + self.categories = [] + self.path_iter = {} + self.catalog_history = [] + self.catalog_history.append({'title': _('Catalogs'), + 'catalogs': self.catalogs}) + for key in self.catalogs.keys(): + self.categories.append({'text': key, 'dentro': []}) + self.treemodel.clear() + + for p in self.categories: + self.path_iter[p['text']] = self.treemodel.append([p['text']]) + + def can_close(self): + self._lang_code_handler.close() + if self.queryresults is not None: + self.queryresults.cancel() + self.queryresults = None + return True + + def selection_cb(self, widget): + # Testing... + selected_book = self.listview.get_selected_book() + if self.source == 'local_books': + if selected_book: + self.download_url = '' + self.selected_book = selected_book + self._download.hide() + self.show_book_data() + self._object_id = selected_book.get_object_id() + self._show_journal_alert(_('Selected book'), + self.selected_title) + else: + self.clear_downloaded_bytes() + if selected_book: + self.update_format_combo(selected_book.get_download_links()) + self.selected_book = selected_book + self._download.show() + self.show_book_data() + + def show_message(self, text): + self.msg_label.set_text(text) + self.msg_label.show() + + def hide_message(self): + self.msg_label.hide() + + def show_book_data(self, load_image=True): + self.selected_title = self.selected_book.get_title() + book_data = _('Title:\t\t') + self.selected_title + '\n' + self.selected_author = self.selected_book.get_author() + book_data += _('Author:\t\t') + self.selected_author + '\n' + self.selected_publisher = self.selected_book.get_publisher() + self.selected_summary = self.selected_book.get_summary() + if (self.selected_summary is not 'Unknown'): + book_data += _('Summary:\t') + self.selected_summary + '\n' + self.selected_language_code = self.selected_book.get_language() + if self.selected_language_code != '': + try: + self.selected_language = \ + self._lang_code_handler.get_full_language_name( + self.selected_book.get_language()) + except: + self.selected_language = self.selected_book.get_language() + book_data += _('Language:\t') + self.selected_language + '\n' + book_data += _('Publisher:\t') + self.selected_publisher + '\n' + if self.source != 'local_books': + try: + self.download_url = self.selected_book.get_download_links()[\ + self.format_combo.props.value] + except: + pass + textbuffer = self.textview.get_buffer() + textbuffer.set_text('\n' + book_data) + self.enable_button(True) + + # Cover Image + self.exist_cover_image = False + if self.show_images and load_image: + if self.source == 'local_books': + cover_image_buffer = self.get_journal_entry_cover_image( + self.selected_book.get_object_id()) + if (cover_image_buffer): + self.add_image_buffer( + self.get_pixbuf_from_buffer(cover_image_buffer)) + else: + self.add_default_image() + else: + url_image = self.selected_book.get_image_url() + self.add_default_image() + if url_image: + self.download_image(url_image.values()[0]) + + def get_pixbuf_from_buffer(self, image_buffer): + """Buffer To Pixbuf""" + pixbuf_loader = GdkPixbuf.PixbufLoader() + pixbuf_loader.write(image_buffer) + pixbuf_loader.close() + pixbuf = pixbuf_loader.get_pixbuf() + return pixbuf + + def get_journal_entry_cover_image(self, object_id): + ds_object = datastore.get(object_id) + if 'cover_image' in ds_object.metadata: + cover_data = ds_object.metadata['cover_image'] + return base64.b64decode(cover_data) + elif 'preview' in ds_object.metadata: + return ds_object.metadata['preview'] + else: + return "" + + def download_image(self, url): + self._inhibit_suspend() + if self.__image_downloader is not None: + self.__image_downloader.stop_download() + self.__image_downloader = opds.ImageDownloader(self, url) + self.__image_downloader.connect('updated', self.__image_updated_cb) + + def __image_updated_cb(self, downloader, file_name): + if file_name is not None: + self.add_image(file_name) + self.exist_cover_image = True + os.remove(file_name) + else: + self.add_default_image() + self.__image_downloader = None + self._allow_suspend() + + def add_default_image(self): + if self.format_combo.props.value == 'audio/mpeg': + file_path = os.path.join(activity.get_bundle_path(), 'musica.svg') + self.add_image(file_path) + elif self.format_combo.props.value == 'audio/ogg': + file_path = os.path.join(activity.get_bundle_path(), 'application-ogg.png') + self.add_image(file_path) + else: + file_path = os.path.join(activity.get_bundle_path(),'generic_cover.png') + self.add_image(file_path) + + def add_image(self, file_path): + pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path) + self.add_image_buffer(pixbuf) + + def add_image_buffer(self, pixbuf): + image_height = int(Gdk.Screen.height() / 4) + image_width = image_height / 3 * 2 + width, height = pixbuf.get_width(), pixbuf.get_height() + scale = 1 + if (width > image_width) or (height > image_height): + scale_x = image_width / float(width) + scale_y = image_height / float(height) + scale = min(scale_x, scale_y) + + pixbuf2 = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, + pixbuf.get_has_alpha(), pixbuf.get_bits_per_sample(), + image_width, image_height) + + pixbuf2.fill(style.COLOR_PANEL_GREY.get_int()) + + margin_x = int((image_width - (width * scale)) / 2) + margin_y = int((image_height - (height * scale)) / 2) + + pixbuf.scale(pixbuf2, margin_x, margin_y, + image_width - (margin_x * 2), image_height - (margin_y * 2), + margin_x, margin_y, scale, scale, + GdkPixbuf.InterpType.BILINEAR) + + self.image.set_from_pixbuf(pixbuf2) + + def get_query_language(self): + query_language = None + if len(self.languages) > 0: + query_language = self._books_toolbar.language_combo.props.value + return query_language + + def find_books(self, search_text=''): + self._inhibit_suspend() + self.source = self._books_toolbar.source_combo.props.value + + query_language = self.get_query_language() + + self.enable_button(False) + self.clear_downloaded_bytes() + self.book_selected = False + self.listview.clear() + + if self.queryresults is not None: + self.queryresults.cancel() + self.queryresults = None + + if self.source == 'local_books': + self.listview.populate_with_books( + self.get_entrys_info(search_text)) + else: + aux = 0 #Arreglado que si apretas ENTER aparezcan TODOS los libros de Ceibal + if self.source == 'Ceibal' and search_text == '': + aux = 1 + #if self.source == 'local_books' and search_text == '': + # aux = 1 + if search_text == '' and aux == 0: + return + if len(search_text) < 3 and aux == 0: + self.show_message(_('You must enter at least 3 letters.')) + self._books_toolbar.search_entry.grab_focus() + return + if self.source == 'Internet Archive': + self.queryresults = \ + opds.InternetArchiveQueryResult(search_text, + query_language, self) + elif self.source in _SOURCES_CONFIG: + #if self.source in _SOURCES_CONFIG: + repo_configuration = _SOURCES_CONFIG[self.source] + self.queryresults = opds.RemoteQueryResult(repo_configuration, + search_text, query_language) + else: + self.queryresults = opds.LocalVolumeQueryResult(self.source, + search_text, query_language) + + self.show_message(_('Performing lookup, please wait...')) + self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH)) + self.queryresults.connect('updated', self.__query_updated_cb) + + def __query_updated_cb(self, query, midway): + self.listview.populate(self.queryresults) + if 'bozo_exception' in self.queryresults._feedobj: + # something went wrong and we have to inform about this + bozo_exception = self.queryresults._feedobj.bozo_exception + if isinstance(bozo_exception, urllib2.URLError): + if isinstance(bozo_exception.reason, socket.gaierror): + if bozo_exception.reason.errno == -2: + self.show_message(_('Could not reach the server. ' + 'Maybe you are not connected to the network')) + self.window.set_cursor(None) + return + self.show_message(_('There was an error downloading the list.')) + elif (len(self.queryresults.get_catalog_list()) > 0): + self.show_message(_('New catalog list %s was found') \ + % self.queryresults._configuration["name"]) + self.catalogs_updated(query, midway) + elif len(self.queryresults) == 0: + self.show_message(_('Sorry, no books could be found.')) + if not midway and len(self.queryresults) > 0: + self.hide_message() + query_language = self.get_query_language() + if query_language != 'all' and query_language != 'en': + # the bookserver send english books if there are not books in + # the requested language + only_english = True + for book in self.queryresults.get_book_list(): + if book.get_language() == query_language: + only_english = False + break + if only_english: + self.show_message( + _('Sorry, we only found english books.')) + self.get_window().set_cursor(None) + self._allow_suspend() + + def catalogs_updated(self, query, midway): + self.catalogs = {} + i = 0 + for catalog_item in self.queryresults.get_catalog_list(): + logging.debug('Add catalog %s', catalog_item.get_title()) + catalog_config = {} + download_link = '' + download_links = catalog_item.get_download_links() + for link in download_links.keys(): + download_link = download_links[link] + break + catalog_config['query_uri'] = download_link + catalog_config['opds_cover'] = \ + catalog_item._configuration['opds_cover'] + catalog_config['source'] = catalog_item._configuration['source'] + source_config = _SOURCES_CONFIG[catalog_config['source']] + catalog_config['name'] = catalog_item.get_title() + catalog_config['summary_field'] = \ + catalog_item._configuration['summary_field'] + catalog_config['index'] = i + if catalog_item.get_title() in source_config['blacklist']: + logging.debug('Catalog "%s" is in blacklist', + catalog_item.get_title()) + else: + self.catalogs[catalog_item.get_title().strip()] = \ + catalog_config + i = i + 1 + if len(self.catalogs) > 0: + len_cat = len(self.catalog_history) + self.catalog_history[len_cat - 1]['catalogs'] = self.catalogs + self.path_iter = {} + self.categories = [] + for key in self.catalogs.keys(): + self.categories.append({'text': key, 'dentro': []}) + self.treemodel.clear() + for p in self.categories: + self.path_iter[p['text']] = \ + self.treemodel.append([p['text']]) + + title = self.catalog_history[len_cat - 1]['title'] + self.bt_move_up_catalog.set_label(title) + self.bt_move_up_catalog.show_image() + + else: + self.catalog_history.pop() + + def __source_changed_cb(self, widget): + search_terms = self.get_search_terms() + if search_terms == '': + self.find_books(None) + else: + self.find_books(search_terms) + # enable/disable catalogs button if configuration is available + self.source = self._books_toolbar.source_combo.props.value + + # Get catalogs for this source + self.load_source_catalogs() + + if len(self.catalogs) > 0: + self.bt_catalogs.show() + self.bt_catalogs.set_active(True) + else: + self.bt_catalogs.set_active(False) + self.bt_catalogs.hide() + + def __vadjustment_value_changed_cb(self, vadjustment): + + if not self.queryresults.is_ready(): + return + try: + # Use various tricks to update resultset as user scrolls down + if ((vadjustment.props.upper - vadjustment.props.lower) > 1000 \ + and (vadjustment.props.upper - vadjustment.props.value - \ + vadjustment.props.page_size) / (vadjustment.props.upper - \ + vadjustment.props.lower) < 0.3) or ((vadjustment.props.upper \ + - vadjustment.props.value + - vadjustment.props.page_size) < 200): + if self.queryresults.has_next(): + self.show_message(_('Tiene mas resultados, espere por favor...')) + self.queryresults.update_with_next() + finally: + return + + def __cancel_btn_clicked_cb(self, btn): + if self._getter is not None: + try: + self._getter.cancel() + except: + logging.debug('Got an exception while trying' + \ + 'to cancel download') + self.progressbox.hide() + self.listview.props.sensitive = True + self._books_toolbar.search_entry.set_sensitive(True) + logging.debug('Download was canceled by the user.') + self._allow_suspend() + + def get_book(self): + self.enable_button(False) + self.progressbox.show_all() + GObject.idle_add(self.download_book, self.download_url) + + def download_book(self, url): + self._inhibit_suspend() + logging.error('DOWNLOAD BOOK %s', url) + self.listview.props.sensitive = False + self._books_toolbar.search_entry.set_sensitive(False) + path = os.path.join(self.get_activity_root(), 'instance', + 'tmp%i' % time.time()) + self._getter = opds.ReadURLDownloader(url) + self._getter.connect("finished", self._get_book_result_cb) + self._getter.connect("progress", self._get_book_progress_cb) + self._getter.connect("error", self._get_book_error_cb) + logging.debug("Starting download from %s to %s", url, path) + try: + self._getter.start(path) + except: + self.show_error_alert(_('Error'), _('Connection timed out for ') + + self.selected_title) + + self._download_content_length = self._getter.get_content_length() + self._download_content_type = self._getter.get_content_type() + + def _get_book_result_cb(self, getter, tempfile, suggested_name): + self.listview.props.sensitive = True + self._books_toolbar.search_entry.set_sensitive(True) + if self._download_content_type.startswith('text/html'): + # got an error page instead + self._get_book_error_cb(getter, 'HTTP Error') + return + self.process_downloaded_book(tempfile, suggested_name) + + def _get_book_progress_cb(self, getter, bytes_downloaded): + if self._download_content_length > 0: + logging.debug("Downloaded %u of %u bytes...", + bytes_downloaded, self._download_content_length) + else: + logging.debug("Downloaded %u bytes...", + bytes_downloaded) + total = self._download_content_length + self.set_downloaded_bytes(bytes_downloaded, total) + while Gtk.events_pending(): + Gtk.main_iteration() + + def _get_book_error_cb(self, getter, err): + self.listview.props.sensitive = True + self.enable_button(True) + self.progressbox.hide() + logging.debug("Error getting document: %s", err) + self._download_content_length = 0 + self._download_content_type = None + self._getter = None + if self.source == 'Internet Archive' and \ + getter._url.endswith('_text.pdf'): + # in the IA server there are files ending with _text.pdf + # smaller and better than the .pdf files, but not for all + # the books. We try to download them and if is not present + # download the .pdf file + self.download_url = self.download_url.replace('_text.pdf', '.pdf') + self.get_book() + else: + self._allow_suspend() + self._show_error_alert(_('Error: Could not download %s. ' + + 'The path in the catalog seems to be incorrect') % + self.selected_title) + + def set_downloaded_bytes(self, downloaded_bytes, total): + fraction = float(downloaded_bytes) / float(total) + self.progressbar.set_fraction(fraction) + + def clear_downloaded_bytes(self): + self.progressbar.set_fraction(0.0) + + def process_downloaded_book(self, tempfile, suggested_name): + logging.debug("Got document %s (%s)", tempfile, suggested_name) + self.create_journal_entry(tempfile) + self._getter = None + self._allow_suspend() + + def create_journal_entry(self, tempfile): + journal_entry = datastore.create() + journal_title = self.selected_title + if self.selected_author != '': + journal_title = journal_title + ', by ' + self.selected_author + journal_entry.metadata['title'] = journal_title + journal_entry.metadata['title_set_by_user'] = '1' + journal_entry.metadata['keep'] = '0' + journal_entry.metadata['mime_type'] = \ + self.format_combo.props.value + # Fix fake mime type for black&white pdfs + if journal_entry.metadata['mime_type'] == _MIMETYPES['PDF BW']: + journal_entry.metadata['mime_type'] = _MIMETYPES['PDF'] + + journal_entry.metadata['buddies'] = '' + journal_entry.metadata['icon-color'] = profile.get_color().to_string() + textbuffer = self.textview.get_buffer() + journal_entry.metadata['description'] = \ + textbuffer.get_text(textbuffer.get_start_iter(), + textbuffer.get_end_iter(), True) + if self.exist_cover_image: + image_buffer = self._get_preview_image_buffer() + journal_entry.metadata['preview'] = dbus.ByteArray(image_buffer) + image_buffer = self._get_cover_image_buffer() + journal_entry.metadata['cover_image'] = \ + dbus.ByteArray(base64.b64encode(image_buffer)) + else: + journal_entry.metadata['cover_image'] = "" + + journal_entry.metadata['tags'] = self.source + journal_entry.metadata['source'] = self.source + journal_entry.metadata['author'] = self.selected_author + journal_entry.metadata['publisher'] = self.selected_publisher + journal_entry.metadata['summary'] = self.selected_summary + journal_entry.metadata['language'] = self.selected_language_code + + journal_entry.file_path = tempfile + datastore.write(journal_entry) + os.remove(tempfile) + self.progressbox.hide() + self._object_id = journal_entry.object_id + self._show_journal_alert(_('Download completed'), self.selected_title) + + def _show_journal_alert(self, title, msg): + _stop_alert = Alert() + _stop_alert.props.title = title + _stop_alert.props.msg = msg + open_icon = Icon(icon_name='zoom-activity') + _stop_alert.add_button(Gtk.ResponseType.APPLY, + _('Show in Journal'), open_icon) + open_icon.show() + ok_icon = Icon(icon_name='dialog-ok') + _stop_alert.add_button(Gtk.ResponseType.OK, _('Ok'), ok_icon) + ok_icon.show() + # Remove other alerts + for alert in self._alerts: + self.remove_alert(alert) + + self.add_alert(_stop_alert) + _stop_alert.connect('response', self.__stop_response_cb) + _stop_alert.show() + + def __stop_response_cb(self, alert, response_id): + if response_id is Gtk.ResponseType.APPLY: + activity.show_object_in_journal(self._object_id) + self.remove_alert(alert) + + def _get_preview_image_buffer(self): + preview_width, preview_height = style.zoom(300), style.zoom(225) + + pixbuf = self.image.get_pixbuf() + width, height = pixbuf.get_width(), pixbuf.get_height() + + scale = 1 + if (width > preview_width) or (height > preview_height): + scale_x = preview_width / float(width) + scale_y = preview_height / float(height) + scale = min(scale_x, scale_y) + + pixbuf2 = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, + pixbuf.get_has_alpha(), pixbuf.get_bits_per_sample(), + preview_width, preview_height) + pixbuf2.fill(style.COLOR_WHITE.get_int()) + + margin_x = int((preview_width - (width * scale)) / 2) + margin_y = int((preview_height - (height * scale)) / 2) + + pixbuf.scale(pixbuf2, margin_x, margin_y, + preview_width - (margin_x * 2), + preview_height - (margin_y * 2), + margin_x, margin_y, scale, scale, + GdkPixbuf.InterpType.BILINEAR) + + succes, data = pixbuf2.save_to_bufferv('png', [], []) + return data + + def _get_cover_image_buffer(self): + pixbuf = self.image.get_pixbuf() + succes, data = pixbuf.save_to_bufferv('png', [], []) + return data + + def _show_error_alert(self, title, text=None): + alert = NotifyAlert(timeout=20) + alert.props.title = title + alert.props.msg = text + self.add_alert(alert) + alert.connect('response', self._alert_cancel_cb) + alert.show() + + def _alert_cancel_cb(self, alert, response_id): + self.remove_alert(alert) + self.textview.grab_focus() + + def get_entrys_info(self, query): + books = [] + for key in _MIMETYPES.keys(): + books.extend(self.get_entry_info_format(query, _MIMETYPES[key])) + return books + + def get_entry_info_format(self, query, mime): + books = [] + if query is not None and len(query) > 0: + ds_objects, num_objects = datastore.find( + {'mime_type': '%s' % mime, + 'query': '*%s*' % query}) + else: + ds_objects, num_objects = datastore.find( + {'mime_type': '%s' % mime}) + + logging.error('Local search %d books found %s format', num_objects, + mime) + for i in range(0, num_objects): + entry = {} + entry['title'] = ds_objects[i].metadata['title'] + entry['mime'] = ds_objects[i].metadata['mime_type'] + entry['object_id'] = ds_objects[i].object_id + + if 'author' in ds_objects[i].metadata: + entry['author'] = ds_objects[i].metadata['author'] + else: + entry['author'] = '' + + if 'publisher' in ds_objects[i].metadata: + entry['dcterms_publisher'] = \ + ds_objects[i].metadata['publisher'] + else: + entry['dcterms_publisher'] = '' + + if 'language' in ds_objects[i].metadata: + entry['dcterms_language'] = \ + ds_objects[i].metadata['language'] + else: + entry['dcterms_language'] = '' + + if 'source' in ds_objects[i].metadata: + entry['source'] = \ + ds_objects[i].metadata['source'] + else: + entry['source'] = '' + + if entry['source'] in _SOURCES_CONFIG: + repo_configuration = _SOURCES_CONFIG[entry['source']] + summary_field = repo_configuration['summary_field'] + if 'summary' in ds_objects[i].metadata: + entry[summary_field] = ds_objects[i].metadata['summary'] + else: + entry[summary_field] = '' + else: + repo_configuration = None + books.append(opds.Book(repo_configuration, entry, '')) + return books + + def close(self, skip_save=False): + "Override the close method so we don't try to create a Journal entry." + activity.Activity.close(self, True) + + def save(self): + pass + + +class ButtonWithImage(Gtk.Button): + + def __init__(self, label_text): + GObject.GObject.__init__(self,) + self.icon_move_up = Icon(icon_name='go-up') + # self.remove(self.get_children()[0]) + self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.add(self.hbox) + self.hbox.add(self.icon_move_up) + self.label = Gtk.Label(label=label_text) + self.hbox.add(self.label) + self.show_all() + + def hide_image(self): + self.icon_move_up.hide() + + def show_image(self): + self.icon_move_up.show() + + def set_label(self, text): + self.label.set_text(text) |