diff --git a/Obok_calibre_plugin/obok_plugin/__init__.py b/Obok_calibre_plugin/obok_plugin/__init__.py index 0b4e7d3..dd643c7 100644 --- a/Obok_calibre_plugin/obok_plugin/__init__.py +++ b/Obok_calibre_plugin/obok_plugin/__init__.py @@ -29,7 +29,7 @@ class ObokDeDRMAction(InterfaceActionBase): name = PLUGIN_NAME description = PLUGIN_DESCRIPTION - supported_platforms = ['windows', 'osx'] + supported_platforms = ['windows', 'osx', 'linux' ] author = PLUGIN_AUTHORS version = PLUGIN_VERSION_TUPLE minimum_calibre_version = (1, 0, 0) diff --git a/Obok_calibre_plugin/obok_plugin/action.py b/Obok_calibre_plugin/obok_plugin/action.py index d93816e..e951e8a 100644 --- a/Obok_calibre_plugin/obok_plugin/action.py +++ b/Obok_calibre_plugin/obok_plugin/action.py @@ -79,7 +79,7 @@ class InterfacePluginAction(InterfaceAction): print ('Running {}'.format(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) # Get the Kobo Library object (obok v3.01) - self.library = KoboLibrary() + self.library = KoboLibrary(cfg['kobo_serials']) # Get a list of Kobo titles books = self.build_book_list() diff --git a/Obok_calibre_plugin/obok_plugin/config.py b/Obok_calibre_plugin/obok_plugin/config.py index c4e470e..8f8fa3d 100644 --- a/Obok_calibre_plugin/obok_plugin/config.py +++ b/Obok_calibre_plugin/obok_plugin/config.py @@ -3,15 +3,23 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) try: - from PyQt5.Qt import (QWidget, QLabel, QVBoxLayout, QHBoxLayout, QComboBox) + from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem) except ImportError: - from PyQt4.Qt import (QWidget, QLabel, QVBoxLayout, QHBoxLayout, QComboBox) - + from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem) + +try: + from PyQt5 import Qt as QtGui +except ImportError: + from PyQt4 import QtGui + +from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url) from calibre.utils.config import JSONConfig, config_dir plugin_prefs = JSONConfig('plugins/obok_dedrm_prefs') plugin_prefs.defaults['finding_homes_for_formats'] = 'Ask' +plugin_prefs.defaults['kobo_serials'] = [] +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION from calibre_plugins.obok_dedrm.utilities import (debug_print) try: debug_print("obok::config.py - loading translations") @@ -26,7 +34,10 @@ class ConfigWidget(QWidget): self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) - + + # copy of preferences + self.tmpserials = plugin_prefs['kobo_serials'] + combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self) layout.addWidget(combo_label) self.find_homes = QComboBox() @@ -35,6 +46,178 @@ class ConfigWidget(QWidget): self.find_homes.addItems([_('Ask'), _('Always'), _('Never')]) index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats']) self.find_homes.setCurrentIndex(index) + + self.serials_button = QtGui.QPushButton(self) + self.serials_button.setToolTip(_(u"Click to manage Kobo serial numbers for Kobo ebooks")) + self.serials_button.setText(u"Kobo devices serials") + self.serials_button.clicked.connect(self.edit_serials) + layout.addWidget(self.serials_button) + + + def edit_serials(self): + d = ManageKeysDialog(self,u"Kobo device serial numbers",self.tmpserials, AddSerialDialog) + d.exec_() + def save_settings(self): plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText()) + plugin_prefs['kobo_serials'] = self.tmpserials + + + + +class ManageKeysDialog(QDialog): + def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u""): + QDialog.__init__(self,parent) + self.parent = parent + self.key_type_name = key_type_name + self.plugin_keys = plugin_keys + self.create_key = create_key + self.keyfile_ext = keyfile_ext + self.json_file = (keyfile_ext == u"k4i") + + self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name)) + + # Start Qt Gui dialog layout + layout = QVBoxLayout(self) + self.setLayout(layout) + + keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self) + layout.addWidget(keys_group_box) + keys_group_box_layout = QHBoxLayout() + keys_group_box.setLayout(keys_group_box_layout) + + self.listy = QListWidget(self) + self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name)) + self.listy.setSelectionMode(QAbstractItemView.SingleSelection) + self.populate_list() + keys_group_box_layout.addWidget(self.listy) + + button_layout = QVBoxLayout() + keys_group_box_layout.addLayout(button_layout) + self._add_key_button = QtGui.QToolButton(self) + self._add_key_button.setIcon(QIcon(I('plus.png'))) + self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name)) + self._add_key_button.clicked.connect(self.add_key) + button_layout.addWidget(self._add_key_button) + + self._delete_key_button = QtGui.QToolButton(self) + self._delete_key_button.setToolTip(_(u"Delete highlighted key")) + self._delete_key_button.setIcon(QIcon(I('list_remove.png'))) + self._delete_key_button.clicked.connect(self.delete_key) + button_layout.addWidget(self._delete_key_button) + + spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + button_layout.addItem(spacerItem) + + layout.addSpacing(5) + migrate_layout = QHBoxLayout() + layout.addLayout(migrate_layout) + migrate_layout.addStretch() + self.button_box = QDialogButtonBox(QDialogButtonBox.Close) + self.button_box.rejected.connect(self.close) + migrate_layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + def populate_list(self): + if type(self.plugin_keys) == dict: + for key in self.plugin_keys.keys(): + self.listy.addItem(QListWidgetItem(key)) + else: + for key in self.plugin_keys: + self.listy.addItem(QListWidgetItem(key)) + + def add_key(self): + d = self.create_key(self) + d.exec_() + + if d.result() != d.Accepted: + # New key generation cancelled. + return + new_key_value = d.key_value + if new_key_value in self.plugin_keys: + info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), + u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True) + return + + self.plugin_keys.append(d.key_value) + self.listy.clear() + self.populate_list() + + def rename_key(self): + if not self.listy.currentItem(): + errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name) + r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + return + + d = RenameKeyDialog(self) + d.exec_() + + if d.result() != d.Accepted: + # rename cancelled or moot. + return + keyname = unicode(self.listy.currentItem().text()) + if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named {0} to {1}?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False): + return + self.plugin_keys[d.key_name] = self.plugin_keys[keyname] + del self.plugin_keys[keyname] + + self.listy.clear() + self.populate_list() + + def delete_key(self): + if not self.listy.currentItem(): + return + keyname = unicode(self.listy.currentItem().text()) + if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} {0}?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): + return + self.plugin_keys.remove(keyname) + + self.listy.clear() + self.populate_list() + +class AddSerialDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Add New eInk Kobo Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"EInk Kobo Serial Number:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"Enter an eInk Kobo serial number. EInk Kobo serial numbers are 13 characters long and usually start with a 'N'. Kobo Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") + key_group.addWidget(self.key_ledit) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text()).strip() + + @property + def key_value(self): + return unicode(self.key_ledit.text()).strip() + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog." + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) != 13: + errmsg = u"EInk Kobo Serial Numbers must be 13 characters long. This is {0:d} characters long.".format(len(self.key_name)) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) diff --git a/Obok_calibre_plugin/obok_plugin/obok/obok.py b/Obok_calibre_plugin/obok_plugin/obok/obok.py index 18e629b..577312e 100644 --- a/Obok_calibre_plugin/obok_plugin/obok/obok.py +++ b/Obok_calibre_plugin/obok_plugin/obok/obok.py @@ -244,7 +244,7 @@ class KoboLibrary(object): written by the Kobo Desktop Edition application, including the list of books, their titles, and the user's encryption key(s).""" - def __init__ (self): + def __init__ (self, serials = []): print u"Obok v{0}\nCopyright © 2012-2015 Physisticated et al.".format(__version__) if sys.platform.startswith('win'): if sys.getwindowsversion().major > 5: @@ -254,13 +254,20 @@ class KoboLibrary(object): self.kobodir = os.path.join(self.kobodir, 'Kobo', 'Kobo Desktop Edition') elif sys.platform.startswith('darwin'): self.kobodir = os.path.join(os.environ['HOME'], 'Library', 'Application Support', 'Kobo', 'Kobo Desktop Edition') + elif sys.platform.startswith('linux'): + # TODO TODO TODO needs change - fixed path to mount point + self.kobodir = '/media/norbert/KOBOeReader/.kobo' self.bookdir = os.path.join(self.kobodir, 'kepub') - kobodb = os.path.join(self.kobodir, 'Kobo.sqlite') + if sys.platform.startswith('linux'): + kobodb = os.path.join(self.kobodir, 'KoboReader.sqlite') + else: + kobodb = os.path.join(self.kobodir, 'Kobo.sqlite') self.__sqlite = sqlite3.connect(kobodb) self.__cursor = self.__sqlite.cursor() self._userkeys = [] self._books = [] self._volumeID = [] + self._serials = serials def close (self): """Closes the database used by the library.""" @@ -319,6 +326,9 @@ class KoboLibrary(object): for m in matches: # print "m:",m[0] macaddrs.append(m[0].upper()) + elif sys.platform.startswith('linux'): + macaddrs.extend(self._serials) + return macaddrs def __getuserids (self): @@ -486,7 +496,7 @@ def cli_main(): lib = KoboLibrary() for i, book in enumerate(lib.books): - print ('%d: %s' % (i + 1, book.title)) + print ('%d: %s' % (i + 1, book.title)).encode('ascii', 'ignore') num_string = raw_input("Convert book number... ") try: