1- import { get , writable } from "svelte/store" ;
1+ import { get , readable , writable } from "svelte/store" ;
22import type PodNotes from "src/main" ;
33import type { Episode } from "src/types/Episode" ;
44import type { PlayedEpisode } from "src/types/PlayedEpisode" ;
@@ -13,6 +13,7 @@ export const plugin = writable<PodNotes>();
1313export const currentTime = writable < number > ( 0 ) ;
1414export const duration = writable < number > ( 0 ) ;
1515export const volume = writable < number > ( 1 ) ;
16+ export const hidePlayedEpisodes = writable < boolean > ( false ) ;
1617
1718export const currentEpisode = ( ( ) => {
1819 const store = writable < Episode > ( ) ;
@@ -102,6 +103,199 @@ export const savedFeeds = writable<{ [podcastName: string]: PodcastFeed }>({});
102103
103104export const episodeCache = writable < { [ podcastName : string ] : Episode [ ] } > ( { } ) ;
104105
106+ const LATEST_EPISODES_PER_FEED = 10 ;
107+
108+ type LatestEpisodesByFeed = Map < string , Episode [ ] > ;
109+ type FeedEpisodeSources = Map < string , Episode [ ] > ;
110+ type LatestEpisodePointer = {
111+ feedTitle : string ;
112+ index : number ;
113+ episode : Episode ;
114+ } ;
115+
116+ function getEpisodeTimestamp ( episode ?: Episode ) : number {
117+ if ( ! episode ?. episodeDate ) return 0 ;
118+
119+ return Number ( episode . episodeDate ) ;
120+ }
121+
122+ function getLatestEpisodesForFeed ( episodes : Episode [ ] ) : Episode [ ] {
123+ if ( ! episodes ?. length ) return [ ] ;
124+
125+ return episodes
126+ . slice ( 0 , LATEST_EPISODES_PER_FEED )
127+ . sort ( ( a , b ) => getEpisodeTimestamp ( b ) - getEpisodeTimestamp ( a ) ) ;
128+ }
129+
130+ function shallowEqualEpisodes ( a ?: Episode [ ] , b ?: Episode [ ] ) : boolean {
131+ if ( ! a || ! b || a . length !== b . length ) return false ;
132+
133+ for ( let i = 0 ; i < a . length ; i += 1 ) {
134+ if ( a [ i ] !== b [ i ] ) return false ;
135+ }
136+
137+ return true ;
138+ }
139+
140+ function pushEpisodePointer (
141+ heap : LatestEpisodePointer [ ] ,
142+ pointer : LatestEpisodePointer ,
143+ ) : void {
144+ heap . push ( pointer ) ;
145+ let idx = heap . length - 1 ;
146+
147+ while ( idx > 0 ) {
148+ const parent = Math . floor ( ( idx - 1 ) / 2 ) ;
149+ if (
150+ getEpisodeTimestamp ( heap [ parent ] . episode ) >=
151+ getEpisodeTimestamp ( heap [ idx ] . episode )
152+ ) {
153+ break ;
154+ }
155+
156+ heap [ idx ] = heap [ parent ] ;
157+ heap [ parent ] = pointer ;
158+ idx = parent ;
159+ }
160+ }
161+
162+ function popEpisodePointer (
163+ heap : LatestEpisodePointer [ ] ,
164+ ) : LatestEpisodePointer | undefined {
165+ if ( heap . length === 0 ) return undefined ;
166+
167+ const top = heap [ 0 ] ;
168+ const last = heap . pop ( ) ;
169+
170+ if ( last && heap . length > 0 ) {
171+ heap [ 0 ] = last ;
172+ let idx = 0 ;
173+
174+ while ( true ) {
175+ const left = idx * 2 + 1 ;
176+ const right = idx * 2 + 2 ;
177+ let largest = idx ;
178+
179+ if (
180+ left < heap . length &&
181+ getEpisodeTimestamp ( heap [ left ] . episode ) >
182+ getEpisodeTimestamp ( heap [ largest ] . episode )
183+ ) {
184+ largest = left ;
185+ }
186+
187+ if (
188+ right < heap . length &&
189+ getEpisodeTimestamp ( heap [ right ] . episode ) >
190+ getEpisodeTimestamp ( heap [ largest ] . episode )
191+ ) {
192+ largest = right ;
193+ }
194+
195+ if ( largest === idx ) break ;
196+
197+ const temp = heap [ idx ] ;
198+ heap [ idx ] = heap [ largest ] ;
199+ heap [ largest ] = temp ;
200+ idx = largest ;
201+ }
202+ }
203+
204+ return top ;
205+ }
206+
207+ // Use a max-heap to merge the latest episodes from each feed without
208+ // resorting the entire cache every time a single feed updates.
209+ function mergeLatestEpisodes ( latestByFeed : LatestEpisodesByFeed ) : Episode [ ] {
210+ const heap : LatestEpisodePointer [ ] = [ ] ;
211+
212+ for ( const [ feedTitle , episodes ] of latestByFeed . entries ( ) ) {
213+ if ( ! episodes . length ) continue ;
214+
215+ pushEpisodePointer ( heap , {
216+ feedTitle,
217+ index : 0 ,
218+ episode : episodes [ 0 ] ,
219+ } ) ;
220+ }
221+
222+ const merged : Episode [ ] = [ ] ;
223+ while ( heap . length > 0 ) {
224+ const pointer = popEpisodePointer ( heap ) ;
225+ if ( ! pointer ) break ;
226+
227+ merged . push ( pointer . episode ) ;
228+
229+ const feedEpisodes = latestByFeed . get ( pointer . feedTitle ) ;
230+ const nextIndex = pointer . index + 1 ;
231+ if ( feedEpisodes && nextIndex < feedEpisodes . length ) {
232+ pushEpisodePointer ( heap , {
233+ feedTitle : pointer . feedTitle ,
234+ index : nextIndex ,
235+ episode : feedEpisodes [ nextIndex ] ,
236+ } ) ;
237+ }
238+ }
239+
240+ return merged ;
241+ }
242+
243+ export const latestEpisodes = readable < Episode [ ] > ( [ ] , ( set ) => {
244+ let latestByFeed : LatestEpisodesByFeed = new Map ( ) ;
245+ let feedSources : FeedEpisodeSources = new Map ( ) ;
246+
247+ const unsubscribe = episodeCache . subscribe ( ( cache ) => {
248+ let changed = false ;
249+ const nextSources : FeedEpisodeSources = new Map ( ) ;
250+ const nextLatestByFeed : LatestEpisodesByFeed = new Map ( ) ;
251+
252+ for ( const [ feedTitle , episodes ] of Object . entries ( cache ) ) {
253+ nextSources . set ( feedTitle , episodes ) ;
254+ const previousSource = feedSources . get ( feedTitle ) ;
255+ const previousLatest = latestByFeed . get ( feedTitle ) ;
256+
257+ if ( previousSource === episodes && previousLatest ) {
258+ nextLatestByFeed . set ( feedTitle , previousLatest ) ;
259+ continue ;
260+ }
261+
262+ const latestForFeed = getLatestEpisodesForFeed ( episodes ) ;
263+ nextLatestByFeed . set ( feedTitle , latestForFeed ) ;
264+
265+ if ( ! changed ) {
266+ changed =
267+ ! previousLatest ||
268+ ! shallowEqualEpisodes ( previousLatest , latestForFeed ) ;
269+ }
270+ }
271+
272+ if ( ! changed ) {
273+ for ( const feedTitle of feedSources . keys ( ) ) {
274+ if ( ! nextSources . has ( feedTitle ) ) {
275+ changed = true ;
276+ break ;
277+ }
278+ }
279+ }
280+
281+ feedSources = nextSources ;
282+
283+ if ( ! changed && nextLatestByFeed . size === latestByFeed . size ) {
284+ latestByFeed = nextLatestByFeed ;
285+ return ;
286+ }
287+
288+ latestByFeed = nextLatestByFeed ;
289+ set ( mergeLatestEpisodes ( latestByFeed ) ) ;
290+ } ) ;
291+
292+ return ( ) => {
293+ latestByFeed . clear ( ) ;
294+ feedSources . clear ( ) ;
295+ unsubscribe ( ) ;
296+ } ;
297+ } ) ;
298+
105299export const downloadedEpisodes = ( ( ) => {
106300 const store = writable < { [ podcastName : string ] : DownloadedEpisode [ ] } > ( { } ) ;
107301 const { subscribe, update, set } = store ;
0 commit comments