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,170 @@ 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+
111+ function getEpisodeTimestamp ( episode ?: Episode ) : number {
112+ if ( ! episode ?. episodeDate ) return 0 ;
113+
114+ return Number ( episode . episodeDate ) ;
115+ }
116+
117+ function getLatestEpisodesForFeed ( episodes : Episode [ ] ) : Episode [ ] {
118+ if ( ! episodes ?. length ) return [ ] ;
119+
120+ return episodes
121+ . slice ( 0 , LATEST_EPISODES_PER_FEED )
122+ . sort ( ( a , b ) => getEpisodeTimestamp ( b ) - getEpisodeTimestamp ( a ) ) ;
123+ }
124+
125+ function shallowEqualEpisodes ( a ?: Episode [ ] , b ?: Episode [ ] ) : boolean {
126+ if ( ! a || ! b || a . length !== b . length ) return false ;
127+
128+ for ( let i = 0 ; i < a . length ; i += 1 ) {
129+ if ( a [ i ] !== b [ i ] ) return false ;
130+ }
131+
132+ return true ;
133+ }
134+
135+ const latestEpisodeIdentifier = ( episode : Episode ) : string =>
136+ `${ episode . podcastName } ::${ episode . title } ` ;
137+
138+ function insertEpisodeSorted (
139+ episodes : Episode [ ] ,
140+ episodeToInsert : Episode ,
141+ limit : number ,
142+ ) : Episode [ ] {
143+ const nextEpisodes = [ ...episodes ] ;
144+ const value = getEpisodeTimestamp ( episodeToInsert ) ;
145+ let low = 0 ;
146+ let high = nextEpisodes . length ;
147+
148+ while ( low < high ) {
149+ const mid = ( low + high ) >> 1 ;
150+ const midValue = getEpisodeTimestamp ( nextEpisodes [ mid ] ) ;
151+
152+ if ( value > midValue ) {
153+ high = mid ;
154+ } else {
155+ low = mid + 1 ;
156+ }
157+ }
158+
159+ nextEpisodes . splice ( low , 0 , episodeToInsert ) ;
160+
161+ if ( nextEpisodes . length > limit ) {
162+ nextEpisodes . length = limit ;
163+ }
164+
165+ return nextEpisodes ;
166+ }
167+
168+ function removeFeedEntries (
169+ currentLatest : Episode [ ] ,
170+ feedEpisodes : Episode [ ] | undefined = [ ] ,
171+ ) : Episode [ ] {
172+ if ( ! feedEpisodes ?. length ) {
173+ return currentLatest ;
174+ }
175+
176+ const feedKeys = new Set ( feedEpisodes . map ( latestEpisodeIdentifier ) ) ;
177+
178+ return currentLatest . filter (
179+ ( episode ) => ! feedKeys . has ( latestEpisodeIdentifier ( episode ) ) ,
180+ ) ;
181+ }
182+
183+ function updateLatestEpisodesForFeed (
184+ currentLatest : Episode [ ] ,
185+ previousFeedEpisodes : Episode [ ] | undefined ,
186+ nextFeedEpisodes : Episode [ ] | undefined ,
187+ limit : number ,
188+ ) : Episode [ ] {
189+ let nextLatest = removeFeedEntries ( currentLatest , previousFeedEpisodes ) ;
190+
191+ if ( ! nextFeedEpisodes ?. length ) {
192+ return nextLatest ;
193+ }
194+
195+ for ( const episode of nextFeedEpisodes ) {
196+ nextLatest = insertEpisodeSorted ( nextLatest , episode , limit ) ;
197+ }
198+
199+ return nextLatest ;
200+ }
201+
202+ export const latestEpisodes = readable < Episode [ ] > ( [ ] , ( set ) => {
203+ let latestByFeed : LatestEpisodesByFeed = new Map ( ) ;
204+ let feedSources : FeedEpisodeSources = new Map ( ) ;
205+ let mergedLatest : Episode [ ] = [ ] ;
206+
207+ const unsubscribe = episodeCache . subscribe ( ( cache ) => {
208+ const cacheEntries = Object . entries ( cache ) ;
209+ const feedCount = cacheEntries . length ;
210+ const latestLimit = Math . max (
211+ 1 ,
212+ LATEST_EPISODES_PER_FEED * Math . max ( feedCount , 1 ) ,
213+ ) ;
214+
215+ let changed = false ;
216+ let nextMerged = mergedLatest ;
217+ const nextSources : FeedEpisodeSources = new Map ( ) ;
218+ const nextLatestByFeed : LatestEpisodesByFeed = new Map ( ) ;
219+
220+ for ( const [ feedTitle , episodes ] of cacheEntries ) {
221+ nextSources . set ( feedTitle , episodes ) ;
222+ const previousSource = feedSources . get ( feedTitle ) ;
223+ const previousLatest = latestByFeed . get ( feedTitle ) || [ ] ;
224+
225+ const nextLatestForFeed =
226+ previousSource === episodes && previousLatest
227+ ? previousLatest
228+ : getLatestEpisodesForFeed ( episodes ) ;
229+
230+ nextLatestByFeed . set ( feedTitle , nextLatestForFeed ) ;
231+
232+ if ( ! shallowEqualEpisodes ( previousLatest , nextLatestForFeed ) ) {
233+ changed = true ;
234+ nextMerged = updateLatestEpisodesForFeed (
235+ nextMerged ,
236+ previousLatest ,
237+ nextLatestForFeed ,
238+ latestLimit ,
239+ ) ;
240+ }
241+ }
242+
243+ for ( const feedTitle of latestByFeed . keys ( ) ) {
244+ if ( ! nextSources . has ( feedTitle ) ) {
245+ changed = true ;
246+ nextMerged = removeFeedEntries (
247+ nextMerged ,
248+ latestByFeed . get ( feedTitle ) ,
249+ ) ;
250+ }
251+ }
252+
253+ feedSources = nextSources ;
254+ latestByFeed = nextLatestByFeed ;
255+
256+ if ( changed ) {
257+ mergedLatest = nextMerged ;
258+ set ( mergedLatest ) ;
259+ }
260+ } ) ;
261+
262+ return ( ) => {
263+ latestByFeed . clear ( ) ;
264+ feedSources . clear ( ) ;
265+ mergedLatest = [ ] ;
266+ unsubscribe ( ) ;
267+ } ;
268+ } ) ;
269+
105270export const downloadedEpisodes = ( ( ) => {
106271 const store = writable < { [ podcastName : string ] : DownloadedEpisode [ ] } > ( { } ) ;
107272 const { subscribe, update, set } = store ;
0 commit comments