%PDF- %PDF-
Direktori : /usr/lib/calibre/calibre/gui2/viewer/ |
Current File : //usr/lib/calibre/calibre/gui2/viewer/annotations.py |
#!/usr/bin/env python3 # License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net> import os from io import BytesIO from operator import itemgetter from threading import Thread from calibre.db.annotations import merge_annot_lists from calibre.gui2.viewer.convert_book import update_book from calibre.gui2.viewer.integration import save_annotations_list_to_library from calibre.gui2.viewer.web_view import viewer_config_dir from calibre.srv.render_book import EPUB_FILE_TYPE_MAGIC from calibre.utils.date import EPOCH from calibre.utils.iso8601 import parse_iso8601 from calibre.utils.serialize import json_dumps, json_loads from calibre.utils.zipfile import safe_replace from polyglot.binary import as_base64_bytes from polyglot.builtins import iteritems from polyglot.queue import Queue annotations_dir = os.path.join(viewer_config_dir, 'annots') parse_annotations = json_loads def annotations_as_copied_list(annots_map): for atype, annots in iteritems(annots_map): for annot in annots: ts = (parse_iso8601(annot['timestamp'], assume_utc=True) - EPOCH).total_seconds() annot = annot.copy() annot['type'] = atype yield annot, ts def annot_list_as_bytes(annots): return json_dumps(tuple(annot for annot, seconds in annots)) def split_lines(chunk, length=80): pos = 0 while pos < len(chunk): yield chunk[pos:pos+length] pos += length def save_annots_to_epub(path, serialized_annots): try: zf = open(path, 'r+b') except OSError: return with zf: serialized_annots = EPUB_FILE_TYPE_MAGIC + b'\n'.join(split_lines(as_base64_bytes(serialized_annots))) safe_replace(zf, 'META-INF/calibre_bookmarks.txt', BytesIO(serialized_annots), add_missing=True) def save_annotations(annotations_list, annotations_path_key, bld, pathtoebook, in_book_file, sync_annots_user): annots = annot_list_as_bytes(annotations_list) with open(os.path.join(annotations_dir, annotations_path_key), 'wb') as f: f.write(annots) if in_book_file and os.access(pathtoebook, os.W_OK): before_stat = os.stat(pathtoebook) save_annots_to_epub(pathtoebook, annots) update_book(pathtoebook, before_stat, {'calibre-book-annotations.json': annots}) if bld: save_annotations_list_to_library(bld, annotations_list, sync_annots_user) class AnnotationsSaveWorker(Thread): def __init__(self): Thread.__init__(self, name='AnnotSaveWorker') self.daemon = True self.queue = Queue() def shutdown(self): if self.is_alive(): self.queue.put(None) self.join() def run(self): while True: x = self.queue.get() if x is None: return annotations_list = x['annotations_list'] annotations_path_key = x['annotations_path_key'] bld = x['book_library_details'] pathtoebook = x['pathtoebook'] in_book_file = x['in_book_file'] sync_annots_user = x['sync_annots_user'] try: save_annotations(annotations_list, annotations_path_key, bld, pathtoebook, in_book_file, sync_annots_user) except Exception: import traceback traceback.print_exc() def save_annotations(self, current_book_data, in_book_file=True, sync_annots_user=''): alist = tuple(annotations_as_copied_list(current_book_data['annotations_map'])) ebp = current_book_data['pathtoebook'] can_save_in_book_file = ebp.lower().endswith('.epub') self.queue.put({ 'annotations_list': alist, 'annotations_path_key': current_book_data['annotations_path_key'], 'book_library_details': current_book_data['book_library_details'], 'pathtoebook': current_book_data['pathtoebook'], 'in_book_file': in_book_file and can_save_in_book_file, 'sync_annots_user': sync_annots_user, }) def find_tests(): import unittest def bm(title, bmid, year=20, first_cfi_number=1): return { 'title': title, 'id': bmid, 'timestamp': f'20{year}-06-29T03:21:48.895323+00:00', 'pos_type': 'epubcfi', 'pos': f'epubcfi(/{first_cfi_number}/4/8)' } def hl(uuid, hlid, year=20, first_cfi_number=1): return { 'uuid': uuid, 'id': hlid, 'timestamp': f'20{year}-06-29T03:21:48.895323+00:00', 'start_cfi': f'epubcfi(/{first_cfi_number}/4/8)' } class AnnotationsTest(unittest.TestCase): def test_merge_annotations(self): for atype in 'bookmark highlight'.split(): f = bm if atype == 'bookmark' else hl a = [f('one', 1, 20, 2), f('two', 2, 20, 4), f('a', 3, 20, 16),] b = [f('one', 10, 30, 2), f('two', 20, 10, 4), f('b', 30, 20, 8),] c = merge_annot_lists(a, b, atype) self.assertEqual(tuple(map(itemgetter('id'), c)), (10, 2, 30, 3)) return unittest.TestLoader().loadTestsFromTestCase(AnnotationsTest)