diff --git a/server/Db.js b/server/Db.js index dc5b3214..fed3981a 100644 --- a/server/Db.js +++ b/server/Db.js @@ -13,6 +13,7 @@ const Library = require('./objects/Library') const Author = require('./objects/entities/Author') const Series = require('./objects/entities/Series') const ServerSettings = require('./objects/ServerSettings') +const PlaybackSession = require('./objects/PlaybackSession') class Db { constructor() { @@ -188,6 +189,17 @@ class Db { getLibraryItem(id) { return this.libraryItems.find(li => li.id === id) } + getPlaybackSession(id) { + return this.sessionsDb.select((pb) => pb.id == id).then((results) => { + if (results.data.length) { + return new PlaybackSession(results.data[0]) + } + return null + }).catch((error) => { + Logger.error('Failed to get session', error) + return null + }) + } async updateLibraryItem(libraryItem) { return this.updateLibraryItems([libraryItem]) diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index 13c10f24..dd91e59a 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -151,5 +151,63 @@ class MeController { settings: req.user.settings }) } + + // POST: api/me/sync-local-progress + async syncLocalMediaProgress(req, res) { + if (!req.body.localMediaProgress) { + Logger.error(`[MeController] syncLocalMediaProgress invalid post body`) + return res.sendStatus(500) + } + const updatedLocalMediaProgress = [] + var numServerProgressUpdates = 0 + var localMediaProgress = req.body.localMediaProgress || [] + localMediaProgress.forEach(localProgress => { + if (!localProgress.libraryItemId) { + Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object`, localProgress) + return + } + var libraryItem = this.db.getLibraryItem(localProgress.libraryItemId) + if (!libraryItem) { + Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object no library item`, localProgress) + return + } + + var mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId) + if (!mediaProgress) { + // New media progress from mobile + Logger.debug(`[MeController] syncLocalMediaProgress local progress is new - creating ${localProgress.id}`) + req.user.createUpdateMediaProgress(libraryItem, localProgress, localProgress.episodeId) + numServerProgressUpdates++ + } else if (mediaProgress.lastUpdate < localProgress.lastUpdate) { + Logger.debug(`[MeController] syncLocalMediaProgress local progress is more recent - updating ${mediaProgress.id}`) + req.user.createUpdateMediaProgress(libraryItem, localProgress, localProgress.episodeId) + numServerProgressUpdates++ + } else if (mediaProgress.lastUpdate > localProgress.lastUpdate) { + var updateTimeDifference = mediaProgress.lastUpdate - localProgress.lastUpdate + Logger.debug(`[MeController] syncLocalMediaProgress server progress is more recent by ${updateTimeDifference}ms - ${mediaProgress.id}`) + + for (const key in localProgress) { + if (mediaProgress[key] != undefined && localProgress[key] !== mediaProgress[key]) { + // Logger.debug(`[MeController] syncLocalMediaProgress key ${key} changed from ${localProgress[key]} to ${mediaProgress[key]} - ${mediaProgress.id}`) + localProgress[key] = mediaProgress[key] + } + } + updatedLocalMediaProgress.push(localProgress) + } else { + Logger.debug(`[MeController] syncLocalMediaProgress server and local are in sync - ${mediaProgress.id}`) + } + }) + + Logger.debug(`[MeController] syncLocalMediaProgress server updates = ${numServerProgressUpdates}, local updates = ${updatedLocalMediaProgress.length}`) + if (numServerProgressUpdates > 0) { + await this.db.updateEntity('user', req.user) + this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + } + + res.json({ + numServerProgressUpdates, + localProgressUpdates: updatedLocalMediaProgress + }) + } } module.exports = new MeController() \ No newline at end of file diff --git a/server/controllers/SessionController.js b/server/controllers/SessionController.js index 544bf3dc..b54c475e 100644 --- a/server/controllers/SessionController.js +++ b/server/controllers/SessionController.js @@ -17,6 +17,11 @@ class SessionController { this.playbackSessionManager.closeSessionRequest(req.user, req.session, req.body, res) } + // POST: api/session/local + syncLocal(req, res) { + this.playbackSessionManager.syncLocalSessionRequest(req.user, req.body, res) + } + middleware(req, res, next) { var playbackSession = this.playbackSessionManager.getSession(req.params.id) if (!playbackSession) return res.sendStatus(404) diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index 409e98c8..2dc81c41 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -37,6 +37,40 @@ class PlaybackSessionManager { } } + async syncLocalSessionRequest(user, sessionJson, res) { + var libraryItem = this.db.getLibraryItem(sessionJson.libraryItemId) + + var session = await this.db.getPlaybackSession(sessionJson.id) + if (!session) { + // New session from local + session = new PlaybackSession(sessionJson) + await this.db.insertEntity('session', session) + } else { + session.timeListening = sessionJson.timeListening + session.updatedAt = sessionJson.updatedAt + await this.db.updateEntity('session', session) + } + + session.currentTime = sessionJson.currentTime + + const itemProgressUpdate = { + duration: session.duration, + currentTime: session.currentTime, + progress: session.progress, + lastUpdate: session.updatedAt // Keep media progress update times the same as local + } + var wasUpdated = user.createUpdateMediaProgress(libraryItem, itemProgressUpdate, session.episodeId) + if (wasUpdated) { + await this.db.updateEntity('user', user) + var itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId) + this.clientEmitter(user.id, 'user_item_progress_updated', { + id: itemProgress.id, + data: itemProgress.toJSON() + }) + } + res.sendStatus(200) + } + async closeSessionRequest(user, session, syncData, res) { await this.closeSession(user, session, syncData) res.sendStatus(200) diff --git a/server/objects/user/User.js b/server/objects/user/User.js index 519f1162..39fde22c 100644 --- a/server/objects/user/User.js +++ b/server/objects/user/User.js @@ -264,6 +264,8 @@ class User { return true } var wasUpdated = itemProgress.update(updatePayload) + + if (updatePayload.lastUpdate) itemProgress.lastUpdate = updatePayload.lastUpdate // For local to keep update times in sync return wasUpdated } @@ -340,32 +342,5 @@ class User { removeBookmark(libraryItemId, time) { this.bookmarks = this.bookmarks.filter(bm => (bm.libraryItemId !== libraryItemId || bm.time !== time)) } - - // TODO: re-do mobile sync - syncLocalUserAudiobookData(localUserAudiobookData, audiobook) { - // if (!localUserAudiobookData || !localUserAudiobookData.audiobookId) { - // Logger.error(`[User] Invalid local user audiobook data`, localUserAudiobookData) - // return false - // } - // if (!this.audiobooks) this.audiobooks = {} - - // if (!this.audiobooks[localUserAudiobookData.audiobookId]) { - // this.audiobooks[localUserAudiobookData.audiobookId] = new UserAudiobookData(localUserAudiobookData) - // return true - // } - - // var userAbD = this.audiobooks[localUserAudiobookData.audiobookId] - // if (userAbD.lastUpdate >= localUserAudiobookData.lastUpdate) { - // // Server audiobook data is more recent - // return false - // } - - // // Local Data More recent - // var wasUpdated = this.audiobooks[localUserAudiobookData.audiobookId].update(localUserAudiobookData) - // if (wasUpdated) { - // Logger.debug(`[User] syncLocalUserAudiobookData local data was more recent for "${audiobook.title}"`) - // } - // return wasUpdated - } } module.exports = User \ No newline at end of file diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 20dc193e..dbf05c11 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -135,6 +135,7 @@ class ApiRouter { this.router.delete('/me/item/:id/bookmark/:time', MeController.removeBookmark.bind(this)) this.router.patch('/me/password', MeController.updatePassword.bind(this)) this.router.patch('/me/settings', MeController.updateSettings.bind(this)) + this.router.post('/me/sync-local-progress', MeController.syncLocalMediaProgress.bind(this)) // // Backup Routes @@ -169,6 +170,7 @@ class ApiRouter { // this.router.post('/session/:id/sync', SessionController.middleware.bind(this), SessionController.sync.bind(this)) this.router.post('/session/:id/close', SessionController.middleware.bind(this), SessionController.close.bind(this)) + this.router.post('/session/local', SessionController.syncLocal.bind(this)) // // Podcast Routes @@ -192,9 +194,6 @@ class ApiRouter { this.router.get('/search/podcast', MiscController.findPodcasts.bind(this)) this.router.get('/search/authors', MiscController.findAuthor.bind(this)) this.router.get('/tags', MiscController.getAllTags.bind(this)) - - // OLD - // this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this)) } async getDirectories(dir, relpath, excludedDirs, level = 0) { @@ -226,38 +225,6 @@ class ApiRouter { } } - async syncUserAudiobookData(req, res) { - // if (!req.body.data) { - // return res.status(403).send('Invalid local user audiobook data') - // } - - // var hasUpdates = false - - // // Local user audiobook data use the latest update - // req.body.data.forEach((uab) => { - // if (!uab || !uab.audiobookId) { - // Logger.error('[ApiController] Invalid user audiobook data', uab) - // return - // } - // var audiobook = this.db.audiobooks.find(ab => ab.id === uab.audiobookId) - // if (!audiobook) { - // Logger.info('[ApiController] syncUserAudiobookData local audiobook data audiobook no longer exists', uab.audiobookId) - // return - // } - // if (req.user.syncLocalUserAudiobookData(uab, audiobook)) { - // this.clientEmitter(req.user.id, 'current_user_audiobook_update', { id: uab.audiobookId, data: uab }) - // hasUpdates = true - // } - // }) - - // if (hasUpdates) { - // await this.db.updateEntity('user', req.user) - // } - - // var allUserAudiobookData = Object.values(req.user.audiobooksToJSON()) - // res.json(allUserAudiobookData) - } - // // Helper Methods // diff --git a/server/routers/StaticRouter.js b/server/routers/StaticRouter.js index 0b628831..b571869f 100644 --- a/server/routers/StaticRouter.js +++ b/server/routers/StaticRouter.js @@ -18,7 +18,6 @@ class StaticRouter { var remainingPath = req.params['0'] var fullPath = Path.join(item.path, remainingPath) - console.log('fullpath', fullPath) res.sendFile(fullPath) }) }