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: