diff --git a/client/components/Reader/ServerStorage/ServerStorage.vue b/client/components/Reader/ServerStorage/ServerStorage.vue index 9c55ec3b..023afcdf 100644 --- a/client/components/Reader/ServerStorage/ServerStorage.vue +++ b/client/components/Reader/ServerStorage/ServerStorage.vue @@ -13,6 +13,11 @@ import readerApi from '../../../api/reader'; import * as utils from '../../../share/utils'; import * as cryptoUtils from '../../../share/cryptoUtils'; +import localForage from 'localforage'; +const ssCacheStore = localForage.createInstance({ + name: 'ssCacheStore' +}); + export default @Component({ watch: { serverSyncEnabled: function() { @@ -58,35 +63,52 @@ class ServerStorage extends Vue { async init() { try { + this.cachedRecent = await ssCacheStore.getItem('recent'); + if (!this.cachedRecent) + await this.setCachedRecent({rev: 0, data: {}}); + + this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch'); + if (!this.cachedRecentPatch) + await this.setCachedRecentPatch({rev: 0, data: {}}); + + this.cachedRecentMod = await ssCacheStore.getItem('recent-mod'); + if (!this.cachedRecentMod) + await this.setCachedRecentMod({rev: 0, data: {}}); + if (!this.serverStorageKey) { //генерируем новый ключ await this.generateNewServerStorageKey(); } else { await this.serverStorageKeyChanged(); } + bookManager.addEventListener(this.bookManagerEvent); } finally { this.inited = true; } } + async setCachedRecent(value) { + await ssCacheStore.setItem('recent', value); + this.cachedRecent = value; + } + + async setCachedRecentPatch(value) { + await ssCacheStore.setItem('recent-patch', value); + this.cachedRecentPatch = value; + } + + async setCachedRecentMod(value) { + await ssCacheStore.setItem('recent-mod', value); + this.cachedRecentMod = value; + } + async bookManagerEvent(eventName, itemKey) { if (!this.serverSyncEnabled) return; if (eventName == 'recent-changed') { if (itemKey) { - if (!this.recentDeltaInited) { - await this.loadRecent(); - this.warning('Функции сохранения на сервер пока недоступны'); - return; - } - - if (!this.recentDelta) - this.recentDelta = {}; - - this.recentDelta[itemKey] = _.cloneDeep(bookManager.recent[itemKey]); - this.debouncedSaveRecent(itemKey); } } @@ -119,8 +141,8 @@ class ServerStorage extends Vue { await this.loadProfiles(force); this.checkCurrentProfile(); await this.currentProfileChanged(force); - await this.loadRecent(); - if (force) + const loadSuccess = await this.loadRecent(); + if (loadSuccess && force) await this.saveRecent(); } } @@ -340,46 +362,26 @@ class ServerStorage extends Vue { } } - async initRecentDelta() { - let recentDelta = null; - try { - recentDelta = await this.storageGet({recentDelta: {}}); - } catch(e) { - this.error(`Ошибка соединения с сервером: ${e.message}`); - return; - } - - if (recentDelta.state == 'success') { - recentDelta = recentDelta.items.recentDelta; - - if (recentDelta.rev == 0) - recentDelta.data = {}; - - this.recentDelta = recentDelta.data; - this.recentDeltaInited = true; - } else { - this.warning(`Неверный ответ сервера: ${recentDelta.state}`); - } - } - async loadRecent(skipRevCheck = false, doNotifySuccess = true) { if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent) return; - this.loadingRecent = true; try { - const oldRecentRev = bookManager.recentRev; - const oldRecentDeltaRev = bookManager.recentDeltaRev; //проверим ревизию на сервере + let query = {recent: {}, recentPatch: {}, recentMod: {}}; let revs = null; if (!skipRevCheck) { try { - revs = await this.storageCheck({recent: {}, recentDelta: {}}); - if (revs.state == 'success' && revs.items.recent.rev == oldRecentRev && - revs.items.recentDelta.rev == oldRecentDeltaRev) { - if (!this.recentDeltaInited) - await this.initRecentDelta(); - return; + revs = await this.storageCheck(query); + if (revs.state == 'success') { + if (revs.items.recent.rev != this.cachedRecent.rev) { + //no changes + } else if (revs.items.recentPatch.rev != this.cachedRecentPatch.rev) { + query = {recentPatch: {}, recentMod: {}}; + } else if (revs.items.recentMod.rev != this.cachedRecentMod.rev) { + query = {recentMod: {}}; + } else + return true; } } catch(e) { this.error(`Ошибка соединения с сервером: ${e.message}`); @@ -389,46 +391,53 @@ class ServerStorage extends Vue { let recent = null; try { - recent = await this.storageGet({recent: {}, recentDelta: {}}); + recent = await this.storageGet(query); } catch(e) { this.error(`Ошибка соединения с сервером: ${e.message}`); return; } if (recent.state == 'success') { - let recentDelta = recent.items.recentDelta; - recent = recent.items.recent; + let newRecent = recent.items.recent; + let newRecentPatch = recent.items.recentPatch; + let newRecentMod = recent.items.recentMod; - if (recent.rev == 0) - recent.data = {}; - - let newRecent = {}; - if (recentDelta && recentDelta.data) { - if (recentDelta.data.diff) { - newRecent = recent.data; - const key = recentDelta.data.diff.key; - if (newRecent[key]) - newRecent[key] = utils.applyObjDiff(newRecent[key], recentDelta.data.diff); - } else { - newRecent = Object.assign(recent.data, recentDelta.data); - } - this.recentDelta = recentDelta.data; - } else { - newRecent = recent.data; - this.recentDelta = {}; + if (!newRecent) { + newRecent = _.cloneDeep(this.cachedRecent); + } + if (!newRecentPatch) { + newRecentPatch = _.cloneDeep(this.cachedRecentPatch); + } + if (!newRecentMod) { + newRecentMod = _.cloneDeep(this.cachedRecentMod); } - this.recentDeltaInited = true; + if (newRecent.rev == 0) newRecent.data = {}; + if (newRecentPatch.rev == 0) newRecentPatch.data = {}; + if (newRecentMod.rev == 0) newRecentMod.data = {}; + + let result = Object.assign({}, newRecent.data, newRecentPatch.data); + + const md = newRecentMod.data; + if (md.key && result[md.key]) + result[md.key] = utils.applyObjDiff(result[md.key], md.mod); + + if (newRecent.rev != this.cachedRecent.rev) + await this.setCachedRecent(newRecent); + if (newRecentPatch.rev != this.cachedRecentPatch.rev) + await this.setCachedRecentPatch(newRecentPatch); + if (newRecentMod.rev != this.cachedRecentMod.rev) + await this.setCachedRecentMod(newRecentMod); if (!bookManager.loaded) { this.warning('Ожидание загрузки списка книг перед синхронизацией'); while (!bookManager.loaded) await utils.sleep(100); } - await bookManager.setRecent(newRecent); - await bookManager.setRecentRev(recent.rev); - await bookManager.setRecentDeltaRev(recentDelta.rev); + + await bookManager.setRecent(result); } else { this.warning(`Неверный ответ сервера: ${recent.state}`); + return; } if (doNotifySuccess) @@ -436,6 +445,7 @@ class ServerStorage extends Vue { } finally { this.loadingRecent = false; } + return true; } async saveRecent(itemKey, recurse) { @@ -444,91 +454,91 @@ class ServerStorage extends Vue { const bm = bookManager; - //вычисление критерия сохранения целиком - if (!this.sameKeyCount) - this.sameKeyCount = 0; - if (this.prevItemKey == itemKey) { - this.sameKeyCount++; - } else { - this.sameKeyCount = 0; + let needSaveRecent = false; + let needSaveRecentPatch = false; + let needSaveRecentMod = false; + + let applyMod = null; + + //newRecentMod + let newRecentMod = {}; + + if (itemKey && this.cachedRecentPatch.data[itemKey]) { + if (this.prevItemKey == itemKey) {//сохраняем только дифф + newRecentMod = _.cloneDeep(this.cachedRecentMod); + newRecentMod.rev++; + + newRecentMod.data.key = itemKey; + newRecentMod.data.mod = utils.getObjDiff(this.cachedRecentPatch.data[itemKey], bm.recent[itemKey]); + needSaveRecentMod = true; + } else {//ключ не совпадает, надо сохранять патч + applyMod = newRecentMod.data; + } } - - const l = Object.keys(this.recentDelta).length - (1*(!!this.recentDelta.diff)); - this.makeDeltaDiff = (l == 1 && this.prevItemKey == itemKey ? this.makeDeltaDiff : false); - const forceSaveRecent = l > 20 || (this.sameKeyCount > 5 && (l > 1)) || (l == 1 && this.sameKeyCount > 10 && !this.makeDeltaDiff); - - this.sameKeyCount = (!forceSaveRecent ? this.sameKeyCount : 0); this.prevItemKey = itemKey; - //дифф от дельты для уменьшения размера передаваемых данных в частном случае - if (this.makeDeltaDiff) { - this.recentDelta.diff = utils.getObjDiff(this.prevSavedItem, bm.recent[itemKey]); - this.recentDelta.diff.key = itemKey; - delete this.recentDelta[itemKey]; - } else if (this.recentDelta.diff) { - const key = this.recentDelta.diff.key; - if (!this.prevSavedItem && bm.recent[key]) - this.prevSavedItem = _.cloneDeep(bm.recent[key]); - if (this.prevSavedItem) { - this.recentDelta[key] = utils.applyObjDiff(this.prevSavedItem, this.recentDelta.diff); - } - delete this.recentDelta.diff; + //newRecentPatch + let newRecentPatch = {}; + if (itemKey && !needSaveRecentMod) { + newRecentPatch = _.cloneDeep(this.cachedRecentPatch); + newRecentPatch.rev++; + newRecentPatch.data[itemKey] = bm.recent[itemKey]; + if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key]) + newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod); + newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}}; + needSaveRecentPatch = true; + needSaveRecentMod = true; + } + + //newRecent + let newRecent = {}; + if (!itemKey || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 2)) { + newRecent = {rev: this.cachedRecent.rev + 1, data: bm.recent}; + newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}}; + newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}}; + needSaveRecent = true; + needSaveRecentPatch = true; + needSaveRecentMod = true; + } + + //query + let query = {}; + if (needSaveRecent) { + query = {recent: newRecent, recentPatch: newRecentPatch, recentMod: newRecentMod}; + } else if (needSaveRecentPatch) { + query = {recentPatch: newRecentPatch, recentMod: newRecentMod}; + } else { + query = {recentMod: newRecentMod}; } //сохранение this.savingRecent = true; try { - if (forceSaveRecent) {//сохраняем recent целиком - let result = {state: ''}; + let result = {state: ''}; - try { - result = await this.storageSet({recent: {rev: bm.recentRev + 1, data: bm.recent}, recentDelta: {rev: bm.recentDeltaRev + 1, data: {}}}); - } catch(e) { - this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`); - } - - if (result.state == 'reject') { - - await this.loadRecent(true, false); - - this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`); - if (!recurse) { - this.savingRecent = false; - this.recentDelta[itemKey] = _.cloneDeep(bm.recent[itemKey]); - this.saveRecent(itemKey, true); - return; - } - } else if (result.state == 'success') { - this.makeDeltaDiff = true; - this.prevSavedItem = _.cloneDeep(bm.recent[itemKey]); - - this.recentDelta = {}; - await bm.setRecentRev(bm.recentRev + 1); - await bm.setRecentDeltaRev(bm.recentDeltaRev + 1); - } - } else {//сохраняем только дифф - let result = {state: ''}; - - try { - result = await this.storageSet({recentDelta: {rev: bm.recentDeltaRev + 1, data: this.recentDelta}}); - } catch(e) { - this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`); - } - - if (result.state == 'reject') { - - await this.loadRecent(true, false); - - this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`); - if (!recurse) { - this.savingRecent = false; - this.recentDelta[itemKey] = _.cloneDeep(bm.recent[itemKey]); - this.saveRecent(itemKey, true); - return; - } - } else if (result.state == 'success') { - await bm.setRecentDeltaRev(bm.recentDeltaRev + 1); + try { + result = await this.storageSet(query); + } catch(e) { + this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`); + } + + if (result.state == 'reject') { + + await this.loadRecent(false, false); + + this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`); + if (!recurse && itemKey) { + this.savingRecent = false; + this.saveRecent(itemKey, true); + return; } + } else if (result.state == 'success') { + if (needSaveRecent && newRecent.rev) + await this.setCachedRecent(newRecent); + if (needSaveRecentPatch && newRecentPatch.rev) + await this.setCachedRecentPatch(newRecentPatch); + if (needSaveRecentMod && newRecentMod.rev) + await this.setCachedRecentMod(newRecentMod); } } finally { this.savingRecent = false; diff --git a/client/components/Reader/share/bookManager.js b/client/components/Reader/share/bookManager.js index e58a2e0b..efef4552 100644 --- a/client/components/Reader/share/bookManager.js +++ b/client/components/Reader/share/bookManager.js @@ -36,9 +36,6 @@ class BookManager { } } - this.recentRev = await bmRecentStore.getItem('recent-rev') || 0; - this.recentDeltaRev = await bmRecentStore.getItem('recent-delta-rev') || 0; - this.recentChanged = true; this.loadStored();//no await @@ -440,7 +437,7 @@ class BookManager { Object.assign(mergedRecent, value); - //подстраховка + //подстраховка от hotReload for (let i of Object.keys(mergedRecent)) { if (!mergedRecent[i].key || mergedRecent[i].key !== i) delete mergedRecent[i]; @@ -466,16 +463,6 @@ class BookManager { this.emit('recent-changed'); } - async setRecentRev(value) { - await bmRecentStore.setItem('recent-rev', value); - this.recentRev = value; - } - - async setRecentDeltaRev(value) { - await bmRecentStore.setItem('recent-delta-rev', value); - this.recentDeltaRev = value; - } - addEventListener(listener) { if (this.eventListeners.indexOf(listener) < 0) this.eventListeners.push(listener); diff --git a/server/dev.js b/server/dev.js index df75cc84..7782d385 100644 --- a/server/dev.js +++ b/server/dev.js @@ -20,7 +20,7 @@ function webpackDevMiddleware(app) { function logQueries(app) { app.use(function(req, res, next) { const start = Date.now(); - log(`${req.method} ${req.originalUrl} ${JSON.stringify(req.body).substr(0, 2000)}`); + log(`${req.method} ${req.originalUrl} ${JSON.stringify(req.body).substr(0, 4000)}`); //log(`${JSON.stringify(req.headers, null, 2)}`) res.once('finish', () => { log(`${Date.now() - start}ms`);