# vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2015, Kovid Goyal from __python__ import bound_methods, hash_literals from uuid import short_uuid from ajax import ajax_send defaults = { # Book list settings 'copy_to_library_dupes': 'add;overwrite', 'last_sort_order': {}, 'show_all_metadata': False, # show all metadata fields in the book details panel 'sort': 'timestamp.desc', # comma separated list of items of the form: field.order 'view_mode': 'cover_grid', # Tag Browser settings 'and_search_terms': False, # how to add search terms to the search expression from the Tag Browser 'collapse_at': 25, # number of items at which sub-groups are created, 0 to disable 'dont_collapse': '', # comma separated list of category names 'hide_empty_categories': 'no', 'partition_method': 'first letter', # other choices: 'disable', 'partition' 'sort_tags_by': 'name', # other choices: popularity, rating # Book reader settings 'background_image_fade': 0, 'background_image_style': 'scaled', 'background_image': None, 'base_font_size': 16, 'book_scrollbar': False, 'columns_per_screen': {'portrait':0, 'landscape':0}, 'controls_help_shown_count': 0, 'controls_help_shown_count_rtl_page_progression': 0, 'cover_preserve_aspect_ratio': True, 'current_color_scheme': 'system', 'footer': {'right': 'progress'}, 'header': {}, 'left-margin': {}, 'right-margin': {}, 'hide_tooltips': False, 'keyboard_shortcuts': {}, 'lines_per_sec_auto': 1, 'lines_per_sec_smooth': 20, 'margin_bottom': 20, 'margin_left': 20, 'margin_right': 20, 'margin_top': 20, 'max_text_height': 0, 'max_text_width': 0, 'override_book_colors': 'never', 'paged_margin_clicks_scroll_by_screen': True, 'paged_wheel_scrolls_by_screen': False, 'paged_taps_scroll_by_screen': False, 'paged_pixel_scroll_threshold': 60, 'read_mode': 'paged', 'scroll_auto_boundary_delay': 5, 'scroll_stop_boundaries': False, 'standalone_font_settings': {}, 'standalone_misc_settings': {}, 'standalone_recently_opened': v'[]', 'user_color_schemes': {}, 'user_stylesheet': '', 'word_actions': v'[]', 'highlight_style': None, 'custom_highlight_colors': v'[]', 'show_selection_bar': True, 'net_search_url': 'https://google.com/search?q={q}', 'selection_bar_actions': v"['copy', 'lookup', 'highlight', 'remove_highlight', 'search_net', 'clear']", } is_local_setting = { 'background_image_fade': True, 'background_image_style': True, 'background_image': True, 'base_font_size': True, 'columns_per_screen': True, 'controls_help_shown_count': True, 'controls_help_shown_count_rtl_page_progression': True, 'current_color_scheme': True, 'lines_per_sec_auto': True, 'lines_per_sec_smooth': True, 'margin_bottom': True, 'margin_left': True, 'margin_right': True, 'margin_top': True, 'max_text_height': True, 'max_text_width': True, 'override_book_colors': True, 'read_mode': 'paged', 'scroll_auto_boundary_delay': True, 'scroll_stop_boundaries': True, 'standalone_font_settings': True, 'standalone_misc_settings': True, 'standalone_recently_opened': True, 'user_stylesheet': True, 'highlight_style': True, } 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 return JSON.parse(ans) 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 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 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': 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, } 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') 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_local_setting[key]: self.set(key, saved_data[key]) self.echo_changes = True def defval(self, key): return defaults[key] def get(self, key, defval): if defval is undefined: defval = defaults[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 = defaults[key] return self.get(lkey, defval) def set(self, key, value): if self.echo_changes and self.has_user and not is_local_setting[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