@@ -27,65 +27,66 @@ import { onMount } from "svelte";
2727 import searchEpisodes from " src/utility/searchEpisodes" ;
2828 import type { Playlist } from " src/types/Playlist" ;
2929 import spawnEpisodeContextMenu from " ./spawnEpisodeContextMenu" ;
30- import {
31- getCachedEpisodes ,
32- setCachedEpisodes ,
33- } from " src/services/FeedCacheService" ;
34- import { get } from " svelte/store" ;
30+ import {
31+ getCachedEpisodes ,
32+ getCachedEpisodesWithStatus ,
33+ setCachedEpisodes ,
34+ } from " src/services/FeedCacheService" ;
35+ import { get } from " svelte/store" ;
3536
3637 let feeds: PodcastFeed [] = [];
3738 let selectedFeed: PodcastFeed | null = null ;
3839 let selectedPlaylist: Playlist | null = null ;
3940 let displayedEpisodes: Episode [] = [];
4041 let displayedPlaylists: Playlist [] = [];
4142 let latestEpisodes: Episode [] = [];
43+ const FEED_REFRESH_CONCURRENCY = 3 ;
4244
43- onMount (() => {
44- const unsubscribePlaylists = playlists .subscribe ((pl ) => {
45- displayedPlaylists = [$queue , $favorites , $localFiles , ... Object .values (pl )];
46- });
45+ onMount (() => {
46+ const unsubscribePlaylists = playlists .subscribe ((pl ) => {
47+ displayedPlaylists = [$queue , $favorites , $localFiles , ... Object .values (pl )];
48+ });
4749
48- const unsubscribeSavedFeeds = savedFeeds .subscribe ((storeValue ) => {
49- feeds = Object .values (storeValue );
50- });
50+ const unsubscribeSavedFeeds = savedFeeds .subscribe ((storeValue ) => {
51+ feeds = Object .values (storeValue );
52+ void hydrateAndRefreshFeeds ();
53+ });
5154
52- const unsubscribeEpisodeCache = episodeCache .subscribe ((cache ) => {
53- latestEpisodes = Object .entries (cache )
54- .map (([_ , episodes ]) => episodes .slice (0 , 10 ))
55- .flat ()
56- .sort ((a , b ) => {
57- if (a .episodeDate && b .episodeDate )
58- return Number (b .episodeDate ) - Number (a .episodeDate );
55+ const unsubscribeEpisodeCache = episodeCache .subscribe ((cache ) => {
56+ latestEpisodes = Object .entries (cache )
57+ .map (([_ , episodes ]) => episodes .slice (0 , 10 ))
58+ .flat ()
59+ .sort ((a , b ) => {
60+ if (a .episodeDate && b .episodeDate )
61+ return Number (b .episodeDate ) - Number (a .episodeDate );
5962
60- return 0 ;
61- });
62- });
63+ return 0 ;
64+ });
65+ });
6366
64- (async () => {
65- await fetchEpisodesInAllFeeds (feeds );
67+ return () => {
68+ unsubscribeEpisodeCache ();
69+ unsubscribeSavedFeeds ();
70+ unsubscribePlaylists ();
71+ };
72+ });
6673
67- if (! selectedFeed ) {
68- displayedEpisodes = latestEpisodes ;
69- }
70- })();
74+ function getFeedCacheSettings() {
75+ const pluginInstance = get (plugin );
76+ const feedCacheSettings = pluginInstance ?.settings ?.feedCache ;
7177
72- return () => {
73- unsubscribeEpisodeCache ();
74- unsubscribeSavedFeeds ();
75- unsubscribePlaylists ();
76- };
77- });
78+ return {
79+ cacheEnabled: feedCacheSettings ?. enabled !== false ,
80+ cacheTtlMs:
81+ Math . max ( 1 , feedCacheSettings ?. ttlHours ?? 6 ) * 60 * 60 * 1000 ,
82+ };
83+ }
7884
7985 async function fetchEpisodes(
8086 feed : PodcastFeed ,
8187 useCache : boolean = true ,
8288 ): Promise <Episode []> {
83-
84- const pluginInstance = get (plugin );
85- const feedCacheSettings = pluginInstance ?.settings ?.feedCache ;
86- const cacheEnabled = feedCacheSettings ?.enabled !== false ;
87- const cacheTtlMs =
88- Math .max (1 , feedCacheSettings ?.ttlHours ?? 6 ) * 60 * 60 * 1000 ;
89+ const { cacheEnabled, cacheTtlMs } = getFeedCacheSettings ();
8990
9091 const cachedEpisodesInFeed = $episodeCache [feed .title ];
9192
@@ -129,14 +130,95 @@ onMount(() => {
129130 }
130131 }
131132
132- function fetchEpisodesInAllFeeds(
133- feedsToSearch : PodcastFeed []
134- ): Promise <Episode []> {
135- return Promise .all (
136- feedsToSearch .map ((feed ) => fetchEpisodes (feed ))
137- ).then ((episodes ) => {
138- return episodes .flat ();
139- });
133+ async function hydrateAndRefreshFeeds() {
134+ if (! feeds .length ) {
135+ return ;
136+ }
137+
138+ const { cacheEnabled, cacheTtlMs } = getFeedCacheSettings ();
139+
140+ const cachedFeeds = cacheEnabled
141+ ? feeds
142+ .map ((feed ) => {
143+ const cached = getCachedEpisodesWithStatus (feed , cacheTtlMs );
144+ if (! cached ?.episodes .length ) {
145+ return null ;
146+ }
147+
148+ return { feed , ... cached };
149+ })
150+ .filter (Boolean ) as Array <{
151+ feed: PodcastFeed ;
152+ episodes: Episode [];
153+ isExpired: boolean ;
154+ }>
155+ : [];
156+
157+ if (cachedFeeds .length ) {
158+ episodeCache .update ((cache ) => {
159+ const updatedCache = { ... cache };
160+
161+ for (const { feed, episodes } of cachedFeeds ) {
162+ if (updatedCache [feed .title ]?.length ) {
163+ continue ;
164+ }
165+
166+ updatedCache [feed .title ] = episodes ;
167+ }
168+
169+ return updatedCache ;
170+ });
171+
172+ if (! selectedFeed && ! selectedPlaylist ) {
173+ displayedEpisodes = latestEpisodes ;
174+ }
175+ }
176+
177+ const feedsToRefresh = cacheEnabled
178+ ? feeds .filter ((feed ) => {
179+ const feedKey = feed .url ?? feed .title ;
180+ const cached = cachedFeeds .find (
181+ ({ feed : cachedFeed }) =>
182+ (cachedFeed .url ?? cachedFeed .title ) === feedKey ,
183+ );
184+
185+ return ! cached || cached .isExpired ;
186+ })
187+ : feeds ;
188+
189+ if (! feedsToRefresh .length ) {
190+ if (! selectedFeed && ! selectedPlaylist ) {
191+ displayedEpisodes = latestEpisodes ;
192+ }
193+ return ;
194+ }
195+
196+ void refreshFeedsWithLimit (feedsToRefresh );
197+ }
198+
199+ async function refreshFeedsWithLimit(feedsToRefresh : PodcastFeed []) {
200+ const queueToRefresh = [... feedsToRefresh ];
201+ const workers = Array .from (
202+ { length: FEED_REFRESH_CONCURRENCY },
203+ async () => {
204+ while (queueToRefresh .length ) {
205+ const feed = queueToRefresh .shift ();
206+ if (! feed ) break ;
207+
208+ await fetchEpisodes (feed , false );
209+ }
210+ },
211+ );
212+
213+ try {
214+ await Promise .all (workers );
215+ } catch (error ) {
216+ console .error (" Failed to refresh saved feeds:" , error );
217+ } finally {
218+ if (! selectedFeed && ! selectedPlaylist ) {
219+ displayedEpisodes = latestEpisodes ;
220+ }
221+ }
140222 }
141223
142224 async function handleClickPodcast(
0 commit comments