diff --git a/src/constants.js b/src/constants.js index a45a71598d52..f45e30fb8211 100644 --- a/src/constants.js +++ b/src/constants.js @@ -25,11 +25,13 @@ const IpcChannels = { DB_HISTORY: 'db-history', DB_PROFILES: 'db-profiles', DB_PLAYLISTS: 'db-playlists', + DB_SUBSCRIPTION_CACHE: 'db-subscription-cache', SYNC_SETTINGS: 'sync-settings', SYNC_HISTORY: 'sync-history', SYNC_PROFILES: 'sync-profiles', SYNC_PLAYLISTS: 'sync-playlists', + SYNC_SUBSCRIPTION_CACHE: 'sync-subscription-cache', GET_REPLACE_HTTP_CACHE: 'get-replace-http-cache', TOGGLE_REPLACE_HTTP_CACHE: 'toggle-replace-http-cache', @@ -67,7 +69,15 @@ const DBActions = { DELETE_VIDEO_ID: 'db-action-playlists-delete-video-by-playlist-name', DELETE_VIDEO_IDS: 'db-action-playlists-delete-video-ids', DELETE_ALL_VIDEOS: 'db-action-playlists-delete-all-videos', - } + }, + + SUBSCRIPTION_CACHE: { + UPDATE_VIDEOS_BY_CHANNEL: 'db-action-subscriptions-update-videos-by-channel', + UPDATE_LIVE_STREAMS_BY_CHANNEL: 'db-action-subscriptions-update-live-streams-by-channel', + UPDATE_SHORTS_BY_CHANNEL: 'db-action-subscriptions-update-shorts-by-channel', + UPDATE_SHORTS_WITH_CHANNEL_PAGE_SHORTS_BY_CHANNEL: 'db-action-subscriptions-update-shorts-with-channel-page-shorts-by-channel', + UPDATE_COMMUNITY_POSTS_BY_CHANNEL: 'db-action-subscriptions-update-community-posts-by-channel', + }, } const SyncEvents = { @@ -92,7 +102,15 @@ const SyncEvents = { PLAYLISTS: { UPSERT_VIDEO: 'sync-playlists-upsert-video', DELETE_VIDEO: 'sync-playlists-delete-video', - } + }, + + SUBSCRIPTION_CACHE: { + UPDATE_VIDEOS_BY_CHANNEL: 'sync-subscriptions-update-videos-by-channel', + UPDATE_LIVE_STREAMS_BY_CHANNEL: 'sync-subscriptions-update-live-streams-by-channel', + UPDATE_SHORTS_BY_CHANNEL: 'sync-subscriptions-update-shorts-by-channel', + UPDATE_SHORTS_WITH_CHANNEL_PAGE_SHORTS_BY_CHANNEL: 'sync-subscriptions-update-shorts-with-channel-page-shorts-by-channel', + UPDATE_COMMUNITY_POSTS_BY_CHANNEL: 'sync-subscriptions-update-community-posts-by-channel', + }, } // Utils diff --git a/src/datastores/handlers/base.js b/src/datastores/handlers/base.js index b3ec944b319e..32457d3c9dac 100644 --- a/src/datastores/handlers/base.js +++ b/src/datastores/handlers/base.js @@ -203,12 +203,92 @@ class Playlists { } } +class SubscriptionCache { + static find() { + return db.subscriptionCache.findAsync({}) + } + + static updateVideosByChannelId({ channelId, entries, timestamp }) { + return db.subscriptionCache.updateAsync( + { _id: channelId }, + { $set: { videos: entries, videosTimestamp: timestamp } }, + { upsert: true } + ) + } + + static updateLiveStreamsByChannelId({ channelId, entries, timestamp }) { + return db.subscriptionCache.updateAsync( + { _id: channelId }, + { $set: { liveStreams: entries, liveStreamsTimestamp: timestamp } }, + { upsert: true } + ) + } + + static updateShortsByChannelId({ channelId, entries, timestamp }) { + return db.subscriptionCache.updateAsync( + { _id: channelId }, + { $set: { shorts: entries, shortsTimestamp: timestamp } }, + { upsert: true } + ) + } + + static updateShortsWithChannelPageShortsByChannelId({ channelId, entries }) { + return db.subscriptionCache.findOneAsync({ _id: channelId }, { shorts: 1 }).then((doc) => { + if (doc == null) { return } + + const shorts = doc.shorts + const cacheShorts = Array.isArray(shorts) ? shorts : [] + + cacheShorts.forEach(cachedVideo => { + const channelVideo = entries.find(short => cachedVideo.videoId === short.videoId) + if (!channelVideo) { return } + + // authorId probably never changes, so we don't need to update that + cachedVideo.title = channelVideo.title + cachedVideo.author = channelVideo.author + + // as the channel shorts page only has compact view counts for numbers above 1000 e.g. 12k + // and the RSS feeds include an exact value, we only want to overwrite it when the number is larger than the cached value + // 12345 vs 12000 => 12345 + // 12345 vs 15000 => 15000 + + if (channelVideo.viewCount > cachedVideo.viewCount) { + cachedVideo.viewCount = channelVideo.viewCount + } + }) + + return db.subscriptionCache.updateAsync( + { _id: channelId }, + { $set: { shorts: cacheShorts } }, + { upsert: true } + ) + }) + } + + static updateCommunityPostsByChannelId({ channelId, entries, timestamp }) { + return db.subscriptionCache.updateAsync( + { _id: channelId }, + { $set: { communityPosts: entries, communityPostsTimestamp: timestamp } }, + { upsert: true } + ) + } + + static deleteMultipleChannels(channelIds) { + return db.subscriptionCache.removeAsync({ _id: { $in: channelIds } }, { multi: true }) + } + + static deleteAll() { + return db.subscriptionCache.removeAsync({}, { multi: true }) + } +} + function compactAllDatastores() { return Promise.allSettled([ db.settings.compactDatafileAsync(), db.history.compactDatafileAsync(), db.profiles.compactDatafileAsync(), db.playlists.compactDatafileAsync(), + db.subscriptionCache.compactDatafileAsync(), ]) } @@ -217,6 +297,7 @@ export { History as history, Profiles as profiles, Playlists as playlists, + SubscriptionCache as subscriptionCache, compactAllDatastores, } diff --git a/src/datastores/handlers/electron.js b/src/datastores/handlers/electron.js index 41d4872e45d8..889c91d4f060 100644 --- a/src/datastores/handlers/electron.js +++ b/src/datastores/handlers/electron.js @@ -218,9 +218,83 @@ class Playlists { } } +class SubscriptionCache { + static find() { + return ipcRenderer.invoke( + IpcChannels.DB_SUBSCRIPTION_CACHE, + { action: DBActions.GENERAL.FIND } + ) + } + + static updateVideosByChannelId({ channelId, entries, timestamp }) { + return ipcRenderer.invoke( + IpcChannels.DB_SUBSCRIPTION_CACHE, + { + action: DBActions.SUBSCRIPTION_CACHE.UPDATE_VIDEOS_BY_CHANNEL, + data: { channelId, entries, timestamp }, + } + ) + } + + static updateLiveStreamsByChannelId({ channelId, entries, timestamp }) { + return ipcRenderer.invoke( + IpcChannels.DB_SUBSCRIPTION_CACHE, + { + action: DBActions.SUBSCRIPTION_CACHE.UPDATE_LIVE_STREAMS_BY_CHANNEL, + data: { channelId, entries, timestamp }, + } + ) + } + + static updateShortsByChannelId({ channelId, entries, timestamp }) { + return ipcRenderer.invoke( + IpcChannels.DB_SUBSCRIPTION_CACHE, + { + action: DBActions.SUBSCRIPTION_CACHE.UPDATE_SHORTS_BY_CHANNEL, + data: { channelId, entries, timestamp }, + } + ) + } + + static updateShortsWithChannelPageShortsByChannelId({ channelId, entries }) { + return ipcRenderer.invoke( + IpcChannels.DB_SUBSCRIPTION_CACHE, + { + action: DBActions.SUBSCRIPTION_CACHE.UPDATE_SHORTS_WITH_CHANNEL_PAGE_SHORTS_BY_CHANNEL, + data: { channelId, entries }, + } + ) + } + + static updateCommunityPostsByChannelId({ channelId, entries, timestamp }) { + return ipcRenderer.invoke( + IpcChannels.DB_SUBSCRIPTION_CACHE, + { + action: DBActions.SUBSCRIPTION_CACHE.UPDATE_COMMUNITY_POSTS_BY_CHANNEL, + data: { channelId, entries, timestamp }, + } + ) + } + + static deleteMultipleChannels(channelIds) { + return ipcRenderer.invoke( + IpcChannels.DB_SUBSCRIPTION_CACHE, + { action: DBActions.GENERAL.DELETE_MULTIPLE, data: channelIds } + ) + } + + static deleteAll() { + return ipcRenderer.invoke( + IpcChannels.DB_SUBSCRIPTION_CACHE, + { action: DBActions.GENERAL.DELETE_ALL } + ) + } +} + export { Settings as settings, History as history, Profiles as profiles, - Playlists as playlists + Playlists as playlists, + SubscriptionCache as subscriptionCache, } diff --git a/src/datastores/handlers/index.js b/src/datastores/handlers/index.js index df6ebffd9d80..6d9b8ab729d4 100644 --- a/src/datastores/handlers/index.js +++ b/src/datastores/handlers/index.js @@ -2,5 +2,6 @@ export { settings as DBSettingHandlers, history as DBHistoryHandlers, profiles as DBProfileHandlers, - playlists as DBPlaylistHandlers + playlists as DBPlaylistHandlers, + subscriptionCache as DBSubscriptionCacheHandlers, } from 'DB_HANDLERS_ELECTRON_RENDERER_OR_WEB' diff --git a/src/datastores/handlers/web.js b/src/datastores/handlers/web.js index 0fa321bcb4be..d68e24f04261 100644 --- a/src/datastores/handlers/web.js +++ b/src/datastores/handlers/web.js @@ -122,9 +122,63 @@ class Playlists { } } +class SubscriptionCache { + static find() { + return baseHandlers.subscriptionCache.find() + } + + static updateVideosByChannelId({ channelId, entries, timestamp }) { + return baseHandlers.subscriptionCache.updateVideosByChannelId({ + channelId, + entries, + timestamp, + }) + } + + static updateLiveStreamsByChannelId({ channelId, entries, timestamp }) { + return baseHandlers.subscriptionCache.updateLiveStreamsByChannelId({ + channelId, + entries, + timestamp, + }) + } + + static updateShortsByChannelId({ channelId, entries, timestamp }) { + return baseHandlers.subscriptionCache.updateShortsByChannelId({ + channelId, + entries, + timestamp, + }) + } + + static updateShortsWithChannelPageShortsByChannelId({ channelId, entries }) { + return baseHandlers.subscriptionCache.updateShortsWithChannelPageShortsByChannelId({ + channelId, + entries, + }) + } + + static updateCommunityPostsByChannelId({ channelId, entries, timestamp }) { + return baseHandlers.subscriptionCache.updateCommunityPostsByChannelId({ + channelId, + entries, + timestamp, + }) + } + + static deleteMultipleChannels(channelIds) { + return baseHandlers.subscriptionCache.deleteMultipleChannels(channelIds) + } + + static deleteAll() { + return baseHandlers.subscriptionCache.deleteAll() + } +} + export { Settings as settings, History as history, Profiles as profiles, - Playlists as playlists + Playlists as playlists, + SubscriptionCache as subscriptionCache, } diff --git a/src/datastores/index.js b/src/datastores/index.js index 442fed6497bf..7a3da53356f0 100644 --- a/src/datastores/index.js +++ b/src/datastores/index.js @@ -26,3 +26,4 @@ export const settings = new Datastore({ filename: dbPath('settings'), autoload: export const profiles = new Datastore({ filename: dbPath('profiles'), autoload: true }) export const playlists = new Datastore({ filename: dbPath('playlists'), autoload: true }) export const history = new Datastore({ filename: dbPath('history'), autoload: true }) +export const subscriptionCache = new Datastore({ filename: dbPath('subscription-cache'), autoload: true }) diff --git a/src/main/index.js b/src/main/index.js index 0848a677c2f1..c9c38fe39368 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1300,6 +1300,89 @@ function runApp() { // *********** // + // *********** // + // Profiles + ipcMain.handle(IpcChannels.DB_SUBSCRIPTION_CACHE, async (event, { action, data }) => { + try { + switch (action) { + case DBActions.GENERAL.FIND: + return await baseHandlers.subscriptionCache.find() + + case DBActions.SUBSCRIPTION_CACHE.UPDATE_VIDEOS_BY_CHANNEL: + await baseHandlers.subscriptionCache.updateVideosByChannelId(data) + syncOtherWindows( + IpcChannels.SYNC_SUBSCRIPTION_CACHE, + event, + { event: SyncEvents.SUBSCRIPTION_CACHE.UPDATE_VIDEOS_BY_CHANNEL, data } + ) + return null + + case DBActions.SUBSCRIPTION_CACHE.UPDATE_LIVE_STREAMS_BY_CHANNEL: + await baseHandlers.subscriptionCache.updateLiveStreamsByChannelId(data) + syncOtherWindows( + IpcChannels.SYNC_SUBSCRIPTION_CACHE, + event, + { event: SyncEvents.SUBSCRIPTION_CACHE.UPDATE_LIVE_STREAMS_BY_CHANNEL, data } + ) + return null + + case DBActions.SUBSCRIPTION_CACHE.UPDATE_SHORTS_BY_CHANNEL: + await baseHandlers.subscriptionCache.updateShortsByChannelId(data) + syncOtherWindows( + IpcChannels.SYNC_SUBSCRIPTION_CACHE, + event, + { event: SyncEvents.SUBSCRIPTION_CACHE.UPDATE_SHORTS_BY_CHANNEL, data } + ) + return null + + case DBActions.SUBSCRIPTION_CACHE.UPDATE_SHORTS_WITH_CHANNEL_PAGE_SHORTS_BY_CHANNEL: + await baseHandlers.subscriptionCache.updateShortsWithChannelPageShortsByChannelId(data) + syncOtherWindows( + IpcChannels.SYNC_SUBSCRIPTION_CACHE, + event, + { event: SyncEvents.SUBSCRIPTION_CACHE.UPDATE_SHORTS_WITH_CHANNEL_PAGE_SHORTS_BY_CHANNEL, data } + ) + return null + + case DBActions.SUBSCRIPTION_CACHE.UPDATE_COMMUNITY_POSTS_BY_CHANNEL: + await baseHandlers.subscriptionCache.updateCommunityPostsByChannelId(data) + syncOtherWindows( + IpcChannels.SYNC_SUBSCRIPTION_CACHE, + event, + { event: SyncEvents.SUBSCRIPTION_CACHE.UPDATE_COMMUNITY_POSTS_BY_CHANNEL, data } + ) + return null + + case DBActions.GENERAL.DELETE_MULTIPLE: + await baseHandlers.subscriptionCache.deleteMultipleChannels(data) + syncOtherWindows( + IpcChannels.SYNC_SUBSCRIPTION_CACHE, + event, + { event: SyncEvents.GENERAL.DELETE_MULTIPLE, data } + ) + return null + + case DBActions.GENERAL.DELETE_ALL: + await baseHandlers.subscriptionCache.deleteAll() + syncOtherWindows( + IpcChannels.SYNC_SUBSCRIPTION_CACHE, + event, + { event: SyncEvents.GENERAL.DELETE_ALL, data } + ) + return null + + default: + // eslint-disable-next-line no-throw-literal + throw 'invalid subscriptionCache db action' + } + } catch (err) { + if (typeof err === 'string') throw err + else throw err.toString() + } + }) + + // *********** // + function syncOtherWindows(channel, event, payload) { const otherWindows = BrowserWindow.getAllWindows().filter((window) => { return window.webContents.id !== event.sender.id diff --git a/src/renderer/App.js b/src/renderer/App.js index 03e46519504d..0308944cb87f 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -178,6 +178,7 @@ export default defineComponent({ this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => { this.grabHistory() this.grabAllPlaylists() + this.grabAllSubscriptions() if (process.env.IS_ELECTRON) { ipcRenderer = require('electron').ipcRenderer @@ -552,6 +553,7 @@ export default defineComponent({ 'grabAllProfiles', 'grabHistory', 'grabAllPlaylists', + 'grabAllSubscriptions', 'getYoutubeUrlInfo', 'getExternalPlayerCmdArgumentsData', 'fetchInvidiousInstances', diff --git a/src/renderer/components/ft-community-post/ft-community-post.js b/src/renderer/components/ft-community-post/ft-community-post.js index cbf30691f14f..617d1c889873 100644 --- a/src/renderer/components/ft-community-post/ft-community-post.js +++ b/src/renderer/components/ft-community-post/ft-community-post.js @@ -7,7 +7,13 @@ import autolinker from 'autolinker' import { A11y, Navigation, Pagination } from 'swiper/modules' -import { createWebURL, deepCopy, formatNumber, toLocalePublicationString } from '../../helpers/utils' +import { + createWebURL, + deepCopy, + formatNumber, + getRelativeTimeFromDate, + toLocalePublicationString, +} from '../../helpers/utils' import { youtubeImageUrlToInvidious } from '../../helpers/api/invidious' export default defineComponent({ @@ -137,7 +143,7 @@ export default defineComponent({ this.postContent = this.data.postContent this.postId = this.data.postId this.publishedText = toLocalePublicationString({ - publishText: this.data.publishedText, + publishText: getRelativeTimeFromDate(this.data.publishedTime), isLive: this.isLive, isUpcoming: this.isUpcoming, isRSS: this.data.isRSS diff --git a/src/renderer/components/subscriptions-community/subscriptions-community.js b/src/renderer/components/subscriptions-community/subscriptions-community.js index 86c81c6ecc1a..57c8e1967e4a 100644 --- a/src/renderer/components/subscriptions-community/subscriptions-community.js +++ b/src/renderer/components/subscriptions-community/subscriptions-community.js @@ -2,7 +2,7 @@ import { defineComponent } from 'vue' import { mapActions, mapMutations } from 'vuex' import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue' -import { calculatePublishedDate, copyToClipboard, getRelativeTimeFromDate, showToast } from '../../helpers/utils' +import { copyToClipboard, getRelativeTimeFromDate, showToast } from '../../helpers/utils' import { getLocalChannelCommunity } from '../../helpers/api/local' import { invidiousGetCommunityPosts } from '../../helpers/api/invidious' @@ -17,6 +17,7 @@ export default defineComponent({ postList: [], errorChannels: [], attemptedFetch: false, + lastRemoteRefreshSuccessTimestamp: null, } }, computed: { @@ -39,6 +40,10 @@ export default defineComponent({ return this.activeProfile._id }, + subscriptionCacheReady: function () { + return this.$store.getters.getSubscriptionCacheReady + }, + cacheEntriesForAllActiveProfileChannels() { const entries = [] this.activeSubscriptionList.forEach((channel) => { @@ -51,7 +56,20 @@ export default defineComponent({ }, lastCommunityRefreshTimestamp: function () { - return getRelativeTimeFromDate(this.$store.getters.getLastCommunityRefreshTimestampByProfile(this.activeProfileId), true) + // Cache is not ready when data is just loaded from remote + if (this.lastRemoteRefreshSuccessTimestamp) { + return getRelativeTimeFromDate(this.lastRemoteRefreshSuccessTimestamp, true) + } + if (!this.postCacheForAllActiveProfileChannelsPresent) { return '' } + if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return '' } + + let minTimestamp = null + this.cacheEntriesForAllActiveProfileChannels.forEach((cacheEntry) => { + if (!minTimestamp || cacheEntry.timestamp.getTime() < minTimestamp.getTime()) { + minTimestamp = cacheEntry.timestamp + } + }) + return getRelativeTimeFromDate(minTimestamp, true) }, postCacheForAllActiveProfileChannelsPresent() { @@ -73,9 +91,14 @@ export default defineComponent({ }, watch: { activeProfile: async function (_) { + this.lastRemoteRefreshSuccessTimestamp = null this.isLoading = true this.loadPostsFromCacheSometimes() }, + + subscriptionCacheReady() { + this.loadPostsFromCacheSometimes() + }, }, mounted: async function () { this.isLoading = true @@ -84,23 +107,15 @@ export default defineComponent({ }, methods: { loadPostsFromCacheSometimes() { + // Can only load reliably when cache ready + if (!this.subscriptionCacheReady) { return } + // This method is called on view visible if (this.postCacheForAllActiveProfileChannelsPresent) { this.loadPostsFromCacheForAllActiveProfileChannels() - if (this.cacheEntriesForAllActiveProfileChannels.length > 0) { - let minTimestamp = null - this.cacheEntriesForAllActiveProfileChannels.forEach((cacheEntry) => { - if (!minTimestamp || cacheEntry.timestamp.getTime() < minTimestamp.getTime()) { - minTimestamp = cacheEntry.timestamp - } - }) - this.updateLastCommunityRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: minTimestamp }) - } return } - // clear timestamp if not all entries are present in the cache - this.updateLastCommunityRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: '' }) this.maybeLoadPostsForSubscriptionsFromRemote() }, @@ -113,7 +128,7 @@ export default defineComponent({ }) postList.sort((a, b) => { - return calculatePublishedDate(b.publishedText) - calculatePublishedDate(a.publishedText) + return b.publishedTime - a.publishedTime }) this.postList = postList @@ -180,14 +195,14 @@ export default defineComponent({ return posts }))).flatMap((o) => o) postList.push(...postListFromRemote) - this.updateLastCommunityRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: new Date() }) postList.sort((a, b) => { - return calculatePublishedDate(b.publishedText) - calculatePublishedDate(a.publishedText) + return b.publishedTime - a.publishedTime }) this.postList = postList this.isLoading = false this.updateShowProgressBar(false) + this.lastRemoteRefreshSuccessTimestamp = new Date() this.batchUpdateSubscriptionDetails(subscriptionUpdates) }, @@ -256,7 +271,6 @@ export default defineComponent({ 'updateShowProgressBar', 'batchUpdateSubscriptionDetails', 'updateSubscriptionPostsCacheByChannel', - 'updateLastCommunityRefreshTimestampByProfile' ]), ...mapMutations([ diff --git a/src/renderer/components/subscriptions-live/subscriptions-live.js b/src/renderer/components/subscriptions-live/subscriptions-live.js index ab3e8d0e7474..969ce7f070f1 100644 --- a/src/renderer/components/subscriptions-live/subscriptions-live.js +++ b/src/renderer/components/subscriptions-live/subscriptions-live.js @@ -24,6 +24,7 @@ export default defineComponent({ videoList: [], errorChannels: [], attemptedFetch: false, + lastRemoteRefreshSuccessTimestamp: null, } }, computed: { @@ -50,6 +51,10 @@ export default defineComponent({ return this.activeProfile._id }, + subscriptionCacheReady: function () { + return this.$store.getters.getSubscriptionCacheReady + }, + cacheEntriesForAllActiveProfileChannels() { const entries = [] this.activeSubscriptionList.forEach((channel) => { @@ -78,14 +83,32 @@ export default defineComponent({ }, lastLiveRefreshTimestamp: function () { - return getRelativeTimeFromDate(this.$store.getters.getLastLiveRefreshTimestampByProfile(this.activeProfileId), true) - } + // Cache is not ready when data is just loaded from remote + if (this.lastRemoteRefreshSuccessTimestamp) { + return getRelativeTimeFromDate(this.lastRemoteRefreshSuccessTimestamp, true) + } + if (!this.videoCacheForAllActiveProfileChannelsPresent) { return '' } + if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return '' } + + let minTimestamp = null + this.cacheEntriesForAllActiveProfileChannels.forEach((cacheEntry) => { + if (!minTimestamp || cacheEntry.timestamp.getTime() < minTimestamp.getTime()) { + minTimestamp = cacheEntry.timestamp + } + }) + return getRelativeTimeFromDate(minTimestamp, true) + }, }, watch: { activeProfile: async function (_) { + this.lastRemoteRefreshSuccessTimestamp = null this.isLoading = true this.loadVideosFromCacheSometimes() }, + + subscriptionCacheReady() { + this.loadVideosFromCacheSometimes() + }, }, mounted: async function () { this.isLoading = true @@ -94,23 +117,15 @@ export default defineComponent({ }, methods: { loadVideosFromCacheSometimes() { + // Can only load reliably when cache ready + if (!this.subscriptionCacheReady) { return } + // This method is called on view visible if (this.videoCacheForAllActiveProfileChannelsPresent) { this.loadVideosFromCacheForAllActiveProfileChannels() - if (this.cacheEntriesForAllActiveProfileChannels.length > 0) { - let minTimestamp = null - this.cacheEntriesForAllActiveProfileChannels.forEach((cacheEntry) => { - if (!minTimestamp || cacheEntry.timestamp.getTime() < minTimestamp.getTime()) { - minTimestamp = cacheEntry.timestamp - } - }) - this.updateLastLiveRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: minTimestamp }) - } return } - // clear timestamp if not all entries are present in the cache - this.updateLastLiveRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: '' }) this.maybeLoadVideosForSubscriptionsFromRemote() }, @@ -189,11 +204,11 @@ export default defineComponent({ return videos }))).flatMap((o) => o) videoList.push(...videoListFromRemote) - this.updateLastLiveRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: new Date() }) this.videoList = updateVideoListAfterProcessing(videoList) this.isLoading = false this.updateShowProgressBar(false) + this.lastRemoteRefreshSuccessTimestamp = new Date() this.batchUpdateSubscriptionDetails(subscriptionUpdates) }, @@ -404,7 +419,6 @@ export default defineComponent({ 'batchUpdateSubscriptionDetails', 'updateShowProgressBar', 'updateSubscriptionLiveCacheByChannel', - 'updateLastLiveRefreshTimestampByProfile' ]), ...mapMutations([ diff --git a/src/renderer/components/subscriptions-shorts/subscriptions-shorts.js b/src/renderer/components/subscriptions-shorts/subscriptions-shorts.js index 101687cccd79..f63d488178ae 100644 --- a/src/renderer/components/subscriptions-shorts/subscriptions-shorts.js +++ b/src/renderer/components/subscriptions-shorts/subscriptions-shorts.js @@ -22,6 +22,7 @@ export default defineComponent({ videoList: [], errorChannels: [], attemptedFetch: false, + lastRemoteRefreshSuccessTimestamp: null, } }, computed: { @@ -37,8 +38,25 @@ export default defineComponent({ return this.$store.getters.getCurrentInvidiousInstanceUrl }, + subscriptionCacheReady: function () { + return this.$store.getters.getSubscriptionCacheReady + }, + lastShortRefreshTimestamp: function () { - return getRelativeTimeFromDate(this.$store.getters.getLastShortRefreshTimestampByProfile(this.activeProfileId), true) + // Cache is not ready when data is just loaded from remote + if (this.lastRemoteRefreshSuccessTimestamp) { + return getRelativeTimeFromDate(this.lastRemoteRefreshSuccessTimestamp, true) + } + if (!this.videoCacheForAllActiveProfileChannelsPresent) { return '' } + if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return '' } + + let minTimestamp = null + this.cacheEntriesForAllActiveProfileChannels.forEach((cacheEntry) => { + if (!minTimestamp || cacheEntry.timestamp.getTime() < minTimestamp.getTime()) { + minTimestamp = cacheEntry.timestamp + } + }) + return getRelativeTimeFromDate(minTimestamp, true) }, activeProfile: function () { @@ -77,9 +95,14 @@ export default defineComponent({ }, watch: { activeProfile: async function (_) { + this.lastRemoteRefreshSuccessTimestamp = null this.isLoading = true this.loadVideosFromCacheSometimes() }, + + subscriptionCacheReady() { + this.loadVideosFromCacheSometimes() + }, }, mounted: async function () { this.isLoading = true @@ -88,24 +111,15 @@ export default defineComponent({ }, methods: { loadVideosFromCacheSometimes() { - // This method is called on view visible + // Can only load reliably when cache ready + if (!this.subscriptionCacheReady) { return } + // This method is called on view visible if (this.videoCacheForAllActiveProfileChannelsPresent) { this.loadVideosFromCacheForAllActiveProfileChannels() - if (this.cacheEntriesForAllActiveProfileChannels.length > 0) { - let minTimestamp = null - this.cacheEntriesForAllActiveProfileChannels.forEach((cacheEntry) => { - if (!minTimestamp || cacheEntry.timestamp.getTime() < minTimestamp.getTime()) { - minTimestamp = cacheEntry.timestamp - } - }) - this.updateLastShortRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: minTimestamp }) - } return } - // clear timestamp if not all entries are present in the cache - this.updateLastShortRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: '' }) this.maybeLoadVideosForSubscriptionsFromRemote() }, @@ -166,11 +180,11 @@ export default defineComponent({ return videos }))).flatMap((o) => o) videoList.push(...videoListFromRemote) - this.updateLastShortRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: new Date() }) this.videoList = updateVideoListAfterProcessing(videoList) this.isLoading = false this.updateShowProgressBar(false) + this.lastRemoteRefreshSuccessTimestamp = new Date() this.batchUpdateSubscriptionDetails(subscriptionUpdates) }, @@ -277,7 +291,6 @@ export default defineComponent({ 'batchUpdateSubscriptionDetails', 'updateShowProgressBar', 'updateSubscriptionShortsCacheByChannel', - 'updateLastShortRefreshTimestampByProfile' ]), ...mapMutations([ diff --git a/src/renderer/components/subscriptions-videos/subscriptions-videos.js b/src/renderer/components/subscriptions-videos/subscriptions-videos.js index d11120615fc2..6e69f9511d56 100644 --- a/src/renderer/components/subscriptions-videos/subscriptions-videos.js +++ b/src/renderer/components/subscriptions-videos/subscriptions-videos.js @@ -24,6 +24,7 @@ export default defineComponent({ videoList: [], errorChannels: [], attemptedFetch: false, + lastRemoteRefreshSuccessTimestamp: null, } }, computed: { @@ -39,12 +40,29 @@ export default defineComponent({ return this.$store.getters.getCurrentInvidiousInstanceUrl }, + subscriptionCacheReady: function () { + return this.$store.getters.getSubscriptionCacheReady + }, + currentLocale: function () { return this.$i18n.locale.replace('_', '-') }, lastVideoRefreshTimestamp: function () { - return getRelativeTimeFromDate(this.$store.getters.getLastVideoRefreshTimestampByProfile(this.activeProfileId), true) + // Cache is not ready when data is just loaded from remote + if (this.lastRemoteRefreshSuccessTimestamp) { + return getRelativeTimeFromDate(this.lastRemoteRefreshSuccessTimestamp, true) + } + if (!this.videoCacheForAllActiveProfileChannelsPresent) { return '' } + if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return '' } + + let minTimestamp = null + this.cacheEntriesForAllActiveProfileChannels.forEach((cacheEntry) => { + if (!minTimestamp || cacheEntry.timestamp.getTime() < minTimestamp.getTime()) { + minTimestamp = cacheEntry.timestamp + } + }) + return getRelativeTimeFromDate(minTimestamp, true) }, useRssFeeds: function () { @@ -87,9 +105,14 @@ export default defineComponent({ }, watch: { activeProfile: async function (_) { + this.lastRemoteRefreshSuccessTimestamp = null this.isLoading = true this.loadVideosFromCacheSometimes() }, + + subscriptionCacheReady() { + this.loadVideosFromCacheSometimes() + }, }, mounted: async function () { this.isLoading = true @@ -98,23 +121,15 @@ export default defineComponent({ }, methods: { loadVideosFromCacheSometimes() { + // Can only load reliably when cache ready + if (!this.subscriptionCacheReady) { return } + // This method is called on view visible if (this.videoCacheForAllActiveProfileChannelsPresent) { this.loadVideosFromCacheForAllActiveProfileChannels() - if (this.cacheEntriesForAllActiveProfileChannels.length > 0) { - let minTimestamp = null - this.cacheEntriesForAllActiveProfileChannels.forEach((cacheEntry) => { - if (!minTimestamp || cacheEntry.timestamp.getTime() < minTimestamp.getTime()) { - minTimestamp = cacheEntry.timestamp - } - }) - this.updateLastVideoRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: minTimestamp }) - } return } - // clear timestamp if not all entries are present in the cache - this.updateLastVideoRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: '' }) this.maybeLoadVideosForSubscriptionsFromRemote() }, @@ -193,11 +208,11 @@ export default defineComponent({ return videos }))).flatMap((o) => o) videoList.push(...videoListFromRemote) - this.updateLastVideoRefreshTimestampByProfile({ profileId: this.activeProfileId, timestamp: new Date() }) this.videoList = updateVideoListAfterProcessing(videoList) this.isLoading = false this.updateShowProgressBar(false) + this.lastRemoteRefreshSuccessTimestamp = new Date() this.batchUpdateSubscriptionDetails(subscriptionUpdates) }, @@ -406,7 +421,6 @@ export default defineComponent({ 'batchUpdateSubscriptionDetails', 'updateShowProgressBar', 'updateSubscriptionVideosCacheByChannel', - 'updateLastVideoRefreshTimestampByProfile' ]), ...mapMutations([ diff --git a/src/renderer/helpers/api/invidious.js b/src/renderer/helpers/api/invidious.js index f05c4af95e92..ee15c722431c 100644 --- a/src/renderer/helpers/api/invidious.js +++ b/src/renderer/helpers/api/invidious.js @@ -1,5 +1,9 @@ import store from '../../store/index' -import { stripHTML, toLocalePublicationString } from '../utils' +import { + calculatePublishedDate, + stripHTML, + toLocalePublicationString, +} from '../utils' import { isNullOrEmpty } from '../strings' import autolinker from 'autolinker' import { FormatUtils, Misc, Player } from 'youtubei.js' @@ -266,7 +270,7 @@ function parseInvidiousCommunityData(data) { thumbnail.url = youtubeImageUrlToInvidious(thumbnail.url) return thumbnail }), - publishedText: data.publishedText, + publishedTime: calculatePublishedDate(data.publishedText), voteCount: data.likeCount, postContent: parseInvidiousCommunityAttachments(data.attachment), commentCount: data?.replyCount ?? 0, // https://github.com/iv-org/invidious/pull/3635/ diff --git a/src/renderer/helpers/api/local.js b/src/renderer/helpers/api/local.js index c65b900a428d..b28833a8771b 100644 --- a/src/renderer/helpers/api/local.js +++ b/src/renderer/helpers/api/local.js @@ -1429,7 +1429,7 @@ function parseLocalCommunityPost(post) { postText: post.content.isEmpty() ? '' : Autolinker.link(parseLocalTextRuns(post.content.runs, 16)), postId: post.id, authorThumbnails: post.author.thumbnails, - publishedText: post.published.text, + publishedTime: calculatePublishedDate(post.published.text), voteCount: parseLocalSubscriberCount(post.vote_count.text), postContent: parseLocalAttachment(post.attachment), commentCount: replyCount, diff --git a/src/renderer/store/modules/index.js b/src/renderer/store/modules/index.js index f7ec3be26ba8..144b0355c696 100644 --- a/src/renderer/store/modules/index.js +++ b/src/renderer/store/modules/index.js @@ -8,7 +8,7 @@ import invidious from './invidious' import playlists from './playlists' import profiles from './profiles' import settings from './settings' -import subscriptions from './subscriptions' +import subscriptionCache from './subscription-cache' import utils from './utils' import player from './player' @@ -18,7 +18,7 @@ export default { playlists, profiles, settings, - subscriptions, + subscriptionCache, utils, - player + player, } diff --git a/src/renderer/store/modules/profiles.js b/src/renderer/store/modules/profiles.js index 9e55e61e3f95..ce2b74fe07c2 100644 --- a/src/renderer/store/modules/profiles.js +++ b/src/renderer/store/modules/profiles.js @@ -27,9 +27,15 @@ const getters = { }, profileById: (state) => (id) => { - const profile = state.profileList.find(p => p._id === id) - return profile - } + return state.profileList.find(p => p._id === id) + }, + + getSubscribedChannelIdSet: (state) => { + // The all channels profile is always the first profile in the array + const mainProfile = state.profileList[0] + + return mainProfile.subscriptions.reduce((set, channel) => set.add(channel.id), new Set()) + }, } function profileSort(a, b) { diff --git a/src/renderer/store/modules/settings.js b/src/renderer/store/modules/settings.js index d272f07d317d..2f223954e721 100644 --- a/src/renderer/store/modules/settings.js +++ b/src/renderer/store/modules/settings.js @@ -567,6 +567,41 @@ const customActions = { console.error('playlists: invalid sync event received') } }) + + ipcRenderer.on(IpcChannels.SYNC_SUBSCRIPTION_CACHE, (_, { event, data }) => { + switch (event) { + case SyncEvents.SUBSCRIPTION_CACHE.UPDATE_VIDEOS_BY_CHANNEL: + commit('updateVideoCacheByChannel', data) + break + + case SyncEvents.SUBSCRIPTION_CACHE.UPDATE_LIVE_STREAMS_BY_CHANNEL: + commit('updateLiveCacheByChannel', data) + break + + case SyncEvents.SUBSCRIPTION_CACHE.UPDATE_SHORTS_BY_CHANNEL: + commit('updateShortsCacheByChannel', data) + break + + case SyncEvents.SUBSCRIPTION_CACHE.UPDATE_SHORTS_WITH_CHANNEL_PAGE_SHORTS_BY_CHANNEL: + commit('updateShortsCacheWithChannelPageShorts', data) + break + + case SyncEvents.SUBSCRIPTION_CACHE.UPDATE_COMMUNITY_POSTS_BY_CHANNEL: + commit('updatePostsCacheByChannel', data) + break + + case SyncEvents.GENERAL.DELETE_MULTIPLE: + commit('clearCachesForManyChannels', data) + break + + case SyncEvents.GENERAL.DELETE_ALL: + commit('clearCaches', data) + break + + default: + console.error('subscription-cache: invalid sync event received') + } + }) } } } diff --git a/src/renderer/store/modules/subscription-cache.js b/src/renderer/store/modules/subscription-cache.js new file mode 100644 index 000000000000..72d35f6361b1 --- /dev/null +++ b/src/renderer/store/modules/subscription-cache.js @@ -0,0 +1,276 @@ +import { + DBSubscriptionCacheHandlers, +} from '../../../datastores/handlers/index' + +const state = { + videoCache: {}, + liveCache: {}, + shortsCache: {}, + postsCache: {}, + + subscriptionCacheReady: false, +} + +const getters = { + getSubscriptionCacheReady: (state) => state.subscriptionCacheReady, + + getVideoCache: (state) => { + return state.videoCache + }, + + getVideoCacheByChannel: (state) => (channelId) => { + return state.videoCache[channelId] + }, + + getShortsCache: (state) => { + return state.shortsCache + }, + + getShortsCacheByChannel: (state) => (channelId) => { + return state.shortsCache[channelId] + }, + + getLiveCache: (state) => { + return state.liveCache + }, + + getLiveCacheByChannel: (state) => (channelId) => { + return state.liveCache[channelId] + }, + + getPostsCache: (state) => { + return state.postsCache + }, + + getPostsCacheByChannel: (state) => (channelId) => { + return state.postsCache[channelId] + }, +} + +const actions = { + async grabAllSubscriptions({ commit, dispatch, rootGetters }) { + try { + const payload = await DBSubscriptionCacheHandlers.find() + + const videos = {} + const liveStreams = {} + const shorts = {} + const communityPosts = {} + + const toBeRemovedChannelIds = [] + const subscribedChannelIdSet = rootGetters.getSubscribedChannelIdSet + + for (const dataEntry of payload) { + const channelId = dataEntry._id + if (!subscribedChannelIdSet.has(channelId)) { + // Clean up cache data for unsubscribed channels + toBeRemovedChannelIds.push(channelId) + // No need to load data for unsubscribed channels + continue + } + + let hasData = false + + if (Array.isArray(dataEntry.videos)) { + videos[channelId] = { videos: dataEntry.videos, timestamp: dataEntry.videosTimestamp } + hasData = true + } + if (Array.isArray(dataEntry.liveStreams)) { + liveStreams[channelId] = { videos: dataEntry.liveStreams, timestamp: dataEntry.liveStreamsTimestamp } + hasData = true + } + if (Array.isArray(dataEntry.shorts)) { + shorts[channelId] = { videos: dataEntry.shorts, timestamp: dataEntry.shortsTimestamp } + hasData = true + } + if (Array.isArray(dataEntry.communityPosts)) { + communityPosts[channelId] = { posts: dataEntry.communityPosts, timestamp: dataEntry.communityPostsTimestamp } + hasData = true + } + + if (!hasData) { toBeRemovedChannelIds.push(channelId) } + } + + if (toBeRemovedChannelIds.length > 0) { + // Delete channels with no data + dispatch('clearSubscriptionsCacheForManyChannels', toBeRemovedChannelIds) + } + commit('setCaches', { videos, liveStreams, shorts, communityPosts }) + commit('setSubscriptionCacheReady', true) + } catch (errMessage) { + console.error(errMessage) + } + }, + + async updateSubscriptionVideosCacheByChannel({ commit }, { channelId, videos, timestamp = new Date() }) { + try { + await DBSubscriptionCacheHandlers.updateVideosByChannelId({ + channelId, + entries: videos, + timestamp, + }) + commit('updateVideoCacheByChannel', { channelId, entries: videos, timestamp }) + } catch (errMessage) { + console.error(errMessage) + } + }, + + async updateSubscriptionShortsCacheByChannel({ commit }, { channelId, videos, timestamp = new Date() }) { + try { + await DBSubscriptionCacheHandlers.updateShortsByChannelId({ + channelId, + entries: videos, + timestamp, + }) + commit('updateShortsCacheByChannel', { channelId, entries: videos, timestamp }) + } catch (errMessage) { + console.error(errMessage) + } + }, + + async updateSubscriptionShortsCacheWithChannelPageShorts({ commit }, { channelId, videos }) { + try { + await DBSubscriptionCacheHandlers.updateShortsWithChannelPageShortsByChannelId({ + channelId, + entries: videos, + }) + commit('updateShortsCacheWithChannelPageShorts', { channelId, entries: videos }) + } catch (errMessage) { + console.error(errMessage) + } + }, + + async updateSubscriptionLiveCacheByChannel({ commit }, { channelId, videos, timestamp = new Date() }) { + try { + await DBSubscriptionCacheHandlers.updateLiveStreamsByChannelId({ + channelId, + entries: videos, + timestamp, + }) + commit('updateLiveCacheByChannel', { channelId, entries: videos, timestamp }) + } catch (errMessage) { + console.error(errMessage) + } + }, + + async updateSubscriptionPostsCacheByChannel({ commit }, { channelId, posts, timestamp = new Date() }) { + try { + await DBSubscriptionCacheHandlers.updateCommunityPostsByChannelId({ + channelId, + entries: posts, + timestamp, + }) + commit('updatePostsCacheByChannel', { channelId, entries: posts, timestamp }) + } catch (errMessage) { + console.error(errMessage) + } + }, + + async clearSubscriptionsCacheForManyChannels({ commit }, channelIds) { + try { + await DBSubscriptionCacheHandlers.deleteMultipleChannels(channelIds) + commit('clearCachesForManyChannels', channelIds) + } catch (errMessage) { + console.error(errMessage) + } + }, + + async clearSubscriptionsCache({ commit }, payload) { + try { + await DBSubscriptionCacheHandlers.deleteAll() + commit('clearCaches') + } catch (errMessage) { + console.error(errMessage) + } + }, +} + +const mutations = { + updateVideoCacheByChannel(state, { channelId, entries, timestamp = new Date() }) { + const existingObject = state.videoCache[channelId] + const newObject = existingObject ?? { videos: null } + if (entries != null) { newObject.videos = entries } + newObject.timestamp = timestamp + state.videoCache[channelId] = newObject + }, + updateShortsCacheByChannel(state, { channelId, entries, timestamp = new Date() }) { + const existingObject = state.shortsCache[channelId] + const newObject = existingObject ?? { videos: null } + if (entries != null) { newObject.videos = entries } + newObject.timestamp = timestamp + state.shortsCache[channelId] = newObject + }, + updateShortsCacheWithChannelPageShorts(state, { channelId, entries }) { + const cachedObject = state.shortsCache[channelId] + + if (cachedObject && cachedObject.videos.length > 0) { + cachedObject.videos.forEach(cachedVideo => { + const channelVideo = entries.find(short => cachedVideo.videoId === short.videoId) + + if (channelVideo) { + // authorId probably never changes, so we don't need to update that + + cachedVideo.title = channelVideo.title + cachedVideo.author = channelVideo.author + + // as the channel shorts page only has compact view counts for numbers above 1000 e.g. 12k + // and the RSS feeds include an exact value, we only want to overwrite it when the number is larger than the cached value + // 12345 vs 12000 => 12345 + // 12345 vs 15000 => 15000 + + if (channelVideo.viewCount > cachedVideo.viewCount) { + cachedVideo.viewCount = channelVideo.viewCount + } + } + }) + } + }, + updateLiveCacheByChannel(state, { channelId, entries, timestamp = new Date() }) { + const existingObject = state.liveCache[channelId] + const newObject = existingObject ?? { videos: null } + if (entries != null) { newObject.videos = entries } + newObject.timestamp = timestamp + state.liveCache[channelId] = newObject + }, + updatePostsCacheByChannel(state, { channelId, entries, timestamp = new Date() }) { + const existingObject = state.postsCache[channelId] + const newObject = existingObject ?? { posts: null } + if (entries != null) { newObject.posts = entries } + newObject.timestamp = timestamp + state.postsCache[channelId] = newObject + }, + + clearCaches(state) { + state.videoCache = {} + state.shortsCache = {} + state.liveCache = {} + state.postsCache = {} + }, + + clearCachesForManyChannels(state, channelIds) { + channelIds.forEach((channelId) => { + state.videoCache[channelId] = null + state.liveCache[channelId] = null + state.shortsCache[channelId] = null + state.postsCache[channelId] = null + }) + }, + + setCaches(state, { videos, liveStreams, shorts, communityPosts }) { + state.videoCache = videos + state.liveCache = liveStreams + state.shortsCache = shorts + state.postsCache = communityPosts + }, + + setSubscriptionCacheReady(state, payload) { + state.subscriptionCacheReady = payload + }, +} + +export default { + state, + getters, + actions, + mutations +} diff --git a/src/renderer/store/modules/subscriptions.js b/src/renderer/store/modules/subscriptions.js deleted file mode 100644 index a2abb5f91be5..000000000000 --- a/src/renderer/store/modules/subscriptions.js +++ /dev/null @@ -1,136 +0,0 @@ -const state = { - videoCache: {}, - liveCache: {}, - shortsCache: {}, - postsCache: {} -} - -const getters = { - getVideoCache: (state) => { - return state.videoCache - }, - - getVideoCacheByChannel: (state) => (channelId) => { - return state.videoCache[channelId] - }, - - getShortsCache: (state) => { - return state.shortsCache - }, - - getShortsCacheByChannel: (state) => (channelId) => { - return state.shortsCache[channelId] - }, - - getLiveCache: (state) => { - return state.liveCache - }, - - getLiveCacheByChannel: (state) => (channelId) => { - return state.liveCache[channelId] - }, - - getPostsCache: (state) => { - return state.postsCache - }, - - getPostsCacheByChannel: (state) => (channelId) => { - return state.postsCache[channelId] - }, -} - -const actions = { - updateSubscriptionVideosCacheByChannel: ({ commit }, payload) => { - commit('updateVideoCacheByChannel', payload) - }, - - updateSubscriptionShortsCacheByChannel: ({ commit }, payload) => { - commit('updateShortsCacheByChannel', payload) - }, - - updateSubscriptionShortsCacheWithChannelPageShorts: ({ commit }, payload) => { - commit('updateShortsCacheWithChannelPageShorts', payload) - }, - - updateSubscriptionLiveCacheByChannel: ({ commit }, payload) => { - commit('updateLiveCacheByChannel', payload) - }, - - updateSubscriptionPostsCacheByChannel: ({ commit }, payload) => { - commit('updatePostsCacheByChannel', payload) - }, - - clearSubscriptionsCache: ({ commit }) => { - commit('clearCaches') - }, -} - -const mutations = { - updateVideoCacheByChannel(state, { channelId, videos, timestamp = new Date() }) { - const existingObject = state.videoCache[channelId] - const newObject = existingObject ?? { videos: null } - if (videos != null) { newObject.videos = videos } - newObject.timestamp = timestamp - state.videoCache[channelId] = newObject - }, - updateShortsCacheByChannel(state, { channelId, videos, timestamp = new Date() }) { - const existingObject = state.shortsCache[channelId] - const newObject = existingObject ?? { videos: null } - if (videos != null) { newObject.videos = videos } - newObject.timestamp = timestamp - state.shortsCache[channelId] = newObject - }, - updateShortsCacheWithChannelPageShorts(state, { channelId, videos }) { - const cachedObject = state.shortsCache[channelId] - - if (cachedObject && cachedObject.videos.length > 0) { - cachedObject.videos.forEach(cachedVideo => { - const channelVideo = videos.find(short => cachedVideo.videoId === short.videoId) - - if (channelVideo) { - // authorId probably never changes, so we don't need to update that - - cachedVideo.title = channelVideo.title - cachedVideo.author = channelVideo.author - - // as the channel shorts page only has compact view counts for numbers above 1000 e.g. 12k - // and the RSS feeds include an exact value, we only want to overwrite it when the number is larger than the cached value - // 12345 vs 12000 => 12345 - // 12345 vs 15000 => 15000 - - if (channelVideo.viewCount > cachedVideo.viewCount) { - cachedVideo.viewCount = channelVideo.viewCount - } - } - }) - } - }, - updateLiveCacheByChannel(state, { channelId, videos, timestamp = new Date() }) { - const existingObject = state.liveCache[channelId] - const newObject = existingObject ?? { videos: null } - if (videos != null) { newObject.videos = videos } - newObject.timestamp = timestamp - state.liveCache[channelId] = newObject - }, - updatePostsCacheByChannel(state, { channelId, posts, timestamp = new Date() }) { - const existingObject = state.postsCache[channelId] - const newObject = existingObject ?? { posts: null } - if (posts != null) { newObject.posts = posts } - newObject.timestamp = timestamp - state.postsCache[channelId] = newObject - }, - - clearCaches(state) { - state.videoCache = {} - state.shortsCache = {} - state.liveCache = {} - state.postsCache = {} - } -} - -export default { - state, - getters, - actions, - mutations -} diff --git a/src/renderer/store/modules/utils.js b/src/renderer/store/modules/utils.js index f257c98018cc..4efb5a0b769d 100644 --- a/src/renderer/store/modules/utils.js +++ b/src/renderer/store/modules/utils.js @@ -164,22 +164,6 @@ const getters = { getLastPopularRefreshTimestamp(state) { return state.lastPopularRefreshTimestamp }, - - getLastCommunityRefreshTimestampByProfile: (state) => (profileId) => { - return state.lastCommunityRefreshTimestampByProfile[profileId] - }, - - getLastShortRefreshTimestampByProfile: (state) => (profileId) => { - return state.lastShortRefreshTimestampByProfile[profileId] - }, - - getLastLiveRefreshTimestampByProfile: (state) => (profileId) => { - return state.lastLiveRefreshTimestampByProfile[profileId] - }, - - getLastVideoRefreshTimestampByProfile: (state) => (profileId) => { - return state.lastVideoRefreshTimestampByProfile[profileId] - }, } const actions = { @@ -798,22 +782,6 @@ const actions = { ipcRenderer.send(IpcChannels.OPEN_IN_EXTERNAL_PLAYER, { executable, args }) } }, - - updateLastCommunityRefreshTimestampByProfile ({ commit }, payload) { - commit('updateLastCommunityRefreshTimestampByProfile', payload) - }, - - updateLastShortRefreshTimestampByProfile ({ commit }, payload) { - commit('updateLastShortRefreshTimestampByProfile', payload) - }, - - updateLastLiveRefreshTimestampByProfile ({ commit }, payload) { - commit('updateLastLiveRefreshTimestampByProfile', payload) - }, - - updateLastVideoRefreshTimestampByProfile ({ commit }, payload) { - commit('updateLastVideoRefreshTimestampByProfile', payload) - } } const mutations = { @@ -917,22 +885,6 @@ const mutations = { state.lastPopularRefreshTimestamp = timestamp }, - updateLastCommunityRefreshTimestampByProfile (state, { profileId, timestamp }) { - vueSet(state.lastCommunityRefreshTimestampByProfile, profileId, timestamp) - }, - - updateLastShortRefreshTimestampByProfile (state, { profileId, timestamp }) { - vueSet(state.lastShortRefreshTimestampByProfile, profileId, timestamp) - }, - - updateLastLiveRefreshTimestampByProfile (state, { profileId, timestamp }) { - vueSet(state.lastLiveRefreshTimestampByProfile, profileId, timestamp) - }, - - updateLastVideoRefreshTimestampByProfile (state, { profileId, timestamp }) { - vueSet(state.lastVideoRefreshTimestampByProfile, profileId, timestamp) - }, - clearTrendingCache(state) { state.trendingCache = { default: null,