diff --git a/resources/images/unpack-book.png b/resources/images/unpack-book.png new file mode 100644 index 0000000000..c7540bb5cf Binary files /dev/null and b/resources/images/unpack-book.png differ diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index f3f34827fc..924fa369c2 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -907,6 +907,11 @@ class ActionTweakEpub(InterfaceActionBase): actual_plugin = 'calibre.gui2.actions.tweak_epub:TweakEpubAction' description = _('Make small tweaks to epub or htmlz files in your calibre library') +class ActionUnpackBook(InterfaceActionBase): + name = 'Unpack Book' + actual_plugin = 'calibre.gui2.actions.unpack_book:UnpackBookAction' + description = _('Make small changes to epub or htmlz files in your calibre library') + class ActionNextMatch(InterfaceActionBase): name = 'Next Match' actual_plugin = 'calibre.gui2.actions.next_match:NextMatchAction' @@ -957,7 +962,7 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionShowBookDetails,ActionRestart, ActionOpenFolder, ActionConnectShare, ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks, ActionAddToLibrary, ActionEditCollections, ActionMatchBooks, ActionChooseLibrary, - ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore, + ActionCopyToLibrary, ActionTweakEpub, ActionUnpackBook, ActionNextMatch, ActionStore, ActionPluginUpdater, ActionPickRandom, ActionEditToC, ActionSortBy, ActionMarkBooks] diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index bf3a807984..0ac01bea2b 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -671,8 +671,8 @@ class Cache(object): ''' Return absolute path to the ebook file of format `format` - Currently used only in calibredb list, the viewer and the catalogs (via - get_data_as_dict()). + Currently used only in calibredb list, the viewer, tweak book and the + catalogs (via get_data_as_dict()). Apart from the viewer, I don't believe any of the others do any file I/O with the results of this call. diff --git a/src/calibre/gui2/actions/tweak_epub.py b/src/calibre/gui2/actions/tweak_epub.py index 5e641a7cdd..fff5d0eb76 100755 --- a/src/calibre/gui2/actions/tweak_epub.py +++ b/src/calibre/gui2/actions/tweak_epub.py @@ -5,290 +5,56 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, weakref, shutil +import time +from functools import partial -from PyQt4.Qt import (QDialog, QVBoxLayout, QHBoxLayout, QRadioButton, QFrame, - QPushButton, QLabel, QGroupBox, QGridLayout, QIcon, QSize, QTimer) +from PyQt4.Qt import QTimer, QDialog, QDialogButtonBox, QCheckBox, QVBoxLayout, QLabel, Qt -from calibre import as_unicode -from calibre.constants import isosx -from calibre.gui2 import error_dialog, question_dialog, open_local_file, gprefs +from calibre.gui2 import error_dialog from calibre.gui2.actions import InterfaceAction -from calibre.ptempfile import (PersistentTemporaryDirectory, - PersistentTemporaryFile) -from calibre.utils.config import prefs, tweaks -class TweakBook(QDialog): +class Choose(QDialog): - def __init__(self, parent, book_id, fmts, db): + def __init__(self, fmts, parent=None): QDialog.__init__(self, parent) - self.setWindowIcon(QIcon(I('tweak.png'))) - self.book_id, self.fmts, self.db_ref = book_id, fmts, weakref.ref(db) - self._exploded = None - self._cleanup_dirs = [] - self._cleanup_files = [] + self.l = l = QVBoxLayout(self) + self.setLayout(l) + self.setWindowTitle(_('Choose format to tweak')) - self.setup_ui() - self.setWindowTitle(_('Tweak Book') + ' - ' + db.title(book_id, - index_is_id=True)) + self.la = la = QLabel(_( + 'This book mas multiple formats that can be tweaked. Choose the format you want to tweak.')) + l.addWidget(la) - button = self.fmt_choice_buttons[0] - button_map = {unicode(x.text()):x for x in self.fmt_choice_buttons} - of = prefs['output_format'].upper() - df = tweaks.get('default_tweak_format', None) - lf = gprefs.get('last_tweak_format', None) - if df and df.lower() == 'remember' and lf in button_map: - button = button_map[lf] - elif df and df.upper() in button_map: - button = button_map[df.upper()] - elif of in button_map: - button = button_map[of] - button.setChecked(True) + self.rem = QCheckBox(_('Always ask when more than one format is available')) + self.rem.setChecked(True) + l.addWidget(self.rem) - self.init_state() - for button in self.fmt_choice_buttons: - button.toggled.connect(self.init_state) + self.bb = bb = QDialogButtonBox(self) + l.addWidget(bb) + bb.accepted.connect(self.accept) + bb.rejected.connect(self.reject) + self.buts = buts = [] + for fmt in fmts: + b = bb.addButton(fmt.upper(), bb.AcceptRole) + b.clicked.connect(partial(self.chosen, fmt)) + buts.append(b) - def init_state(self, *args): - self._exploded = None - self.preview_button.setEnabled(False) - self.rebuild_button.setEnabled(False) - self.explode_button.setEnabled(True) + self.fmt = None + self.resize(self.sizeHint()) - def setup_ui(self): # {{{ - self._g = g = QHBoxLayout(self) - self.setLayout(g) - self._l = l = QVBoxLayout() - g.addLayout(l) + def chosen(self, fmt): + self.fmt = fmt - fmts = sorted(x.upper() for x in self.fmts) - self.fmt_choice_box = QGroupBox(_('Choose the format to tweak:'), self) - self._fl = fl = QHBoxLayout() - self.fmt_choice_box.setLayout(self._fl) - self.fmt_choice_buttons = [QRadioButton(y, self) for y in fmts] - for x in self.fmt_choice_buttons: - fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else - 0) - l.addWidget(self.fmt_choice_box) - self.fmt_choice_box.setVisible(len(fmts) > 1) + def accept(self): + from calibre.gui2.tweak_book import tprefs + tprefs['choose_tweak_fmt'] = self.rem.isChecked() + QDialog.accept(self) - self.help_label = QLabel(_('''\ -

About Tweak Book

-

Tweak Book allows you to fine tune the appearance of an ebook by - making small changes to its internals. In order to use Tweak Book, - you need to know a little bit about HTML and CSS, technologies that - are used in ebooks. Follow the steps:

-
-
    -
  1. Click "Explode Book": This will "explode" the book into its - individual internal components.
  2. -
  3. Right click on any individual file and select "Open with..." to - edit it in your favorite text editor.
  4. -
  5. When you are done Tweaking: close the file browser window - and the editor windows you used to make your tweaks. Then click - the "Rebuild Book" button, to update the book in your calibre - library.
  6. -
''')) - self.help_label.setWordWrap(True) - self._fr = QFrame() - self._fr.setFrameShape(QFrame.VLine) - g.addWidget(self._fr) - g.addWidget(self.help_label) - - self._b = b = QGridLayout() - left, top, right, bottom = b.getContentsMargins() - top += top - b.setContentsMargins(left, top, right, bottom) - l.addLayout(b, stretch=10) - - self.explode_button = QPushButton(QIcon(I('wizard.png')), _('&Explode Book')) - self.preview_button = QPushButton(QIcon(I('view.png')), _('&Preview Book')) - self.cancel_button = QPushButton(QIcon(I('window-close.png')), _('&Cancel')) - self.rebuild_button = QPushButton(QIcon(I('exec.png')), _('&Rebuild Book')) - - self.explode_button.setToolTip( - _('Explode the book to edit its components')) - self.preview_button.setToolTip( - _('Preview the result of your tweaks')) - self.cancel_button.setToolTip( - _('Abort without saving any changes')) - self.rebuild_button.setToolTip( - _('Save your changes and update the book in the calibre library')) - - a = b.addWidget - a(self.explode_button, 0, 0, 1, 1) - a(self.preview_button, 0, 1, 1, 1) - a(self.cancel_button, 1, 0, 1, 1) - a(self.rebuild_button, 1, 1, 1, 1) - - for x in ('explode', 'preview', 'cancel', 'rebuild'): - getattr(self, x+'_button').clicked.connect(getattr(self, x)) - - self.msg = QLabel('dummy', self) - self.msg.setVisible(False) - self.msg.setStyleSheet(''' - QLabel { - text-align: center; - background-color: white; - color: black; - border-width: 1px; - border-style: solid; - border-radius: 20px; - font-size: x-large; - font-weight: bold; - } - ''') - - self.resize(self.sizeHint() + QSize(40, 10)) - # }}} - - def show_msg(self, msg): - self.msg.setText(msg) - self.msg.resize(self.size() - QSize(50, 25)) - self.msg.move((self.width() - self.msg.width())//2, - (self.height() - self.msg.height())//2) - self.msg.setVisible(True) - - def hide_msg(self): - self.msg.setVisible(False) - - def explode(self): - self.show_msg(_('Exploding, please wait...')) - if len(self.fmt_choice_buttons) > 1: - gprefs.set('last_tweak_format', self.current_format.upper()) - QTimer.singleShot(5, self.do_explode) - - def ask_question(self, msg): - return question_dialog(self, _('Are you sure?'), msg) - - def do_explode(self): - from calibre.ebooks.tweak import get_tools, Error, WorkerError - tdir = PersistentTemporaryDirectory('_tweak_explode') - self._cleanup_dirs.append(tdir) - det_msg = None - try: - src = self.db.format(self.book_id, self.current_format, - index_is_id=True, as_path=True) - self._cleanup_files.append(src) - exploder = get_tools(self.current_format)[0] - opf = exploder(src, tdir, question=self.ask_question) - except WorkerError as e: - det_msg = e.orig_tb - except Error as e: - return error_dialog(self, _('Failed to unpack'), - (_('Could not explode the %s file.')%self.current_format) + ' ' - + as_unicode(e), show=True) - except: - import traceback - det_msg = traceback.format_exc() - finally: - self.hide_msg() - - if det_msg is not None: - return error_dialog(self, _('Failed to unpack'), - _('Could not explode the %s file. Click "Show Details" for ' - 'more information.')%self.current_format, det_msg=det_msg, - show=True) - - if opf is None: - # The question was answered with No - return - - self._exploded = tdir - self.explode_button.setEnabled(False) - self.preview_button.setEnabled(True) - self.rebuild_button.setEnabled(True) - open_local_file(tdir) - - def rebuild_it(self): - from calibre.ebooks.tweak import get_tools, WorkerError - src_dir = self._exploded - det_msg = None - of = PersistentTemporaryFile('_tweak_rebuild.'+self.current_format.lower()) - of.close() - of = of.name - self._cleanup_files.append(of) - try: - rebuilder = get_tools(self.current_format)[1] - rebuilder(src_dir, of) - except WorkerError as e: - det_msg = e.orig_tb - except: - import traceback - det_msg = traceback.format_exc() - finally: - self.hide_msg() - - if det_msg is not None: - error_dialog(self, _('Failed to rebuild file'), - _('Failed to rebuild %s. For more information, click ' - '"Show details".')%self.current_format, - det_msg=det_msg, show=True) - return None - - return of - - def preview(self): - self.show_msg(_('Rebuilding, please wait...')) - QTimer.singleShot(5, self.do_preview) - - def do_preview(self): - rebuilt = self.rebuild_it() - if rebuilt is not None: - self.parent().iactions['View']._view_file(rebuilt) - - def rebuild(self): - self.show_msg(_('Rebuilding, please wait...')) - QTimer.singleShot(5, self.do_rebuild) - - def do_rebuild(self): - rebuilt = self.rebuild_it() - if rebuilt is not None: - fmt = os.path.splitext(rebuilt)[1][1:].upper() - with open(rebuilt, 'rb') as f: - self.db.add_format(self.book_id, fmt, f, index_is_id=True) - self.accept() - - def cancel(self): - self.reject() - - def cleanup(self): - if isosx and self._exploded: - try: - import appscript - self.finder = appscript.app('Finder') - self.finder.Finder_windows[os.path.basename(self._exploded)].close() - except: - pass - - for f in self._cleanup_files: - try: - os.remove(f) - except: - pass - - for d in self._cleanup_dirs: - try: - shutil.rmtree(d) - except: - pass - - @property - def db(self): - return self.db_ref() - - @property - def current_format(self): - for b in self.fmt_choice_buttons: - if b.isChecked(): - return unicode(b.text()) class TweakEpubAction(InterfaceAction): name = 'Tweak ePub' - action_spec = (_('Tweak Book'), 'tweak.png', - _('Make small changes to ePub, HTMLZ or AZW3 format books'), - _('T')) + action_spec = (_('Tweak Book'), 'tweak.png', _('Edit eBooks'), _('T')) dont_add_to = frozenset(['context-menu-device']) action_type = 'current' @@ -324,25 +90,46 @@ class TweakEpubAction(InterfaceAction): def tweak_book(self): row = self.gui.library_view.currentIndex() if not row.isValid(): - return error_dialog(self.gui, _('Cannot tweak Book'), + return error_dialog(self.gui, _('Cannot Tweak Book'), _('No book selected'), show=True) book_id = self.gui.library_view.model().id(row) self.do_tweak(book_id) def do_tweak(self, book_id): + from calibre.ebooks.oeb.polish.main import SUPPORTED db = self.gui.library_view.model().db fmts = db.formats(book_id, index_is_id=True) or '' - fmts = [x.lower().strip() for x in fmts.split(',')] - tweakable_fmts = set(fmts).intersection({'epub', 'htmlz', 'azw3', - 'mobi', 'azw'}) + fmts = [x.upper().strip() for x in fmts.split(',')] + tweakable_fmts = set(fmts).intersection(SUPPORTED) if not tweakable_fmts: return error_dialog(self.gui, _('Cannot Tweak Book'), - _('The book must be in ePub, HTMLZ or AZW3 formats to tweak.' - '\n\nFirst convert the book to one of these formats.'), + _('The book must be in the %s formats to tweak.' + '\n\nFirst convert the book to one of these formats.') % (_(' or '.join(SUPPORTED))), show=True) - dlg = TweakBook(self.gui, book_id, tweakable_fmts, db) - dlg.exec_() - dlg.cleanup() - + if len(tweakable_fmts) > 1: + from calibre.gui2.tweak_book import tprefs + if tprefs['choose_tweak_fmt']: + d = Choose(sorted(tweakable_fmts, key=tprefs.defaults['tweak_fmt_order'].index), self.gui) + if d.exec_() != d.Accepted: + return + tweakable_fmts = {d.fmt} + else: + fmts = [f for f in tprefs['tweak_fmt_order'] if f in tweakable_fmts] + if not fmts: + fmts = [f for f in tprefs.defaults['tweak_fmt_order'] if f in tweakable_fmts] + tweakable_fmts = {fmts[0]} + fmt = tuple(tweakable_fmts)[0] + path = db.new_api.format_abspath(book_id, fmt) + if path is None: + return error_dialog(self.gui, _('File missing'), _( + 'The %s format is missing from the calibre library. You should run' + ' library maintenance.') % fmt, show=True) + tweak = 'ebook-tweak' + self.gui.setCursor(Qt.BusyCursor) + try: + self.gui.job_manager.launch_gui_app(tweak, kwargs=dict(args=[tweak, path])) + time.sleep(2) + finally: + self.gui.unsetCursor() diff --git a/src/calibre/gui2/actions/unpack_book.py b/src/calibre/gui2/actions/unpack_book.py new file mode 100644 index 0000000000..d204554211 --- /dev/null +++ b/src/calibre/gui2/actions/unpack_book.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os, weakref, shutil + +from PyQt4.Qt import (QDialog, QVBoxLayout, QHBoxLayout, QRadioButton, QFrame, + QPushButton, QLabel, QGroupBox, QGridLayout, QIcon, QSize, QTimer) + +from calibre import as_unicode +from calibre.constants import isosx +from calibre.gui2 import error_dialog, question_dialog, open_local_file, gprefs +from calibre.gui2.actions import InterfaceAction +from calibre.ptempfile import (PersistentTemporaryDirectory, + PersistentTemporaryFile) +from calibre.utils.config import prefs, tweaks + +class UnpackBook(QDialog): + + def __init__(self, parent, book_id, fmts, db): + QDialog.__init__(self, parent) + self.setWindowIcon(QIcon(I('unpack-book.png'))) + self.book_id, self.fmts, self.db_ref = book_id, fmts, weakref.ref(db) + self._exploded = None + self._cleanup_dirs = [] + self._cleanup_files = [] + + self.setup_ui() + self.setWindowTitle(_('Unpack Book') + ' - ' + db.title(book_id, + index_is_id=True)) + + button = self.fmt_choice_buttons[0] + button_map = {unicode(x.text()):x for x in self.fmt_choice_buttons} + of = prefs['output_format'].upper() + df = tweaks.get('default_tweak_format', None) + lf = gprefs.get('last_tweak_format', None) + if df and df.lower() == 'remember' and lf in button_map: + button = button_map[lf] + elif df and df.upper() in button_map: + button = button_map[df.upper()] + elif of in button_map: + button = button_map[of] + button.setChecked(True) + + self.init_state() + for button in self.fmt_choice_buttons: + button.toggled.connect(self.init_state) + + def init_state(self, *args): + self._exploded = None + self.preview_button.setEnabled(False) + self.rebuild_button.setEnabled(False) + self.explode_button.setEnabled(True) + + def setup_ui(self): # {{{ + self._g = g = QHBoxLayout(self) + self.setLayout(g) + self._l = l = QVBoxLayout() + g.addLayout(l) + + fmts = sorted(x.upper() for x in self.fmts) + self.fmt_choice_box = QGroupBox(_('Choose the format to unpack:'), self) + self._fl = fl = QHBoxLayout() + self.fmt_choice_box.setLayout(self._fl) + self.fmt_choice_buttons = [QRadioButton(y, self) for y in fmts] + for x in self.fmt_choice_buttons: + fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else + 0) + l.addWidget(self.fmt_choice_box) + self.fmt_choice_box.setVisible(len(fmts) > 1) + + self.help_label = QLabel(_('''\ +

About Unpack Book

+

Unpack Book allows you to fine tune the appearance of an ebook by + making small changes to its internals. In order to use Unpack Book, + you need to know a little bit about HTML and CSS, technologies that + are used in ebooks. Follow the steps:

+
+
    +
  1. Click "Explode Book": This will "explode" the book into its + individual internal components.
  2. +
  3. Right click on any individual file and select "Open with..." to + edit it in your favorite text editor.
  4. +
  5. When you are done: close the file browser window + and the editor windows you used to make your tweaks. Then click + the "Rebuild Book" button, to update the book in your calibre + library.
  6. +
''')) + self.help_label.setWordWrap(True) + self._fr = QFrame() + self._fr.setFrameShape(QFrame.VLine) + g.addWidget(self._fr) + g.addWidget(self.help_label) + + self._b = b = QGridLayout() + left, top, right, bottom = b.getContentsMargins() + top += top + b.setContentsMargins(left, top, right, bottom) + l.addLayout(b, stretch=10) + + self.explode_button = QPushButton(QIcon(I('wizard.png')), _('&Explode Book')) + self.preview_button = QPushButton(QIcon(I('view.png')), _('&Preview Book')) + self.cancel_button = QPushButton(QIcon(I('window-close.png')), _('&Cancel')) + self.rebuild_button = QPushButton(QIcon(I('exec.png')), _('&Rebuild Book')) + + self.explode_button.setToolTip( + _('Explode the book to edit its components')) + self.preview_button.setToolTip( + _('Preview the result of your changes')) + self.cancel_button.setToolTip( + _('Abort without saving any changes')) + self.rebuild_button.setToolTip( + _('Save your changes and update the book in the calibre library')) + + a = b.addWidget + a(self.explode_button, 0, 0, 1, 1) + a(self.preview_button, 0, 1, 1, 1) + a(self.cancel_button, 1, 0, 1, 1) + a(self.rebuild_button, 1, 1, 1, 1) + + for x in ('explode', 'preview', 'cancel', 'rebuild'): + getattr(self, x+'_button').clicked.connect(getattr(self, x)) + + self.msg = QLabel('dummy', self) + self.msg.setVisible(False) + self.msg.setStyleSheet(''' + QLabel { + text-align: center; + background-color: white; + color: black; + border-width: 1px; + border-style: solid; + border-radius: 20px; + font-size: x-large; + font-weight: bold; + } + ''') + + self.resize(self.sizeHint() + QSize(40, 10)) + # }}} + + def show_msg(self, msg): + self.msg.setText(msg) + self.msg.resize(self.size() - QSize(50, 25)) + self.msg.move((self.width() - self.msg.width())//2, + (self.height() - self.msg.height())//2) + self.msg.setVisible(True) + + def hide_msg(self): + self.msg.setVisible(False) + + def explode(self): + self.show_msg(_('Exploding, please wait...')) + if len(self.fmt_choice_buttons) > 1: + gprefs.set('last_tweak_format', self.current_format.upper()) + QTimer.singleShot(5, self.do_explode) + + def ask_question(self, msg): + return question_dialog(self, _('Are you sure?'), msg) + + def do_explode(self): + from calibre.ebooks.tweak import get_tools, Error, WorkerError + tdir = PersistentTemporaryDirectory('_tweak_explode') + self._cleanup_dirs.append(tdir) + det_msg = None + try: + src = self.db.format(self.book_id, self.current_format, + index_is_id=True, as_path=True) + self._cleanup_files.append(src) + exploder = get_tools(self.current_format)[0] + opf = exploder(src, tdir, question=self.ask_question) + except WorkerError as e: + det_msg = e.orig_tb + except Error as e: + return error_dialog(self, _('Failed to unpack'), + (_('Could not explode the %s file.')%self.current_format) + ' ' + + as_unicode(e), show=True) + except: + import traceback + det_msg = traceback.format_exc() + finally: + self.hide_msg() + + if det_msg is not None: + return error_dialog(self, _('Failed to unpack'), + _('Could not explode the %s file. Click "Show Details" for ' + 'more information.')%self.current_format, det_msg=det_msg, + show=True) + + if opf is None: + # The question was answered with No + return + + self._exploded = tdir + self.explode_button.setEnabled(False) + self.preview_button.setEnabled(True) + self.rebuild_button.setEnabled(True) + open_local_file(tdir) + + def rebuild_it(self): + from calibre.ebooks.tweak import get_tools, WorkerError + src_dir = self._exploded + det_msg = None + of = PersistentTemporaryFile('_tweak_rebuild.'+self.current_format.lower()) + of.close() + of = of.name + self._cleanup_files.append(of) + try: + rebuilder = get_tools(self.current_format)[1] + rebuilder(src_dir, of) + except WorkerError as e: + det_msg = e.orig_tb + except: + import traceback + det_msg = traceback.format_exc() + finally: + self.hide_msg() + + if det_msg is not None: + error_dialog(self, _('Failed to rebuild file'), + _('Failed to rebuild %s. For more information, click ' + '"Show details".')%self.current_format, + det_msg=det_msg, show=True) + return None + + return of + + def preview(self): + self.show_msg(_('Rebuilding, please wait...')) + QTimer.singleShot(5, self.do_preview) + + def do_preview(self): + rebuilt = self.rebuild_it() + if rebuilt is not None: + self.parent().iactions['View']._view_file(rebuilt) + + def rebuild(self): + self.show_msg(_('Rebuilding, please wait...')) + QTimer.singleShot(5, self.do_rebuild) + + def do_rebuild(self): + rebuilt = self.rebuild_it() + if rebuilt is not None: + fmt = os.path.splitext(rebuilt)[1][1:].upper() + with open(rebuilt, 'rb') as f: + self.db.add_format(self.book_id, fmt, f, index_is_id=True) + self.accept() + + def cancel(self): + self.reject() + + def cleanup(self): + if isosx and self._exploded: + try: + import appscript + self.finder = appscript.app('Finder') + self.finder.Finder_windows[os.path.basename(self._exploded)].close() + except: + pass + + for f in self._cleanup_files: + try: + os.remove(f) + except: + pass + + for d in self._cleanup_dirs: + try: + shutil.rmtree(d) + except: + pass + + @property + def db(self): + return self.db_ref() + + @property + def current_format(self): + for b in self.fmt_choice_buttons: + if b.isChecked(): + return unicode(b.text()) + +class UnpackBookAction(InterfaceAction): + + name = 'Unpack Book' + action_spec = (_('Unpack Book'), 'unpack-book.png', + _('Unpack books in the EPUB, AZW3, HTMLZ formats into their individual components'), None) + dont_add_to = frozenset(['context-menu-device']) + action_type = 'current' + + accepts_drops = True + + def accept_enter_event(self, event, mime_data): + if mime_data.hasFormat("application/calibre+from_library"): + return True + return False + + def accept_drag_move_event(self, event, mime_data): + if mime_data.hasFormat("application/calibre+from_library"): + return True + return False + + def drop_event(self, event, mime_data): + mime = 'application/calibre+from_library' + if mime_data.hasFormat(mime): + self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split())) + QTimer.singleShot(1, self.do_drop) + return True + return False + + def do_drop(self): + book_ids = self.dropped_ids + del self.dropped_ids + if book_ids: + self.do_tweak(book_ids[0]) + + def genesis(self): + self.qaction.triggered.connect(self.tweak_book) + + def tweak_book(self): + row = self.gui.library_view.currentIndex() + if not row.isValid(): + return error_dialog(self.gui, _('Cannot unpack Book'), + _('No book selected'), show=True) + + book_id = self.gui.library_view.model().id(row) + self.do_tweak(book_id) + + def do_tweak(self, book_id): + db = self.gui.library_view.model().db + fmts = db.formats(book_id, index_is_id=True) or '' + fmts = [x.lower().strip() for x in fmts.split(',')] + tweakable_fmts = set(fmts).intersection({'epub', 'htmlz', 'azw3', + 'mobi', 'azw'}) + if not tweakable_fmts: + return error_dialog(self.gui, _('Cannot unpack Book'), + _('The book must be in ePub, HTMLZ or AZW3 formats to unpack.' + '\n\nFirst convert the book to one of these formats.'), + show=True) + dlg = UnpackBook(self.gui, book_id, tweakable_fmts, db) + dlg.exec_() + dlg.cleanup() + + + diff --git a/src/calibre/gui2/tweak_book/__init__.py b/src/calibre/gui2/tweak_book/__init__.py index 498986ee25..0bd840b508 100644 --- a/src/calibre/gui2/tweak_book/__init__.py +++ b/src/calibre/gui2/tweak_book/__init__.py @@ -14,6 +14,8 @@ tprefs.defaults['editor_font_family'] = None tprefs.defaults['editor_font_size'] = 12 tprefs.defaults['editor_line_wrap'] = True tprefs.defaults['preview_refresh_time'] = 2 +tprefs.defaults['choose_tweak_fmt'] = True +tprefs.defaults['tweak_fmt_order'] = ['EPUB', 'AZW3'] _current_container = None diff --git a/src/calibre/utils/ipc/worker.py b/src/calibre/utils/ipc/worker.py index 0cebdfee07..e8a257da4d 100644 --- a/src/calibre/utils/ipc/worker.py +++ b/src/calibre/utils/ipc/worker.py @@ -25,6 +25,9 @@ PARALLEL_FUNCS = { 'ebook-viewer' : ('calibre.gui2.viewer.main', 'main', None), + 'ebook-tweak' : + ('calibre.gui2.tweak_book.main', 'main', None), + 'render_pages' : ('calibre.ebooks.comic.input', 'render_pages', 'notification'), @@ -197,6 +200,5 @@ def main(): return 0 - if __name__ == '__main__': sys.exit(main())