def _exec(self): """Spawns the process, and returns its stdout. (NOTE: separate function to make python2.4 exception syntax happy) """ try: # Cannot spawn processes with PythonW/Win32 unless stdin and # stderr are redirected to a pipe as well. self._proc = subprocess.Popen( self._args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=self._startupinfo(), ) return self._proc.stdout except Exception, ex: cmd = len(self._args) > 0 and self._args[0] or "<invalid>" log.info( ( _('! Error spawning process "%(command)s": %(error)s.') + u" " + _('"%(command)s" must be on your system PATH to be found.') ) % {"command": unicode(cmd, errors="replace"), "error": unicode(str(ex), errors="replace")} ) return None
def _upgrade_database(self, from_version, to_version): """ Performs sequential upgrades to the database, bringing it from C{from_version} to C{to_version}. If C{from_version} is -1, the database structure will simply be re-created at the current version. """ if from_version == -1: self._create_tables() return if from_version != to_version: upgrades = range(from_version, to_version) log.info(_("Upgrading library database version from %(from)d to %(to)d."), { "from" : from_version, "to" : to_version }) if 0 in upgrades: # Upgrade from Comix database structure to DB version 1 # (Added table 'info') self._create_table_info() if 1 in upgrades: # Upgrade to database structure version 2. # (Added table 'watchlist' for storing auto-add directories) self._create_table_watchlist() self._con.execute('''update info set value = ? where key = 'version' ''', (str(_LibraryBackend.DB_VERSION),))
def _upgrade_database(self, from_version, to_version): """ Performs sequential upgrades to the database, bringing it from C{from_version} to C{to_version}. If C{from_version} is -1, the database structure will simply be re-created at the current version. """ if from_version == -1: self._create_tables() return if from_version != to_version: upgrades = range(from_version, to_version) log.info( _("Upgrading library database version from %(from)d to %(to)d."), {"from": from_version, "to": to_version}, ) if 0 in upgrades: # Upgrade from Comix database structure to DB version 1 # (Added table 'info') self._create_table_info() if 1 in upgrades: # Upgrade to database structure version 2. # (Added table 'watchlist' for storing auto-add directories) self._create_table_watchlist() if 2 in upgrades: # Changed 'added' field in 'book' from date to datetime. self._con.execute("""alter table book rename to book_old""") self._create_table_book() self._con.execute( """insert into book (id, name, path, pages, format, size, added) select id, name, path, pages, format, size, datetime(added) from book_old""" ) self._con.execute("""drop table book_old""") if 3 in upgrades: # Added field 'recursive' to table 'watchlist' self._con.execute("""alter table watchlist rename to watchlist_old""") self._create_table_watchlist() self._con.execute( """insert into watchlist (path, collection, recursive) select path, collection, 0 from watchlist_old""" ) self._con.execute("""drop table watchlist_old""") if 4 in upgrades: # Added table 'recent' to store recently viewed book information and # create a collection (-2, Recent) self._create_table_recent() lastread = last_read_page.LastReadPage(self) lastread.migrate_database_to_library(COLLECTION_RECENT) self._con.execute( """update info set value = ? where key = 'version' """, (str(_LibraryBackend.DB_VERSION),) )
def is_available(): global _pdf_possible if _pdf_possible is None: proc = process.Process(['mudraw']) fd = proc.spawn() if fd is not None: fd.close() proc.wait() _pdf_possible = True else: log.info('MuPDF not available.') _pdf_possible = False return _pdf_possible
def is_available(): global _pdf_possible if _pdf_possible is not None: return _pdf_possible global _mutool_exec, _mudraw_exec, _mudraw_trace_args mutool = process.find_executable((u"mutool",)) _pdf_possible = False version = None if mutool is None: log.debug("mutool executable not found") else: _mutool_exec = [mutool] # Find MuPDF version; assume 1.6 version since # the '-v' switch is only supported from 1.7 onward... version = "1.6" proc = process.popen([mutool, "-v"], stdout=process.NULL, stderr=process.PIPE) try: output = proc.stderr.read() if output.startswith("mutool version "): version = output[15:].rstrip() finally: proc.stderr.close() proc.wait() version = LooseVersion(version) if version >= LooseVersion("1.8"): # Mutool executable with draw support. _mudraw_exec = [mutool, "draw"] _mudraw_trace_args = ["-F", "trace"] _pdf_possible = True else: # Separate mudraw executable. mudraw = process.find_executable((u"mudraw",)) if mudraw is None: log.debug("mudraw executable not found") else: _mudraw_exec = [mudraw] if version >= LooseVersion("1.7"): _mudraw_trace_args = ["-F", "trace"] else: _mudraw_trace_args = ["-x"] _pdf_possible = True if _pdf_possible: log.info("Using MuPDF version: %s", version) log.debug("mutool: %s", " ".join(_mutool_exec)) log.debug("mudraw: %s", " ".join(_mudraw_exec)) log.debug("mudraw trace arguments: %s", " ".join(_mudraw_trace_args)) else: log.info("MuPDF not available.") return _pdf_possible
def _exec(self, stdin, stderr): """Spawns the process, and returns its stdout. (NOTE: separate function to make python2.4 exception syntax happy) """ try: self._proc = subprocess.Popen(self._args, stdout=subprocess.PIPE, stdin=stdin, stderr=stderr, startupinfo=self._startupinfo()) return self._proc.stdout except Exception, ex: cmd = len(self._args) > 0 and self._args[0] or "<invalid>" log.info(( _('! Error spawning process "%(command)s": %(error)s.') + u' ' + _('"%(command)s" must be on your system PATH to be found.')) % { 'command' : unicode(cmd, errors='replace'), 'error' : unicode(str(ex), errors='replace')}) return None
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
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)
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)
def _upgrade_database(self, from_version, to_version): ''' Performs sequential upgrades to the database, bringing it from C{from_version} to C{to_version}. If C{from_version} is -1, the database structure will simply be re-created at the current version. ''' if from_version < 5: self._create_tables() return if from_version != to_version: upgrades = range(from_version, to_version) log.info( _('Upgrading library database version from %(from)d to %(to)d.' ), { 'from': from_version, 'to': to_version }) if 5 in upgrades: # Changed all 'string' columns into 'text' columns self._con.execute('''alter table book rename to book_old''') self._create_table_book() self._con.execute('''insert into book (id, name, path, pages, format, size, added) select id, name, path, pages, format, size, added from book_old''' ) self._con.execute('''drop table book_old''') self._con.execute( '''alter table collection rename to collection_old''') self._create_table_collection() self._con.execute('''insert into collection (id, name, supercollection) select id, name, supercollection from collection_old''') self._con.execute('''drop table collection_old''') self._con.execute( '''update info set value = ? where key = 'version' ''', (str(_LibraryBackend.DB_VERSION), ))
def get_archive_handler(path, type=None): """ Returns a fitting extractor handler for the archive passed in <path> (with optional mime type <type>. Returns None if no matching extractor was found. """ if type is None: type = archive_mime_type(path) if type == constants.ZIP: return zip.ZipArchive(path) elif type == constants.ZIP_EXTERNAL and zip_external.ZipExecArchive.is_available(): return zip_external.ZipExecArchive(path) elif type == constants.ZIP_EXTERNAL and sevenzip.SevenZipArchive.is_available(): log.info('Using Sevenzip for unsupported zip archives.') return sevenzip.SevenZipArchive(path) elif type in (constants.TAR, constants.GZIP, constants.BZIP2): return tar.TarArchive(path) elif type == constants.RAR and rar.RarArchive.is_available(): return rar.RarArchive(path) elif type == constants.RAR and sevenzip.SevenZipArchive.is_available(): log.info('Using Sevenzip for RAR archives.') return sevenzip.SevenZipArchive(path) elif type == constants.SEVENZIP and sevenzip.SevenZipArchive.is_available(): return sevenzip.SevenZipArchive(path) elif type == constants.LHA and lha.LhaArchive.is_available(): return lha.LhaArchive(path) elif type == constants.LHA and sevenzip.SevenZipArchive.is_available(): log.info('Using Sevenzip for LHA archives.') return sevenzip.SevenZipArchive(path) elif type == constants.PDF and pdf.PdfArchive.is_available(): return pdf.PdfArchive(path) else: return None
def _upgrade_database(self, from_version, to_version): """ Performs sequential upgrades to the database, bringing it from C{from_version} to C{to_version}. If C{from_version} is -1, the database structure will simply be re-created at the current version. """ if from_version == -1: self._create_tables() return if from_version != to_version: upgrades = range(from_version, to_version) log.info(_("Upgrading library database version from %(from)d to %(to)d."), { "from" : from_version, "to" : to_version }) if 0 in upgrades: # Upgrade from Comix database structure to DB version 1 # (Added table 'info') self._create_table_info() if 1 in upgrades: # Upgrade to database structure version 2. # (Added table 'watchlist' for storing auto-add directories) self._create_table_watchlist() if 2 in upgrades: # Changed 'added' field in 'book' from date to datetime. self._con.execute('''alter table book rename to book_old''') self._create_table_book() self._con.execute('''insert into book (id, name, path, pages, format, size, added) select id, name, path, pages, format, size, datetime(added) from book_old''') self._con.execute('''drop table book_old''') self._con.execute('''update info set value = ? where key = 'version' ''', (str(_LibraryBackend.DB_VERSION),))
def get_archive_handler(path): """ Returns a fitting extractor handler for the archive passed in <path>. Returns None if no matching extractor was found. """ mime = archive_mime_type(path) if mime == constants.ZIP: return zip.ZipArchive(path) elif mime in (constants.TAR, constants.GZIP, constants.BZIP2): return tar.TarArchive(path) elif mime == constants.RAR and rar.RarArchive.is_available(): return rar.RarArchive(path) elif mime == constants.RAR and sevenzip.SevenZipArchive.is_available(): log.info('Using Sevenzip for RAR archives.') return sevenzip.SevenZipArchive(path) elif mime == constants.SEVENZIP and sevenzip.SevenZipArchive.is_available(): return sevenzip.SevenZipArchive(path) elif mime == constants.LHA and lha.LhaArchive.is_available(): return lha.LhaArchive(path) elif mime == constants.LHA and sevenzip.SevenZipArchive.is_available(): log.info('Using Sevenzip for LHA archives.') return sevenzip.SevenZipArchive(path) else: return None
def get_archive_handler(path, type=None): """ Returns a fitting extractor handler for the archive passed in <path> (with optional mime type <type>. Returns None if no matching extractor was found. """ if type is None: type = archive_mime_type(path) if type == constants.ZIP: return zip.ZipArchive(path) elif type == constants.ZIP_EXTERNAL and zip_external.ZipExecArchive.is_available( ): return zip_external.ZipExecArchive(path) elif type == constants.ZIP_EXTERNAL and sevenzip.SevenZipArchive.is_available( ): log.info('Using Sevenzip for unsupported zip archives.') return sevenzip.SevenZipArchive(path) elif type in (constants.TAR, constants.GZIP, constants.BZIP2): return tar.TarArchive(path) elif type == constants.RAR and rar.RarArchive.is_available(): return rar.RarArchive(path) elif type == constants.RAR and sevenzip.SevenZipArchive.is_available(): log.info('Using Sevenzip for RAR archives.') return sevenzip.SevenZipArchive(path) elif type == constants.SEVENZIP and sevenzip.SevenZipArchive.is_available( ): return sevenzip.SevenZipArchive(path) elif type == constants.LHA and lha.LhaArchive.is_available(): return lha.LhaArchive(path) elif type == constants.LHA and sevenzip.SevenZipArchive.is_available(): log.info('Using Sevenzip for LHA archives.') return sevenzip.SevenZipArchive(path) elif type == constants.PDF and pdf.PdfArchive.is_available(): return pdf.PdfArchive(path) else: return None
from mcomix.preferences import prefs from mcomix import constants from mcomix import log if sys.platform == 'win32': # Works around GTK's slowness on Win32 by using PIL # for loading instead and converting it afterwards. # NOTE: Using False here should work fine for Windows, too. USE_PIL = False else: USE_PIL = False log.info('Using %s for loading images (versions: %s [%s], GDK [%s])', 'PIL' if USE_PIL else 'GDK', PIL_VERSION[0], PIL_VERSION[1], # Unfortunately gdk_pixbuf_version is not exported, # so show the GTK+ version instead. 'GTK+ ' + '.'.join(map(str, gtk.gtk_version))) def rotate_pixbuf(src, rotation): rotation %= 360 if 0 == rotation: return src if 90 == rotation: return src.rotate_simple(gtk.gdk.PIXBUF_ROTATE_CLOCKWISE) if 180 == rotation: return src.rotate_simple(gtk.gdk.PIXBUF_ROTATE_UPSIDEDOWN) if 270 == rotation: return src.rotate_simple(gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE) raise ValueError("unsupported rotation: %s" % rotation)
try: from PIL import PILLOW_VERSION PIL_VERSION = ('Pillow', PILLOW_VERSION) except ImportError: from PIL import VERSION as PIL_VERSION PIL_VERSION = ('PIL', PIL_VERSION) from cStringIO import StringIO from mcomix.preferences import prefs from mcomix import constants from mcomix import log from mcomix import tools # Unfortunately gdk_pixbuf_version is not exported, so show the GTK+ version instead. log.info('GDK version: %s', 'GTK+ ' + '.'.join(map(str, gtk.gtk_version))) log.info('PIL version: %s [%s]', PIL_VERSION[0], PIL_VERSION[1]) # Fallback pixbuf for missing images. MISSING_IMAGE_ICON = None _missing_icon_dialog = gtk.Dialog() _missing_icon_pixbuf = _missing_icon_dialog.render_icon( gtk.STOCK_MISSING_IMAGE, gtk.ICON_SIZE_LARGE_TOOLBAR) MISSING_IMAGE_ICON = _missing_icon_pixbuf assert MISSING_IMAGE_ICON GTK_GDK_COLOR_BLACK = gtk.gdk.color_parse('black') GTK_GDK_COLOR_WHITE = gtk.gdk.color_parse('white')
def _upgrade_database(self, from_version, to_version): """ Performs sequential upgrades to the database, bringing it from C{from_version} to C{to_version}. If C{from_version} is -1, the database structure will simply be re-created at the current version. """ if from_version == -1: self._create_tables() return if from_version != to_version: upgrades = range(from_version, to_version) log.info( _("Upgrading library database version from %(from)d to %(to)d." ), { "from": from_version, "to": to_version }) if 0 in upgrades: # Upgrade from Comix database structure to DB version 1 # (Added table 'info') self._create_table_info() if 1 in upgrades: # Upgrade to database structure version 2. # (Added table 'watchlist' for storing auto-add directories) self._create_table_watchlist() if 2 in upgrades: # Changed 'added' field in 'book' from date to datetime. self._con.execute('''alter table book rename to book_old''') self._create_table_book() self._con.execute('''insert into book (id, name, path, pages, format, size, added) select id, name, path, pages, format, size, datetime(added) from book_old''') self._con.execute('''drop table book_old''') if 3 in upgrades: # Added field 'recursive' to table 'watchlist' self._con.execute( '''alter table watchlist rename to watchlist_old''') self._create_table_watchlist() self._con.execute('''insert into watchlist (path, collection, recursive) select path, collection, 0 from watchlist_old''') self._con.execute('''drop table watchlist_old''') if 4 in upgrades: # Added table 'recent' to store recently viewed book information and # create a collection (-2, Recent) self._create_table_recent() lastread = last_read_page.LastReadPage(self) lastread.migrate_database_to_library(COLLECTION_RECENT) if 5 in upgrades: # Changed all 'string' columns into 'text' columns self._con.execute('''alter table book rename to book_old''') self._create_table_book() self._con.execute('''insert into book (id, name, path, pages, format, size, added) select id, name, path, pages, format, size, added from book_old''' ) self._con.execute('''drop table book_old''') self._con.execute( '''alter table collection rename to collection_old''') self._create_table_collection() self._con.execute('''insert into collection (id, name, supercollection) select id, name, supercollection from collection_old''') self._con.execute('''drop table collection_old''') self._con.execute( '''update info set value = ? where key = 'version' ''', (str(_LibraryBackend.DB_VERSION), ))
def run(): '''Run the program.''' # Load configuration and setup localisation. preferences.read_preferences_file() from mcomix import i18n i18n.install_gettext() # Retrieve and parse command line arguments. args = parse_arguments() if args.version: print_version() # First things first: set the log level. log.setLevel(log.levels[args.loglevel]) # Check Python version try: assert sys.version_info[:3] >= constants.REQUIRED_PYTHON_VERSION except AssertionError: log.error(_('You don\'t have the required version of the Python installed.')) log.error(_('Installed Python version is: %s') % '.'.join(str(n) for n in sys.version_info)) log.error(_('Required Python version is: %s or higher') % '.'.join(str(n) for n in constants.REQUIRED_PYTHON_VERSION)) wait_and_exit() # Check for PyGTK and PIL dependencies. try: from gi import version_info as gi_version_info if gi_version_info < (3, 30, 0): log.error(_('You do not have the required versions of PyGObject installed.')) wait_and_exit() from gi import require_version require_version('PangoCairo', '1.0') require_version('Gtk', '3.0') require_version('Gdk', '3.0') from gi.repository import Gdk, GdkPixbuf, Gtk, GLib if (Gtk.get_major_version(), Gtk.get_minor_version()) < (3, 24): log.error(_('You do not have the required versions of GTK+ 3 gir bindings installed.')) wait_and_exit() except ValueError: log.error(_('You do not have the required versions of GTK+ 3.0 installed.')) wait_and_exit() except ImportError: log.error(_('No version of GObject was found on your system.')) log.error(_('This error might be caused by missing GTK+ libraries.')) wait_and_exit() try: import PIL assert [int(n) for n in PIL.__version__.split('.')[:3]] >= [int(n) for n in constants.REQUIRED_PIL_VERSION.split('.')] except AttributeError: log.error(_('You don\'t have the required version of the Pillow installed.')) log.error(_('Required Pillow version is: %s or higher') % constants.REQUIRED_PIL_VERSION) wait_and_exit() except ValueError: log.error(_('Unrecognized Pillow version: %s') % PIL.__version__) log.error(_('Required Pillow version is: %s or higher') % constants.REQUIRED_PIL_VERSION) wait_and_exit() except ImportError: log.error(_('Pillow %s or higher is required.') % constants.REQUIRED_PIL_VERSION) log.error(_('No version of the Pillow was found on your system.')) wait_and_exit() except AssertionError: log.error(_('You don\'t have the required version of the Pillow installed.')) log.error(_('Installed PIL version is: %s') % PIL.__version__) log.error(_('Required Pillow version is: %s or higher') % constants.REQUIRED_PIL_VERSION) wait_and_exit() log.info('Image loaders: Pillow [%s], GDK [%s])', PIL.__version__,GdkPixbuf.PIXBUF_VERSION) if not os.path.exists(constants.DATA_DIR): os.makedirs(constants.DATA_DIR, 0o700) if not os.path.exists(constants.CONFIG_DIR): os.makedirs(constants.CONFIG_DIR, 0o700) from mcomix import icons icons.load_icons() open_path = args.path or None open_page = 1 if isinstance(open_path, list): n = 0 while n<len(open_path): p = os.path.join(constants.STARTDIR, open_path[n]) p = os.path.normpath(p) if not os.path.exists(p): log.error(_('{} not exists.').format(p)) open_path.pop(n) continue open_path[n] = p n += 1 if not open_path: open_path = None if not open_path and 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.TextDirection.RTL) Gdk.set_program_class(constants.APPNAME) settings = Gtk.Settings.get_default() # Enable icons for menu items. settings.props.gtk_menu_images = True from mcomix import main window = main.MainWindow(fullscreen = args.fullscreen, is_slideshow = args.slideshow, show_library = args.library, manga_mode = args.manga, double_page = args.doublepage, zoom_mode = args.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) for sig in (signal.SIGINT, signal.SIGTERM): signal.signal(sig, lambda signum, stack: GLib.idle_add(window.terminate_program)) try: Gtk.main() except KeyboardInterrupt: # Will not always work because of threading. window.terminate_program()
from mcomix.preferences import prefs from mcomix import constants from mcomix import log if sys.platform == 'win32': # Works around GTK's slowness on Win32 by using PIL # for loading instead and converting it afterwards. # NOTE: Using False here should work fine for Windows, too. USE_PIL = False else: USE_PIL = False log.info('Using %s for loading images (versions: %s [%s], GDK [%s])', 'PIL' if USE_PIL else 'GDK', PIL_VERSION[0], PIL_VERSION[1], # Unfortunately gdk_pixbuf_version is not exported, # so show the GTK+ version instead. 'GTK+ ' + '.'.join(map(str, gtk.gtk_version))) # Fallback pixbuf for missing images. MISSING_IMAGE_ICON = None _missing_icon_dialog = gtk.Dialog() _missing_icon_pixbuf = _missing_icon_dialog.render_icon( gtk.STOCK_MISSING_IMAGE, gtk.ICON_SIZE_LARGE_TOOLBAR) MISSING_IMAGE_ICON = _missing_icon_pixbuf assert MISSING_IMAGE_ICON GTK_GDK_COLOR_BLACK = gtk.gdk.color_parse('black') GTK_GDK_COLOR_WHITE = gtk.gdk.color_parse('white')
from PIL import ImageOps from PIL.JpegImagePlugin import _getexif try: from PIL import PILLOW_VERSION PIL_VERSION = ('Pillow', PILLOW_VERSION) except ImportError: from PIL import VERSION as PIL_VERSION PIL_VERSION = ('PIL', PIL_VERSION) from io import BytesIO from functools import reduce from mcomix.preferences import prefs from mcomix import constants from mcomix import log log.info('Using %s for loading images (versions: PIL [%s], GDK [%s])', PIL_VERSION[0], PIL_VERSION[1], GdkPixbuf.PIXBUF_VERSION) # Fallback pixbuf for missing images. MISSING_IMAGE_ICON = None _missing_icon_dialog = Gtk.Dialog() _missing_icon_pixbuf = _missing_icon_dialog.render_icon( Gtk.STOCK_MISSING_IMAGE, Gtk.IconSize.LARGE_TOOLBAR) MISSING_IMAGE_ICON = _missing_icon_pixbuf assert MISSING_IMAGE_ICON GTK_GDK_COLOR_BLACK = Gdk.color_parse('black') GTK_GDK_COLOR_WHITE = Gdk.color_parse('white') def rotate_pixbuf(src, rotation):