Fix:Clean user data on server start removing invalid media progress items

This commit is contained in:
advplyr 2022-09-28 17:12:27 -05:00
parent 9ee6eaade9
commit ac30a971c5
9 changed files with 107 additions and 70 deletions

View file

@ -145,7 +145,7 @@ class Server {
await this.auth.initTokenSecret()
}
await this.checkUserMediaProgress() // Remove invalid user item progress
await this.cleanUserData() // Remove invalid user item progress
await this.purgeMetadata() // Remove metadata folders without library item
await this.playbackSessionManager.removeInvalidSessions()
await this.cacheManager.ensureCachePaths()
@ -368,21 +368,37 @@ class Server {
return purged
}
// Remove user media progress entries that dont have a library item
// TODO: Check podcast episode exists still
async checkUserMediaProgress() {
// Remove user media progress with items that no longer exist & remove seriesHideFrom that no longer exist
async cleanUserData() {
for (let i = 0; i < this.db.users.length; i++) {
var _user = this.db.users[i]
if (_user.mediaProgress) {
var itemProgressIdsToRemove = _user.mediaProgress.map(lip => lip.id).filter(lipId => !this.db.libraryItems.find(_li => _li.id == lipId))
if (itemProgressIdsToRemove.length) {
Logger.debug(`[Server] Found ${itemProgressIdsToRemove.length} media progress data to remove from user ${_user.username}`)
for (const lipId of itemProgressIdsToRemove) {
_user.removeMediaProgress(lipId)
}
await this.db.updateEntity('user', _user)
var hasUpdated = false
if (_user.mediaProgress.length) {
const lengthBefore = _user.mediaProgress.length
_user.mediaProgress = _user.mediaProgress.filter(mp => {
const libraryItem = this.db.libraryItems.find(li => li.id === mp.libraryItemId)
if (!libraryItem) return false
if (mp.episodeId && (libraryItem.mediaType !== 'podcast' || !libraryItem.media.checkHasEpisode(mp.episodeId))) return false // Episode not found
return true
})
if (lengthBefore > _user.mediaProgress.length) {
Logger.debug(`[Server] Removing ${_user.mediaProgress.length - lengthBefore} media progress data from user ${_user.username}`)
hasUpdated = true
}
}
if (_user.seriesHideFromContinueListening.length) {
_user.seriesHideFromContinueListening = _user.seriesHideFromContinueListening.filter(seriesId => {
if (!this.db.series.some(se => se.id === seriesId)) { // Series removed
hasUpdated = true
return false
}
return true
})
}
if (hasUpdated) {
await this.db.updateEntity('user', _user)
}
}
}

View file

@ -276,5 +276,21 @@ class MeController {
libraryItems: itemsInProgress
})
}
// GET: api/me/series/:id/hide
async hideSeriesFromContinueListening(req, res) {
const series = this.db.series.find(se => se.id === req.params.id)
if (!series) {
Logger.error(`[MeController] hideSeriesFromContinueListening: Series ${req.params.id} not found`)
return res.sendStatus(404)
}
const hasUpdated = req.user.addSeriesToHideFromContinueListening(req.params.id)
if (hasUpdated) {
await this.db.updateEntity('user', req.user)
this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
}
res.json(req.user.toJSONForBrowser())
}
}
module.exports = new MeController()

View file

@ -5,7 +5,6 @@ class Series {
this.id = null
this.name = null
this.description = null
this.hideFromHome = false
this.addedAt = null
this.updatedAt = null
@ -18,7 +17,6 @@ class Series {
this.id = series.id
this.name = series.name
this.description = series.description || null
this.hideFromHome = !!series.hideFromHome
this.addedAt = series.addedAt
this.updatedAt = series.updatedAt
}
@ -28,7 +26,6 @@ class Series {
id: this.id,
name: this.name,
description: this.description,
hideFromHome: this.hideFromHome,
addedAt: this.addedAt,
updatedAt: this.updatedAt
}
@ -46,14 +43,13 @@ class Series {
this.id = getId('ser')
this.name = data.name
this.description = data.description || null
this.hideFromHome = !!data.hideFromHome
this.addedAt = Date.now()
this.updatedAt = Date.now()
}
update(series) {
if (!series) return false
const keysToUpdate = ['name', 'description', 'hideFromHome']
const keysToUpdate = ['name', 'description']
var hasUpdated = false
for (const key of keysToUpdate) {
if (series[key] !== undefined && series[key] !== this[key]) {

View file

@ -15,6 +15,7 @@ class User {
this.createdAt = null
this.mediaProgress = []
this.seriesHideFromContinueListening = [] // Series IDs that should not show on home page continue listening
this.bookmarks = []
this.settings = {}
@ -92,6 +93,7 @@ class User {
type: this.type,
token: this.token,
mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
seriesHideFromContinueListening: [...this.seriesHideFromContinueListening],
bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
isActive: this.isActive,
isLocked: this.isLocked,
@ -111,6 +113,7 @@ class User {
type: this.type,
token: this.token,
mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
seriesHideFromContinueListening: [...this.seriesHideFromContinueListening],
bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
isActive: this.isActive,
isLocked: this.isLocked,
@ -161,6 +164,9 @@ class User {
this.bookmarks = user.bookmarks.filter(bm => typeof bm.libraryItemId == 'string').map(bm => new AudioBookmark(bm))
}
this.seriesHideFromContinueListening = []
if (user.seriesHideFromContinueListening) this.seriesHideFromContinueListening = [...user.seriesHideFromContinueListening]
this.isActive = (user.isActive === undefined || user.type === 'root') ? true : !!user.isActive
this.isLocked = user.type === 'root' ? false : !!user.isLocked
this.lastSeen = user.lastSeen || null
@ -196,6 +202,13 @@ class User {
}
})
if (payload.seriesHideFromContinueListening && Array.isArray(payload.seriesHideFromContinueListening)) {
if (this.seriesHideFromContinueListening.join(',') !== payload.seriesHideFromContinueListening.join(',')) {
hasUpdates = true
this.seriesHideFromContinueListening = [...payload.seriesHideFromContinueListening]
}
}
// And update permissions
if (payload.permissions) {
for (const key in payload.permissions) {
@ -297,7 +310,13 @@ class User {
return wasUpdated
}
removeMediaProgress(libraryItemId) {
removeMediaProgress(id) {
if (!this.mediaProgress.some(mp => mp.id === id)) return false
this.mediaProgress = this.mediaProgress.filter(mp => mp.id !== id)
return true
}
removeMediaProgressForLibraryItem(libraryItemId) {
if (!this.mediaProgress.some(lip => lip.libraryItemId == libraryItemId)) return false
this.mediaProgress = this.mediaProgress.filter(lip => lip.libraryItemId != libraryItemId)
return true
@ -378,5 +397,15 @@ class User {
removeBookmark(libraryItemId, time) {
this.bookmarks = this.bookmarks.filter(bm => (bm.libraryItemId !== libraryItemId || bm.time !== time))
}
checkShouldHideSeriesFromContinueListening(seriesId) {
return this.seriesHideFromContinueListening.includes(seriesId)
}
addSeriesToHideFromContinueListening(seriesId) {
if (this.seriesHideFromContinueListening.includes(seriesId)) return false
this.seriesHideFromContinueListening.push(seriesId)
return true
}
}
module.exports = User

View file

@ -150,6 +150,7 @@ class ApiRouter {
this.router.patch('/me/settings', MeController.updateSettings.bind(this))
this.router.post('/me/sync-local-progress', MeController.syncLocalMediaProgress.bind(this))
this.router.get('/me/items-in-progress', MeController.getAllLibraryItemsInProgress.bind(this))
this.router.post('/me/series/:id/hide', MeController.hideSeriesFromContinueListening.bind(this))
//
// Backup Routes
@ -304,7 +305,7 @@ class ApiRouter {
// Remove libraryItem from users
for (let i = 0; i < this.db.users.length; i++) {
var user = this.db.users[i]
var madeUpdates = user.removeMediaProgress(libraryItem.id)
var madeUpdates = user.removeMediaProgressForLibraryItem(libraryItem.id)
if (madeUpdates) {
await this.db.updateEntity('user', user)
}

View file

@ -415,13 +415,16 @@ module.exports = {
const libraryItemJson = libraryItem.toJSONMinified()
libraryItemJson.seriesSequence = librarySeries.sequence
const hideFromContinueListening = user.checkShouldHideSeriesFromContinueListening(librarySeries.id)
if (!seriesMap[librarySeries.id]) {
const seriesObj = allSeries.find(se => se.id === librarySeries.id)
if (seriesObj && !seriesObj.hideFromHome) {
if (seriesObj) {
var series = {
...seriesObj.toJSON(),
books: [libraryItemJson],
inProgress: bookInProgress,
hideFromContinueListening,
bookInProgressLastUpdate: bookInProgress ? mediaProgress.lastUpdate : null,
firstBookUnread: bookInProgress ? null : libraryItemJson
}
@ -555,7 +558,7 @@ module.exports = {
// For Continue Series - Find next book in series for series that are in progress
for (const seriesId in seriesMap) {
if (seriesMap[seriesId].inProgress) {
if (seriesMap[seriesId].inProgress && !seriesMap[seriesId].hideFromContinueListening) {
seriesMap[seriesId].books = naturalSort(seriesMap[seriesId].books).asc(li => li.seriesSequence)
// NEW implementation takes the first book unread with the smallest series sequence