Skip to content

Commit

Permalink
Cache vuex store (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
robbevp authored Jul 14, 2021
1 parent ecf0506 commit 5f9c48f
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 31 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"@mdi/font": "^5.9.55",
"@mdi/svg": "^5.9.55",
"fetch-retry": "^4.1.1",
"localforage": "^1.9.0",
"lodash.debounce": "^4.0.8",
"roboto-fontface": "^0.10.0",
"vue": "^2.6.14",
"vue-i18n": "^8.24.5",
Expand All @@ -20,7 +22,7 @@
"vuedraggable": "^2.24.3",
"vuetify": "^2.5.6",
"vuex": "^3.6.2",
"vuex-persistedstate": "^3.2.0"
"vuex-persist": "^3.1.3"
},
"devDependencies": {
"@intlify/vue-i18n-loader": "^1.1.0",
Expand Down
2 changes: 2 additions & 0 deletions src/store/albums.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export default {
async index({ commit, rootState }, scope = new AlbumsScope()) {
const generator = index(rootState.auth, scope);
try {
await this.albumsRestored;
await fetchAll(commit, generator, "setAlbums", scope);
return true;
} catch (error) {
Expand All @@ -128,6 +129,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const result = await read(rootState.auth, id);
await this.albumsRestored;
commit("setAlbum", { id, album: result });
return result.id;
} catch (error) {
Expand Down
2 changes: 2 additions & 0 deletions src/store/artists.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default {
async index({ commit, rootState }, scope = new ArtistsScope()) {
const generator = index(rootState.auth, scope);
try {
await this.artistsRestored;
await fetchAll(commit, generator, "setArtists", scope);
return true;
} catch (error) {
Expand All @@ -78,6 +79,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const result = await read(rootState.auth, id);
await this.artistsRestored;
commit("setArtist", { id, artist: result });
return result.id;
} catch (error) {
Expand Down
2 changes: 2 additions & 0 deletions src/store/genres.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export default {
async index({ commit, rootState }) {
const generator = index(rootState.auth);
try {
await this.genresRestored;
await fetchAll(commit, generator, "setGenres");
return true;
} catch (error) {
Expand All @@ -77,6 +78,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const result = await read(rootState.auth, id);
await this.genresRestored;
commit("setGenre", { id, genre: result });
return result.id;
} catch (error) {
Expand Down
2 changes: 2 additions & 0 deletions src/store/labels.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export default {
async index({ commit, rootState }) {
const generator = index(rootState.auth);
try {
await this.labelsRestored;
await fetchAll(commit, generator, "setLabels");
return true;
} catch (error) {
Expand All @@ -77,6 +78,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const result = await read(rootState.auth, id);
await this.labelsRestored;
commit("setLabel", { id, label: result });
return result.id;
} catch (error) {
Expand Down
114 changes: 114 additions & 0 deletions src/store/persistence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import VuexPersistence from "vuex-persist";
import localForage from "localforage";
import debounce from "lodash.debounce";

// A subclass for VuexPersistence to deal with single namespaced modules and large collections
// Should be provided with a module and an async storage
// IMPORTANT: This class assumes a lot about the structure of state and mutations available in each module
// If this structure changes, we inevitably have to update this class
class VuexPersistentModule extends VuexPersistence {
constructor(module, storage) {
const lowerCaseModule = module.toLowerCase();
// Write startLoading and the main collection (as an array) to storage
async function saveModule(module, state, storage) {
const modifiedState = {
startLoading: state[module].startLoading,
};
modifiedState[module] = Object.values(state[module][module]);
await storage.setItem(module, modifiedState);
}

// Set all options in parent class
super({
storage,
asyncStorage: true,
modules: [lowerCaseModule],
key: lowerCaseModule,
filter: (m) =>
m.type.substring(0, m.type.indexOf("/")) === lowerCaseModule,
saveState: debounce(saveModule, 60000),
});

this.capitilizedModule = module;

// Get item from an async storage and commit them back to the store
this.replaceState = async (
lowerCaseModule,
capitilizedModule,
storage,
store
) => {
const savedState = await storage.getItem(lowerCaseModule);
if (savedState) {
if (savedState.startLoading) {
store.commit(
`${lowerCaseModule}/setStartLoading`,
savedState.startLoading
);
}
if (savedState[lowerCaseModule]) {
store.commit(
`${lowerCaseModule}/set${capitilizedModule}`,
savedState[lowerCaseModule]
);
}
}
};

// Overwrite plugin to use replaceState and set a custom restored prop for each module
this.plugin = (store) => {
store[`${this.key}Restored`] = this.replaceState(
this.key,
this.capitilizedModule,
this.storage,
store
).then(() => {
// The subscriber is the same as in VuexPersistence, but we have to add it ourselves since we overwrite the plugin method
this.subscriber(store)((mutation, state) => {
if (this.filter(mutation)) {
this._mutex.enqueue(
this.saveState(this.key, this.reducer(state), this.storage)
);
}
});
this.subscribed = true;
});
};
}
}

const plugins = [
"Albums",
"Artists",
"Genres",
"Labels",
"Plays",
"Tracks",
].map((module) => {
const plugin = new VuexPersistentModule(module, localForage);
return plugin.plugin;
});

const localStorageModules = [
"auth",
"codecConversions",
"codecs",
"coverFilenames",
"imageTypes",
"locations",
"rescan",
"users",
"userSettings",
];

export const vuexLocalStorage = new VuexPersistence({
storage: window.localStorage,
strictMode: process.env.NODE_ENV !== "production",
modules: localStorageModules,
filter: (m) =>
localStorageModules.includes(m.type.substring(0, m.type.indexOf("/"))),
});

plugins.push(vuexLocalStorage.plugin);

export default plugins;
1 change: 1 addition & 0 deletions src/store/plays.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default {
async index({ commit, rootState }) {
const generator = index(rootState.auth);
try {
await this.playsRestored;
await fetchAll(commit, generator, "setPlays");
return true;
} catch (error) {
Expand Down
42 changes: 22 additions & 20 deletions src/store/store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Vue from "vue";
import Vuex from "vuex";
import createPersistedState from "vuex-persistedstate";
import persistencePlugins, { vuexLocalStorage } from "./persistence";
import albums from "./albums";
import artists from "./artists";
import auth from "./auth";
Expand All @@ -20,12 +20,27 @@ import userSettings from "./user_settings";

Vue.use(Vuex);

const mutations = {
addError(state, error) {
if (error.unauthorized) {
this.commit("auth/logout");
}
state.errors.push(error);
},
clearErrors(state) {
state.errors = [];
},
updateCurrentDay(state) {
state.currentDay = new Date().setHours(0, 0, 0, 0);
},
};

if (process.env.NODE_ENV !== "production") {
mutations.RESTORE_MUTATION = vuexLocalStorage.RESTORE_MUTATION;
}

export default new Vuex.Store({
plugins: [
createPersistedState({
paths: ["auth", "userSettings"],
}),
],
plugins: persistencePlugins,
strict: process.env.NODE_ENV !== "production",
modules: {
albums,
Expand All @@ -49,20 +64,7 @@ export default new Vuex.Store({
errors: [],
currentDay: new Date().setHours(0, 0, 0, 0),
},
mutations: {
addError(state, error) {
if (error.unauthorized) {
this.commit("auth/logout");
}
state.errors.push(error);
},
clearErrors(state) {
state.errors = [];
},
updateCurrentDay(state) {
state.currentDay = new Date().setHours(0, 0, 0, 0);
},
},
mutations,
actions: {},
getters: {
numberOfFlaggedItems(state, getters) {
Expand Down
2 changes: 2 additions & 0 deletions src/store/tracks.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export default {
async index({ commit, rootState }, scope = new TracksScope()) {
const generator = index(rootState.auth, scope);
try {
await this.tracksRestored;
await fetchAll(commit, generator, "setTracks", scope);
return true;
} catch (error) {
Expand All @@ -121,6 +122,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const track = await read(rootState.auth, id);
await this.tracksRestored;
commit("setTrack", { id, track });
return true;
} catch (error) {
Expand Down
39 changes: 29 additions & 10 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5f9c48f

Please sign in to comment.