Fix: more sync issues, Add: link audiobooks with folder names in selected download folder

This commit is contained in:
advplyr 2021-11-20 10:59:34 -06:00
parent 3b6e7e1ce2
commit f40e971b90
15 changed files with 95 additions and 293 deletions

View file

@ -13,8 +13,8 @@ android {
applicationId "com.audiobookshelf.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 41
versionName "0.9.22-beta"
versionCode 42
versionName "0.9.23-beta"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View file

@ -394,6 +394,7 @@ export default {
this.isPaused = !data.isPlaying
this.currentTime = Number((data.currentTime / 1000).toFixed(2))
this.totalDuration = Number((data.duration / 1000).toFixed(2))
this.$emit('setTotalDuration', this.totalDuration)
this.timeupdate()
if (data.isPlaying) {
console.log('playing - continue')
@ -496,6 +497,7 @@ export default {
onMetadata(data) {
console.log('Native Audio On Metadata', JSON.stringify(data))
this.totalDuration = Number((data.duration / 1000).toFixed(2))
this.$emit('setTotalDuration', this.totalDuration)
this.currentTime = Number((data.currentTime / 1000).toFixed(2))
this.stateName = data.stateName

View file

@ -12,6 +12,7 @@
:sleep-timeout-current-time="sleepTimeoutCurrentTime"
@close="cancelStream"
@sync="sync"
@setTotalDuration="setTotalDuration"
@selectPlaybackSpeed="showPlaybackSpeedModal = true"
@selectChapter="clickChapterBtn"
@showSleepTimer="showSleepTimer"
@ -49,7 +50,8 @@ export default {
sleepTimerEndOfChapterTime: 0,
onSleepTimerEndedListener: null,
sleepInterval: null,
currentEndOfChapterTime: 0
currentEndOfChapterTime: 0,
totalDuration: 0
}
},
watch: {
@ -66,7 +68,7 @@ export default {
},
userAudiobook() {
if (!this.audiobookId) return
return this.$store.getters['user/getMostRecentUserAudiobookData'](this.audiobookId)
return this.$store.getters['user/getUserAudiobookData'](this.audiobookId)
},
bookmarks() {
if (!this.userAudiobook) return []
@ -274,7 +276,7 @@ export default {
audiobookId: syncData.audiobookId,
currentTime: syncData.currentTime,
totalDuration: syncData.totalDuration,
progress: Number((syncData.currentTime / syncData.totalDuration).toFixed(3)),
progress: syncData.totalDuration ? Number((syncData.currentTime / syncData.totalDuration).toFixed(3)) : 0,
lastUpdate: Date.now(),
isRead: false
}
@ -283,45 +285,21 @@ export default {
this.$server.socket.emit('progress_update', progressUpdate)
} else {
this.$store.dispatch('user/updateUserAudiobookData', progressUpdate)
// this.$localStore.updateUserAudiobookData(progressUpdate).then(() => {
// console.log('Updated user audiobook progress', currentTime)
// })
}
}
},
updateTime(currentTime) {
this.currentTime = currentTime
var diff = currentTime - this.lastProgressTimeUpdate
if (diff > 4 || diff < 0) {
this.lastProgressTimeUpdate = currentTime
if (this.stream) {
var updatePayload = {
currentTime,
streamId: this.stream.id
}
this.$server.socket.emit('stream_update', updatePayload)
} else if (this.download) {
var progressUpdate = {
audiobookId: this.download.id,
currentTime: currentTime,
totalDuration: this.download.audiobook.duration,
progress: Number((currentTime / this.download.audiobook.duration).toFixed(3)),
lastUpdate: Date.now(),
isRead: false
}
if (this.$server.connected) {
this.$server.socket.emit('progress_update', progressUpdate)
}
this.$localStore.updateUserAudiobookData(progressUpdate).then(() => {
console.log('Updated user audiobook progress', currentTime)
})
}
}
this.sync({
currentTime,
audiobookId: this.audiobookId,
streamId: this.stream ? this.stream.id : null,
timeListened: 0,
totalDuration: this.totalDuration || 0
})
},
setTotalDuration(duration) {
this.totalDuration = duration
},
closeStream() {},
streamClosed(audiobookId) {
console.log('Stream Closed')
if (this.stream.audiobook.id === audiobookId || audiobookId === 'n/a') {
@ -349,7 +327,7 @@ export default {
}
},
async getDownloadStartTime() {
var userAudiobook = await this.$localStore.getMostRecentUserAudiobook(this.audiobookId)
var userAudiobook = this.$store.getters['user/getUserAudiobookData'](this.audiobookId)
if (!userAudiobook) {
console.log('[StreamContainer] getDownloadStartTime no user audiobook record found')
return 0
@ -505,7 +483,6 @@ export default {
this.setListeners()
this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated })
// this.$store.commit('user/addUserAudiobookListener', { id: 'streamContainer', meth: this.userAudiobooksUpdated })
this.$store.commit('setStreamListener', this.streamUpdated)
},
beforeDestroy() {
@ -520,7 +497,6 @@ export default {
}
this.$store.commit('user/removeSettingsListener', 'streamContainer')
// this.$store.commit('user/removeUserAudiobookListener', 'streamContainer')
this.$store.commit('removeStreamListener')
}
}

View file

@ -70,7 +70,7 @@ export default {
return this.audiobook.id
},
mostRecentUserProgress() {
return this.$store.getters['user/getMostRecentUserAudiobookData'](this.audiobookId)
return this.$store.getters['user/getUserAudiobookData'](this.audiobookId)
},
userProgressPercent() {
return this.mostRecentUserProgress ? this.mostRecentUserProgress.progress || 0 : 0

View file

@ -82,7 +82,7 @@ export default {
return this.$store.getters['user/getUserSetting']('mobileOrderBy')
},
mostRecentUserProgress() {
return this.$store.getters['user/getMostRecentUserAudiobookData'](this.audiobookId)
return this.$store.getters['user/getUserAudiobookData'](this.audiobookId)
},
userProgressPercent() {
return this.mostRecentUserProgress ? this.mostRecentUserProgress.progress || 0 : 0

View file

@ -158,7 +158,7 @@ export default {
return this.$store.state.downloads.downloads
},
mediaScanResults() {
return this.$store.state.mediaScanResults
return this.$store.state.downloads.mediaScanResults
}
},
methods: {
@ -210,7 +210,7 @@ export default {
}
return sr
})
this.$store.commit('setMediaScanResults', searchResults)
this.$store.commit('downloads/setMediaScanResults', searchResults)
} else {
this.$toast.warning('No audio or image files found')
}
@ -218,7 +218,7 @@ export default {
},
async resetFolder() {
await this.$localStore.setDownloadFolder(null)
this.$store.commit('setMediaScanResults', {})
this.$store.commit('downloads/setMediaScanResults', {})
this.$toast.info('Unlinked Folder')
},
updateDownloadProgress({ audiobookId, progress }) {

View file

@ -17,7 +17,6 @@ import { Capacitor } from '@capacitor/core'
import { Network } from '@capacitor/network'
import { AppUpdate } from '@robingenz/capacitor-app-update'
import AudioDownloader from '@/plugins/audio-downloader'
import MyNativeAudio from '@/plugins/my-native-audio'
import StorageManager from '@/plugins/storage-manager'
export default {
@ -47,7 +46,6 @@ export default {
methods: {
async connected(isConnected) {
if (isConnected) {
// this.syncUserProgress()
console.log('[Default] Connected socket sync user ab data')
this.$store.dispatch('user/syncUserAudiobookData')
@ -58,63 +56,11 @@ export default {
socketConnectionFailed(err) {
this.$toast.error('Socket connection error: ' + err.message)
},
updateAudiobookProgressOnServer(audiobookProgress) {
if (this.$server.socket) {
console.log(`[PROGRESSSYNC] Updating AB Progress on server ${JSON.stringify(audiobookProgress)}`)
this.$server.socket.emit('progress_update', audiobookProgress)
}
},
syncUserProgress() {
if (!this.$store.state.user.user) return
var userAudiobooks = this.$store.state.user.user.audiobooks
var localAudiobooks = this.$store.state.user.localUserAudiobooks
var localHasUpdates = false
// console.log('[PROGRESSSYNC] Starting Sync USER', JSON.stringify(userAudiobooks))
// console.log('[PROGRESSSYNC] Starting Sync LOCAL', JSON.stringify(localAudiobooks))
var newestLocal = { ...localAudiobooks }
for (const audiobookId in userAudiobooks) {
if (!audiobookId || !userAudiobooks[audiobookId] || audiobookId === 'undefined') {
console.error(`[PROGRESSSYNC] Invalid audiobookId ${audiobookId} - ${JSON.stringify(userAudiobooks[audiobookId])}`)
} else if (localAudiobooks[audiobookId]) {
if (localAudiobooks[audiobookId].lastUpdate > userAudiobooks[audiobookId].lastUpdate) {
// Local progress is more recent than user progress
this.updateAudiobookProgressOnServer(localAudiobooks[audiobookId])
} else if (localAudiobooks[audiobookId].lastUpdate < userAudiobooks[audiobookId].lastUpdate) {
// Server is more recent than local
newestLocal[audiobookId] = userAudiobooks[audiobookId]
// console.log('[PROGRESSSYNC] Server IS MORE RECENT for', audiobookId, JSON.stringify(newestLocal[audiobookId]))
localHasUpdates = true
}
} else {
// Not on local yet - store on local
newestLocal[audiobookId] = userAudiobooks[audiobookId]
// console.log('[PROGRESSSYNC] LOCAL Is NOT Stored YET for', audiobookId, JSON.stringify(newestLocal[audiobookId]))
localHasUpdates = true
}
}
for (const audiobookId in localAudiobooks) {
if (!userAudiobooks[audiobookId]) {
// Local progress is not on server
this.updateAudiobookProgressOnServer(localAudiobooks[audiobookId])
}
}
if (localHasUpdates) {
// console.log('[PROGRESSSYNC] Local audiobook progress has updates from server')
this.$localStore.setAllAudiobookProgress(newestLocal)
}
},
currentUserAudiobookUpdate({ id, data }) {
if (data) {
console.log(`Current User Audiobook Updated ${id} ${JSON.stringify(data)}`)
// this.$localStore.updateUserAudiobookData(data)
this.$sqlStore.setUserAudiobookData(data)
} else {
// this.$localStore.removeAudiobookProgress(id)
this.$sqlStore.removeUserAudiobookData(id)
}
},
@ -131,16 +77,6 @@ export default {
await AppUpdate.openAppStore()
}
},
// showUpdateToast(availableVersion, immediateUpdateAllowed) {
// var toastText = immediateUpdateAllowed ? `Click here to update` : `Click here to open app store`
// this.$toast.info(`Update is available for v${availableVersion}! ${toastText}`, {
// draggable: false,
// hideProgressBar: false,
// timeout: 10000,
// closeButton: false,
// onClick: this.clickUpdateToast()
// })
// },
async checkForUpdate() {
console.log('Checking for app update')
const result = await AppUpdate.getAppUpdateInfo()
@ -274,10 +210,9 @@ export default {
},
async syncDownloads(downloads, downloadFolder) {
console.log('Syncing downloads ' + downloads.length)
var mediaScanResults = await this.searchFolder(downloadFolder)
this.$store.commit('setMediaScanResults', mediaScanResults)
this.$store.commit('downloads/setMediaScanResults', mediaScanResults)
// Filter out media folders without any audio files
var mediaFolders = mediaScanResults.folders.filter((sr) => {
@ -308,42 +243,6 @@ export default {
}
})
},
// async onMediaLoaded(items) {
// var jsitems = JSON.parse(items)
// jsitems = jsitems.map((item) => {
// return {
// filename: item.name,
// size: item.size,
// contentUrl: item.uri,
// coverUrl: item.coverUrl || null
// }
// })
// var downloads = await this.$sqlStore.getAllDownloads()
// for (let i = 0; i < downloads.length; i++) {
// var download = downloads[i]
// var jsitem = jsitems.find((item) => item.contentUrl === download.contentUrl)
// if (!jsitem) {
// console.error('Removing download was not found', JSON.stringify(download))
// await this.$sqlStore.removeDownload(download.id)
// } else if (download.coverUrl && !jsitem.coverUrl) {
// console.error('Removing cover for download was not found')
// download.cover = null
// download.coverUrl = null
// download.size = jsitem.size || 0
// this.$store.commit('downloads/addUpdateDownload', download)
// this.$store.commit('audiobooks/addUpdate', download.audiobook)
// } else {
// download.size = jsitem.size || 0
// this.$store.commit('downloads/addUpdateDownload', download)
// this.$store.commit('audiobooks/addUpdate', download.audiobook)
// }
// }
// this.checkLoadCurrent()
// this.$store.dispatch('audiobooks/setNativeAudiobooks')
// },
async initMediaStore() {
// Request and setup listeners for media files on native
AudioDownloader.addListener('onDownloadComplete', (data) => {
@ -352,19 +251,14 @@ export default {
AudioDownloader.addListener('onDownloadFailed', (data) => {
this.onDownloadFailed(data)
})
// AudioDownloader.addListener('onMediaLoaded', (data) => {
// this.onMediaLoaded(data.items)
// })
AudioDownloader.addListener('onDownloadProgress', (data) => {
this.onDownloadProgress(data)
})
await this.$localStore.loadUserAudiobooks()
var downloads = await this.$sqlStore.getAllDownloads()
var downloads = (await this.$sqlStore.getAllDownloads()) || []
var downloadFolder = await this.$localStore.getDownloadFolder()
if (downloadFolder && downloads.length) {
if (downloadFolder) {
await this.syncDownloads(downloads, downloadFolder)
}
@ -373,29 +267,6 @@ export default {
this.$store.commit('user/setSettings', userSavedSettings)
}
// if (downloads.length) {
// var urls = downloads
// .map((d) => {
// return {
// contentUrl: d.contentUrl,
// coverUrl: d.coverUrl || '',
// storageId: d.storageId,
// basePath: d.basePath,
// coverBasePath: d.coverBasePath || ''
// }
// })
// .filter((d) => {
// if (!d.contentUrl) {
// console.error('Invalid Download no Content URL', JSON.stringify(d))
// return false
// }
// return true
// })
// AudioDownloader.load({
// audiobookUrls: urls
// })
// }
var checkPermission = await StorageManager.checkStoragePermission()
console.log('Storage Permission is' + checkPermission.value)
if (!checkPermission.value) {

View file

@ -1,6 +1,6 @@
{
"name": "audiobookshelf-app",
"version": "v0.9.22-beta",
"version": "v0.9.23-beta",
"author": "advplyr",
"scripts": {
"dev": "nuxt --hostname localhost --port 1337",

View file

@ -19,7 +19,7 @@ export default {
},
booksWithUserAbData() {
var books = this.books.map((b) => {
var userAbData = this.$store.getters['user/getMostRecentUserAudiobookData'](b.id)
var userAbData = this.$store.getters['user/getUserAudiobookData'](b.id)
return { ...b, userAbData }
})
return books

View file

@ -130,7 +130,7 @@ export default {
return this.$store.state.downloads.downloads
},
mediaScanResults() {
return this.$store.state.mediaScanResults
return this.$store.state.downloads.mediaScanResults
}
},
methods: {
@ -156,7 +156,12 @@ export default {
await this.$localStore.setDownloadFolder(folderObj)
this.searchFolder()
await this.searchFolder()
var audiobooks = this.$store.state.audiobooks.audiobooks || []
if (audiobooks.length) {
this.$store.dispatch('downloads/linkOrphanDownloads', audiobooks)
}
}
},
async searchFolder() {
@ -175,7 +180,7 @@ export default {
}
return sr
})
this.$store.commit('setMediaScanResults', searchResults)
this.$store.commit('downloads/setMediaScanResults', searchResults)
} else {
this.$toast.warning('No audio or image files found')
}
@ -183,7 +188,7 @@ export default {
},
async resetFolder() {
await this.$localStore.setDownloadFolder(null)
this.$store.commit('setMediaScanResults', {})
this.$store.commit('downloads/setMediaScanResults', {})
this.$toast.info('Unlinked Folder')
},
jumpToAudiobook(download) {

View file

@ -6,80 +6,6 @@ class LocalStorage {
this.userAudiobooksLoaded = false
this.downloadFolder = null
this.userAudiobooks = {}
}
async getMostRecentUserAudiobook(audiobookId) {
if (!this.userAudiobooksLoaded) {
await this.loadUserAudiobooks()
}
var local = this.getUserAudiobook(audiobookId)
var server = this.vuexStore.getters['user/getUserAudiobook'](audiobookId)
if (local && server) {
if (local.lastUpdate > server.lastUpdate) {
console.log('[LocalStorage] Most recent user audiobook is from LOCAL')
return local
}
console.log('[LocalStorage] Most recent user audiobook is from SERVER')
return server
} else if (local) {
console.log('[LocalStorage] Most recent user audiobook is from LOCAL')
return local
} else if (server) {
console.log('[LocalStorage] Most recent user audiobook is from SERVER')
return server
}
return null
}
async loadUserAudiobooks() {
try {
var val = (await Storage.get({ key: 'userAudiobooks' }) || {}).value || null
this.userAudiobooks = val ? JSON.parse(val) : {}
this.userAudiobooksLoaded = true
this.vuexStore.commit('user/setLocalUserAudiobooks', { ...this.userAudiobooks })
} catch (error) {
console.error('[LocalStorage] Failed to load user audiobooks', error)
}
}
async saveUserAudiobooks() {
try {
await Storage.set({ key: 'userAudiobooks', value: JSON.stringify(this.userAudiobooks) })
} catch (error) {
console.error('[LocalStorage] Failed to set user audiobooks', error)
}
}
async setAllAudiobookProgress(progresses) {
this.userAudiobooks = progresses
await this.saveUserAudiobooks()
this.vuexStore.commit('user/setLocalUserAudiobooks', { ...this.userAudiobooks })
}
async updateUserAudiobookData(progressPayload) {
this.userAudiobooks[progressPayload.audiobookId] = {
...progressPayload
}
await this.saveUserAudiobooks()
this.vuexStore.commit('user/setUserAudiobooks', { ...this.userAudiobooks })
this.vuexStore.commit('user/setLocalUserAudiobooks', { ...this.userAudiobooks })
}
async removeAudiobookProgress(audiobookId) {
if (!this.userAudiobooks[audiobookId]) return
delete this.userAudiobooks[audiobookId]
await this.saveUserAudiobooks()
this.vuexStore.commit('user/setUserAudiobooks', { ...this.userAudiobooks })
this.vuexStore.commit('user/setLocalUserAudiobooks', { ...this.userAudiobooks })
}
getUserAudiobook(audiobookId) {
return this.userAudiobooks[audiobookId] || null
}
async setToken(token) {

View file

@ -1,4 +1,3 @@
import MyNativeAudio from '@/plugins/my-native-audio'
import { sort } from '@/assets/fastSort'
import { decode } from '@/plugins/init.client'
@ -38,7 +37,7 @@ export const getters = {
else if (group === 'authors') filtered = filtered.filter(ab => ab.book && ab.book.author === filter)
else if (group === 'progress') {
filtered = filtered.filter(ab => {
var userAudiobook = rootGetters['user/getUserAudiobook'](ab.id)
var userAudiobook = rootGetters['user/getUserAudiobookData'](ab.id)
var isRead = userAudiobook && userAudiobook.isRead
if (filter === 'Read' && isRead) return true
if (filter === 'Unread' && !isRead) return true
@ -57,7 +56,7 @@ export const getters = {
if (settings.mobileOrderBy === 'recent') {
return sort(filtered)[direction]((ab) => {
var abprogress = rootGetters['user/getMostRecentUserAudiobookData'](ab.id)
var abprogress = rootGetters['user/getUserAudiobookData'](ab.id)
if (!abprogress) return 0
return abprogress.lastUpdate
})
@ -142,7 +141,7 @@ export const getters = {
}
export const actions = {
load({ state, commit, rootState }) {
load({ state, commit, dispatch, rootState }) {
if (!rootState.user || !rootState.user.user) {
console.error('audiobooks/load - User not set')
return false
@ -169,6 +168,8 @@ export const actions = {
commit('set', data)
commit('setLastLoad')
commit('setLoading', false)
dispatch('downloads/linkOrphanDownloads', data, { root: true })
})
.catch((error) => {
console.error('Failed', error)

View file

@ -1,6 +1,9 @@
import { Capacitor } from '@capacitor/core'
export const state = () => ({
downloads: [],
showModal: false
showModal: false,
mediaScanResults: {},
})
export const getters = {
@ -28,6 +31,48 @@ export const actions = {
ab.isPreparing = false
commit('setDownload', ab)
})
},
linkOrphanDownloads({ state, commit }, audiobooks) {
if (!state.mediaScanResults || !state.mediaScanResults.folders) {
return
}
console.log('Link orphan downloads', JSON.stringify(state.mediaScanResults.folders))
state.mediaScanResults.folders.forEach((folder) => {
if (!folder.files || !folder.files.length) return
console.log('Link orphan downloads check folder', folder.name)
var download = state.downloads.find(dl => dl.folderName === folder.name)
if (!download) {
var matchingAb = audiobooks.find(ab => ab.book.title === folder.name)
if (matchingAb) {
// Found matching download for ab
var audioFile = folder.files.find(f => f.isAudio)
if (!audioFile) {
return
}
var coverImg = folder.files.find(f => !f.isAudio)
const downloadObj = {
id: matchingAb.id,
audiobook: { ...matchingAb },
contentUrl: audioFile.uri,
simplePath: audioFile.simplePath,
folderUrl: folder.uri,
folderName: folder.name,
storageType: '',
storageId: '',
basePath: '',
size: audioFile.size,
coverUrl: coverImg ? coverImg.uri : null,
cover: coverImg ? Capacitor.convertFileSrc(coverImg.uri) : null,
coverSize: coverImg ? coverImg.size : 0,
coverBasePath: ''
}
console.log('Linking orphan download: ' + JSON.stringify(downloadObj))
commit('addUpdateDownload', downloadObj)
}
}
})
}
}
@ -61,5 +106,8 @@ export const mutations = {
removeDownload(state, download) {
state.downloads = state.downloads.filter(d => d.id !== download.id)
this.$sqlStore.removeDownload(download.id)
},
setMediaScanResults(state, val) {
state.mediaScanResults = val
}
}

View file

@ -15,7 +15,7 @@ export const state = () => ({
selectedBook: null,
showReader: false,
downloadFolder: null,
mediaScanResults: {},
showSideDrawer: false,
bookshelfView: 'grid'
})
@ -100,9 +100,6 @@ export const mutations = {
setDownloadFolder(state, val) {
state.downloadFolder = val
},
setMediaScanResults(state, val) {
state.mediaScanResults = val
},
setShowSideDrawer(state, val) {
state.showSideDrawer = val
},

View file

@ -1,7 +1,6 @@
export const state = () => ({
user: null,
userAudiobookData: [],
localUserAudiobooks: {},
settings: {
mobileOrderBy: 'recent',
mobileOrderDesc: true,
@ -23,19 +22,8 @@ export const getters = {
getToken: (state) => {
return state.user ? state.user.token : null
},
getUserAudiobook: (state) => (audiobookId) => {
return state.user && state.user.audiobooks ? state.user.audiobooks[audiobookId] || null : null
},
getLocalUserAudiobook: (state) => (audiobookId) => {
return state.localUserAudiobooks ? state.localUserAudiobooks[audiobookId] || null : null
},
getMostRecentUserAudiobookData: (state, getters) => (audiobookId) => {
getUserAudiobookData: (state, getters) => (audiobookId) => {
return state.userAudiobookData.find(uabd => uabd.audiobookId === audiobookId)
// var userAb = getters.getUserAudiobook(audiobookId)
// var localUserAb = getters.getLocalUserAudiobook(audiobookId)
// if (!localUserAb) return userAb
// if (!userAb) return localUserAb
// return localUserAb.lastUpdate > userAb.lastUpdate ? localUserAb : userAb
},
getUserSetting: (state) => (key) => {
return state.settings ? state.settings[key] || null : null
@ -131,18 +119,6 @@ export const mutations = {
setAllUserAudiobookData(state, allAbData) {
state.userAudiobookData = allAbData
},
setLocalUserAudiobooks(state, userAudiobooks) {
// state.localUserAudiobooks = userAudiobooks
// state.userAudiobooksListeners.forEach((listener) => {
// listener.meth()
// })
},
setUserAudiobooks(state, userAudiobooks) {
if (!state.user) return
state.user.audiobooks = {
...userAudiobooks
}
},
setUser(state, user) {
state.user = user
if (user) {