mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Allow syninc annots from viewer to specific content server user
This commit is contained in:
parent
d11b9b6f29
commit
18c779a1a8
@ -74,13 +74,17 @@ def merge_annot_lists(a, b, annot_type):
|
|||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
def merge_annotations(annots, annots_map):
|
def merge_annotations(annots, annots_map, merge_last_read=True):
|
||||||
# If you make changes to this algorithm also update the
|
# If you make changes to this algorithm also update the
|
||||||
# implementation in read_book.annotations
|
# implementation in read_book.annotations
|
||||||
|
if isinstance(annots, dict):
|
||||||
|
amap = annots
|
||||||
|
else:
|
||||||
amap = defaultdict(list)
|
amap = defaultdict(list)
|
||||||
for annot in annots:
|
for annot in annots:
|
||||||
amap[annot['type']].append(annot)
|
amap[annot['type']].append(annot)
|
||||||
|
|
||||||
|
if merge_last_read:
|
||||||
lr = amap.get('last-read')
|
lr = amap.get('last-read')
|
||||||
if lr:
|
if lr:
|
||||||
existing = annots_map.get('last-read')
|
existing = annots_map.get('last-read')
|
||||||
@ -92,7 +96,7 @@ def merge_annotations(annots, annots_map):
|
|||||||
|
|
||||||
for annot_type, field in merge_field_map.items():
|
for annot_type, field in merge_field_map.items():
|
||||||
a = annots_map.get(annot_type)
|
a = annots_map.get(annot_type)
|
||||||
b = amap[annot_type]
|
b = amap.get(annot_type)
|
||||||
if not b:
|
if not b:
|
||||||
continue
|
continue
|
||||||
changed, annots_map[annot_type] = merge_annots_with_identical_field(a or [], b, field=field)
|
changed, annots_map[annot_type] = merge_annots_with_identical_field(a or [], b, field=field)
|
||||||
|
@ -100,8 +100,16 @@ class ViewAction(InterfaceAction):
|
|||||||
self.view_format_by_id(id_, format)
|
self.view_format_by_id(id_, format)
|
||||||
|
|
||||||
def calibre_book_data(self, book_id, fmt):
|
def calibre_book_data(self, book_id, fmt):
|
||||||
|
from calibre.gui2.viewer.config import vprefs, get_session_pref
|
||||||
|
from calibre.db.annotations import merge_annotations
|
||||||
|
vprefs.refresh()
|
||||||
|
sync_annots_user = get_session_pref('sync_annots_user', default='')
|
||||||
db = self.gui.current_db.new_api
|
db = self.gui.current_db.new_api
|
||||||
annotations_map = db.annotations_map_for_book(book_id, fmt)
|
annotations_map = db.annotations_map_for_book(book_id, fmt)
|
||||||
|
if sync_annots_user:
|
||||||
|
other_annotations_map = db.annotations_map_for_book(book_id, fmt, user_type='web', user=sync_annots_user)
|
||||||
|
if other_annotations_map:
|
||||||
|
merge_annotations(other_annotations_map, annotations_map, merge_last_read=False)
|
||||||
return {
|
return {
|
||||||
'book_id': book_id, 'uuid': db.field_for('uuid', book_id), 'fmt': fmt.upper(),
|
'book_id': book_id, 'uuid': db.field_for('uuid', book_id), 'fmt': fmt.upper(),
|
||||||
'annotations_map': annotations_map,
|
'annotations_map': annotations_map,
|
||||||
|
@ -55,7 +55,7 @@ def save_annots_to_epub(path, serialized_annots):
|
|||||||
safe_replace(zf, 'META-INF/calibre_bookmarks.txt', BytesIO(serialized_annots), add_missing=True)
|
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):
|
def save_annotations(annotations_list, annotations_path_key, bld, pathtoebook, in_book_file, sync_annots_user):
|
||||||
annots = annot_list_as_bytes(annotations_list)
|
annots = annot_list_as_bytes(annotations_list)
|
||||||
with open(os.path.join(annotations_dir, annotations_path_key), 'wb') as f:
|
with open(os.path.join(annotations_dir, annotations_path_key), 'wb') as f:
|
||||||
f.write(annots)
|
f.write(annots)
|
||||||
@ -64,7 +64,7 @@ def save_annotations(annotations_list, annotations_path_key, bld, pathtoebook, i
|
|||||||
save_annots_to_epub(pathtoebook, annots)
|
save_annots_to_epub(pathtoebook, annots)
|
||||||
update_book(pathtoebook, before_stat, {'calibre-book-annotations.json': annots})
|
update_book(pathtoebook, before_stat, {'calibre-book-annotations.json': annots})
|
||||||
if bld:
|
if bld:
|
||||||
save_annotations_list_to_library(bld, annotations_list)
|
save_annotations_list_to_library(bld, annotations_list, sync_annots_user)
|
||||||
|
|
||||||
|
|
||||||
class AnnotationsSaveWorker(Thread):
|
class AnnotationsSaveWorker(Thread):
|
||||||
@ -89,13 +89,14 @@ class AnnotationsSaveWorker(Thread):
|
|||||||
bld = x['book_library_details']
|
bld = x['book_library_details']
|
||||||
pathtoebook = x['pathtoebook']
|
pathtoebook = x['pathtoebook']
|
||||||
in_book_file = x['in_book_file']
|
in_book_file = x['in_book_file']
|
||||||
|
sync_annots_user = x['sync_annots_user']
|
||||||
try:
|
try:
|
||||||
save_annotations(annotations_list, annotations_path_key, bld, pathtoebook, in_book_file)
|
save_annotations(annotations_list, annotations_path_key, bld, pathtoebook, in_book_file, sync_annots_user)
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
def save_annotations(self, current_book_data, in_book_file=True):
|
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']))
|
alist = tuple(annotations_as_copied_list(current_book_data['annotations_map']))
|
||||||
ebp = current_book_data['pathtoebook']
|
ebp = current_book_data['pathtoebook']
|
||||||
can_save_in_book_file = ebp.lower().endswith('.epub')
|
can_save_in_book_file = ebp.lower().endswith('.epub')
|
||||||
@ -104,7 +105,8 @@ class AnnotationsSaveWorker(Thread):
|
|||||||
'annotations_path_key': current_book_data['annotations_path_key'],
|
'annotations_path_key': current_book_data['annotations_path_key'],
|
||||||
'book_library_details': current_book_data['book_library_details'],
|
'book_library_details': current_book_data['book_library_details'],
|
||||||
'pathtoebook': current_book_data['pathtoebook'],
|
'pathtoebook': current_book_data['pathtoebook'],
|
||||||
'in_book_file': in_book_file and can_save_in_book_file
|
'in_book_file': in_book_file and can_save_in_book_file,
|
||||||
|
'sync_annots_user': sync_annots_user,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ def database_has_annotations_support(cursor):
|
|||||||
return next(cursor.execute('pragma user_version;'))[0] > 23
|
return next(cursor.execute('pragma user_version;'))[0] > 23
|
||||||
|
|
||||||
|
|
||||||
def load_annotations_map_from_library(book_library_details):
|
def load_annotations_map_from_library(book_library_details, user_type='local', user='viewer'):
|
||||||
import apsw
|
import apsw
|
||||||
from calibre.db.backend import annotations_for_book, Connection
|
from calibre.db.backend import annotations_for_book, Connection
|
||||||
ans = {}
|
ans = {}
|
||||||
@ -39,14 +39,17 @@ def load_annotations_map_from_library(book_library_details):
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
if not database_has_annotations_support(cursor):
|
if not database_has_annotations_support(cursor):
|
||||||
return ans
|
return ans
|
||||||
for annot in annotations_for_book(cursor, book_library_details['book_id'], book_library_details['fmt']):
|
for annot in annotations_for_book(
|
||||||
|
cursor, book_library_details['book_id'], book_library_details['fmt'],
|
||||||
|
user_type=user_type, user=user
|
||||||
|
):
|
||||||
ans.setdefault(annot['type'], []).append(annot)
|
ans.setdefault(annot['type'], []).append(annot)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def save_annotations_list_to_library(book_library_details, alist):
|
def save_annotations_list_to_library(book_library_details, alist, sync_annots_user=''):
|
||||||
import apsw
|
import apsw
|
||||||
from calibre.db.backend import save_annotations_for_book, Connection, annotations_for_book
|
from calibre.db.backend import save_annotations_for_book, Connection, annotations_for_book
|
||||||
from calibre.gui2.viewer.annotations import annotations_as_copied_list
|
from calibre.gui2.viewer.annotations import annotations_as_copied_list
|
||||||
@ -66,7 +69,15 @@ def save_annotations_list_to_library(book_library_details, alist):
|
|||||||
for annot in annotations_for_book(cursor, book_library_details['book_id'], book_library_details['fmt']):
|
for annot in annotations_for_book(cursor, book_library_details['book_id'], book_library_details['fmt']):
|
||||||
amap.setdefault(annot['type'], []).append(annot)
|
amap.setdefault(annot['type'], []).append(annot)
|
||||||
merge_annotations((x[0] for x in alist), amap)
|
merge_annotations((x[0] for x in alist), amap)
|
||||||
|
if sync_annots_user:
|
||||||
|
other_amap = {}
|
||||||
|
for annot in annotations_for_book(cursor, book_library_details['book_id'], book_library_details['fmt'], user_type='web', user=sync_annots_user):
|
||||||
|
other_amap.setdefault(annot['type'], []).append(annot)
|
||||||
|
merge_annotations(amap, other_amap)
|
||||||
alist = tuple(annotations_as_copied_list(amap))
|
alist = tuple(annotations_as_copied_list(amap))
|
||||||
save_annotations_for_book(cursor, book_library_details['book_id'], book_library_details['fmt'], alist)
|
save_annotations_for_book(cursor, book_library_details['book_id'], book_library_details['fmt'], alist)
|
||||||
|
if sync_annots_user:
|
||||||
|
alist = tuple(annotations_as_copied_list(other_amap))
|
||||||
|
save_annotations_for_book(cursor, book_library_details['book_id'], book_library_details['fmt'], alist, user_type='web', user=sync_annots_user)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
@ -564,6 +564,11 @@ class EbookViewer(MainWindow):
|
|||||||
bld = self.current_book_data['book_library_details']
|
bld = self.current_book_data['book_library_details']
|
||||||
if bld is not None:
|
if bld is not None:
|
||||||
lib_amap = load_annotations_map_from_library(bld)
|
lib_amap = load_annotations_map_from_library(bld)
|
||||||
|
sau = get_session_pref('sync_annots_user', default='')
|
||||||
|
if sau:
|
||||||
|
other_amap = load_annotations_map_from_library(bld, user_type='web', user=sau)
|
||||||
|
if other_amap:
|
||||||
|
merge_annotations(other_amap, lib_amap)
|
||||||
if lib_amap:
|
if lib_amap:
|
||||||
for annot_type, annots in iteritems(lib_amap):
|
for annot_type, annots in iteritems(lib_amap):
|
||||||
merge_annotations(annots, amap)
|
merge_annotations(annots, amap)
|
||||||
@ -604,7 +609,11 @@ class EbookViewer(MainWindow):
|
|||||||
if self.annotations_saver is None:
|
if self.annotations_saver is None:
|
||||||
self.annotations_saver = AnnotationsSaveWorker()
|
self.annotations_saver = AnnotationsSaveWorker()
|
||||||
self.annotations_saver.start()
|
self.annotations_saver.start()
|
||||||
self.annotations_saver.save_annotations(self.current_book_data, in_book_file and get_session_pref('save_annotations_in_ebook', default=True))
|
self.annotations_saver.save_annotations(
|
||||||
|
self.current_book_data,
|
||||||
|
in_book_file and get_session_pref('save_annotations_in_ebook', default=True),
|
||||||
|
get_session_pref('sync_annots_user', default='')
|
||||||
|
)
|
||||||
|
|
||||||
def highlights_changed(self, highlights):
|
def highlights_changed(self, highlights):
|
||||||
if not self.current_book_data:
|
if not self.current_book_data:
|
||||||
|
@ -19,6 +19,7 @@ DEFAULTS = {
|
|||||||
'show_actions_toolbar': False,
|
'show_actions_toolbar': False,
|
||||||
'show_actions_toolbar_in_fullscreen': False,
|
'show_actions_toolbar_in_fullscreen': False,
|
||||||
'save_annotations_in_ebook': True,
|
'save_annotations_in_ebook': True,
|
||||||
|
'sync_annots_user': '',
|
||||||
'singleinstance': False,
|
'singleinstance': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +27,11 @@ DEFAULTS = {
|
|||||||
def restore_defaults():
|
def restore_defaults():
|
||||||
container = get_container()
|
container = get_container()
|
||||||
for q in Object.keys(DEFAULTS):
|
for q in Object.keys(DEFAULTS):
|
||||||
container.querySelector(f'[name={q}]').checked = DEFAULTS[q]
|
control = container.querySelector(f'[name={q}]')
|
||||||
|
if jstype(DEFAULTS[q]) is 'boolean':
|
||||||
|
control.checked = DEFAULTS[q]
|
||||||
|
else:
|
||||||
|
control.value = DEFAULTS[q]
|
||||||
container.querySelector(f'[name=hide_tooltips]').checked = defaults.hide_tooltips
|
container.querySelector(f'[name=hide_tooltips]').checked = defaults.hide_tooltips
|
||||||
|
|
||||||
|
|
||||||
@ -46,6 +51,16 @@ def create_misc_panel(container, apply_func, cancel_func):
|
|||||||
ans.checked = settings[name] if jstype(settings[name]) is 'boolean' else DEFAULTS[name]
|
ans.checked = settings[name] if jstype(settings[name]) is 'boolean' else DEFAULTS[name]
|
||||||
return E.div(style='margin-top:1ex', E.label(ans, '\xa0' + text))
|
return E.div(style='margin-top:1ex', E.label(ans, '\xa0' + text))
|
||||||
|
|
||||||
|
sai = E.input(name='sync_annots_user', title=_(
|
||||||
|
'The username of a Content server user that you want all annotations synced with.'
|
||||||
|
' Use the special value * to sync with anonymous users'
|
||||||
|
))
|
||||||
|
sai.value = settings.sync_annots_user if jstype(settings.sync_annots_user) is 'string' else DEFAULTS.sync_annots_user
|
||||||
|
sync_annots = E.div(
|
||||||
|
style='margin-top: 1ex; margin-left: 3px',
|
||||||
|
E.label(_('Sync bookmarks/highlights with Content server user:') + '\xa0', sai)
|
||||||
|
)
|
||||||
|
|
||||||
container.append(cb('remember_window_geometry', _('Remember last used window size and position')))
|
container.append(cb('remember_window_geometry', _('Remember last used window size and position')))
|
||||||
container.append(cb('show_actions_toolbar', _('Show a toolbar with the most useful actions')))
|
container.append(cb('show_actions_toolbar', _('Show a toolbar with the most useful actions')))
|
||||||
container.lastChild.append(E.span('\xa0'))
|
container.lastChild.append(E.span('\xa0'))
|
||||||
@ -54,6 +69,7 @@ def create_misc_panel(container, apply_func, cancel_func):
|
|||||||
container.append(cb('show_actions_toolbar_in_fullscreen', _('Keep the toolbar in full screen mode (needs restart)')))
|
container.append(cb('show_actions_toolbar_in_fullscreen', _('Keep the toolbar in full screen mode (needs restart)')))
|
||||||
container.append(cb('remember_last_read', _('Remember current page when quitting')))
|
container.append(cb('remember_last_read', _('Remember current page when quitting')))
|
||||||
container.append(cb('save_annotations_in_ebook', _('Keep a copy of annotations/bookmarks in the e-book file, for easy sharing')))
|
container.append(cb('save_annotations_in_ebook', _('Keep a copy of annotations/bookmarks in the e-book file, for easy sharing')))
|
||||||
|
container.append(sync_annots)
|
||||||
container.append(cb('singleinstance', _('Allow only a single instance of the viewer (needs restart)')))
|
container.append(cb('singleinstance', _('Allow only a single instance of the viewer (needs restart)')))
|
||||||
container.append(cb('hide_tooltips', _('Hide mouse-over tooltips in the book text')))
|
container.append(cb('hide_tooltips', _('Hide mouse-over tooltips in the book text')))
|
||||||
|
|
||||||
@ -68,7 +84,11 @@ def commit_misc(onchange):
|
|||||||
container = get_container()
|
container = get_container()
|
||||||
vals = {}
|
vals = {}
|
||||||
for q in Object.keys(DEFAULTS):
|
for q in Object.keys(DEFAULTS):
|
||||||
val = container.querySelector(f'[name={q}]').checked
|
control = container.querySelector(f'[name={q}]')
|
||||||
|
if jstype(DEFAULTS[q]) is 'boolean':
|
||||||
|
val = control.checked
|
||||||
|
else:
|
||||||
|
val = control.value.strip()
|
||||||
if val is not DEFAULTS[q]:
|
if val is not DEFAULTS[q]:
|
||||||
vals[q] = val
|
vals[q] = val
|
||||||
sd.set('standalone_misc_settings', vals)
|
sd.set('standalone_misc_settings', vals)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user