initial prefs storage work

This commit is contained in:
Henry Jameson 2022-08-10 02:19:07 +03:00
parent 750696643f
commit aa41cedd93
2 changed files with 144 additions and 4 deletions

View File

@ -1,5 +1,5 @@
import { toRaw } from 'vue' import { toRaw } from 'vue'
import { isEqual, cloneDeep } from 'lodash' import { isEqual, uniqBy, cloneDeep, set } from 'lodash'
import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
export const VERSION = 1 export const VERSION = 1
@ -22,6 +22,10 @@ export const defaultState = {
// 1000: trim keys to those known by currently running FE // 1000: trim keys to those known by currently running FE
// 1001: same as above + reset everything to 0 // 1001: same as above + reset everything to 0
}, },
prefsStorage: {
_journal: [],
simple: {}
},
// raw data // raw data
raw: null, raw: null,
// local cache // local cache
@ -93,6 +97,42 @@ export const _mergeFlags = (recent, stale, allFlagKeys) => {
})) }))
} }
export const _mergePrefs = (recent, stale, allFlagKeys) => {
if (!stale) return recent
if (!recent) return stale
const { _journal: recentJournal, ...recentData } = recent
const { _journal: staleJournal } = stale
/** Journal entry format:
* path: path to entry in prefsStorage
* timestamp: timestamp of the change
* operation: operation type
* arguments: array of arguments, depends on operation type
*
* currently only supported operation type is "set" which just sets the value
* to requested one. Intended only to be used with simple preferences (boolean, number)
* shouldn't be used with collections!
*/
const resultOutput = { ...recentData }
const totalJournal = uniqBy(
[...recentJournal, ...staleJournal].sort((a, b) => a.timestamp > b.timestamp ? -1 : 1),
'path'
).reverse()
totalJournal.forEach(({ path, timestamp, operation, args }) => {
if (path.startsWith('_')) {
console.error(`journal contains entry to edit internal (starts with _) field '${path}', something is incorrect here, ignoring.`)
return
}
switch (operation) {
case 'set':
set(resultOutput, path, args[0])
break
default:
console.error(`Unknown journal operation: '${operation}', did we forget to run reverse migrations beforehand?`)
}
})
return { ...resultOutput, _journal: totalJournal }
}
export const _resetFlags = (totalFlags, knownKeys = defaultState.flagStorage) => { export const _resetFlags = (totalFlags, knownKeys = defaultState.flagStorage) => {
let result = { ...totalFlags } let result = { ...totalFlags }
const allFlagKeys = Object.keys(totalFlags) const allFlagKeys = Object.keys(totalFlags)
@ -165,7 +205,8 @@ export const mutations = {
if (recent === null) { if (recent === null) {
console.debug(`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`) console.debug(`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`)
recent = _wrapData({ recent = _wrapData({
flagStorage: { ...flagsTemplate } flagStorage: { ...flagsTemplate },
prefsStorage: { ...defaultState.prefsStorage }
}) })
} }
@ -180,17 +221,21 @@ export const mutations = {
const allFlagKeys = _getAllFlags(recent, stale) const allFlagKeys = _getAllFlags(recent, stale)
let totalFlags let totalFlags
let totalPrefs
if (dirty) { if (dirty) {
// Merge the flags // Merge the flags
console.debug('Merging the flags...') console.debug('Merging the data...')
totalFlags = _mergeFlags(recent, stale, allFlagKeys) totalFlags = _mergeFlags(recent, stale, allFlagKeys)
totalPrefs = _mergePrefs(recent.prefsStorage, stale.prefsStorage)
} else { } else {
totalFlags = recent.flagStorage totalFlags = recent.flagStorage
totalPrefs = recent.prefsStorage
} }
totalFlags = _resetFlags(totalFlags) totalFlags = _resetFlags(totalFlags)
recent.flagStorage = totalFlags recent.flagStorage = totalFlags
recent.prefsStorage = totalPrefs
state.dirty = dirty || needsUpload state.dirty = dirty || needsUpload
state.cache = recent state.cache = recent
@ -216,7 +261,8 @@ const serverSideStorage = {
const needPush = state.dirty || force const needPush = state.dirty || force
if (!needPush) return if (!needPush) return
state.cache = _wrapData({ state.cache = _wrapData({
flagStorage: toRaw(state.flagStorage) flagStorage: toRaw(state.flagStorage),
prefsStorage: toRaw(state.flagStorage)
}) })
const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } } const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } }
rootState.api.backendInteractor rootState.api.backendInteractor

View File

@ -7,6 +7,7 @@ import {
_getRecentData, _getRecentData,
_getAllFlags, _getAllFlags,
_mergeFlags, _mergeFlags,
_mergePrefs,
_resetFlags, _resetFlags,
mutations, mutations,
defaultState, defaultState,
@ -28,6 +29,7 @@ describe('The serverSideStorage module', () => {
expect(state.cache._version).to.eql(VERSION) expect(state.cache._version).to.eql(VERSION)
expect(state.cache._timestamp).to.be.a('number') expect(state.cache._timestamp).to.be.a('number')
expect(state.cache.flagStorage).to.eql(defaultState.flagStorage) expect(state.cache.flagStorage).to.eql(defaultState.flagStorage)
expect(state.cache.prefsStorage).to.eql(defaultState.prefsStorage)
}) })
it('should initialize storage with proper flags for new users if none present', () => { it('should initialize storage with proper flags for new users if none present', () => {
@ -36,6 +38,7 @@ describe('The serverSideStorage module', () => {
expect(state.cache._version).to.eql(VERSION) expect(state.cache._version).to.eql(VERSION)
expect(state.cache._timestamp).to.be.a('number') expect(state.cache._timestamp).to.be.a('number')
expect(state.cache.flagStorage).to.eql(newUserFlags) expect(state.cache.flagStorage).to.eql(newUserFlags)
expect(state.cache.prefsStorage).to.eql(defaultState.prefsStorage)
}) })
it('should merge flags even if remote timestamp is older', () => { it('should merge flags even if remote timestamp is older', () => {
@ -57,6 +60,9 @@ describe('The serverSideStorage module', () => {
flagStorage: { flagStorage: {
...defaultState.flagStorage, ...defaultState.flagStorage,
updateCounter: 1 updateCounter: 1
},
prefsStorage: {
...defaultState.flagStorage,
} }
} }
} }
@ -157,6 +163,94 @@ describe('The serverSideStorage module', () => {
}) })
}) })
describe('_mergePrefs', () => {
it('should prefer recent and apply journal to it', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { a: 1, b: 0, c: true },
_journal: [
{ path: 'simple.b', operation: 'set', args: [0], timestamp: 2 },
{ path: 'simple.c', operation: 'set', args: [true], timestamp: 4 }
]
},
// STALE
{
simple: { a: 1, b: 1, c: false },
_journal: [
{ path: 'simple.a', operation: 'set', args: [1], timestamp: 1 },
{ path: 'simple.b', operation: 'set', args: [1], timestamp: 3 }
]
}
)
).to.eql({
simple: { a: 1, b: 1, c: true },
_journal: [
{ path: 'simple.a', operation: 'set', args: [1], timestamp: 1 },
{ path: 'simple.b', operation: 'set', args: [1], timestamp: 3 },
{ path: 'simple.c', operation: 'set', args: [true], timestamp: 4 }
]
})
})
it('should allow setting falsy values', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { a: 1, b: 0, c: false },
_journal: [
{ path: 'simple.b', operation: 'set', args: [0], timestamp: 2 },
{ path: 'simple.c', operation: 'set', args: [false], timestamp: 4 }
]
},
// STALE
{
simple: { a: 0, b: 0, c: true },
_journal: [
{ path: 'simple.a', operation: 'set', args: [0], timestamp: 1 },
{ path: 'simple.b', operation: 'set', args: [0], timestamp: 3 }
]
}
)
).to.eql({
simple: { a: 0, b: 0, c: false },
_journal: [
{ path: 'simple.a', operation: 'set', args: [0], timestamp: 1 },
{ path: 'simple.b', operation: 'set', args: [0], timestamp: 3 },
{ path: 'simple.c', operation: 'set', args: [false], timestamp: 4 }
]
})
})
it('should work with strings', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { a: 'foo' },
_journal: [
{ path: 'simple.a', operation: 'set', args: ['foo'], timestamp: 2 }
]
},
// STALE
{
simple: { a: 'bar' },
_journal: [
{ path: 'simple.a', operation: 'set', args: ['bar'], timestamp: 4 }
]
}
)
).to.eql({
simple: { a: 'bar' },
_journal: [
{ path: 'simple.a', operation: 'set', args: ['bar'], timestamp: 4 }
]
})
})
})
describe('_resetFlags', () => { describe('_resetFlags', () => {
it('should reset all known flags to 0 when reset flag is set to > 0 and < 9000', () => { it('should reset all known flags to 0 when reset flag is set to > 0 and < 9000', () => {
const totalFlags = { a: 0, b: 3, reset: 1 } const totalFlags = { a: 0, b: 3, reset: 1 }