mirror of
				https://github.com/noDRM/DeDRM_tools.git
				synced 2025-10-23 23:07:47 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			455 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			455 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
 | |
| from __future__ import (unicode_literals, division, absolute_import,
 | |
|                         print_function)
 | |
| 
 | |
| __license__   = 'GPL v3'
 | |
| __docformat__ = 'restructuredtext en'
 | |
| 
 | |
| TEXT_DRM_FREE = ' (*: drm - free)'
 | |
| LAB_DRM_FREE = '* : drm - free'
 | |
| 
 | |
| try:
 | |
|     from PyQt5.Qt import (Qt, QVBoxLayout, QLabel, QApplication, QGroupBox, 
 | |
|                           QDialogButtonBox, QHBoxLayout, QTextBrowser, QProgressDialog, 
 | |
|                           QTimer, QSize, QDialog, QIcon, QTableWidget, QTableWidgetItem)
 | |
| except ImportError:
 | |
|     from PyQt4.Qt import (Qt, QVBoxLayout, QLabel, QApplication, QGroupBox, 
 | |
|                           QDialogButtonBox, QHBoxLayout, QTextBrowser, QProgressDialog, 
 | |
|                           QTimer, QSize, QDialog, QIcon, QTableWidget, QTableWidgetItem)
 | |
| 
 | |
| try:
 | |
|     from PyQt5.QtWidgets import (QListWidget, QAbstractItemView)
 | |
| except ImportError:
 | |
|         from PyQt4.QtGui import (QListWidget, QAbstractItemView)
 | |
| 
 | |
| from calibre.gui2 import gprefs, warning_dialog, error_dialog
 | |
| from calibre.gui2.dialogs.message_box import MessageBox
 | |
| 
 | |
| #from calibre.ptempfile import remove_dir
 | |
| 
 | |
| from calibre_plugins.obok_dedrm.utilities import (SizePersistedDialog, ImageTitleLayout, 
 | |
|                                         showErrorDlg, get_icon, convert_qvariant, debug_print
 | |
|                                         )
 | |
| from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME,  
 | |
|                         PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION)
 | |
| 
 | |
| try:
 | |
|     debug_print("obok::dialogs.py - loading translations")
 | |
|     load_translations()
 | |
| except NameError:
 | |
|     debug_print("obok::dialogs.py - exception when loading translations")
 | |
|     pass # load_translations() added in calibre 1.9
 | |
| 
 | |
| class SelectionDialog(SizePersistedDialog):
 | |
|     '''
 | |
|     Dialog to select the kobo books to decrypt
 | |
|     '''
 | |
|     def __init__(self, gui, interface_action, books):
 | |
|         '''
 | |
|         :param gui: Parent gui
 | |
|         :param interface_action: InterfaceActionObject (InterfacePluginAction class from action.py)
 | |
|         :param books: list of Kobo book
 | |
|         '''
 | |
|         
 | |
|         self.books = books
 | |
|         self.gui = gui
 | |
|         self.interface_action = interface_action
 | |
|         self.books = books
 | |
| 
 | |
|         SizePersistedDialog.__init__(self, gui, PLUGIN_NAME + 'plugin:selections dialog')
 | |
|         self.setWindowTitle(_(PLUGIN_NAME + ' v' + PLUGIN_VERSION))
 | |
|         self.setMinimumWidth(300)
 | |
|         self.setMinimumHeight(300)
 | |
|         layout = QVBoxLayout(self)
 | |
|         self.setLayout(layout)
 | |
|         title_layout = ImageTitleLayout(self, 'images/obok.png', _('Obok DeDRM'))
 | |
|         layout.addLayout(title_layout)
 | |
| 
 | |
|         help_label = QLabel(_('<a href="http://www.foo.com/">Help</a>'), self)
 | |
|         help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
 | |
|         help_label.setAlignment(Qt.AlignRight)
 | |
|         help_label.linkActivated.connect(self._help_link_activated)
 | |
|         title_layout.addWidget(help_label)
 | |
|         title_layout.setAlignment(Qt.AlignTop)
 | |
| 
 | |
|         layout.addSpacing(5)
 | |
|         main_layout = QHBoxLayout()
 | |
|         layout.addLayout(main_layout)
 | |
| #        self.listy = QListWidget()
 | |
| #        self.listy.setSelectionMode(QAbstractItemView.ExtendedSelection)
 | |
| #        main_layout.addWidget(self.listy)
 | |
| #        self.listy.addItems(books)
 | |
|         self.books_table = BookListTableWidget(self)
 | |
|         main_layout.addWidget(self.books_table)
 | |
| 
 | |
|         layout.addSpacing(10)
 | |
|         button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 | |
|         button_box.accepted.connect(self._ok_clicked)
 | |
|         button_box.rejected.connect(self.reject)
 | |
|         self.select_all_button = button_box.addButton(_("Select All"), QDialogButtonBox.ResetRole)
 | |
|         self.select_all_button.setToolTip(_("Select all books to add them to the calibre library."))
 | |
|         self.select_all_button.clicked.connect(self._select_all_clicked)
 | |
|         self.select_drm_button = button_box.addButton(_("All with DRM"), QDialogButtonBox.ResetRole)
 | |
|         self.select_drm_button.setToolTip(_("Select all books with DRM."))
 | |
|         self.select_drm_button.clicked.connect(self._select_drm_clicked)
 | |
|         self.select_free_button = button_box.addButton(_("All DRM free"), QDialogButtonBox.ResetRole)
 | |
|         self.select_free_button.setToolTip(_("Select all books without DRM."))
 | |
|         self.select_free_button.clicked.connect(self._select_free_clicked)
 | |
|         layout.addWidget(button_box)
 | |
| 
 | |
|         # Cause our dialog size to be restored from prefs or created on first usage
 | |
|         self.resize_dialog()
 | |
|         self.books_table.populate_table(self.books)
 | |
| 
 | |
|     def _select_all_clicked(self):
 | |
|         self.books_table.select_all()
 | |
| 
 | |
|     def _select_drm_clicked(self):
 | |
|         self.books_table.select_drm(True)
 | |
| 
 | |
|     def _select_free_clicked(self):
 | |
|         self.books_table.select_drm(False)
 | |
| 
 | |
|     def _help_link_activated(self, url):
 | |
|         '''
 | |
|         :param url: Dummy url to pass to the show_help method of the InterfacePluginAction class
 | |
|         '''
 | |
|         self.interface_action.show_help()
 | |
| 
 | |
|     def _ok_clicked(self):
 | |
|         '''
 | |
|         Build an index of the selected titles
 | |
|         '''
 | |
|         if len(self.books_table.selectedItems()):
 | |
|             self.accept()
 | |
|         else:
 | |
|             msg = 'You must make a selection!'
 | |
|             showErrorDlg(msg, self)
 | |
| 
 | |
|     def getBooks(self):
 | |
|         '''
 | |
|         Method to return the selected books
 | |
|         '''
 | |
|         return self.books_table.get_books()
 | |
| 
 | |
| 
 | |
| class BookListTableWidget(QTableWidget):
 | |
| 
 | |
|     def __init__(self, parent):
 | |
|         QTableWidget.__init__(self, parent)
 | |
|         self.setSelectionBehavior(QAbstractItemView.SelectRows)
 | |
| 
 | |
|     def populate_table(self, books):
 | |
|         self.clear()
 | |
|         self.setAlternatingRowColors(True)
 | |
|         self.setRowCount(len(books))
 | |
|         header_labels = ['DRM', _('Title'), _('Author'), _('Series'), 'book_id']
 | |
|         self.setColumnCount(len(header_labels))
 | |
|         self.setHorizontalHeaderLabels(header_labels)
 | |
|         self.verticalHeader().setDefaultSectionSize(24)
 | |
|         self.horizontalHeader().setStretchLastSection(True)
 | |
| 
 | |
|         self.books = {}
 | |
|         for row, book in enumerate(books):
 | |
|             self.populate_table_row(row, book)
 | |
|             self.books[row] = book
 | |
| 
 | |
|         self.setSortingEnabled(False)
 | |
|         self.resizeColumnsToContents()
 | |
|         self.setMinimumColumnWidth(1, 100)
 | |
|         self.setMinimumColumnWidth(2, 100)
 | |
|         self.setMinimumSize(300, 0)
 | |
|         if len(books) > 0:
 | |
|             self.selectRow(0)
 | |
|         self.hideColumn(4)
 | |
|         self.setSortingEnabled(True)
 | |
| 
 | |
|     def setMinimumColumnWidth(self, col, minimum):
 | |
|         if self.columnWidth(col) < minimum:
 | |
|             self.setColumnWidth(col, minimum)
 | |
| 
 | |
|     def populate_table_row(self, row, book):
 | |
|         if book.has_drm:
 | |
|             icon = get_icon('drm-locked.png')
 | |
|             val = 1
 | |
|         else:
 | |
|             icon = get_icon('drm-unlocked.png')
 | |
|             val = 0
 | |
| 
 | |
|         status_cell = IconWidgetItem(None, icon, val)
 | |
|         status_cell.setData(Qt.UserRole, val)
 | |
|         self.setItem(row, 0, status_cell)
 | |
|         self.setItem(row, 1, ReadOnlyTableWidgetItem(book.title))
 | |
|         self.setItem(row, 2, AuthorTableWidgetItem(book.author, book.author))
 | |
|         self.setItem(row, 3, SeriesTableWidgetItem(book.series, book.series_index))
 | |
|         self.setItem(row, 4, NumericTableWidgetItem(row))
 | |
| 
 | |
|     def get_books(self):
 | |
| #        debug_print("BookListTableWidget:get_books - self.books:", self.books)
 | |
|         books = []
 | |
|         if len(self.selectedItems()):
 | |
|             for row in range(self.rowCount()):
 | |
| #                debug_print("BookListTableWidget:get_books - row:", row)
 | |
|                 if self.item(row, 0).isSelected():
 | |
|                     book_num = convert_qvariant(self.item(row, 4).data(Qt.DisplayRole))
 | |
|                     debug_print("BookListTableWidget:get_books - book_num:", book_num)
 | |
|                     book = self.books[book_num]
 | |
|                     debug_print("BookListTableWidget:get_books - book:", book.title)
 | |
|                     books.append(book)
 | |
|         return books
 | |
| 
 | |
|     def select_all(self):
 | |
|         self .selectAll()
 | |
| 
 | |
|     def select_drm(self, has_drm):
 | |
|         self.clearSelection()
 | |
|         current_selection_mode = self.selectionMode()
 | |
|         self.setSelectionMode(QAbstractItemView.MultiSelection)
 | |
|         for row in range(self.rowCount()):
 | |
| #           debug_print("BookListTableWidget:select_drm - row:", row)
 | |
|             if convert_qvariant(self.item(row, 0).data(Qt.UserRole)) == 1:
 | |
| #                debug_print("BookListTableWidget:select_drm - has DRM:", row)
 | |
|                 if has_drm:
 | |
|                     self.selectRow(row)
 | |
|             else:
 | |
| #                debug_print("BookListTableWidget:select_drm - DRM free:", row)
 | |
|                 if not has_drm:
 | |
|                     self.selectRow(row)
 | |
|         self.setSelectionMode(current_selection_mode)
 | |
| 
 | |
| 
 | |
| class DecryptAddProgressDialog(QProgressDialog):
 | |
|     '''
 | |
|     Use the QTimer singleShot method to dole out books one at
 | |
|     a time to the indicated callback function from action.py
 | |
|     '''
 | |
|     def __init__(self, gui, indices, callback_fn, db, db_type='calibre', status_msg_type='books', action_type=('Decrypting','Decryption')):
 | |
|         '''
 | |
|         :param gui: Parent gui
 | |
|         :param indices: List of Kobo books or list calibre book maps (indicated by param db_type)
 | |
|         :param callback_fn: the function from action.py that will do the heavy lifting (get_decrypted_kobo_books or add_new_books)
 | |
|         :param db: kobo database object or calibre database cache (indicated by param db_type)
 | |
|         :param db_type: string indicating what kind of database param db is
 | |
|         :param status_msg_type: string to indicate what the ProgressDialog is operating on (cosmetic only)
 | |
|         :param action_type: 2-Tuple of strings indicating what the ProgressDialog is doing to param status_msg_type (cosmetic only)
 | |
|         '''
 | |
| 
 | |
|         self.total_count = len(indices)
 | |
|         QProgressDialog.__init__(self, '', 'Cancel', 0, self.total_count, gui)
 | |
|         self.setMinimumWidth(500)
 | |
|         self.indices, self.callback_fn, self.db, self.db_type = indices, callback_fn, db, db_type
 | |
|         self.action_type, self.status_msg_type = action_type, status_msg_type
 | |
|         self.gui = gui
 | |
|         self.setWindowTitle('{0} {1} {2}...'.format(self.action_type[0], self.total_count, self.status_msg_type))
 | |
|         self.i, self.successes, self.failures = 0, [], []
 | |
|         QTimer.singleShot(0, self.do_book_action)
 | |
|         self.exec_()
 | |
| 
 | |
|     def do_book_action(self):
 | |
|         if self.wasCanceled():
 | |
|             return self.do_close()
 | |
|         if self.i >= self.total_count:
 | |
|             return self.do_close()
 | |
|         book = self.indices[self.i]
 | |
|         self.i += 1
 | |
| 
 | |
|         # Get the title and build the caption and label text from the string parameters provided
 | |
|         if self.db_type == 'calibre':
 | |
|             dtitle = book[0].title
 | |
|         elif self.db_type == 'kobo':
 | |
|             dtitle = book.title
 | |
|         self.setWindowTitle('{0} {1} {2}  ({3} {4} failures)...'.format(self.action_type[0], self.total_count,
 | |
|                                                                 self.status_msg_type, len(self.failures), self.action_type[1]))
 | |
|         self.setLabelText('{0}: {1}'.format(self.action_type[0], dtitle))
 | |
|         # If a calibre db, feed the calibre bookmap to action.py's add_new_books method
 | |
|         if self.db_type == 'calibre':
 | |
|             if self.callback_fn([book]):
 | |
|                 self.successes.append(book)
 | |
|             else:
 | |
|                 self.failures.append(book)
 | |
|         # If a kobo db, feed the index to the kobo book to action.py's get_decrypted_kobo_books method
 | |
|         elif self.db_type == 'kobo':
 | |
|             if self.callback_fn(book):
 | |
|                 debug_print("DecryptAddProgressDialog::do_book_action - decrypted book: '%s'" % dtitle)
 | |
|                 self.successes.append(book)
 | |
|             else:
 | |
|                 debug_print("DecryptAddProgressDialog::do_book_action - book decryption failed: '%s'" % dtitle)
 | |
|                 self.failures.append(book)
 | |
|         self.setValue(self.i)
 | |
| 
 | |
|         # Lather, rinse, repeat.
 | |
|         QTimer.singleShot(0, self.do_book_action)
 | |
| 
 | |
|     def do_close(self):
 | |
|         self.hide()
 | |
|         self.gui = None
 | |
| 
 | |
| class AddEpubFormatsProgressDialog(QProgressDialog):
 | |
|     '''
 | |
|     Use the QTimer singleShot method to dole out epub formats one at
 | |
|     a time to the indicated callback function from action.py
 | |
|     '''
 | |
|     def __init__(self, gui, entries, callback_fn, status_msg_type='formats', action_type=('Adding','Added')):
 | |
|         '''
 | |
|         :param gui: Parent gui
 | |
|         :param entries: List of 3-tuples  [(target calibre id, calibre metadata object, path to epub file)]
 | |
|         :param callback_fn: the function from action.py that will do the heavy lifting (process_epub_formats)
 | |
|         :param status_msg_type: string to indicate what the ProgressDialog is operating on (cosmetic only)
 | |
|         :param action_type: 2-tuple of strings indicating what the ProgressDialog is doing to param status_msg_type (cosmetic only)
 | |
|         '''
 | |
| 
 | |
|         self.total_count = len(entries)
 | |
|         QProgressDialog.__init__(self, '', 'Cancel', 0, self.total_count, gui)
 | |
|         self.setMinimumWidth(500)
 | |
|         self.entries, self.callback_fn = entries, callback_fn
 | |
|         self.action_type, self.status_msg_type = action_type, status_msg_type
 | |
|         self.gui = gui
 | |
|         self.setWindowTitle('{0} {1} {2}...'.format(self.action_type[0], self.total_count, self.status_msg_type))
 | |
|         self.i, self.successes, self.failures = 0, [], []
 | |
|         QTimer.singleShot(0, self.do_book_action)
 | |
|         self.exec_()
 | |
| 
 | |
|     def do_book_action(self):
 | |
|         if self.wasCanceled():
 | |
|             return self.do_close()
 | |
|         if self.i >= self.total_count:
 | |
|             return self.do_close()
 | |
|         epub_format = self.entries[self.i]
 | |
|         self.i += 1
 | |
| 
 | |
|         # assign the elements of the 3-tuple details to legible variables
 | |
|         book_id, mi, path = epub_format[0], epub_format[1], epub_format[2]
 | |
|         
 | |
|         # Get the title and build the caption and label text from the string parameters provided
 | |
|         dtitle = mi.title
 | |
|         self.setWindowTitle('{0} {1} {2}  ({3} {4} failures)...'.format(self.action_type[0], self.total_count,
 | |
|                                                                 self.status_msg_type, len(self.failures), self.action_type[1]))
 | |
|         self.setLabelText('{0}: {1}'.format(self.action_type[0], dtitle))
 | |
|         # Send the necessary elements to the process_epub_formats callback function (action.py)
 | |
|         # and record the results
 | |
|         if self.callback_fn(book_id, mi, path):
 | |
|             self.successes.append((book_id, mi, path))
 | |
|         else:
 | |
|             self.failures.append((book_id, mi, path))
 | |
|         self.setValue(self.i)
 | |
| 
 | |
|         # Lather, rinse, repeat
 | |
|         QTimer.singleShot(0, self.do_book_action)
 | |
| 
 | |
|     def do_close(self):
 | |
|         self.hide()
 | |
|         self.gui = None
 | |
| 
 | |
| class ViewLog(QDialog):
 | |
|     '''
 | |
|     Show a detailed summary of results as html.
 | |
|     '''
 | |
|     def __init__(self, title, html, parent=None):
 | |
|         '''
 | |
|         :param title: Caption for window title
 | |
|         :param html: HTML string log/report
 | |
|         '''
 | |
|         QDialog.__init__(self, parent)
 | |
|         self.l = l = QVBoxLayout()
 | |
|         self.setLayout(l)
 | |
| 
 | |
|         self.tb = QTextBrowser(self)
 | |
|         QApplication.setOverrideCursor(Qt.WaitCursor)
 | |
|         # Rather than formatting the text in <pre> blocks like the calibre
 | |
|         # ViewLog does, instead just format it inside divs to keep style formatting
 | |
|         html = html.replace('\t','    ')#.replace('\n', '<br/>')
 | |
|         html = html.replace('> ','> ')
 | |
|         self.tb.setHtml('<div>{0}</div>'.format(html))
 | |
|         QApplication.restoreOverrideCursor()
 | |
|         l.addWidget(self.tb)
 | |
| 
 | |
|         self.bb = QDialogButtonBox(QDialogButtonBox.Ok)
 | |
|         self.bb.accepted.connect(self.accept)
 | |
|         self.bb.rejected.connect(self.reject)
 | |
|         self.copy_button = self.bb.addButton(_('Copy to clipboard'),
 | |
|                 self.bb.ActionRole)
 | |
|         self.copy_button.setIcon(QIcon(I('edit-copy.png')))
 | |
|         self.copy_button.clicked.connect(self.copy_to_clipboard)
 | |
|         l.addWidget(self.bb)
 | |
|         self.setModal(False)
 | |
|         self.resize(QSize(700, 500))
 | |
|         self.setWindowTitle(title)
 | |
|         self.setWindowIcon(QIcon(I('dialog_information.png')))
 | |
|         self.show()
 | |
| 
 | |
|     def copy_to_clipboard(self):
 | |
|         txt = self.tb.toPlainText()
 | |
|         QApplication.clipboard().setText(txt)
 | |
| 
 | |
| 
 | |
| class ResultsSummaryDialog(MessageBox): 
 | |
|     def __init__(self, parent, title, msg, log='', det_msg=''):
 | |
|         '''
 | |
|         :param log: An HTML log
 | |
|         :param title: The title for this popup
 | |
|         :param msg: The msg to display
 | |
|         :param det_msg: Detailed message
 | |
|         '''
 | |
|         MessageBox.__init__(self, MessageBox.INFO, title, msg,
 | |
|                 det_msg=det_msg, show_copy_button=False,
 | |
|                 parent=parent)
 | |
|         self.log = log
 | |
|         self.vlb = self.bb.addButton(_('View Report'), self.bb.ActionRole)
 | |
|         self.vlb.setIcon(QIcon(I('dialog_information.png')))
 | |
|         self.vlb.clicked.connect(self.show_log)
 | |
|         self.det_msg_toggle.setVisible(bool(det_msg))
 | |
|         self.vlb.setVisible(bool(log))
 | |
| 
 | |
|     def show_log(self):
 | |
|         self.log_viewer = ViewLog(PLUGIN_NAME + ' v' + PLUGIN_VERSION, self.log,
 | |
|                 parent=self)
 | |
| 
 | |
| 
 | |
| class ReadOnlyTableWidgetItem(QTableWidgetItem):
 | |
|     def __init__(self, text):
 | |
|         if text is None:
 | |
|             text = ''
 | |
|         QTableWidgetItem.__init__(self, text, QTableWidgetItem.ItemType.UserType)
 | |
|         self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
 | |
| 
 | |
| class AuthorTableWidgetItem(ReadOnlyTableWidgetItem):
 | |
|     def __init__(self, text, sort_key):
 | |
|         ReadOnlyTableWidgetItem.__init__(self, text)
 | |
|         self.sort_key = sort_key
 | |
| 
 | |
|     #Qt uses a simple < check for sorting items, override this to use the sortKey
 | |
|     def __lt__(self, other):
 | |
|         return self.sort_key < other.sort_key
 | |
| 
 | |
| class SeriesTableWidgetItem(ReadOnlyTableWidgetItem):
 | |
|     def __init__(self, series, series_index=None):
 | |
|         display = ''
 | |
|         if series:
 | |
|             if series_index:
 | |
|                 from calibre.ebooks.metadata import fmt_sidx
 | |
|                 display = '%s [%s]' % (series, fmt_sidx(series_index))
 | |
|                 self.sortKey = '%s%04d' % (series, series_index)
 | |
|             else:
 | |
|                 display = series
 | |
|                 self.sortKey = series
 | |
|         ReadOnlyTableWidgetItem.__init__(self, display)
 | |
| 
 | |
| class IconWidgetItem(ReadOnlyTableWidgetItem):
 | |
|     def __init__(self, text, icon, sort_key):
 | |
|         ReadOnlyTableWidgetItem.__init__(self, text)
 | |
|         if icon:
 | |
|             self.setIcon(icon)
 | |
|         self.sort_key = sort_key
 | |
| 
 | |
|     #Qt uses a simple < check for sorting items, override this to use the sortKey
 | |
|     def __lt__(self, other):
 | |
|         return self.sort_key < other.sort_key
 | |
| 
 | |
| class NumericTableWidgetItem(QTableWidgetItem):
 | |
| 
 | |
|     def __init__(self, number, is_read_only=False):
 | |
|         QTableWidgetItem.__init__(self, '', QTableWidgetItem.ItemType.UserType)
 | |
|         self.setData(Qt.DisplayRole, number)
 | |
|         if is_read_only:
 | |
|             self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
 | |
| 
 | 
