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

View File

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

View File

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

View File

@ -1,12 +1,21 @@
import { set as vueSet, del as vueDel } from 'vue'
import { DBHistoryHandlers } from '../../../datastores/handlers/index' import { DBHistoryHandlers } from '../../../datastores/handlers/index'
const state = { 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 = { const getters = {
getHistoryCache: () => { getHistoryCacheSorted: () => {
return state.historyCache return state.historyCacheSorted
},
getHistoryCacheById: () => {
return state.historyCacheById
} }
} }
@ -14,7 +23,14 @@ const actions = {
async grabHistory({ commit }) { async grabHistory({ commit }) {
try { try {
const results = await DBHistoryHandlers.find() 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) { } catch (errMessage) {
console.error(errMessage) console.error(errMessage)
} }
@ -41,7 +57,8 @@ const actions = {
async removeAllHistory({ commit }) { async removeAllHistory({ commit }) {
try { try {
await DBHistoryHandlers.deleteAll() await DBHistoryHandlers.deleteAll()
commit('setHistoryCache', []) commit('setHistoryCacheSorted', [])
commit('setHistoryCacheById', {})
} catch (errMessage) { } catch (errMessage) {
console.error(errMessage) console.error(errMessage)
} }
@ -67,56 +84,60 @@ const actions = {
} }
const mutations = { const mutations = {
setHistoryCache(state, historyCache) { setHistoryCacheSorted(state, historyCacheSorted) {
state.historyCache = historyCache state.historyCacheSorted = historyCacheSorted
}, },
hoistEntryToTopOfHistoryCache(state, { currentIndex, updatedEntry }) { setHistoryCacheById(state, historyCacheById) {
state.historyCache.splice(currentIndex, 1) state.historyCacheById = historyCacheById
state.historyCache.unshift(updatedEntry)
}, },
upsertToHistoryCache(state, record) { upsertToHistoryCache(state, record) {
const i = state.historyCache.findIndex((currentRecord) => { const i = state.historyCacheSorted.findIndex((currentRecord) => {
return record.videoId === currentRecord.videoId return record.videoId === currentRecord.videoId
}) })
if (i !== -1) { if (i !== -1) {
// Already in cache // Already in cache
// Must be hoisted to top, remove it and then unshift it // 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 }) { updateRecordWatchProgressInHistoryCache(state, { videoId, watchProgress }) {
const i = state.historyCache.findIndex((currentRecord) => { const i = state.historyCacheSorted.findIndex((currentRecord) => {
return currentRecord.videoId === videoId return currentRecord.videoId === videoId
}) })
const targetRecord = Object.assign({}, state.historyCache[i]) const targetRecord = Object.assign({}, state.historyCacheSorted[i])
targetRecord.watchProgress = watchProgress targetRecord.watchProgress = watchProgress
state.historyCache.splice(i, 1, targetRecord) state.historyCacheSorted.splice(i, 1, targetRecord)
vueSet(state.historyCacheById, videoId, targetRecord)
}, },
updateRecordLastViewedPlaylistIdInHistoryCache(state, { videoId, lastViewedPlaylistId }) { updateRecordLastViewedPlaylistIdInHistoryCache(state, { videoId, lastViewedPlaylistId }) {
const i = state.historyCache.findIndex((currentRecord) => { const i = state.historyCacheSorted.findIndex((currentRecord) => {
return currentRecord.videoId === videoId return currentRecord.videoId === videoId
}) })
const targetRecord = Object.assign({}, state.historyCache[i]) const targetRecord = Object.assign({}, state.historyCacheSorted[i])
targetRecord.lastViewedPlaylistId = lastViewedPlaylistId targetRecord.lastViewedPlaylistId = lastViewedPlaylistId
state.historyCache.splice(i, 1, targetRecord) state.historyCacheSorted.splice(i, 1, targetRecord)
vueSet(state.historyCacheById, videoId, targetRecord)
}, },
removeFromHistoryCacheById(state, videoId) { removeFromHistoryCacheById(state, videoId) {
for (let i = 0; i < state.historyCache.length; i++) { for (let i = 0; i < state.historyCacheSorted.length; i++) {
if (state.historyCache[i].videoId === videoId) { if (state.historyCacheSorted[i].videoId === videoId) {
state.historyCache.splice(i, 1) state.historyCacheSorted.splice(i, 1)
break break
} }
} }
vueDel(state.historyCacheById, videoId)
} }
} }

View File

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

View File

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

View File

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