#! /usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2009 James D. Simmons # Copyright (C) 2009 Sayamindu Dasgupta # # 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 # no estamos compartiendo 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'): # leyendo configuracion self._read_configuration('/etc/get-books.cfg') else: self._read_configuration() # Toolbar toolbar_box = ToolbarBox() #activity_button = ToolButton() # boton de icono de actividad #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) # anadiendo busqueda separator = Gtk.SeparatorToolItem() # separador separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) stop_button = StopButton(self) # stop button stop_button.props.accelerator = 'q' toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbar_box) toolbar_box.show_all() self._books_toolbar = toolbar_box.toolbar # Main layout. Layout inicial self.menubox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self.mislibros = Gtk.Button(label="Mis Libros") self.mislibros.connect("clicked", self.on_mislibros_clicked) self.menubox.pack_start(self.mislibros, True, True, 0) self.menubox.add(self.mislibros) self.menubox.show() #self.add(self.menubox) def on_mislibros_clicked(self, widget): #control on mis libros print "Mis libros" #self._create_controls() #importante para todos los controles de getbooks # suspend-resume 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 # Reading configuration 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) # painting search/catalogs on toolbar 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) # se inserta en el toolbar book_search_item.show() toolbar.source_combo = ComboBox() # combo de fuentes de donde salen los libros. 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) # se inserta en el toolbar 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) #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 # catalog buttons 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) # Formats combo 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) # getting search terms 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) # updating sources 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) # Callbacks 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() # visualizing downloads 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) # caja listas # 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 # selection callback 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() # showing 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: 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) # Query updated 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() # Catalogs updated 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() # Source changed 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() # Getting book def get_book(self): self.enable_button(False) self.progressbox.show_all() GObject.idle_add(self.download_book, self.download_url) # Downloading book 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() # getting book result callback 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) # get book progress callback 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() # get book error callback 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() # creating journal entry 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) # obtaining image preview 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)