Beispiel #1
0
 def add_collection(self, name):
     """Add a new collection with <name> to the library. Return True
     if the collection was successfully added.
     """
     try:
         # The Recent pseudo collection initializes the lowest rowid
         # with -2, meaning that instead of starting from 1,
         # auto-incremental will start from -1. Avoid this.
         cur = self._con.execute("""select max(id) from collection""")
         maxid = cur.fetchone()
         if maxid is not None and maxid < 1:
             self._con.execute(
                 """insert into collection
                 (id, name) values (?, ?)""",
                 (1, name),
             )
         else:
             self._con.execute(
                 """insert into Collection
                 (name) values (?)""",
                 (name,),
             )
         return True
     except dbapi2.Error:
         log.error(_('! Could not add collection "%s"'), name)
     return False
Beispiel #2
0
 def _extract_files_errcb(self, name, etype, value, tb):
     # Better to ignore any failed extractions (e.g. from a corrupt
     # archive) than to crash here and leave the main thread in a
     # possible infinite block. Damaged or missing files *should* be
     # handled gracefully by the main program anyway.
     log.error(_('! Extraction error: %s'), value)
     log.debug('Traceback:\n%s', ''.join(traceback.format_tb(tb)).strip())
Beispiel #3
0
    def load_bookmarks(self):
        """ Loads persisted bookmarks from a local file.
        @return: Tuple of (bookmarks, file mtime)
        """

        path = constants.BOOKMARK_PICKLE_PATH
        bookmarks = []
        mtime = 0

        if os.path.isfile(path):
            try:
                mtime = os.stat(path).st_mtime
                with open(path, 'rb') as fd:
                    version = pickle.load(fd)
                    packs = pickle.load(fd)

                    for pack in packs:
                        # Handle old bookmarks without date_added attribute
                        if len(pack) == 5:
                            pack = pack + (datetime.datetime.now(), )

                        bookmark = bookmark_menu_item._Bookmark(
                            self._window, self._file_handler, *pack)
                        bookmarks.append(bookmark)

            except Exception:
                log.error(_('! Could not parse bookmarks file %s'), path)

        return bookmarks, mtime
Beispiel #4
0
    def __init__(self):
        self._initialized = False
        self._window = None
        self._file_handler = None
        self._image_handler = None
        self._bookmarks = []

        if os.path.isfile(constants.BOOKMARK_PICKLE_PATH):

            try:
                fd = open(constants.BOOKMARK_PICKLE_PATH, 'rb')
                version = cPickle.load(fd)
                packs = cPickle.load(fd)

                for pack in packs:
                    # Handle old bookmarks without date_added attribute
                    if len(pack) == 5:
                        pack = pack + (datetime.datetime.now(),)

                    self.add_bookmark_by_values(*pack)

                fd.close()

            except Exception:
                log.error(_('! Could not parse bookmarks file %s'),
                          constants.BOOKMARK_PICKLE_PATH)
                log.error(_('! Deleting corrupt bookmarks file.'))
                fd.close()
                os.remove(constants.BOOKMARK_PICKLE_PATH)
                self.clear_bookmarks()
Beispiel #5
0
    def __run_callbacks(self, *args, **kwargs):
        ''' Executes callback functions. '''
        for obj_ref, func in self.__callbacks:

            if obj_ref is None:
                # Callback is a normal function
                callback = func
            elif obj_ref() is not None:
                # Callback is a bound method.
                # Recreate it by binding the function to the object.
                callback = func.__get__(obj_ref())
            else:
                # Callback is a bound method, object
                # no longer exists.
                callback = None

            if callback:
                try:
                    callback(*args, **kwargs)
                except Exception as e:
                    log.error(_('! Callback %(function)r failed: %(error)s'), {
                        'function': callback,
                        'error': e
                    })
                    log.debug('Traceback:\n%s', traceback.format_exc())
    def __init__(self):
        self._initialized = False
        self._window = None
        self._file_handler = None
        self._image_handler = None
        self._bookmarks = []

        if os.path.isfile(constants.BOOKMARK_PICKLE_PATH):

            try:
                fd = open(constants.BOOKMARK_PICKLE_PATH, 'rb')
                version = cPickle.load(fd)
                packs = cPickle.load(fd)

                for pack in packs:
                    # Handle old bookmarks without date_added attribute
                    if len(pack) == 5:
                        pack = pack + (datetime.datetime.now(), )

                    self.add_bookmark_by_values(*pack)

                fd.close()

            except Exception:
                log.error(_('! Could not parse bookmarks file %s'),
                          constants.BOOKMARK_PICKLE_PATH)
                log.error(_('! Deleting corrupt bookmarks file.'))
                fd.close()
                os.remove(constants.BOOKMARK_PICKLE_PATH)
                self.clear_bookmarks()
Beispiel #7
0
    def add_book(self, path, collection=None):
        """Add the archive at <path> to the library. If <collection> is
        not None, it is the collection that the books should be put in.
        Return True if the book was successfully added (or was already
        added).
        """
        path = os.path.abspath(path)
        name = os.path.basename(path)
        info = archive_tools.get_archive_info(path)
        mi = archive_tools.get_archive_metadata(path)
        if info is None:
            return False
        format, pages, size = info

        if not mi:
            mi = [None, None, None, None, None, None]

        mi = ["NULL" if i == None else i for i in mi]

        # Thumbnail for the newly added book will be generated once it
        # is actually needed with get_book_thumbnail().
        old = self._con.execute(
            '''select id from Book
            where path = ?''', (path, )).fetchone()
        try:
            cursor = self._con.cursor()
            if old is not None:
                cursor.execute(
                    '''update Book set
                    name = ?, pages = ?, format = ?, size = ?, 
                    writer = ?, summary = ?, characters = ?, 
                    genre = ?, year = ?, language = ?, where path = ?''',
                    (name, pages, format, size, mi[0], mi[1], mi[2], mi[3],
                     mi[4], mi[5], path))
                book_id = old
            else:
                cursor.execute(
                    '''insert into Book
                    (name, pages, format, size, writer, summary, characters, genre, year, language, path)
                    values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
                    (name, pages, format, size, mi[0], mi[1], mi[2], mi[3],
                     mi[4], mi[5], path))
                book_id = cursor.lastrowid

                book = backend_types._Book(book_id, name, path, pages, format,
                                           size,
                                           datetime.datetime.now().isoformat(),
                                           mi[0], mi[1], mi[2], mi[3], mi[4],
                                           mi[5])
                self.book_added(book)

            cursor.close()

            if collection is not None:
                self.add_book_to_collection(book_id, collection)

            return True
        except dbapi2.Error:
            log.error(_('! Could not add book "%s" to the library'), path)
            return False
Beispiel #8
0
    def add_book(self, path, collection=None):
        """Add the archive at <path> to the library. If <collection> is
        not None, it is the collection that the books should be put in.
        Return True if the book was successfully added (or was already
        added).
        """
        path = os.path.abspath(path)
        name = os.path.basename(path)
        info = archive_tools.get_archive_info(path)
        if info is None:
            return False
        format, pages, size = info

        # Thumbnail for the newly added book will be generated once it
        # is actually needed with get_book_thumbnail().
        old = self._con.execute('''select id from Book
            where path = ?''', (path,)).fetchone()
        try:
            if old is not None:
                self._con.execute('''update Book set
                    name = ?, pages = ?, format = ?, size = ?
                    where path = ?''', (name, pages, format, size, path))
            else:
                self._con.execute('''insert into Book
                    (name, path, pages, format, size)
                    values (?, ?, ?, ?, ?)''',
                    (name, path, pages, format, size))
        except dbapi2.Error:
            log.error( _('! Could not add book "%s" to the library'), path )
            return False
        if collection is not None:
            book = self._con.execute('''select id from Book
                where path = ?''', (path,)).fetchone()
            self.add_book_to_collection(book, collection)
        return True
Beispiel #9
0
 def delete(self, filepath):
     ''' Deletes the thumbnail for <filepath> (if it exists) '''
     thumbpath = self._path_to_thumbpath(filepath)
     if os.path.isfile(thumbpath):
         try:
             os.remove(thumbpath)
         except IOError as error:
             log.error(_('! Could not remove file "%s"'), thumbpath)
             log.error(error)
Beispiel #10
0
 def delete(self, filepath):
     """ Deletes the thumbnail for <filepath> (if it exists) """
     thumbpath = self._path_to_thumbpath(filepath)
     if os.path.isfile(thumbpath):
         try:
             os.remove(thumbpath)
         except IOError, error:
             log.error(_("! Could not remove file \"%s\""), thumbpath)
             log.error(error)
Beispiel #11
0
 def _initialize(self):
     """ Restore keybindings from disk. """
     try:
         fp = file(constants.KEYBINDINGS_CONF_PATH, "r")
         stored_action_bindings = json.load(fp)
         fp.close()
     except Exception, e:
         log.error(_("Couldn't load keybindings: %s"), e)
         stored_action_bindings = {}
Beispiel #12
0
 def delete(self, filepath):
     """ Deletes the thumbnail for <filepath> (if it exists) """
     thumbpath = self._path_to_thumbpath(filepath)
     if os.path.isfile(thumbpath):
         try:
             os.remove(thumbpath)
         except IOError, error:
             log.error(_("! Could not remove file \"%s\""), thumbpath)
             log.error(error)
Beispiel #13
0
 def _initialize(self):
     """ Restore keybindings from disk. """
     try:
         fp = open(constants.KEYBINDINGS_CONF_PATH, "r")
         stored_action_bindings = json.load(fp)
         fp.close()
     except Exception, e:
         log.error(_("Couldn't load keybindings: %s"), e)
         stored_action_bindings = {}
Beispiel #14
0
 def add_book_to_collection(self, book, collection):
     """Put <book> into <collection>."""
     try:
         self._con.execute('''insert into Contain
             (collection, book) values (?, ?)''', (collection, book))
     except dbapi2.DatabaseError: # E.g. book already in collection.
         pass
     except dbapi2.Error:
         log.error( _('! Could not add book %(book)s to collection %(collection)s'),
             {"book" : book, "collection" : collection} )
Beispiel #15
0
    def add_book(self, path, collection=None):
        '''Add the archive at <path> to the library. If <collection> is
        not None, it is the collection that the books should be put in.
        Return True if the book was successfully added (or was already
        added).
        '''
        path = tools.relpath2root(path,
                                  abs_fallback=prefs['portable allow abspath'])
        if not path:
            # path is None, means running in portable mode
            # and currect path is out of same mount point
            # so do not add book to library
            return
        name = os.path.basename(path)
        info = archive_tools.get_archive_info(path)
        if info is None:
            return False
        format, pages, size = info

        # Thumbnail for the newly added book will be generated once it
        # is actually needed with get_book_thumbnail().
        old = self._con.execute(
            '''select id from Book
            where path = ?''', (path, )).fetchone()
        try:
            cursor = self._con.cursor()
            if old is not None:
                cursor.execute(
                    '''update Book set
                    name = ?, pages = ?, format = ?, size = ?
                    where path = ?''', (name, pages, format, size, path))
                book_id = old
            else:
                cursor.execute(
                    '''insert into Book
                    (name, path, pages, format, size)
                    values (?, ?, ?, ?, ?)''',
                    (name, path, pages, format, size))
                book_id = cursor.lastrowid

                book = backend_types._Book(book_id, name, path, pages, format,
                                           size,
                                           datetime.datetime.now().isoformat())
                self.book_added(book)

            cursor.close()

            if collection is not None:
                self.add_book_to_collection(book_id, collection)

            return True
        except sqlite3.Error:
            log.error(_('! Could not add book "%s" to the library'), path)
            return False
Beispiel #16
0
 def add_collection(self, name):
     """Add a new collection with <name> to the library. Return True
     if the collection was successfully added.
     """
     try:
         self._con.execute('''insert into Collection
             (name) values (?)''', (name,))
         return True
     except dbapi2.Error:
         log.error( _('! Could not add collection "%s"'), name )
     return False
Beispiel #17
0
    def get_book_path(self, book):
        """Return the filesystem path to <book>, or None if <book> isn't
        in the library.
        """
        try:
            path = self._con.execute('''select path from Book
                where id = ?''', (book,)).fetchone()
        except Exception:
            log.error( _('! Non-existant book #%i'), book )
            return None

        return path
Beispiel #18
0
 def add_collection(self, name):
     """Add a new collection with <name> to the library. Return True
     if the collection was successfully added.
     """
     try:
         self._con.execute(
             '''insert into Collection
             (name) values (?)''', (name, ))
         return True
     except dbapi2.Error:
         log.error(_('! Could not add collection "%s"'), name)
     return False
Beispiel #19
0
    def get_book_cover(self, book):
        """Return a pixbuf with a thumbnail of the cover of <book>, or
        None if the cover can not be fetched.
        """
        try:
            path = self._con.execute('''select path from Book
                where id = ?''', (book,)).fetchone()
        except Exception:
            log.error( _('! Non-existant book #%i'), book )
            return None

        return self.get_book_thumbnail(path)
Beispiel #20
0
    def add_book(self, path, collection=None):
        """Add the archive at <path> to the library. If <collection> is
        not None, it is the collection that the books should be put in.
        Return True if the book was successfully added (or was already
        added).
        """
        path = os.path.abspath(path)
        name = os.path.basename(path)
        info = archive_tools.get_archive_info(path)
        if info is None:
            return False
        format, pages, size = info

        # Thumbnail for the newly added book will be generated once it
        # is actually needed with get_book_thumbnail().
        old = self._con.execute(
            """select id from Book
            where path = ?""",
            (path,),
        ).fetchone()
        try:
            cursor = self._con.cursor()
            if old is not None:
                cursor.execute(
                    """update Book set
                    name = ?, pages = ?, format = ?, size = ?
                    where path = ?""",
                    (name, pages, format, size, path),
                )
                book_id = old
            else:
                cursor.execute(
                    """insert into Book
                    (name, path, pages, format, size)
                    values (?, ?, ?, ?, ?)""",
                    (name, path, pages, format, size),
                )
                book_id = cursor.lastrowid

                book = backend_types._Book(
                    book_id, name, path, pages, format, size, datetime.datetime.now().isoformat()
                )
                self.book_added(book)

            cursor.close()

            if collection is not None:
                self.add_book_to_collection(book_id, collection)

            return True
        except dbapi2.Error:
            log.error(_('! Could not add book "%s" to the library'), path)
            return False
Beispiel #21
0
    def get_book_cover(self, book):
        """Return a pixbuf with a thumbnail of the cover of <book>, or
        None if the cover can not be fetched.
        """
        try:
            path = self._con.execute(
                '''select path from Book
                where id = ?''', (book, )).fetchone()
        except Exception:
            log.error(_('! Non-existant book #%i'), book)
            return None

        return self.get_book_thumbnail(path)
Beispiel #22
0
def open_dialog(action, window):
    global _dialog

    if _dialog is None:

        if library_backend.dbapi2 is None:
            log.error( _('! You need an sqlite wrapper to use the library.') )

        else:
            _dialog = _LibraryDialog(window, window.filehandler)

    else:
        _dialog.present()
Beispiel #23
0
 def rename_collection(self, collection, name):
     """Rename the <collection> to <name>. Return True if the renaming
     was successful.
     """
     try:
         self._con.execute('''update Collection set name = ?
             where id = ?''', (name, collection))
         return True
     except dbapi2.DatabaseError: # E.g. name taken.
         pass
     except dbapi2.Error:
         log.error( _('! Could not rename collection to "%s"'), name )
     return False
Beispiel #24
0
def open_dialog(action, window):
    global _dialog

    if _dialog is None:

        if library_backend.dbapi2 is None:
            log.error( _('! You need an sqlite wrapper to use the library.') )

        else:
            _dialog = _LibraryDialog(window, window.filehandler)

    else:
        _dialog.present()
Beispiel #25
0
    def get_book_path(self, book):
        """Return the filesystem path to <book>, or None if <book> isn't
        in the library.
        """
        try:
            path = self._con.execute(
                '''select path from Book
                where id = ?''', (book, )).fetchone()
        except Exception:
            log.error(_('! Non-existant book #%i'), book)
            return None

        return path
Beispiel #26
0
def install_gettext():
    ''' Initialize gettext with the correct directory that contains
    MComix translations. This has to be done before any calls to gettext.gettext
    have been made to ensure all strings are actually translated. '''

    # Add the sources' base directory to PATH to allow development without
    # explicitly installing the package.
    sys.path.append(constants.BASE_PATH)

    # Initialize default locale
    locale.setlocale(locale.LC_ALL, '')

    lang_identifiers = []
    if preferences.prefs['language'] != 'auto':
        lang = preferences.prefs['language']
        if lang not in ('en', 'en_US'):
            # .mo is not needed for english
            lang_identifiers.append(lang)
    else:
        # Get the user's current locale
        lang = portability.get_default_locale()
        for s in gettext._expand_lang(lang):
            lang = s.split('.')[0]
            if lang in ('en', 'en_US'):
                # .mo is not needed for english
                continue
            if lang not in lang_identifiers:
                lang_identifiers.append(lang)

    # Make sure GTK uses the correct language.
    os.environ['LANGUAGE'] = lang

    domain = constants.APPNAME.lower()

    for lang in lang_identifiers:
        resource_path = tools.pkg_path('messages', lang, 'LC_MESSAGES',
                                       '%s.mo' % domain)
        try:
            with open(resource_path, mode='rb') as fp:
                translation = gettext.GNUTranslations(fp)
            break
        except IOError:
            log.error('locale file: %s not found.', resource_path)
    else:
        translation = gettext.NullTranslations()

    translation.install()

    global _translation
    _translation = translation
Beispiel #27
0
 def rename_collection(self, collection, name):
     """Rename the <collection> to <name>. Return True if the renaming
     was successful.
     """
     try:
         self._con.execute(
             '''update Collection set name = ?
             where id = ?''', (name, collection))
         return True
     except dbapi2.DatabaseError:  # E.g. name taken.
         pass
     except dbapi2.Error:
         log.error(_('! Could not rename collection to "%s"'), name)
     return False
Beispiel #28
0
    def migrate_database_to_library(self, recent_collection):
        """ Moves all information saved in the legacy database
        constants.LASTPAGE_DATABASE_PATH into the library,
        and deleting the old database. """

        if not self.backend.enabled:
            return

        database = self._init_database(constants.LASTPAGE_DATABASE_PATH)

        if database:
            cursor = database.execute('''SELECT path, page, time_set
                                         FROM lastread''')
            rows = cursor.fetchall()
            cursor.close()
            database.close()

            for path, page, time_set in rows:
                book = self.backend.get_book_by_path(path)

                if not book:
                    # The path doesn't exist in the library yet
                    if not os.path.exists(path):
                        # File might no longer be available
                        continue

                    self.backend.add_book(path, recent_collection)
                    book = self.backend.get_book_by_path(path)

                    if not book:
                        # The book could not be added
                        continue
                else:
                    # The book exists, move into recent collection
                    self.backend.add_book_to_collection(
                        book.id, recent_collection)

                # Set recent info on retrieved book
                # XXX: If the book calls get_backend during migrate_database,
                # the library isn't constructed yet and breaks in an
                # endless recursion.
                book.get_backend = lambda: self.backend
                book.set_last_read_page(page, time_set)

            try:
                os.unlink(constants.LASTPAGE_DATABASE_PATH)
            except IOError, e:
                log.error(_('! Could not remove file "%s"'),
                          constants.LASTPAGE_DATABASE_PATH)
Beispiel #29
0
    def migrate_database_to_library(self, recent_collection):
        """ Moves all information saved in the legacy database
        constants.LASTPAGE_DATABASE_PATH into the library,
        and deleting the old database. """

        if not self.backend.enabled:
            return

        database = self._init_database(constants.LASTPAGE_DATABASE_PATH)

        if database:
            cursor = database.execute(
                """SELECT path, page, time_set
                                         FROM lastread"""
            )
            rows = cursor.fetchall()
            cursor.close()
            database.close()

            for path, page, time_set in rows:
                book = self.backend.get_book_by_path(path)

                if not book:
                    # The path doesn't exist in the library yet
                    if not os.path.exists(path):
                        # File might no longer be available
                        continue

                    self.backend.add_book(path, recent_collection)
                    book = self.backend.get_book_by_path(path)

                    if not book:
                        # The book could not be added
                        continue
                else:
                    # The book exists, move into recent collection
                    self.backend.add_book_to_collection(book.id, recent_collection)

                # Set recent info on retrieved book
                # XXX: If the book calls get_backend during migrate_database,
                # the library isn't constructed yet and breaks in an
                # endless recursion.
                book.get_backend = lambda: self.backend
                book.set_last_read_page(page, time_set)

            try:
                os.unlink(constants.LASTPAGE_DATABASE_PATH)
            except IOError, e:
                log.error(_('! Could not remove file "%s"'), constants.LASTPAGE_DATABASE_PATH)
Beispiel #30
0
 def add_book_to_collection(self, book, collection):
     """Put <book> into <collection>."""
     try:
         self._con.execute(
             '''insert into Contain
             (collection, book) values (?, ?)''', (collection, book))
     except dbapi2.DatabaseError:  # E.g. book already in collection.
         pass
     except dbapi2.Error:
         log.error(
             _('! Could not add book %(book)s to collection %(collection)s'
               ), {
                   "book": book,
                   "collection": collection
               })
Beispiel #31
0
    def _completely_remove_book(self, request_response=True, *args):
        '''Remove the currently selected books from the library and the
        hard drive.
        '''

        if request_response:

            choice_dialog = message_dialog.MessageDialog(
                self._library,
                flags=Gtk.DialogFlags.MODAL,
                message_type=Gtk.MessageType.QUESTION,
                buttons=Gtk.ButtonsType.NONE)
            choice_dialog.add_buttons(Gtk.STOCK_CANCEL,
                                      Gtk.ResponseType.CANCEL,
                                      Gtk.STOCK_DELETE, Gtk.ResponseType.OK)
            choice_dialog.set_default_response(Gtk.ResponseType.OK)
            choice_dialog.set_should_remember_choice(
                'library-remove-book-from-disk', (Gtk.ResponseType.OK, ))
            choice_dialog.set_text(
                _('Delete selected books?'),
                _('The selected books will be permanently deleted from your drive.'
                  ))
            response = choice_dialog.run()

        # if no request is needed or the user has told us they definitely want to delete the book
        if not request_response or (request_response
                                    and response == Gtk.ResponseType.OK):

            # get the array of currently selected books in the book window
            selected_books = self._iconview.get_selected_items()
            book_ids = [self.get_book_at_path(book) for book in selected_books]
            paths = [
                self._library.backend.get_book_path(book_id)
                for book_id in book_ids
            ]

            # Remove books from library
            self._remove_books_from_library()

            # Remove from the harddisk
            for book_path in paths:
                try:
                    # try to delete the book.
                    # this can throw an exception if the path points to folder instead
                    # of a single file
                    os.remove(book_path)
                except Exception:
                    log.error(_('! Could not remove file "%s"'), book_path)
Beispiel #32
0
    def _get_pixbuf(self, index):
        """Return the pixbuf indexed by <index> from cache.
        Pixbufs not found in cache are fetched from disk first.
        """
        pixbuf = constants.MISSING_IMAGE_ICON

        if index not in self._raw_pixbufs:
            self._wait_on_page(index + 1)

            try:
                pixbuf = image_tools.load_pixbuf(self._image_files[index])
                self._raw_pixbufs[index] = pixbuf
                tools.garbage_collect()
            except Exception, e:
                self._raw_pixbufs[index] = constants.MISSING_IMAGE_ICON
                log.error("Could not load pixbuf for page %u: %r", index + 1, e)
Beispiel #33
0
 def add_book_to_collection(self, book, collection):
     """Put <book> into <collection>."""
     try:
         self._con.execute(
             """insert into Contain
             (collection, book) values (?, ?)""",
             (collection, book),
         )
         self.book_added_to_collection(self.get_book_by_id(book), collection)
     except dbapi2.DatabaseError:  # E.g. book already in collection.
         pass
     except dbapi2.Error:
         log.error(
             _("! Could not add book %(book)s to collection %(collection)s"),
             {"book": book, "collection": collection},
         )
Beispiel #34
0
    def _wait_on_file(self, path):
        """Block the running (main) thread if the file <path> is from an
        archive and has not yet been extracted. Return when the file is
        ready.
        """
        if self.archive_type == None or path == None:
            return

        try:
            name = self._name_table[path]
            with self._condition:
                while not self._extractor.is_ready(name) and not self._stop_waiting:
                    self._condition.wait()
        except Exception, ex:
            log.error(u'Waiting on extraction of "%s" failed: %s', path, ex)
            return
Beispiel #35
0
    def _get_pixbuf(self, index):
        """Return the pixbuf indexed by <index> from cache.
        Pixbufs not found in cache are fetched from disk first.
        """
        pixbuf = image_tools.MISSING_IMAGE_ICON

        if index not in self._raw_pixbufs:
            self._wait_on_page(index + 1)

            try:
                pixbuf = image_tools.load_pixbuf(self._image_files[index])
                self._raw_pixbufs[index] = pixbuf
                tools.garbage_collect()
            except Exception, e:
                self._raw_pixbufs[index] = image_tools.MISSING_IMAGE_ICON
                log.error('Could not load pixbuf for page %u: %r', index + 1, e)
Beispiel #36
0
    def _wait_on_file(self, path):
        """Block the running (main) thread if the file <path> is from an
        archive and has not yet been extracted. Return when the file is
        ready.
        """
        if self.archive_type == None or path == None:
            return

        try:
            name = self._name_table[path]
            with self._condition:
                while not self._extractor.is_ready(name) and not self._stop_waiting:
                    self._condition.wait()
        except Exception, ex:
            log.error(u'Waiting on extraction of "%s" failed: %s', path, ex)
            return
Beispiel #37
0
 def _cache_pixbuf(self, index, force=False):
     self._wait_on_page(index + 1)
     with self._cache_lock[index]:
         if index in self._raw_pixbufs:
             return
         with self._lock:
             if not force and index not in self._wanted_pixbufs:
                 return
         log.debug('Caching page %u', index + 1)
         try:
             pixbuf = image_tools.load_pixbuf(self._image_files[index])
             tools.garbage_collect()
         except Exception as e:
             log.error('Could not load pixbuf for page %u: %r', index + 1, e)
             pixbuf = image_tools.MISSING_IMAGE_ICON
         self._raw_pixbufs[index] = pixbuf
Beispiel #38
0
 def add_book_to_collection(self, book, collection):
     '''Put <book> into <collection>.'''
     try:
         self._con.execute(
             '''insert into Contain
             (collection, book) values (?, ?)''', (collection, book))
         self.book_added_to_collection(self.get_book_by_id(book),
                                       collection)
     except sqlite3.DatabaseError:  # E.g. book already in collection.
         pass
     except sqlite3.Error:
         log.error(
             _('! Could not add book %(book)s to collection %(collection)s'
               ), {
                   'book': book,
                   'collection': collection
               })
Beispiel #39
0
    def _extract_file(self, name):
        """Extract the file named <name> to the destination directory,
        mark the file as "ready", then signal a notify() on the Condition
        returned by setup().
        """

        try:
            log.debug(u'Extracting from "%s" to "%s": "%s"', self._src, self._dst, name)
            self._archive.extract(name, self._dst)

        except Exception, ex:
            # Better to ignore any failed extractions (e.g. from a corrupt
            # archive) than to crash here and leave the main thread in a
            # possible infinite block. Damaged or missing files *should* be
            # handled gracefully by the main program anyway.
            log.error(_('! Extraction error: %s'), ex)
            log.debug('Traceback:\n%s', traceback.format_exc())
Beispiel #40
0
    def _extract_file(self, name):
        """Extract the file named <name> to the destination directory,
        mark the file as "ready", then signal a notify() on the Condition
        returned by setup().
        """

        try:
            log.debug(u'Extracting from "%s" to "%s": "%s"', self._src,
                      self._dst, name)
            self._archive.extract(name, self._dst)

        except Exception, ex:
            # Better to ignore any failed extractions (e.g. from a corrupt
            # archive) than to crash here and leave the main thread in a
            # possible infinite block. Damaged or missing files *should* be
            # handled gracefully by the main program anyway.
            log.error(_('! Extraction error: %s'), ex)
            log.debug('Traceback:\n%s', traceback.format_exc())
    def read_fileinfo_file(self):
        '''Read last loaded file info from disk.'''

        fileinfo = None

        if os.path.isfile(constants.FILEINFO_JSON_PATH):
            try:
                with open(constants.FILEINFO_JSON_PATH,
                          mode='rt',
                          encoding='utf8') as config:
                    fileinfo = json.load(config)
            except Exception as ex:
                log.error(_('! Corrupt preferences file "%s", deleting...'),
                          constants.FILEINFO_JSON_PATH)
                log.info('Error was: %s', ex)
                os.remove(constants.FILEINFO_JSON_PATH)

        return fileinfo
Beispiel #42
0
 def _run(self):
     order = None
     while True:
         with self._condition:
             if order is not None:
                 self._processing_orders.remove(order)
             while not self._stop and 0 == len(self._waiting_orders):
                 self._condition.wait()
             if self._stop:
                 return
             order = self._waiting_orders.pop(0)
             self._processing_orders.append(order)
         try:
             self._process_order(order)
         except Exception, e:
             log.error(_('! Worker thread processing %(function)r failed: %(error)s'),
                       { 'function' : self._process_order, 'error' : e })
             log.debug('Traceback:\n%s', traceback.format_exc())
Beispiel #43
0
    def _completely_remove_book(self, request_response=True, *args):
        """Remove the currently selected books from the library and the
        hard drive.
        """

        if request_response:

            choice_dialog = message_dialog.MessageDialog(
                self._library, 0, Gtk.MessageType.QUESTION,
                Gtk.ButtonsType.YES_NO)
            choice_dialog.set_default_response(Gtk.ResponseType.YES)
            choice_dialog.set_should_remember_choice(
                'library-remove-book-from-disk', (Gtk.ResponseType.YES, ))
            choice_dialog.set_text(
                _('Remove books from the library?'),
                _('The selected books will be removed from the library and '
                  'permanently deleted. Are you sure that you want to continue?'
                  ))
            response = choice_dialog.run()

        # if no request is needed or the user has told us they definitely want to delete the book
        if not request_response or (request_response
                                    and response == Gtk.ResponseType.YES):

            # get the array of currently selected books in the book window
            selected_books = self._iconview.get_selected_items()
            book_ids = [self.get_book_at_path(book) for book in selected_books]
            paths = [
                self._library.backend.get_book_path(book_id)
                for book_id in book_ids
            ]

            # Remove books from library
            self._remove_books_from_library()

            # Remove from the harddisk
            for book_path in paths:
                try:
                    # try to delete the book.
                    # this can throw an exception if the path points to folder instead
                    # of a single file
                    os.remove(book_path)
                except Exception:
                    log.error(_('! Could not remove file "%s"'), book_path)
Beispiel #44
0
 def _run(self):
     order_uid = None
     while True:
         with self._condition:
             if order_uid is not None:
                 self._orders_set.remove(order_uid)
             while not self._stop and 0 == len(self._orders_queue):
                 self._condition.wait()
             if self._stop:
                 return
             order = self._orders_queue.pop(0)
             if self._unique_orders:
                 order_uid = self._order_uid(order)
         try:
             self._process_order(order)
         except Exception as e:
             log.error(_('! Worker thread processing %(function)r failed: %(error)s'),
                       { 'function' : self._process_order, 'error' : e })
             log.debug('Traceback:\n%s', traceback.format_exc())
Beispiel #45
0
    def _extract_file(self, name):
        """Extract the file named <name> to the destination directory,
        mark the file as "ready", then signal a notify() on the Condition
        returned by setup().
        """
        if self._stop:
            self.close()
            return

        try:
            dst_path = os.path.join(self._dst, name)
            log.debug(u'Extracting "%s" to "%s"', name, dst_path)
            self._archive.extract(name, dst_path)

        except Exception, ex:
            # Better to ignore any failed extractions (e.g. from a corrupt
            # archive) than to crash here and leave the main thread in a
            # possible infinite block. Damaged or missing files *should* be
            # handled gracefully by the main program anyway.
            log.error(_('! Extraction error: %s'), ex)
Beispiel #46
0
    def _initialize(self):
        """ Restore keybindings from disk. """
        try:
            with open(constants.KEYBINDINGS_CONF_PATH, "r") as fp:
                stored_action_bindings = json.load(fp)
        except Exception as e:
            log.error(_("Couldn't load keybindings: %s"), e)
            stored_action_bindings = {}

        for action in BINDING_INFO.keys():
            if action in stored_action_bindings:
                bindings = [
                    Gtk.accelerator_parse(keyname)
                    for keyname in stored_action_bindings[action]
                ]
                self._action_to_bindings[action] = bindings
                for binding in bindings:
                    self._binding_to_action[binding] = action
            else:
                self._action_to_bindings[action] = []
Beispiel #47
0
    def _extract_file(self, name):
        """Extract the file named <name> to the destination directory,
        mark the file as "ready", then signal a notify() on the Condition
        returned by setup().
        """
        if self._stop:
            self.close()
            return

        try:
            dst_path = os.path.join(self._dst, name)
            log.debug(u'Extracting "%s" to "%s"', name, dst_path)
            self._archive.extract(name, dst_path)

        except Exception, ex:
            # Better to ignore any failed extractions (e.g. from a corrupt
            # archive) than to crash here and leave the main thread in a
            # possible infinite block. Damaged or missing files *should* be
            # handled gracefully by the main program anyway.
            log.error(_('! Extraction error: %s'), ex)
Beispiel #48
0
    def _completely_remove_book(self, request_response=True, *args):
        """Remove the currently selected books from the library and the
        hard drive.
        """

        if request_response:

            choice_dialog = gtk.MessageDialog(self._library, 0,
                gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO)
            choice_dialog.set_markup('<span weight="bold" size="larger">' +
                _('Remove books from the library?') +
                '</span>'
            )
            choice_dialog.format_secondary_text(
                _('The selected books will be removed from the library and '
                  'permanently deleted. Are you sure that you want to continue?')
            )
            choice_dialog.set_default_response(gtk.RESPONSE_YES)
            response = choice_dialog.run()
            choice_dialog.destroy()

        # if no request is needed or the user has told us they definitely want to delete the book
        if not request_response or (request_response and response == gtk.RESPONSE_YES):

            # get the array of currently selected books in the book window
            selected_books = self._iconview.get_selected_items()
            book_ids = [ self.get_book_at_path(book) for book in selected_books ]
            paths = [ self._library.backend.get_book_path(book_id) for book_id in book_ids ]

            # Remove books from library
            self._remove_books_from_library()

            # Remove from the harddisk
            for book_path in paths:
                try:
                    # try to delete the book.
                    # this can throw an exception if the path points to folder instead
                    # of a single file
                    os.remove(book_path)
                except Exception:
                    log.error(_('! Could not remove file "%s"') % book_path)
Beispiel #49
0
    def read_fileinfo_file(self):
        """Read last loaded file info from disk."""

        fileinfo = None

        if os.path.isfile(constants.FILEINFO_PICKLE_PATH):
            config = None
            try:
                config = open(constants.FILEINFO_PICKLE_PATH, 'rb')

                fileinfo = cPickle.load(config)

                config.close()

            except Exception, ex:
                log.error(_('! Corrupt preferences file "%s", deleting...'),
                          constants.FILEINFO_PICKLE_PATH)
                log.info(u'Error was: %s', ex)
                if config is not None:
                    config.close()
                os.remove(constants.FILEINFO_PICKLE_PATH)
Beispiel #50
0
    def read_fileinfo_file(self):
        """Read last loaded file info from disk."""

        fileinfo = None

        if os.path.isfile(constants.FILEINFO_PICKLE_PATH):
            config = None
            try:
                config = open(constants.FILEINFO_PICKLE_PATH, 'rb')

                fileinfo = cPickle.load(config)

                config.close()

            except Exception, ex:
                log.error(_('! Corrupt preferences file "%s", deleting...'),
                        constants.FILEINFO_PICKLE_PATH )
                log.info(u'Error was: %s', ex)
                if config is not None:
                    config.close()
                os.remove(constants.FILEINFO_PICKLE_PATH)
Beispiel #51
0
    def __run_callbacks(self, *args, **kwargs):
        """ Executes callback functions. """
        for obj_ref, func in self.__callbacks:

            if obj_ref is None:
                # Callback is a normal function
                callback = func
            elif obj_ref() is not None:
                # Callback is a bound method.
                # Recreate it by binding the function to the object.
                callback = func.__get__(obj_ref())
            else:
                # Callback is a bound method, object
                # no longer exists.
                callback = None

            if callback:
                try:
                    callback(*args, **kwargs)
                except Exception, e:
                    log.error(_('! Callback %(function)r failed: %(error)s'),
                              { 'function' : callback, 'error' : e })
Beispiel #52
0
 def iter_contents(self):
     """ List archive contents. """
     self._close()
     self._open()
     try:
         while True:
             self._read_header()
             if 0 != (0x10 & self._headerdata.Flags):
                 self._is_solid = True
             filename = self._current_filename
             yield filename
             # Skip to the next entry if we're still on the same name
             # (extract may have been called by iter_extract).
             if filename == self._current_filename:
                 self._process()
     except UnrarException as exc:
         log.error("Error while listing contents: %s", str(exc))
     except EOFError:
         # End of archive reached.
         pass
     finally:
         self._close()
Beispiel #53
0
 def iter_contents(self):
     ''' List archive contents. '''
     self._close()
     self._open()
     try:
         while True:
             self._read_header()
             if 0 != (0x10 & self._headerdata.Flags):
                 self._is_solid = True
             filename = self._current_filename
             yield filename
             # Skip to the next entry if we're still on the same name
             # (extract may have been called by iter_extract).
             if filename == self._current_filename:
                 self._process()
     except UnrarException as exc:
         log.error('Error while listing contents: %s', str(exc))
     except EOFError:
         # End of archive reached.
         pass
     finally:
         self._close()
Beispiel #54
0
    def _extract_all_files(self, files):

        # With multiple extractions for each pass, some of the files might have
        # already been extracted.
        with self._condition:
            files = list(set(files) - self._extracted)
            files.sort()

        try:
            log.debug(u'Extracting from "%s" to "%s": "%s"', self._src, self._dst, '", "'.join(files))
            for f in self._archive.iter_extract(files, self._dst):
                if self._extract_thread.must_stop():
                    return
                self._extraction_finished(f)

        except Exception, ex:
            # Better to ignore any failed extractions (e.g. from a corrupt
            # archive) than to crash here and leave the main thread in a
            # possible infinite block. Damaged or missing files *should* be
            # handled gracefully by the main program anyway.
            log.error(_('! Extraction error: %s'), ex)
            log.debug('Traceback:\n%s', traceback.format_exc())
Beispiel #55
0
def open_dialog(action, window):
    """ Shows the library window. If sqlite is not available, this method
    does nothing and returns False. Otherwise, True is returned. """
    global _dialog

    if _dialog is None:

        if library_backend.dbapi2 is None:
            text = _('! You need an sqlite wrapper to use the library.')
            window.osd.show(text)
            log.error(text)
            return False

        else:
            _dialog = _LibraryDialog(window, window.filehandler)

    else:
        _dialog.present()

    if prefs['scan for new books on library startup']:
        _dialog.scan_for_new_files()

    return True
Beispiel #56
0
    def load_bookmarks(self):
        """ Loads persisted bookmarks from a local file.
        @return: Tuple of (bookmarks, file mtime)
        """

        path = constants.BOOKMARK_PICKLE_PATH
        bookmarks = []
        mtime = 0L

        if os.path.isfile(path):
            fd = None
            try:
                mtime = long(os.stat(path).st_mtime)
                fd = open(path, 'rb')
                version = cPickle.load(fd)
                packs = cPickle.load(fd)

                for pack in packs:
                    # Handle old bookmarks without date_added attribute
                    if len(pack) == 5:
                        pack = pack + (datetime.datetime.now(),)

                    bookmark = bookmark_menu_item._Bookmark(self._window,
                            self._file_handler, *pack)
                    bookmarks.append(bookmark)

            except Exception:
                log.error(_('! Could not parse bookmarks file %s'), path)
            finally:
                try:
                    if fd:
                        fd.close()
                except IOError:
                    pass

        return bookmarks, mtime
Beispiel #57
0
def run():
    """Run the program."""

    try:
        import pkg_resources

    except ImportError:
        # gettext isn't initialized yet, since pkg_resources is required to find translation files.
        # Thus, localizing these messages is pointless.
        log._print("The package 'pkg_resources' could not be found.")
        log._print("You need to install the 'setuptools' package, which also includes pkg_resources.")
        log._print("Note: On most distributions, 'distribute' supersedes 'setuptools'.")
        wait_and_exit()

    # Load configuration and setup localisation.
    preferences.read_preferences_file()
    from mcomix import i18n
    i18n.install_gettext()

    # Retrieve and parse command line arguments.
    argv = portability.get_commandline_args()
    opts, args = parse_arguments(argv)

    # First things first: set the log level.
    log.setLevel(opts.loglevel)

    # On Windows, update the fontconfig cache manually, before MComix starts
    # using Gtk, since the process may take several minutes, during which the
    # main window will just be frozen if the work is left to Gtk itself...
    if opts.update_fontconfig_cache:
        # First, update fontconfig cache.
        log.debug('starting fontconfig cache update')
        try:
            from mcomix.win32 import fc_cache
            from mcomix import process
            fc_cache.update()
            log.debug('fontconfig cache updated')
        except Exception as e:
            log.error('during fontconfig cache update', exc_info=e)
        # And then replace current MComix process with a fresh one
        # (that will not try to update the cache again).
        exe = sys.argv[0]
        if sys.platform == 'win32' and exe.endswith('.py'):
            # Find the interpreter.
            exe = process.find_executable(('pythonw.exe', 'python.exe'))
            args = [exe, sys.argv[0]]
        else:
            args = [exe]
        if sys.platform == 'win32':
            args.append('--no-update-fontconfig-cache')
        args.extend(argv)
        if '--update-fontconfig-cache' in args:
            args.remove('--update-fontconfig-cache')
        log.debug('restarting MComix from fresh: os.execv(%s, %s)', repr(exe), args)
        try:
            if sys.platform == 'win32':
                # Of course we can't use os.execv on Windows because it will
                # mangle arguments containing spaces or non-ascii characters...
                process.Win32Popen(args)
                sys.exit(0)
            else:
                os.execv(exe, args)
        except Exception as e:
            log.error('os.execv(%s, %s) failed', exe, str(args), exc_info=e)
        wait_and_exit()

    # Check for PyGTK and PIL dependencies.
    try:
        import pygtk
        pygtk.require('2.0')

        import gtk
        assert gtk.gtk_version >= (2, 12, 0)
        assert gtk.pygtk_version >= (2, 12, 0)

        import gobject
        gobject.threads_init()

    except AssertionError:
        log.error( _("You do not have the required versions of GTK+ and PyGTK installed.") )
        log.error( _('Installed GTK+ version is: %s') % \
                  '.'.join([str(n) for n in gtk.gtk_version]) )
        log.error( _('Required GTK+ version is: 2.12.0 or higher') )
        log.error( _('Installed PyGTK version is: %s') % \
                  '.'.join([str(n) for n in gtk.pygtk_version]) )
        log.error( _('Required PyGTK version is: 2.12.0 or higher') )
        wait_and_exit()

    except ImportError:
        log.error( _('Required PyGTK version is: 2.12.0 or higher') )
        log.error( _('No version of PyGTK was found on your system.') )
        log.error( _('This error might be caused by missing GTK+ libraries.') )
        wait_and_exit()

    try:
        import PIL.Image
        assert PIL.Image.VERSION >= '1.1.5'

    except AssertionError:
        log.error( _("You don't have the required version of the Python Imaging"), end=' ')
        log.error( _('Library (PIL) installed.') )
        log.error( _('Installed PIL version is: %s') % Image.VERSION )
        log.error( _('Required PIL version is: 1.1.5 or higher') )
        wait_and_exit()

    except ImportError:
        log.error( _('Python Imaging Library (PIL) 1.1.5 or higher is required.') )
        log.error( _('No version of the Python Imaging Library was found on your system.') )
        wait_and_exit()

    if not os.path.exists(constants.DATA_DIR):
        os.makedirs(constants.DATA_DIR, 0700)

    if not os.path.exists(constants.CONFIG_DIR):
        os.makedirs(constants.CONFIG_DIR, 0700)

    from mcomix import icons
    icons.load_icons()

    open_path = None
    open_page = 1
    if len(args) == 1:
        open_path = args[0]
    elif len(args) > 1:
        open_path = args

    elif preferences.prefs['auto load last file'] \
        and preferences.prefs['path to last file'] \
        and os.path.isfile(preferences.prefs['path to last file']):
        open_path = preferences.prefs['path to last file']
        open_page = preferences.prefs['page of last file']

    # Some languages require a RTL layout
    if preferences.prefs['language'] in ('he', 'fa'):
        gtk.widget_set_default_direction(gtk.TEXT_DIR_RTL)

    gtk.gdk.set_program_class(constants.APPNAME)

    from mcomix import main
    window = main.MainWindow(fullscreen = opts.fullscreen, is_slideshow = opts.slideshow,
            show_library = opts.library, manga_mode = opts.manga,
            double_page = opts.doublepage, zoom_mode = opts.zoommode,
            open_path = open_path, open_page = open_page)
    main.set_main_window(window)

    if 'win32' != sys.platform:
        # Add a SIGCHLD handler to reap zombie processes.
        def on_sigchld(signum, frame):
            try:
                os.waitpid(-1, os.WNOHANG)
            except OSError:
                pass
        signal.signal(signal.SIGCHLD, on_sigchld)

    signal.signal(signal.SIGTERM, lambda: gobject.idle_add(window.terminate_program))
    try:
        gtk.main()
    except KeyboardInterrupt: # Will not always work because of threading.
        window.terminate_program()
Beispiel #58
0
    def _thread_pack(self):
        try:
            zfile = zipfile.ZipFile(self._archive_path, 'w')
        except Exception:
            log.error(_('! Could not create archive at path "%s"'),
                      self._archive_path)
            return

        used_names = []
        pattern = '%%0%dd - %s%%s' % (len(str(len(self._image_files))),
            self._base_name)

        for i, path in enumerate(self._image_files):
            filename = pattern % (i + 1, os.path.splitext(path)[1])

            try:
                zfile.write(path, filename, zipfile.ZIP_STORED)
            except Exception:
                log.error(_('! Could not add file %(sourcefile)s '
                            'to archive %(archivefile)s, aborting...'),
                          { "sourcefile" : path,
                            "archivefile" : self._archive_path})

                zfile.close()

                try:
                    os.remove(self._archive_path)
                except:
                    pass

                return

            used_names.append(filename)

        for path in self._other_files:
            filename = os.path.basename(path)

            while filename in used_names:
                filename = '_%s' % filename

            try:
                zfile.write(path, filename, zipfile.ZIP_DEFLATED)
            except Exception:
                log.error(_('! Could not add file %(sourcefile)s '
                            'to archive %(archivefile)s, aborting...'),
                          { "sourcefile" : path,
                            "archivefile" : self._archive_path})

                zfile.close()

                try:
                    os.remove(self._archive_path)
                except:
                    pass

                return

            used_names.append(filename)

        zfile.close()
        self._packing_successful = True