Improve performance of the watch history handling (#4017)

* Improve performance of the watch history handling

* Remove duplicate checks for whether history entries exist

Co-authored-by: PikachuEXE <pikachuexe@gmail.com>

* Remove leftover `typeof`

---------

Co-authored-by: PikachuEXE <pikachuexe@gmail.com>
This commit is contained in:
absidue 2023-09-14 03:31:07 +02:00 committed by GitHub
parent 3db6f437c9
commit d333990fbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 87 additions and 72 deletions

View File

@ -54,8 +54,8 @@ export default defineComponent({
allPlaylists: function () {
return this.$store.getters.getAllPlaylists
},
historyCache: function () {
return this.$store.getters.getHistoryCache
historyCacheSorted: function () {
return this.$store.getters.getHistoryCacheSorted
},
exportSubscriptionsPromptNames: function () {
const exportFreeTube = this.$t('Settings.Data Settings.Export FreeTube')
@ -825,7 +825,7 @@ export default defineComponent({
},
exportHistory: async function () {
const historyDb = this.historyCache.map((historyEntry) => {
const historyDb = this.historyCacheSorted.map((historyEntry) => {
return JSON.stringify(historyEntry)
}).join('\n') + '\n'
const dateStr = getTodayDateStrLocalTimezone()

View File

@ -76,8 +76,12 @@ export default defineComponent({
}
},
computed: {
historyCache: function () {
return this.$store.getters.getHistoryCache
historyEntry: function () {
return this.$store.getters.getHistoryCacheById[this.id]
},
historyEntryExists: function () {
return typeof this.historyEntry !== 'undefined'
},
listType: function () {
@ -313,12 +317,6 @@ export default defineComponent({
}
},
historyIndex: function() {
return this.historyCache.findIndex((video) => {
return video.videoId === this.id
})
},
playlistIdFinal: function () {
if (this.playlistId) {
return this.playlistId
@ -327,12 +325,8 @@ export default defineComponent({
// Get playlist ID from history ONLY if option enabled
if (!this.showVideoWithLastViewedPlaylist) { return }
if (!this.saveVideoHistoryWithLastViewedPlaylist) { return }
const historyIndex = this.historyIndex
if (historyIndex === -1) {
return undefined
}
return this.historyCache[historyIndex].lastViewedPlaylistId
return this.historyEntry?.lastViewedPlaylistId
},
currentLocale: function () {
@ -348,7 +342,7 @@ export default defineComponent({
}
},
watch: {
historyIndex() {
historyEntry() {
this.checkIfWatched()
},
},
@ -451,8 +445,8 @@ export default defineComponent({
this.channelName = this.data.author ?? null
this.channelId = this.data.authorId ?? null
if (this.data.isRSS && this.historyIndex !== -1) {
this.duration = formatDurationAsTimestamp(this.historyCache[this.historyIndex].lengthSeconds)
if (this.data.isRSS && this.historyEntryExists) {
this.duration = formatDurationAsTimestamp(this.historyEntry.lengthSeconds)
} else {
this.duration = formatDurationAsTimestamp(this.data.lengthSeconds)
}
@ -538,22 +532,26 @@ export default defineComponent({
},
checkIfWatched: function () {
const historyIndex = this.historyIndex
if (historyIndex !== -1) {
if (this.historyEntryExists) {
this.watched = true
const historyEntry = this.historyEntry
if (this.saveWatchedProgress) {
// For UX consistency, no progress reading if writing disabled
this.watchProgress = this.historyCache[historyIndex].watchProgress
this.watchProgress = historyEntry.watchProgress
}
if (this.historyCache[historyIndex].published !== '') {
const videoPublished = this.historyCache[historyIndex].published
if (historyEntry.published !== '') {
const videoPublished = historyEntry.published
const videoPublishedDate = new Date(videoPublished)
this.publishedText = videoPublishedDate.toLocaleDateString()
} else {
this.publishedText = ''
}
} else {
this.watched = false
this.watchProgress = 0
}
},

View File

@ -38,14 +38,10 @@ export function updateVideoListAfterProcessing(videos) {
}
if (store.getters.getHideWatchedSubs) {
const historyCache = store.getters.getHistoryCache
const historyCacheById = store.getters.getHistoryCacheById
videoList = videoList.filter((video) => {
const historyIndex = historyCache.findIndex((x) => {
return x.videoId === video.videoId
})
return historyIndex === -1
return !Object.hasOwn(historyCacheById, video.videoId)
})
}

View File

@ -1,12 +1,21 @@
import { set as vueSet, del as vueDel } from 'vue'
import { DBHistoryHandlers } from '../../../datastores/handlers/index'
const state = {
historyCache: []
historyCacheSorted: [],
// Vuex doesn't support Maps, so we have to use an object here instead
// TODO: switch to a Map during the Pinia migration
historyCacheById: {}
}
const getters = {
getHistoryCache: () => {
return state.historyCache
getHistoryCacheSorted: () => {
return state.historyCacheSorted
},
getHistoryCacheById: () => {
return state.historyCacheById
}
}
@ -14,7 +23,14 @@ const actions = {
async grabHistory({ commit }) {
try {
const results = await DBHistoryHandlers.find()
commit('setHistoryCache', results)
const resultsById = {}
results.forEach(video => {
resultsById[video.videoId] = video
})
commit('setHistoryCacheSorted', results)
commit('setHistoryCacheById', resultsById)
} catch (errMessage) {
console.error(errMessage)
}
@ -41,7 +57,8 @@ const actions = {
async removeAllHistory({ commit }) {
try {
await DBHistoryHandlers.deleteAll()
commit('setHistoryCache', [])
commit('setHistoryCacheSorted', [])
commit('setHistoryCacheById', {})
} catch (errMessage) {
console.error(errMessage)
}
@ -67,56 +84,60 @@ const actions = {
}
const mutations = {
setHistoryCache(state, historyCache) {
state.historyCache = historyCache
setHistoryCacheSorted(state, historyCacheSorted) {
state.historyCacheSorted = historyCacheSorted
},
hoistEntryToTopOfHistoryCache(state, { currentIndex, updatedEntry }) {
state.historyCache.splice(currentIndex, 1)
state.historyCache.unshift(updatedEntry)
setHistoryCacheById(state, historyCacheById) {
state.historyCacheById = historyCacheById
},
upsertToHistoryCache(state, record) {
const i = state.historyCache.findIndex((currentRecord) => {
const i = state.historyCacheSorted.findIndex((currentRecord) => {
return record.videoId === currentRecord.videoId
})
if (i !== -1) {
// Already in cache
// Must be hoisted to top, remove it and then unshift it
state.historyCache.splice(i, 1)
state.historyCacheSorted.splice(i, 1)
}
state.historyCache.unshift(record)
state.historyCacheSorted.unshift(record)
vueSet(state.historyCacheById, record.videoId, record)
},
updateRecordWatchProgressInHistoryCache(state, { videoId, watchProgress }) {
const i = state.historyCache.findIndex((currentRecord) => {
const i = state.historyCacheSorted.findIndex((currentRecord) => {
return currentRecord.videoId === videoId
})
const targetRecord = Object.assign({}, state.historyCache[i])
const targetRecord = Object.assign({}, state.historyCacheSorted[i])
targetRecord.watchProgress = watchProgress
state.historyCache.splice(i, 1, targetRecord)
state.historyCacheSorted.splice(i, 1, targetRecord)
vueSet(state.historyCacheById, videoId, targetRecord)
},
updateRecordLastViewedPlaylistIdInHistoryCache(state, { videoId, lastViewedPlaylistId }) {
const i = state.historyCache.findIndex((currentRecord) => {
const i = state.historyCacheSorted.findIndex((currentRecord) => {
return currentRecord.videoId === videoId
})
const targetRecord = Object.assign({}, state.historyCache[i])
const targetRecord = Object.assign({}, state.historyCacheSorted[i])
targetRecord.lastViewedPlaylistId = lastViewedPlaylistId
state.historyCache.splice(i, 1, targetRecord)
state.historyCacheSorted.splice(i, 1, targetRecord)
vueSet(state.historyCacheById, videoId, targetRecord)
},
removeFromHistoryCacheById(state, videoId) {
for (let i = 0; i < state.historyCache.length; i++) {
if (state.historyCache[i].videoId === videoId) {
state.historyCache.splice(i, 1)
for (let i = 0; i < state.historyCacheSorted.length; i++) {
if (state.historyCacheSorted[i].videoId === videoId) {
state.historyCacheSorted.splice(i, 1)
break
}
}
vueDel(state.historyCacheById, videoId)
}
}

View File

@ -469,7 +469,8 @@ const customActions = {
break
case SyncEvents.GENERAL.DELETE_ALL:
commit('setHistoryCache', [])
commit('setHistoryCacheSorted', [])
commit('setHistoryCacheById', {})
break
default:

View File

@ -28,15 +28,15 @@ export default defineComponent({
}
},
computed: {
historyCache: function () {
return this.$store.getters.getHistoryCache
historyCacheSorted: function () {
return this.$store.getters.getHistoryCacheSorted
},
fullData: function () {
if (this.historyCache.length < this.dataLimit) {
return this.historyCache
if (this.historyCacheSorted.length < this.dataLimit) {
return this.historyCacheSorted
} else {
return this.historyCache.slice(0, this.dataLimit)
return this.historyCacheSorted.slice(0, this.dataLimit)
}
}
},
@ -59,7 +59,7 @@ export default defineComponent({
this.activeData = this.fullData
if (this.activeData.length < this.historyCache.length) {
if (this.activeData.length < this.historyCacheSorted.length) {
this.showLoadMoreButton = true
} else {
this.showLoadMoreButton = false
@ -85,14 +85,14 @@ export default defineComponent({
filterHistory: function() {
if (this.query === '') {
this.activeData = this.fullData
if (this.activeData.length < this.historyCache.length) {
if (this.activeData.length < this.historyCacheSorted.length) {
this.showLoadMoreButton = true
} else {
this.showLoadMoreButton = false
}
} else {
const lowerCaseQuery = this.query.toLowerCase()
const filteredQuery = this.historyCache.filter((video) => {
const filteredQuery = this.historyCacheSorted.filter((video) => {
if (typeof (video.title) !== 'string' || typeof (video.author) !== 'string') {
return false
} else {

View File

@ -123,8 +123,11 @@ export default defineComponent({
}
},
computed: {
historyCache: function () {
return this.$store.getters.getHistoryCache
historyEntry: function () {
return this.$store.getters.getHistoryCacheById[this.videoId]
},
historyEntryExists: function () {
return typeof this.historyEntry !== 'undefined'
},
rememberHistory: function () {
return this.$store.getters.getRememberHistory
@ -1040,10 +1043,6 @@ export default defineComponent({
},
checkIfWatched: function () {
const historyIndex = this.historyCache.findIndex((video) => {
return video.videoId === this.videoId
})
if (!this.isLive) {
if (this.timestamp) {
if (this.timestamp < 0) {
@ -1053,9 +1052,9 @@ export default defineComponent({
} else {
this.$refs.videoPlayer.player.currentTime(this.timestamp)
}
} else if (this.saveWatchedProgress && historyIndex !== -1) {
} else if (this.saveWatchedProgress && this.historyEntryExists) {
// For UX consistency, no progress reading if writing disabled
const watchProgress = this.historyCache[historyIndex].watchProgress
const watchProgress = this.historyEntry.watchProgress
if (watchProgress < (this.videoLengthSeconds - 10)) {
this.$refs.videoPlayer.player.currentTime(watchProgress)
@ -1066,8 +1065,8 @@ export default defineComponent({
if (this.rememberHistory) {
if (this.timestamp) {
this.addToHistory(this.timestamp)
} else if (historyIndex !== -1) {
this.addToHistory(this.historyCache[historyIndex].watchProgress)
} else if (this.historyEntryExists) {
this.addToHistory(this.historyEntry.watchProgress)
} else {
this.addToHistory(0)
}