diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index d956d0d212..3a674ea568 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) -import re, httplib +import re from functools import partial from threading import Lock from json import load as load_json_file @@ -15,10 +15,12 @@ from calibre.srv.ajax import get_db, search_result from calibre.srv.errors import HTTPNotFound, HTTPBadRequest from calibre.srv.metadata import book_as_json from calibre.srv.routes import endpoint, json +from calibre.utils.icu import sort_key html_cache = {} cache_lock = Lock() autoreload_js = None +POSTABLE = frozenset({'GET', 'POST', 'HEAD'}) def get_html(name, auto_reload_port, **replacements): global autoreload_js @@ -74,7 +76,7 @@ def get_basic_query_data(ctx, query): if s in skeys: sorts.append(s), orders.append(o) if not sorts: - sorts, orders = ['date'], ['desc'] + sorts, orders = ['timestamp'], ['desc'] return library_id, db, sorts, orders DEFAULT_NUMBER_OF_BOOKS = 50 @@ -84,7 +86,7 @@ def interface_data(ctx, rd): ''' Return the data needed to create the server main UI - Optional: ?num=50&sort=date.desc&library_id= + Optional: ?num=50&sort=timestamp.desc&library_id= ''' ans = {'username':rd.username} ans['library_map'], ans['default_library'] = ctx.library_map @@ -107,8 +109,11 @@ def interface_data(ctx, rd): raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num')) with db.safe_read_lock: ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders)) - ans['sortable_fields'] = sf = db.field_metadata.ui_sortable_field_keys() + sf = db.field_metadata.ui_sortable_field_keys() sf.pop('ondevice', None) + ans['sortable_fields'] = sorted((( + sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()), + key=lambda (field, name):sort_key(name)) ans['field_metadata'] = db.field_metadata.all_metadata() # ans['categories'] = ctx.get_categories(rd, db) mdata = ans['metadata'] = {} @@ -118,7 +123,7 @@ def interface_data(ctx, rd): return ans -@endpoint('/interface-data/more-books', postprocess=json, methods={'GET', 'POST', 'HEAD'}, ok_code=httplib.OK) +@endpoint('/interface-data/more-books', postprocess=json, methods=POSTABLE) def more_books(ctx, rd): ''' Get more results from the specified search-query, which must @@ -148,3 +153,20 @@ def more_books(ctx, rd): mdata[book_id] = data return ans + +@endpoint('/interface-data/set-session-data', postprocess=json, methods=POSTABLE) +def set_session_data(ctx, rd): + ''' + Store session data persistently so that it is propagated automatically to + new logged in clients + ''' + if rd.username: + try: + new_data = load_json_file(rd.request_body_file) + if not isinstance(new_data, dict): + raise Exception('session data must be a dict') + except Exception as err: + raise HTTPBadRequest('Invalid data: %s' % as_unicode(err)) + ud = ctx.user_manager.get_session_data(rd.username) + ud.update(new_data) + ctx.user_manager.set_session_data(rd.username, ud) diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index 6cabcb6958..e991e9639f 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -1,9 +1,11 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2015, Kovid Goyal +from ajax import ajax_send + defaults = { 'view_mode': 'cover_grid', - 'sort': 'date.desc', + 'sort': 'timestamp.desc', } def storage_available(which): @@ -50,12 +52,15 @@ def get_session_storage(): class SessionData: + global_prefix = 'calibre-' + def __init__(self): 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: @@ -69,6 +74,7 @@ class SessionData: 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]' @@ -91,11 +97,18 @@ class SessionData: class UserSessionData(SessionData): - def __init__(self, username): - self.prefix = (username or '') + ':' + 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 + for key in saved_data: + self.set(key, saved_data[key]) + self.echo_changes = True def get(self, key, defval): if defval is undefined: @@ -103,5 +116,19 @@ class UserSessionData(SessionData): return SessionData.get(self, (self.prefix + key), defval) def set(self, key, value): - # TODO: Send updated data to server if self.has_user + if self.echo_changes and self.has_user: + 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 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 != 'load': + console.error('Failed to send session data to server: ' + xhr.error_string) + ) + self.changes = {} + self.has_changes = False diff --git a/src/pyj/srv.pyj b/src/pyj/srv.pyj index 16f05cc956..ff0ffce60c 100644 --- a/src/pyj/srv.pyj +++ b/src/pyj/srv.pyj @@ -12,9 +12,8 @@ def on_library_loaded(end_type, xhr, ev): p.parentNode.removeChild(p) if end_type == 'load': interface_data = JSON.parse(xhr.responseText) - set_session_data(UserSessionData(interface_data['username'])) - # TODO: Copy any user specific session data from the server to - # the local session data object, overriding local data + sd = UserSessionData(interface_data['username'], interface_data['user_session_data']) + set_session_data(sd) boss = Boss(interface_data) set_boss(boss) else: