def __init__(self, derrors, parent=None): QDialog.__init__(self, parent=parent) self.setWindowFlags(Qt.Window) self.derrors = derrors buttonBox = QDialogButtonBox(QDialogButtonBox.Close) browser = QTextBrowser() browser.setText('') browser.setFontFamily("Courier") browser.setFontWeight(QFont.Bold) browser.setLineWrapMode(QTextBrowser.NoWrap) browser.setReadOnly(True) self.ctc_button = QPushButton('Copy to clipboard') self.ctc_button.setMaximumWidth(100) gpbox2 = QGroupBox() lay2 = QHBoxLayout() gpbox2.setLayout(lay2) lay2.addWidget(browser) gpbox2.setMinimumHeight(500) gpbox2.setMinimumWidth(1000) lay = QVBoxLayout() self.setLayout(lay) lay.addWidget(gpbox2) lay.addWidget(self.ctc_button) lay.addWidget(buttonBox) buttonBox.rejected.connect(self.reject) self.ctc_button.clicked.connect(self.copy_to_clipboard) self.setWindowTitle('%s: Calibre error checks' % CAPTION) if 'scramb' in self.derrors: msg = 'Original vs. After scrambling' else: msg = 'Original - ebook not yet scrambled' gpbox2.setTitle(msg) self.report = self.summarise_errors() browser.setText(self.report)
def __init__(self, metadata, parent=None): QDialog.__init__(self, parent=parent) self.setWindowFlags(Qt.Window) buttonBox = QDialogButtonBox(QDialogButtonBox.Close) origbrowser = QTextBrowser() origbrowser.setText('') origbrowser.setReadOnly(True) browser = QTextBrowser() browser.setText('') browser.setReadOnly(True) gpbox2 = QGroupBox('Metadata: Original') lay2 = QHBoxLayout() gpbox2.setLayout(lay2) lay2.addWidget(origbrowser) gpbox4 = QGroupBox('Metadata: After scrambling') lay4 = QHBoxLayout() gpbox4.setLayout(lay4) lay4.addWidget(browser) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(gpbox2) splitter.addWidget(gpbox4) splitter.setMinimumHeight(500) splitter.setMinimumWidth(1000) lay = QVBoxLayout() self.setLayout(lay) lay.addWidget(splitter) lay.addWidget(buttonBox) # create connect signals/slots buttonBox.rejected.connect(self.reject) metaorig = metadata.get('orig', '') metaorig = re.sub(r'\n\s*', r'\n ', metaorig) origbrowser.setText(metaorig) browser.setText(metadata.get('scramb', '')) self.setWindowTitle('%s: Metadata' % CAPTION) if not 'scramb' in metadata: gpbox4.setVisible(False)
class CheckExternalLinks(Dialog): progress_made = pyqtSignal(object, object) def __init__(self, parent=None): Dialog.__init__(self, _('Check external links'), 'check-external-links-dialog', parent) self.progress_made.connect(self.on_progress_made, type=Qt.QueuedConnection) def show(self): if self.rb.isEnabled(): self.refresh() return Dialog.show(self) def refresh(self): self.stack.setCurrentIndex(0) self.rb.setEnabled(False) t = Thread(name='CheckLinksMaster', target=self.run) t.daemon = True t.start() def setup_ui(self): self.pb = pb = QProgressBar(self) pb.setTextVisible(True) pb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) pb.setRange(0, 0) self.w = w = QWidget(self) self.w.l = l = QVBoxLayout(w) l.addStretch(), l.addWidget(pb) self.w.la = la = QLabel(_('Checking external links, please wait...')) la.setStyleSheet('QLabel { font-size: 20px; font-weight: bold }') l.addWidget(la, 0, Qt.AlignCenter), l.addStretch() self.l = l = QVBoxLayout(self) self.results = QTextBrowser(self) self.results.setOpenLinks(False) self.results.anchorClicked.connect(self.anchor_clicked) self.stack = s = QStackedWidget(self) s.addWidget(w), s.addWidget(self.results) l.addWidget(s) self.bh = h = QHBoxLayout() self.check_anchors = ca = QCheckBox(_('Check &anchors')) ca.setToolTip( _('Check HTML anchors in links (the part after the #).\n' ' This can be a little slow, since it requires downloading and parsing all the HTML pages.' )) ca.setChecked(tprefs.get('check_external_link_anchors', True)) ca.stateChanged.connect(self.anchors_changed) h.addWidget(ca), h.addStretch(100), h.addWidget(self.bb) l.addLayout(h) self.bb.setStandardButtons(self.bb.Close) self.rb = b = self.bb.addButton(_('&Refresh'), self.bb.ActionRole) b.setIcon(QIcon(I('view-refresh.png'))) b.clicked.connect(self.refresh) def anchors_changed(self): tprefs.set('check_external_link_anchors', self.check_anchors.isChecked()) def sizeHint(self): ans = Dialog.sizeHint(self) ans.setHeight(600) ans.setWidth(max(ans.width(), 800)) return ans def run(self): from calibre.ebooks.oeb.polish.check.links import check_external_links self.tb = None self.errors = [] try: self.errors = check_external_links( current_container(), self.progress_made.emit, check_anchors=self.check_anchors.isChecked()) except Exception: import traceback self.tb = traceback.format_exc() self.progress_made.emit(None, None) def on_progress_made(self, curr, total): if curr is None: self.results.setText('') self.stack.setCurrentIndex(1) self.fixed_errors = set() self.rb.setEnabled(True) if self.tb is not None: return error_dialog( self, _('Checking failed'), _('There was an error while checking links, click "Show details" for more information' ), det_msg=self.tb, show=True) if not self.errors: self.results.setText(_('No broken links found')) else: self.populate_results() else: self.pb.setMaximum(total), self.pb.setValue(curr) def populate_results(self, preserve_pos=False): num = len(self.errors) - len(self.fixed_errors) text = '<h3>%s</h3><ol>' % (ngettext( 'Found a broken link', 'Found {} broken links', num).format(num)) for i, (locations, err, url) in enumerate(self.errors): if i in self.fixed_errors: continue text += '<li><b>%s</b> \xa0<a href="err:%d">[%s]</a><br>%s<br><ul>' % ( url, i, _('Fix this link'), err) for name, href, lnum, col in locations: text += '<li>{name} \xa0<a href="loc:{lnum},{name}">[{line}: {lnum}]</a></li>'.format( name=name, lnum=lnum, line=_('line number')) text += '</ul></li><hr>' self.results.setHtml(text) def anchor_clicked(self, qurl): url = qurl.toString() if url.startswith('err:'): errnum = int(url[4:]) err = self.errors[errnum] newurl, ok = QInputDialog.getText(self, _('Fix URL'), _('Enter the corrected URL:') + '\xa0' * 40, text=err[2]) if not ok: return nmap = defaultdict(set) for name, href in {(l[0], l[1]) for l in err[0]}: nmap[name].add(href) for name, hrefs in nmap.iteritems(): raw = oraw = get_data(name) for href in hrefs: raw = raw.replace(href, newurl) if raw != oraw: set_data(name, raw) self.fixed_errors.add(errnum) self.populate_results() elif url.startswith('loc:'): lnum, name = url[4:].partition(',')[::2] lnum = int(lnum or 1) editor = get_boss().edit_file(name) if lnum and editor is not None and editor.has_line_numbers: editor.current_line = lnum
class CheckExternalLinks(Dialog): progress_made = pyqtSignal(object, object) def __init__(self, parent=None): Dialog.__init__(self, _('Check external links'), 'check-external-links-dialog', parent) self.progress_made.connect(self.on_progress_made, type=Qt.QueuedConnection) def show(self): if self.rb.isEnabled(): self.refresh() return Dialog.show(self) def refresh(self): self.stack.setCurrentIndex(0) self.rb.setEnabled(False) t = Thread(name='CheckLinksMaster', target=self.run) t.daemon = True t.start() def setup_ui(self): self.pb = pb = QProgressBar(self) pb.setTextVisible(True) pb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) pb.setRange(0, 0) self.w = w = QWidget(self) self.w.l = l = QVBoxLayout(w) l.addStretch(), l.addWidget(pb) self.w.la = la = QLabel(_('Checking external links, please wait...')) la.setStyleSheet('QLabel { font-size: 20px; font-weight: bold }') l.addWidget(la, 0, Qt.AlignCenter), l.addStretch() self.l = l = QVBoxLayout(self) self.results = QTextBrowser(self) self.results.setOpenLinks(False) self.results.anchorClicked.connect(self.anchor_clicked) self.stack = s = QStackedWidget(self) s.addWidget(w), s.addWidget(self.results) l.addWidget(s) self.bh = h = QHBoxLayout() self.check_anchors = ca = QCheckBox(_('Check &anchors')) ca.setToolTip(_('Check HTML anchors in links (the part after the #).\n' ' This can be a little slow, since it requires downloading and parsing all the HTML pages.')) ca.setChecked(tprefs.get('check_external_link_anchors', True)) ca.stateChanged.connect(self.anchors_changed) h.addWidget(ca), h.addStretch(100), h.addWidget(self.bb) l.addLayout(h) self.bb.setStandardButtons(self.bb.Close) self.rb = b = self.bb.addButton(_('&Refresh'), self.bb.ActionRole) b.setIcon(QIcon(I('view-refresh.png'))) b.clicked.connect(self.refresh) def anchors_changed(self): tprefs.set('check_external_link_anchors', self.check_anchors.isChecked()) def sizeHint(self): ans = Dialog.sizeHint(self) ans.setHeight(600) ans.setWidth(max(ans.width(), 800)) return ans def run(self): from calibre.ebooks.oeb.polish.check.links import check_external_links self.tb = None self.errors = [] try: self.errors = check_external_links(current_container(), self.progress_made.emit, check_anchors=self.check_anchors.isChecked()) except Exception: import traceback self.tb = traceback.format_exc() self.progress_made.emit(None, None) def on_progress_made(self, curr, total): if curr is None: self.results.setText('') self.stack.setCurrentIndex(1) self.fixed_errors = set() self.rb.setEnabled(True) if self.tb is not None: return error_dialog(self, _('Checking failed'), _( 'There was an error while checking links, click "Show details" for more information'), det_msg=self.tb, show=True) if not self.errors: self.results.setText(_('No broken links found')) else: self.populate_results() else: self.pb.setMaximum(total), self.pb.setValue(curr) def populate_results(self, preserve_pos=False): num = len(self.errors) - len(self.fixed_errors) text = '<h3>%s</h3><ol>' % (ngettext( 'Found a broken link', 'Found {} broken links', num).format(num)) for i, (locations, err, url) in enumerate(self.errors): if i in self.fixed_errors: continue text += '<li><b>%s</b> \xa0<a href="err:%d">[%s]</a><br>%s<br><ul>' % (url, i, _('Fix this link'), err) for name, href, lnum, col in locations: text += '<li>{name} \xa0<a href="loc:{lnum},{name}">[{line}: {lnum}]</a></li>'.format( name=name, lnum=lnum, line=_('line number')) text += '</ul></li><hr>' self.results.setHtml(text) def anchor_clicked(self, qurl): url = qurl.toString() if url.startswith('err:'): errnum = int(url[4:]) err = self.errors[errnum] newurl, ok = QInputDialog.getText(self, _('Fix URL'), _('Enter the corrected URL:') + '\xa0'*40, text=err[2]) if not ok: return nmap = defaultdict(set) for name, href in {(l[0], l[1]) for l in err[0]}: nmap[name].add(href) for name, hrefs in nmap.iteritems(): raw = oraw = get_data(name) for href in hrefs: raw = raw.replace(href, newurl) if raw != oraw: set_data(name, raw) self.fixed_errors.add(errnum) self.populate_results() elif url.startswith('loc:'): lnum, name = url[4:].partition(',')[::2] lnum = int(lnum or 1) editor = get_boss().edit_file(name) if lnum and editor is not None and editor.has_line_numbers: editor.current_line = lnum
class EbookScramble(QDialog): ''' Read an EPUB/KEPUB/AZW3 de-DRM'd ebook file and scramble various contents ''' def __init__(self, pathtoebook, book_id=None, from_calibre=False, dsettings={}, calibre_libpaths=[], parent=None): QDialog.__init__(self, parent=parent) self.gui = parent self.pathtoebook = pathtoebook self.book_id = book_id self.from_calibre = from_calibre self.calibre_libpaths = calibre_libpaths self.dsettings = MR_SETTINGS.copy() self.dsettings.update(dsettings) self.ebook = None self.eborig = None self.cleanup_dirs = [] self.cleanup_files = [] self.log = [] self.rename_file_map = {} self.meta, self.errors = {}, {} self.is_scrambled = False self.dummyimg = None self.dummysvg = '' self.setWindowTitle(CAPTION) self.setWindowIcon(get_icons('images/plugin_icon.png')) # create widgets self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) self.buttonBox.button( QDialogButtonBox.Save).setText('Save scrambled ebook && Exit') self.browser = QTextBrowser() self.browser.setText('') self.browser.setLineWrapMode(QTextBrowser.NoWrap) self.browser.setMinimumWidth(600) self.browser.setMinimumHeight(150) self.browser.setReadOnly(True) self.savefile = QLineEdit() self.savefile.setReadOnly(True) self.sourcefile = QLineEdit() self.sourcefile.setMinimumWidth(100) self.sourcefile.setReadOnly(True) self.browsesource = QPushButton('...') self.browsesource.setMaximumWidth(30) about_button = QPushButton('About', self) self.runButton = QPushButton('Scramble now') previewButton = QPushButton('Preview content') if Webview is None: previewButton.setEnabled(False) previewButton.setToolTip( 'Preview not currently available for this book') configButton = QPushButton('Change rules *') configButton.setToolTip( 'Only available in standalone version, not calibre plugin') metadataButton = QPushButton('View metadata *') metadataButton.setToolTip( 'Only available in standalone version, not calibre plugin') errorsButton = QPushButton('View errors *') errorsButton.setToolTip( 'Only available in standalone version, not calibre plugin') # layout widgets gpsource = QGroupBox('Source ebook:') laysource = QGridLayout() gpsource.setLayout(laysource) laysource.addWidget(self.sourcefile, 0, 0) laysource.addWidget(self.browsesource, 0, 1) gptarget = QGroupBox('Scrambled ebook:') laytarget = QGridLayout() gptarget.setLayout(laytarget) laytarget.addWidget(self.savefile, 0, 0) gpaction = QGroupBox('Actions:') layaction = QVBoxLayout() gpaction.setLayout(layaction) layaction.addWidget(self.runButton) layaction.addStretch() layaction.addWidget(previewButton) layaction.addStretch() gpextras = QGroupBox('Extras:') layaction2 = QVBoxLayout() gpextras.setLayout(layaction2) layaction2.addWidget(configButton) layaction2.addWidget(metadataButton) layaction2.addWidget(errorsButton) layaction3 = QVBoxLayout() layaction3.addWidget(about_button) layaction3.addStretch() layaction3.addWidget(gpextras) grid = QGridLayout() grid.addWidget(self.browser, 0, 0) grid.addLayout(layaction3, 0, 1) grid.addWidget(gpsource, 2, 0) grid.addWidget(gptarget, 3, 0) grid.addWidget(gpaction, 2, 1, 2, 1) grid.addWidget(self.buttonBox, 5, 0, 1, 2) self.setLayout(grid) # create connect signals/slots about_button.clicked.connect(self.about_button_clicked) self.runButton.clicked.connect(self.create_scramble_book) previewButton.clicked.connect(self.preview_ebook) configButton.clicked.connect(self.change_rules) metadataButton.clicked.connect(self.view_metadata) errorsButton.clicked.connect(self.view_errors) self.browsesource.clicked.connect(self.choose_source_ebook) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) if self.from_calibre: gpextras.setVisible( False) # Extras not available in calibre plugin self.browsesource.setVisible( False) # ebook file selection done by calibre self.initialise_new_file(self.pathtoebook) def initialise_new_file(self, pathtoebook): self.meta, self.errors = {}, {} self.rename_file_map = {} self.is_scrambled = False self.dummyimg = None self.dummysvg = '' self.runButton.setEnabled(True) self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False) fileok = True if not os.path.isfile(pathtoebook): fileok = False else: try: self.ebook = get_container(pathtoebook) except: fileok = False msg = "Source ebook must be de-DRM'd and in one of these formats:" \ "\n- azw3\n- epub\n- kepub\n- kepub.epub.\n\nPlease select another." error_dialog(self, CAPTION, msg, show=True, show_copy_button=True) if not fileok: self.log.append('No ebook selected yet') else: self.cleanup_dirs.append(self.ebook.root) tdir = PersistentTemporaryDirectory('_scramble_clone_orig') self.cleanup_dirs.append(tdir) self.eborig = clone_container(self.ebook, tdir) dirn, fname, ext, is_kepub_epub = get_fileparts( self.ebook.path_to_ebook) ext = ext.lower() format = 'kepub' if is_kepub_epub else ext if self.book_id is not None: # calibre library book self.cleanup_files.append(self.ebook.path_to_ebook) sourcepath = self.ebook.path_to_ebook self.dummyimg = get_resources('images/' + format + '.png') self.dummysvg = get_resources('images/' + format + '.svg') if self.from_calibre: # calibre plugin self.dirout = '' else: # standalone version self.dirout = dirn self.log.append('\n--- New ebook: %s' % sourcepath) fn = fname + '_scrambled.' fn += 'kepub.' + ext if is_kepub_epub else ext self.fname_scrambled_ebook = ascii_text(fn) self.sourcefile.setText(sourcepath) self.savefile.setText(self.fname_scrambled_ebook) self.meta['orig'] = get_metadata(self.ebook) self.errors['orig'] = get_run_check_error(self.ebook) self.viewlog() def accept(self): # Any accept actions which need to be done before returning to caller savedir = self.choose_save_dir(self.dirout) if savedir is not None: self.buttonBox.button(QDialogButtonBox.Save).setText('Saving ...') self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False) msg = '' if self.ebook.book_type.lower() == 'azw3': msg = '\n ... please note, rebuilding an AZW3 may take a little longer ...' self.log.append('\nSaving now ... %s' % msg) self.viewlog() path_to_scrambled_ebook = os.path.join(savedir, self.fname_scrambled_ebook) self.ebook.commit(path_to_scrambled_ebook) self.cleanup() QDialog.accept(self) def reject(self): self.cleanup() QDialog.reject(self) def cleanup(self): # delete calibre plugin temp files if self.book_id: for f in self.cleanup_files: try: os.remove(f) except: pass if self.from_calibre: for d in self.cleanup_dirs: try: shutil.rmtree(d) except: pass def choose_save_dir(self, default_dir): savedir = None askagain = True no_save_dir = False if default_dir: no_save_dir = True title = _('Choose destination directory for scrambled ebook') while askagain: savedir = choose_dir(window=self, name='', title=title, default_dir=default_dir, no_save_dir=no_save_dir) askagain = False if savedir is not None: savedir = os.path.normpath(savedir) if savedir.startswith(tuple(self.calibre_libpaths)): askagain = True msg = [] msg.append( 'You have selected a destination inside your Calibre library.' ) msg.append(savedir) msg.append('\nThis is NOT recommended. Try again.') msg.append('\nPlease avoid the following:') [ msg.append(path) for path in sorted(self.calibre_libpaths) ] warning_dialog(self, 'Calibre library chosen', '\n'.join(msg), show=True, show_copy_button=True) return savedir def choose_source_ebook(self): sf = self.sourcefile.text() seldir = get_fileparts(sf)[0] if sf else '' title = _('Select source ebook') selfiles = choose_files(self, name='', title=title, filters=[('Ebooks', ['epub', 'kepub', 'azw3'])], select_only_single_file=True, default_dir=seldir) if selfiles: self.pathtoebook = os.path.normpath(selfiles[0]) self.initialise_new_file(self.pathtoebook) def create_scramble_book(self): if self.ebook is None: return sf = self.sourcefile.text() self.log.append('\nScrambling %s ...' % sf) self.viewlog() scrambler = EbookScrambleAction(self.ebook, self.dsettings, self.dummyimg, self.dummysvg) self.rename_file_map = { k: v for (k, v) in iteritems(scrambler.file_map) } self.meta['scramb'] = get_metadata(self.ebook) self.errors['scramb'] = get_run_check_error(self.ebook) self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True) self.runButton.setEnabled(False) self.is_scrambled = True self.log.append(scrambler.results) self.log.append('\n... finished') self.viewlog() def change_rules(self): dlg = EbookScrambleRulesDlg(self.dsettings, parent=self.gui) if dlg.exec_(): self.dsettings.update(dlg.dsettings) self.log.append('\n--- Scrambling rules updated ---') self.viewlog() def preview_ebook(self): if self.ebook is None: return dlg = EbookScramblePreviewDlg(self.ebook, self.eborig, self.is_scrambled, self.rename_file_map, parent=self.gui) dlg.exec_() dlg.raise_() def view_metadata(self): if self.ebook is None: return dlg = EbookScrambleMetadataDlg(self.meta, parent=self.gui) dlg.exec_() dlg.raise_() def view_errors(self): if self.ebook is None: return dlg = EbookScrambleErrorsDlg(self.errors, parent=self.gui) dlg.exec_() dlg.raise_() def display_settings(self): self.log.append('\nCurrent Scramble rules:') [ self.log.append('%s: %s' % (k, v)) for (k, v) in sorted(iteritems(self.dsettings)) ] def viewlog(self): self.browser.setText('\n'.join(self.log)) self.browser.moveCursor(QTextCursor.End) QApplication.instance().processEvents() def about_button_clicked(self): # Get the about text from a file inside the plugin zip file # The get_resources function is a builtin function defined for all your # plugin code. It loads files from the plugin zip file. It returns # the bytes from the specified file. # # Note that if you are loading more than one file, for performance, you # should pass a list of names to get_resources. In this case, # get_resources will return a dictionary mapping names to bytes. Names that # are not found in the zip file will not be in the returned dictionary. source = 'calibre plugin' if self.from_calibre else 'standalone' text = get_resources('about.txt') QMessageBox.about(self, 'About %s %s' % (CAPTION, source), text)