mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-11-13 01:57:00 -05:00
415 lines
16 KiB
Plaintext
415 lines
16 KiB
Plaintext
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
|
from __python__ import bound_methods, hash_literals
|
|
|
|
from uuid import short_uuid
|
|
|
|
from ajax import ajax_send
|
|
|
|
all_settings = {
|
|
'copy_to_library_dupes': {'default': 'add;overwrite', 'category': 'book_list'},
|
|
'last_sort_order': {'default': {}, 'category': 'book_list'},
|
|
'show_all_metadata': {'default': False, 'category': 'book_list'}, # show all metadata fields in the book details panel
|
|
'sort': {'default': 'timestamp.desc', 'category': 'book_list'}, # comma separated list of items of the form: field.order
|
|
'view_mode': {'default': 'cover_grid', 'category': 'book_list'},
|
|
'fts_related_words': {'default': True, 'category': 'book_list'},
|
|
|
|
# Tag Browser settings
|
|
'and_search_terms': {'default': False, 'category': 'tag_browser'}, # how to add search terms to the search expression from the Tag Browser
|
|
'collapse_at': {'default': 25, 'category': 'tag_browser'}, # number of items at which sub-groups are created, 0 to disable
|
|
'dont_collapse': {'default': '', 'category': 'tag_browser'}, # comma separated list of category names
|
|
'hide_empty_categories': {'default': 'no', 'category': 'tag_browser'},
|
|
'partition_method': {'default': 'first letter', 'category': 'tag_browser'}, # other choices: 'disable', 'partition'
|
|
'sort_tags_by': {'default': 'name', 'category': 'tag_browser'}, # other choices: popularity, rating
|
|
|
|
# Book reader settings
|
|
'background_image_fade': {'default': 0, 'category': 'read_book', 'is_local': True},
|
|
'background_image_style': {'default': 'scaled', 'category': 'read_book', 'is_local': True},
|
|
'background_image': {'default': None, 'category': 'read_book', 'is_local': True},
|
|
'base_font_size': {'default': 16, 'category': 'read_book', 'is_local': True},
|
|
'book_scrollbar': {'default': False, 'category': 'read_book'},
|
|
'columns_per_screen': {'default': {'portrait':0, 'landscape':0}, 'category': 'read_book', 'is_local': True},
|
|
'controls_help_shown_count': {'default': 0, 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True},
|
|
'controls_help_shown_count_rtl_page_progression': {'default': 0, 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True},
|
|
'cover_preserve_aspect_ratio': {'default': True, 'category': 'read_book'},
|
|
'current_color_scheme': {'default': 'system', 'category': 'read_book'},
|
|
'footer': {'default': {'right': 'progress'}, 'category': 'read_book'},
|
|
'header': {'default': {}, 'category': 'read_book'},
|
|
'controls_footer': {'default': {'right': 'progress'}, 'category': 'read_book'},
|
|
'left-margin': {'default': {}, 'category': 'read_book'},
|
|
'right-margin': {'default': {}, 'category': 'read_book'},
|
|
'hide_tooltips': {'default': False, 'category': 'read_book'},
|
|
'keyboard_shortcuts': {'default': {}, 'category': 'read_book'},
|
|
'lines_per_sec_auto': {'default': 1, 'category': 'read_book', 'is_local': True},
|
|
'lines_per_sec_smooth': {'default': 20, 'category': 'read_book', 'is_local': True},
|
|
'margin_bottom': {'default': 20, 'category': 'read_book', 'is_local': True},
|
|
'margin_left': {'default': 20, 'category': 'read_book', 'is_local': True},
|
|
'margin_right': {'default': 20, 'category': 'read_book', 'is_local': True},
|
|
'margin_top': {'default': 20, 'category': 'read_book', 'is_local': True},
|
|
'max_text_height': {'default': 0, 'category': 'read_book', 'is_local': True},
|
|
'max_text_width': {'default': 0, 'category': 'read_book', 'is_local': True},
|
|
'override_book_colors': {'default': 'never', 'category': 'read_book'},
|
|
'paged_margin_clicks_scroll_by_screen': {'default': True, 'category': 'read_book'},
|
|
'paged_wheel_scrolls_by_screen': {'default': False, 'category': 'read_book'},
|
|
'paged_wheel_section_jumps': {'default': True, 'category': 'read_book'},
|
|
'paged_pixel_scroll_threshold': {'default': 60, 'category': 'read_book'},
|
|
'read_mode': {'default': 'paged', 'category': 'read_book', 'is_local': True},
|
|
'scroll_auto_boundary_delay': {'default': 5, 'category': 'read_book', 'is_local': True},
|
|
'scroll_stop_boundaries': {'default': False, 'category': 'read_book', 'is_local': True},
|
|
'standalone_font_settings': {'default': {}, 'category': 'read_book', 'is_local': True},
|
|
'standalone_misc_settings': {'default': {}, 'category': 'read_book', 'is_local': True},
|
|
'standalone_recently_opened': {'default': v'[]', 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True},
|
|
'user_color_schemes': {'default': {}, 'category': 'read_book'},
|
|
'user_stylesheet': {'default': '', 'category': 'read_book', 'is_local': True},
|
|
'word_actions': {'default': v'[]', 'category': 'read_book'},
|
|
'highlight_style': {'default': None, 'category': 'read_book', 'is_local': True},
|
|
'highlights_export_format': {'default': 'text', 'category': 'read_book', 'is_local': True},
|
|
'custom_highlight_styles': {'default': v'[]', 'category': 'read_book'},
|
|
'show_selection_bar': {'default': True, 'category': 'read_book'},
|
|
'net_search_url': {'default': 'https://google.com/search?q={q}', 'category': 'read_book'},
|
|
'selection_bar_actions': {'default': v"['copy', 'lookup', 'highlight', 'remove_highlight', 'search_net', 'clear']", 'category': 'read_book'},
|
|
'selection_bar_quick_highlights': {'default': v"[]", 'category': 'read_book'},
|
|
'skipped_dialogs': {'default': v'{}', 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True},
|
|
'tts': {'default': v'{}', 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True},
|
|
'tts_backend': {'default': v'{}', 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True},
|
|
'fullscreen_when_opening': {'default': 'auto', 'category': 'read_book', 'is_local': True},
|
|
'book_search_mode': {'default': 'contains', 'category': 'read_book', 'is_local': True},
|
|
'book_search_case_sensitive': {'default': False, 'category': 'read_book', 'is_local': True},
|
|
'reverse_page_turn_zones': {'default': False, 'category': 'read_book', 'is_local': True},
|
|
'gesture_overrides': {'default': {}, 'category': 'read_book'},
|
|
}
|
|
|
|
defaults = {}
|
|
for x in Object.entries(all_settings):
|
|
defaults[x[0]] = x[1].default
|
|
|
|
|
|
def session_defaults():
|
|
return defaults
|
|
|
|
|
|
def storage_available(which):
|
|
which = which or 'localStorage'
|
|
try:
|
|
storage = window[which]
|
|
x = '__storage__test__'
|
|
storage.setItem(x, x)
|
|
storage.removeItem(x)
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
class FakeStorage:
|
|
|
|
def __init__(self):
|
|
self.data = {}
|
|
|
|
def getItem(self, key):
|
|
return self.data[key]
|
|
|
|
def setItem(self, key, value):
|
|
if jstype(value) is not 'string':
|
|
value = JSON.stringify(value)
|
|
self.data[key] = value
|
|
|
|
def clear(self):
|
|
self.data = {}
|
|
|
|
def get_session_storage():
|
|
if not get_session_storage.ans:
|
|
if storage_available('localStorage'):
|
|
get_session_storage.ans = window.localStorage
|
|
elif storage_available('sessionStorage'):
|
|
get_session_storage.ans = window.sessionStorage
|
|
console.error('localStorage not available using sessionStorage instead')
|
|
else:
|
|
get_session_storage.ans = FakeStorage()
|
|
console.error('sessionStorage and localStorage not available using a temp cache instead')
|
|
return get_session_storage.ans
|
|
|
|
|
|
class SessionData:
|
|
|
|
def __init__(self, global_prefix=None):
|
|
self.global_prefix = global_prefix or 'calibre-session-'
|
|
self.storage = get_session_storage()
|
|
self.overflow_storage = {}
|
|
self.has_overflow = False
|
|
|
|
def get(self, key, defval):
|
|
key = self.global_prefix + key
|
|
if self.has_overflow:
|
|
ans = self.overflow_storage[key]
|
|
if ans is undefined:
|
|
ans = self.storage.getItem(key)
|
|
else:
|
|
ans = self.storage.getItem(key)
|
|
if ans is undefined or ans is None:
|
|
if defval is undefined:
|
|
defval = None
|
|
return defval
|
|
try:
|
|
return JSON.parse(ans)
|
|
except:
|
|
if defval is undefined:
|
|
defval = None
|
|
return defval
|
|
|
|
def set(self, key, value):
|
|
key = self.global_prefix + key
|
|
if value is None:
|
|
self.storage.removeItem(key)
|
|
v'delete self.overflow_storage[key]'
|
|
return True
|
|
value = JSON.stringify(value)
|
|
try:
|
|
self.storage.setItem(key, value)
|
|
v'delete self.overflow_storage[key]'
|
|
return True
|
|
except:
|
|
self.overflow_storage[key] = value
|
|
self.has_overflow = True
|
|
console.error('session storage has overflowed, using a temp cache instead')
|
|
return False
|
|
|
|
def set_bulk(self, changes):
|
|
for key in Object.keys(changes):
|
|
self.set(key, changes[key])
|
|
|
|
def clear(self):
|
|
self.storage.clear()
|
|
self.overflow_storage = {}
|
|
self.has_overflow = False
|
|
|
|
|
|
def local_storage():
|
|
if not local_storage.storage:
|
|
local_storage.storage = SessionData('calibre-local-')
|
|
return local_storage.storage
|
|
|
|
|
|
def get_device_uuid():
|
|
if not get_device_uuid.ans:
|
|
s = local_storage()
|
|
ans = s.get('device_uuid')
|
|
if not ans:
|
|
ans = short_uuid()
|
|
s.set('device_uuid', ans)
|
|
get_device_uuid.ans = ans
|
|
return get_device_uuid.ans
|
|
|
|
|
|
def deep_eq(a, b):
|
|
if a is b:
|
|
return True
|
|
if Array.isArray(a) and Array.isArray(b):
|
|
if a.length is not b.length:
|
|
return False
|
|
for i in range(a.length):
|
|
if not deep_eq(a[i], b[i]):
|
|
return False
|
|
return True
|
|
if a is None or b is None or a is undefined or b is undefined:
|
|
return False
|
|
if jstype(a) is not 'object' or jstype(b) is not 'object':
|
|
return False
|
|
ka = Object.keys(a)
|
|
if ka.length is not Object.keys(b).length:
|
|
return False
|
|
for key in ka:
|
|
if not deep_eq(a[key], b[key]):
|
|
return False
|
|
return True
|
|
|
|
|
|
def setting_val_is_default(setting_name, curval):
|
|
defval = defaults[setting_name]
|
|
return deep_eq(defval, curval)
|
|
|
|
|
|
def get_subset_of_settings(sd, filter_func):
|
|
ans = {}
|
|
for setting_name in Object.keys(all_settings):
|
|
curval = sd.get(setting_name, ans)
|
|
metadata = all_settings[setting_name]
|
|
is_set = curval is not ans
|
|
if filter_func(setting_name, metadata, is_set, curval):
|
|
ans[setting_name] = window.structuredClone(curval if is_set else metadata.default)
|
|
return ans
|
|
|
|
def apply_profile(sd, profile, filter_func):
|
|
changes = {}
|
|
for setting_name in Object.keys(all_settings):
|
|
metadata = all_settings[setting_name]
|
|
curval = sd.get(setting_name, changes)
|
|
is_set = curval is not changes
|
|
if filter_func(setting_name, metadata, is_set, curval):
|
|
newval = None
|
|
if Object.prototype.hasOwnProperty.call(profile, setting_name):
|
|
newval = profile[setting_name]
|
|
if deep_eq(newval, metadata.default):
|
|
newval = None
|
|
changes[setting_name] = window.structuredClone(newval)
|
|
sd.set_bulk(changes)
|
|
return changes
|
|
|
|
|
|
standalone_reader_defaults = {
|
|
'remember_window_geometry': False,
|
|
'remember_last_read': True,
|
|
'show_actions_toolbar': False,
|
|
'show_actions_toolbar_in_fullscreen': False,
|
|
'save_annotations_in_ebook': True,
|
|
'sync_annots_user': '',
|
|
'singleinstance': False,
|
|
'auto_hide_mouse': True,
|
|
'restore_docks': True,
|
|
}
|
|
|
|
|
|
def settings_for_reader_profile(sd):
|
|
|
|
margin_text_settings = {'footer': True, 'header': True, 'controls_footer': True, 'left-margin': True, 'right-margin': True}
|
|
|
|
def filter_func(setting_name, metadata, is_set, curval):
|
|
if margin_text_settings[setting_name]:
|
|
curval = window.structuredClone(curval)
|
|
for x in v"['left', 'right', 'middle']":
|
|
if curval[x] is 'empty':
|
|
v'delete curval[x]'
|
|
elif setting_name is 'standalone_misc_settings':
|
|
curval = window.structuredClone(curval)
|
|
for key in Object.keys(curval):
|
|
if curval[key] is standalone_reader_defaults[key]:
|
|
v'delete curval[key]'
|
|
|
|
if not is_set or metadata.category is not 'read_book' or metadata.disallowed_in_profile or deep_eq(curval, metadata.default):
|
|
return False
|
|
return True
|
|
|
|
return get_subset_of_settings(sd, filter_func)
|
|
|
|
|
|
def apply_reader_profile(sd, profile):
|
|
def filter_func(setting_name, metadata, is_set, curval):
|
|
return metadata.category is 'read_book' and not metadata.disallowed_in_profile
|
|
apply_profile(sd, profile, filter_func)
|
|
|
|
|
|
default_interface_data = {
|
|
'username': None,
|
|
'output_format': 'EPUB',
|
|
'input_formats': {'EPUB', 'MOBI', 'AZW3'},
|
|
'gui_pubdate_display_format': 'MMM yyyy',
|
|
'gui_timestamp_display_format': 'dd MMM yyyy',
|
|
'gui_last_modified_display_format': 'dd MMM yyyy',
|
|
'use_roman_numerals_for_series_number': True,
|
|
'default_library_id': None,
|
|
'default_book_list_mode': session_defaults().view_mode,
|
|
'library_map': None,
|
|
'search_the_net_urls': [],
|
|
'donate_link': 'https://calibre-ebook.com/donate',
|
|
'icon_map': {},
|
|
'icon_path': '',
|
|
'custom_list_template': None,
|
|
'num_per_page': 50,
|
|
'lang_code_for_user_manual': '',
|
|
}
|
|
|
|
def get_interface_data():
|
|
if not get_interface_data.storage:
|
|
get_interface_data.storage = SessionData('calibre-interface-data-')
|
|
ans = get_interface_data.storage.get('current')
|
|
if ans:
|
|
ans.is_default = False
|
|
else:
|
|
ans = {'is_default': True}
|
|
for k in default_interface_data:
|
|
ans[k] = default_interface_data[k]
|
|
return ans
|
|
|
|
|
|
def update_interface_data(new_data):
|
|
data = get_interface_data()
|
|
for k in default_interface_data:
|
|
nval = new_data[k]
|
|
if k is not undefined:
|
|
data[k] = nval
|
|
if not get_interface_data.storage:
|
|
get_interface_data.storage = SessionData('calibre-interface-data-')
|
|
get_interface_data.storage.set('current', data)
|
|
|
|
|
|
def get_translations(newval):
|
|
if not get_translations.storage:
|
|
get_translations.storage = SessionData('calibre-translations-')
|
|
if newval?:
|
|
get_translations.storage.set('current', newval)
|
|
else:
|
|
return get_translations.storage.get('current')
|
|
|
|
|
|
def is_setting_local(name):
|
|
m = all_settings[name]
|
|
return m.is_local if m else True
|
|
|
|
|
|
class UserSessionData(SessionData):
|
|
|
|
def __init__(self, username, saved_data):
|
|
self.prefix = (username or '') + '-'
|
|
self.has_user = bool(username)
|
|
self.username = username
|
|
SessionData.__init__(self)
|
|
self.echo_changes = False
|
|
self.changes = {}
|
|
self.has_changes = False
|
|
self.push_timer_id = None
|
|
if saved_data:
|
|
for key in saved_data:
|
|
if not is_setting_local(key):
|
|
self.set(key, saved_data[key])
|
|
self.echo_changes = True
|
|
|
|
def defval(self, key):
|
|
ans = session_defaults()[key]
|
|
if ans:
|
|
ans = window.structuredClone(ans)
|
|
return ans
|
|
|
|
def get(self, key, defval):
|
|
if defval is undefined:
|
|
defval = self.defval(key)
|
|
return SessionData.get(self, (self.prefix + key), defval)
|
|
|
|
def get_library_option(self, library_id, key, defval):
|
|
if not library_id:
|
|
return self.get(key, defval)
|
|
lkey = key + '-||-' + library_id
|
|
if defval is undefined:
|
|
defval = self.defval(key)
|
|
return self.get(lkey, defval)
|
|
|
|
def set(self, key, value):
|
|
if self.echo_changes and self.has_user and not is_setting_local(key):
|
|
self.changes[key] = value
|
|
self.has_changes = True
|
|
if self.push_timer_id is not None:
|
|
clearTimeout(self.push_timer_id)
|
|
self.push_timer_id = setTimeout(self.push_to_server.bind(self), 1000)
|
|
return SessionData.set(self, (self.prefix + key), value)
|
|
|
|
def set_library_option(self, library_id, key, value):
|
|
if library_id:
|
|
key = key + '-||-' + library_id
|
|
return self.set(key, value)
|
|
|
|
def push_to_server(self):
|
|
if self.has_changes:
|
|
ajax_send('interface-data/set-session-data', self.changes, def(end_type, xhr, ev):
|
|
if end_type is not 'load':
|
|
console.error('Failed to send session data to server: ' + xhr.error_html)
|
|
)
|
|
self.changes = {}
|
|
self.has_changes = False
|