From f09e8984885dbeb2c2c4df42cfab61c911656ff8 Mon Sep 17 00:00:00 2001 From: John Schember Date: Tue, 2 Mar 2010 07:53:43 -0500 Subject: [PATCH 1/3] Implement bug #4990: Include \x\vChapter\x\v in TOC with PML input. --- src/calibre/ebooks/pml/pmlconverter.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/calibre/ebooks/pml/pmlconverter.py b/src/calibre/ebooks/pml/pmlconverter.py index 1044dc9593..166695ff5c 100644 --- a/src/calibre/ebooks/pml/pmlconverter.py +++ b/src/calibre/ebooks/pml/pmlconverter.py @@ -79,7 +79,7 @@ class PML_HTMLizer(object): 'd': ('', ''), 'b': ('', ''), 'l': ('', ''), - 'k': ('', ''), + 'k': ('', ''), 'FN': ('

', '

<return
'), 'SB': ('

', '

return
'), } @@ -154,6 +154,11 @@ class PML_HTMLizer(object): self.file_name = '' def prepare_pml(self, pml): + # Give Chapters the form \\*='text'text\\*. This is used for generating + # the TOC later. + pml = re.sub(r'(?<=\\x)(?P.*?)(?=\\x)', lambda match: '="%s"%s' % (self.strip_pml(match.group('text')), match.group('text')), pml) + pml = re.sub(r'(?<=\\X[0-4])(?P.*?)(?=\\X[0-4])', lambda match: '="%s"%s' % (self.strip_pml(match.group('text')), match.group('text')), pml) + # Remove comments pml = re.sub(r'(?mus)\\v(?P.*?)\\v', '', pml) @@ -163,7 +168,7 @@ class PML_HTMLizer(object): pml = re.sub(r'(?mus)(?<=.)[ ]*$', '', pml) pml = re.sub(r'(?mus)^[ ]*$', '', pml) - # Footnotes and Sidebars + # Footnotes and Sidebars. pml = re.sub(r'(?mus).+?)">\s*(?P.*?)\s*', lambda match: '\\FN="%s"%s\\FN' % (match.group('target'), match.group('text')) if match.group('text') else '', pml) pml = re.sub(r'(?mus).+?)">\s*(?P.*?)\s*', lambda match: '\\SB="%s"%s\\SB' % (match.group('target'), match.group('text')) if match.group('text') else '', pml) @@ -171,9 +176,7 @@ class PML_HTMLizer(object): # &. It will display as & pml = pml.replace('&', '&') - pml = re.sub(r'(?<=\\x)(?P.*?)(?=\\x)', lambda match: '="%s"%s' % (self.strip_pml(match.group('text')), match.group('text')), pml) - pml = re.sub(r'(?<=\\X[0-4])(?P.*?)(?=\\X[0-4])', lambda match: '="%s"%s' % (self.strip_pml(match.group('text')), match.group('text')), pml) - + # Replace \\a and \\U with either the unicode character or the entity. pml = re.sub(r'\\a(?P\d{3})', lambda match: '&#%s;' % match.group('num'), pml) pml = re.sub(r'\\U(?P[0-9a-f]{4})', lambda match: '%s' % my_unichr(int(match.group('num'), 16)), pml) @@ -536,6 +539,7 @@ class PML_HTMLizer(object): elif '%s%s' % (c, l) == 'Sd': text = self.process_code('Sd', line, 'sb') elif c in 'xXC': + empty = False # The PML was modified eariler so x and X put the text # inside of ="" so we don't have do special processing # for C. @@ -578,10 +582,7 @@ class PML_HTMLizer(object): else: if c != ' ': empty = False - if self.state['k'][0]: - text = c.upper() - else: - text = c + text = c parsed.append(text) c = line.read(1) From 243400ee70a313345e92f2a2373eb8e3200d0441 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 2 Mar 2010 09:51:13 -0700 Subject: [PATCH 2/3] ... --- src/calibre/customize/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 2d4e89492b..0d6f041736 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -129,18 +129,18 @@ class Plugin(object): zip_safe = False if zip_safe: sys.path.insert(0, self.plugin_path) - self._sys_insertion_path = self.plugin_path + self.sys_insertion_path = self.plugin_path else: from calibre.ptempfile import TemporaryDirectory self._sys_insertion_tdir = TemporaryDirectory('plugin_unzip') - self._sys_insertion_path = self._sys_insertion_tdir.__enter__(*args) - zf.extractall(self._sys_insertion_path) - sys.path.insert(0, self._sys_insertion_path) + self.sys_insertion_path = self._sys_insertion_tdir.__enter__(*args) + zf.extractall(self.sys_insertion_path) + sys.path.insert(0, self.sys_insertion_path) zf.close() def __exit__(self, *args): - ip, it = getattr(self, '_sys_insertion_path', None), getattr(self, + ip, it = getattr(self, 'sys_insertion_path', None), getattr(self, '_sys_insertion_tdir', None) if ip in sys.path: sys.path.remove(ip) From 5238aab3f8b3a2eb5e18714c27ae3257ad9f3d70 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 2 Mar 2010 13:12:48 -0700 Subject: [PATCH 3/3] Framework for fetching annotations --- src/calibre/devices/kindle/driver.py | 4 ++++ src/calibre/gui2/device.py | 28 +++++++++++++++++++++++++--- src/calibre/gui2/ui.py | 24 +++++++++++++++++++++++- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index 96aa296b5d..3499f3b076 100644 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -44,6 +44,7 @@ class KINDLE(USBMS): EBOOK_DIR_CARD_A = 'documents' DELETE_EXTS = ['.mbp'] SUPPORTS_SUB_DIRS = True + SUPPORTS_ANNOTATIONS = True WIRELESS_FILE_NAME_PATTERN = re.compile( r'(?P[^-]+)-asin_(?P<asin>[a-zA-Z\d]{10,})-type_(?P<type>\w{4})-v_(?P<index>\d+).*') @@ -60,6 +61,9 @@ class KINDLE(USBMS): 'replace') return mi + def get_annotations(self, path_map): + return {} + class KINDLE2(KINDLE): diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 679e86ab48..04219a387f 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -8,7 +8,7 @@ from functools import partial from binascii import unhexlify from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \ - Qt + Qt, pyqtSignal from calibre.customize.ui import available_input_formats, available_output_formats, \ device_plugins @@ -218,6 +218,16 @@ class DeviceManager(Thread): '''Return callable that returns the list of books on device as two booklists''' return self.create_job(self._books, done, description=_('Get list of books on device')) + def _annotations(self, path_map): + return self.device.get_annotations(path_map) + + def annotations(self, path_map, done): + '''Return mapping of ids to annotations. Each annotation is of the + form (type, location_info, content). path_map is a mapping of + ids to paths on the device.''' + return self.create_job(self._annotations, done, args=[path_map], + description=_('Get annotations from device')) + def _sync_booklists(self, booklists): '''Sync metadata to device''' self.device.sync_booklists(booklists, end_session=False) @@ -298,6 +308,8 @@ class DeviceAction(QAction): class DeviceMenu(QMenu): + fetch_annotations = pyqtSignal() + def __init__(self, parent=None): QMenu.__init__(self, parent) self.group = QActionGroup(self) @@ -389,10 +401,16 @@ class DeviceMenu(QMenu): self.connect(self.group, SIGNAL('triggered(QAction*)'), self.change_default_action) - self.enable_device_actions(False) if opts.accounts: self.addSeparator() self.addMenu(self.email_to_menu) + self.addSeparator() + annot = self.addAction(_('Fetch annotations (experimental)')) + annot.setEnabled(False) + annot.triggered.connect(lambda x : + self.fetch_annotations.emit()) + self.annotation_action = annot + self.enable_device_actions(False) def change_default_action(self, action): config['default_send_to_device_action'] = repr(action) @@ -409,7 +427,8 @@ class DeviceMenu(QMenu): self.action_triggered(action) break - def enable_device_actions(self, enable, card_prefix=(None, None)): + def enable_device_actions(self, enable, card_prefix=(None, None), + device=None): for action in self.actions: if action.dest in ('main:', 'carda:0', 'cardb:0'): if not enable: @@ -428,6 +447,9 @@ class DeviceMenu(QMenu): else: action.setEnabled(False) + annot_enable = enable and getattr(device, 'SUPPORTS_ANNOTATIONS', False) + self.annotation_action.setEnabled(annot_enable) + class Emailer(Thread): diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index abafd4e58c..888600ebb2 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -617,6 +617,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.dispatch_sync_event) self.connect(self.action_sync, SIGNAL('triggered(bool)'), self._sync_menu.trigger_default) + self._sync_menu.fetch_annotations.connect(self.fetch_annotations) def add_spare_server(self, *args): self.spare_servers.append(Server(limit=int(config['worker_limit']/2.0))) @@ -855,7 +856,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.device_manager.device.__class__.get_gui_name()+\ _(' detected.'), 3000) self.device_connected = True - self._sync_menu.enable_device_actions(True, self.device_manager.device.card_prefix()) + self._sync_menu.enable_device_actions(True, + self.device_manager.device.card_prefix(), + self.device_manager.device) self.location_view.model().device_connected(self.device_manager.device) else: self.save_device_view_settings() @@ -918,7 +921,26 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.sync_catalogs() ############################################################################ + ######################### Fetch annotations ################################ + def fetch_annotations(self, *args): + #current_device = self.device_manager.device + path_map = {} + # code to calculate path_map + self.device_manager.annotations(Dispatcher(self.annotations_fetched), + path_map) + + def annotations_fetched(self, annotation_map): + if not annotation_map: return + from calibre.gui2.dialogs.progress import ProgressDialog + pd = ProgressDialog(_('Adding annotations'), + _('Annotations will be saved in the comments field'), + min=0, max=0, parent=self) + # code to add annotations to database should run in a separate + # thread as it could potentially take a long time + pd.exec_() + + ############################################################################ ################################# Add books ################################