diff --git a/client/components/Reader/ServerStorage/ServerStorage.vue b/client/components/Reader/ServerStorage/ServerStorage.vue index 05005609..c3dccab5 100644 --- a/client/components/Reader/ServerStorage/ServerStorage.vue +++ b/client/components/Reader/ServerStorage/ServerStorage.vue @@ -13,7 +13,14 @@ import readerApi from '../../../api/reader'; import * as utils from '../../../share/utils'; import * as cryptoUtils from '../../../share/cryptoUtils'; +const maxSetTries = 5; + export default @Component({ + watch: { + profiles: function() { + this.saveProfiles(); + }, + }, }) class ServerStorage extends Vue { created() { @@ -27,9 +34,7 @@ class ServerStorage extends Vue { } this.hashedStorageKey = utils.toBase58(await cryptoUtils.sha256(this.serverStorageKey)); - //console.log(await this.storageSet({'id1': {rev: 1, data: {test: 123}}})); - //console.log(await this.storageGet({'id1': {}})); - //console.log(await this.storageCheck({'id1': {rev: 1, data: {test: 123}}})); + await this.loadProfiles(); } get settings() { @@ -40,6 +45,73 @@ class ServerStorage extends Vue { return this.$store.state.reader.serverStorageKey; } + get profiles() { + return this.$store.state.reader.profiles; + } + + get profilesRev() { + return this.$store.state.reader.profilesRev; + } + + notifySuccessIfNeeded(rev1, rev2) { + if (rev1 != rev2) + this.$notify.success({message: 'Данные синхронизированы с сервером'}); + } + + warning(message) { + this.$notify.warning({message}); + } + + error(message) { + this.$notify.error({message}); + } + + async loadProfiles() { + let prof = await this.storageGet({'profiles': {}}); + + if (prof.state == 'success') { + const oldRev = this.profilesRev; + prof = prof.items.profiles; + this.commit('reader/setProfiles', prof.data); + this.commit('reader/setProfilesRev', prof.rev); + + this.oldProfiles = this.profiles; + this.notifySuccessIfNeeded(oldRev, prof.rev); + } else { + this.warning(`Неверный ответ сервера: ${prof.state}`); + } + } + + async saveProfiles() { + if (!this.savingProfiles) { + this.savingProfiles = true; + + const diff = utils.getObjDiff(this.oldProfiles, this.profiles); + let result = {state: ''}; + let tries = 0; + while (result.state != 'success' && tries < maxSetTries) { + result = await this.storageSet({'profiles': {rev: this.profilesRev + 1, data: this.profiles}}); + + if (result.state == 'reject') { + await this.loadProfiles(); + const newProfiles = utils.applyObjDiff(this.profiles, diff); + this.commit('reader/setProfiles', newProfiles); + this.commit('reader/setProfilesRev', result.items.profiles.rev); + } + + tries++; + } + + this.commit('reader/setProfilesRev', this.profilesRev + 1); + + if (tries >= maxSetTries) { + throw new Error('Не удалось отправить данные на сервер'); + } + + this.savingProfiles = false; + } + } + generateNewServerStorageKey() { const key = utils.toBase58(utils.randomArray(32)); this.commit('reader/setServerStorageKey', key); diff --git a/client/share/utils.js b/client/share/utils.js index 24a68cdb..3c6fb4e2 100644 --- a/client/share/utils.js +++ b/client/share/utils.js @@ -1,3 +1,4 @@ +import _ from 'lodash'; import baseX from 'base-x'; import PAKO from 'pako'; import {Buffer} from 'safe-buffer'; @@ -92,3 +93,67 @@ export function toBase64(data) { export function fromBase64(data) { return bs64.decode(data); } + +export function getObjDiff(oldObj, newObj) { + const result = {__isDiff: true, change: {}, add: {}, del: []}; + + for (const key of Object.keys(oldObj)) { + if (newObj.hasOwnProperty(key)) { + if (!_.isEqual(oldObj[key], newObj[key])) { + if (_.isObject(oldObj[key]) && _.isObject(newObj[key])) { + result.change[key] = getObjDiff(oldObj[key], newObj[key]); + } else { + result.change[key] = _.cloneDeep(newObj[key]); + } + } + } else { + result.del.push(key); + } + } + + for (const key of Object.keys(newObj)) { + if (!oldObj.hasOwnProperty(key)) { + result.add[key] = _.cloneDeep(newObj[key]); + } + } + + return result; +} + +export function isEmptyObjDiff(diff) { + return (!_.isObject(diff) || !diff.__isDiff || + (!Object.keys(diff.change).length && + !Object.keys(diff.add).length && + !diff.del.length + ) + ); +} + +export function applyObjDiff(obj, diff, isAddChanged) { + const result = _.cloneDeep(obj); + if (!diff.__isDiff) + return result; + + const change = diff.change; + for (const key of Object.keys(change)) { + if (result.hasOwnProperty(key)) { + if (_.isObject(change[key])) { + result[key] = applyObjDiff(result[key], change[key], isAddChanged); + } else { + result[key] = _.cloneDeep(change[key]); + } + } else if (isAddChanged) { + result[key] = _.cloneDeep(change[key]); + } + } + + for (const key of Object.keys(diff.add)) { + result[key] = _.cloneDeep(diff.add[key]); + } + + for (const key of diff.del) { + delete result[key]; + } + + return result; +} diff --git a/client/store/modules/reader.js b/client/store/modules/reader.js index 17d6a3f0..34acbd08 100644 --- a/client/store/modules/reader.js +++ b/client/store/modules/reader.js @@ -123,50 +123,50 @@ const webFonts = [ ]; const settingDefaults = { - textColor: '#000000', - backgroundColor: '#EBE2C9', - wallpaper: '', - fontStyle: '',// 'italic' - fontWeight: '',// 'bold' - fontSize: 20,// px - fontName: 'ReaderDefault', - webFontName: '', - fontVertShift: 0, - textVertShift: -20, + textColor: '#000000', + backgroundColor: '#EBE2C9', + wallpaper: '', + fontStyle: '',// 'italic' + fontWeight: '',// 'bold' + fontSize: 20,// px + fontName: 'ReaderDefault', + webFontName: '', + fontVertShift: 0, + textVertShift: -20, - lineInterval: 3,// px, межстрочный интервал - textAlignJustify: true,// выравнивание по ширине - p: 25,// px, отступ параграфа - indentLR: 15,// px, отступ всего текста слева и справа - indentTB: 0,// px, отступ всего текста сверху и снизу - wordWrap: true,//перенос по слогам - keepLastToFirst: true,// перенос последней строки в первую при листании + lineInterval: 3,// px, межстрочный интервал + textAlignJustify: true,// выравнивание по ширине + p: 25,// px, отступ параграфа + indentLR: 15,// px, отступ всего текста слева и справа + indentTB: 0,// px, отступ всего текста сверху и снизу + wordWrap: true,//перенос по слогам + keepLastToFirst: true,// перенос последней строки в первую при листании - showStatusBar: true, - statusBarTop: false,// top, bottom - statusBarHeight: 19,// px - statusBarColorAlpha: 0.4, + showStatusBar: true, + statusBarTop: false,// top, bottom + statusBarHeight: 19,// px + statusBarColorAlpha: 0.4, - scrollingDelay: 3000,// замедление, ms - scrollingType: 'ease-in-out', //linear, ease, ease-in, ease-out, ease-in-out + scrollingDelay: 3000,// замедление, ms + scrollingType: 'ease-in-out', //linear, ease, ease-in, ease-out, ease-in-out - pageChangeAnimation: 'blink',// '' - нет, downShift, rightShift, thaw - протаивание, blink - мерцание - pageChangeAnimationSpeed: 80, //0-100% + pageChangeAnimation: 'blink',// '' - нет, downShift, rightShift, thaw - протаивание, blink - мерцание + pageChangeAnimationSpeed: 80, //0-100% - allowUrlParamBookPos: false, - lazyParseEnabled: false, - copyFullText: false, - showClickMapPage: true, - clickControl: true, - cutEmptyParagraphs: false, - addEmptyParagraphs: 0, - blinkCachedLoad: true, - showImages: true, - showInlineImagesInCenter: true, - imageHeightLines: 100, - imageFitWidth: true, + allowUrlParamBookPos: false, + lazyParseEnabled: false, + copyFullText: false, + showClickMapPage: true, + clickControl: true, + cutEmptyParagraphs: false, + addEmptyParagraphs: 0, + blinkCachedLoad: true, + showImages: true, + showInlineImagesInCenter: true, + imageHeightLines: 100, + imageFitWidth: true, - fontShifts: {}, + fontShifts: {}, }; for (const font of fonts) @@ -178,6 +178,9 @@ for (const font of webFonts) const state = { toolBarActive: true, serverStorageKey: '', + profiles: [], + profilesRev: 0, + currentProfile: '', settings: Object.assign({}, settingDefaults), }; @@ -195,6 +198,15 @@ const mutations = { setServerStorageKey(state, value) { state.serverStorageKey = value; }, + setProfiles(state, value) { + state.profiles = value; + }, + setProfilesRev(state, value) { + state.profilesRev = value; + }, + setCurrentProfile(state, value) { + state.currentProfile = value; + }, setSettings(state, value) { state.settings = Object.assign({}, state.settings, value); }