Skip to content

Commit 0dbd6cc

Browse files
committed
cache
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
1 parent 69337c3 commit 0dbd6cc

File tree

3 files changed

+45
-4
lines changed

3 files changed

+45
-4
lines changed

Core/Services/API/APICache.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ final class APICache {
3131
static let playlist: TimeInterval = 30 * 60 // 30 minutes
3232
static let artist: TimeInterval = 60 * 60 // 1 hour
3333
static let search: TimeInterval = 2 * 60 // 2 minutes
34+
static let library: TimeInterval = 5 * 60 // 5 minutes
35+
static let lyrics: TimeInterval = 24 * 60 * 60 // 24 hours
36+
static let songMetadata: TimeInterval = 30 * 60 // 30 minutes
3437
}
3538

3639
/// Maximum number of cached entries before LRU eviction kicks in.

Core/Services/API/YTMusicClient.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ final class YTMusicClient: YTMusicClientProtocol {
194194
"browseId": "FEmusic_liked_playlists",
195195
]
196196

197-
let data = try await request("browse", body: body)
197+
let data = try await request("browse", body: body, ttl: APICache.TTL.library)
198198
let playlists = PlaylistParser.parseLibraryPlaylists(data)
199199
logger.info("Parsed \(playlists.count) library playlists")
200200
return playlists
@@ -208,7 +208,7 @@ final class YTMusicClient: YTMusicClientProtocol {
208208
"browseId": "FEmusic_liked_videos",
209209
]
210210

211-
let data = try await request("browse", body: body)
211+
let data = try await request("browse", body: body, ttl: APICache.TTL.library)
212212
let detail = PlaylistParser.parsePlaylistDetail(data, playlistId: "LM")
213213
logger.info("Parsed \(detail.tracks.count) liked songs")
214214
return detail.tracks
@@ -312,7 +312,7 @@ final class YTMusicClient: YTMusicClientProtocol {
312312
"browseId": lyricsBrowseId,
313313
]
314314

315-
let browseData = try await request("browse", body: browseBody)
315+
let browseData = try await request("browse", body: browseBody, ttl: APICache.TTL.lyrics)
316316
let lyrics = parseLyrics(from: browseData)
317317
logger.info("Fetched lyrics for \(videoId): \(lyrics.isAvailable ? "available" : "unavailable")")
318318
return lyrics
@@ -406,7 +406,7 @@ final class YTMusicClient: YTMusicClientProtocol {
406406
"tunerSettingValue": "AUTOMIX_SETTING_NORMAL",
407407
]
408408

409-
let data = try await request("next", body: body)
409+
let data = try await request("next", body: body, ttl: APICache.TTL.songMetadata)
410410
return try parseSongMetadata(data, videoId: videoId)
411411
}
412412

@@ -637,6 +637,8 @@ final class YTMusicClient: YTMusicClientProtocol {
637637

638638
// Invalidate liked playlist cache so UI updates immediately
639639
APICache.shared.invalidate(matching: "browse:")
640+
// Invalidate song metadata cache (next: endpoint)
641+
APICache.shared.invalidate(matching: "next:")
640642
}
641643

642644
/// Adds or removes a song from the user's library.
@@ -655,6 +657,10 @@ final class YTMusicClient: YTMusicClientProtocol {
655657

656658
_ = try await request("feedback", body: body)
657659
logger.info("Successfully edited library status")
660+
661+
// Invalidate library and song metadata cache so UI updates
662+
APICache.shared.invalidate(matching: "browse:")
663+
APICache.shared.invalidate(matching: "next:")
658664
}
659665

660666
/// Adds a playlist to the user's library using the like/like endpoint.

Tests/KasetTests/APICacheTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,38 @@ final class APICacheTests: XCTestCase {
8383
XCTAssertEqual(APICache.TTL.playlist, 30 * 60) // 30 minutes
8484
XCTAssertEqual(APICache.TTL.artist, 60 * 60) // 1 hour
8585
XCTAssertEqual(APICache.TTL.search, 2 * 60) // 2 minutes
86+
XCTAssertEqual(APICache.TTL.library, 5 * 60) // 5 minutes
87+
XCTAssertEqual(APICache.TTL.lyrics, 24 * 60 * 60) // 24 hours
88+
XCTAssertEqual(APICache.TTL.songMetadata, 30 * 60) // 30 minutes
89+
}
90+
91+
func testLyricsCacheNotInvalidatedByMutations() {
92+
// Lyrics use browse: prefix but should NOT be invalidated by mutation operations
93+
// This test verifies that invalidating next: prefix doesn't affect lyrics
94+
cache.set(key: "browse:lyrics_abc123", data: ["text": "lyrics content"], ttl: APICache.TTL.lyrics)
95+
cache.set(key: "next:song_abc123", data: ["title": "song"], ttl: APICache.TTL.songMetadata)
96+
97+
// Simulate mutation invalidation (like rateSong would do)
98+
cache.invalidate(matching: "next:")
99+
100+
// Lyrics should still be cached (browse: prefix not invalidated)
101+
XCTAssertNotNil(cache.get(key: "browse:lyrics_abc123"))
102+
// Song metadata should be invalidated
103+
XCTAssertNil(cache.get(key: "next:song_abc123"))
104+
}
105+
106+
func testSongMetadataCacheInvalidatedByMutations() {
107+
// Song metadata uses next: prefix and should be invalidated by mutations
108+
cache.set(key: "next:song_abc123", data: ["title": "song"], ttl: APICache.TTL.songMetadata)
109+
cache.set(key: "browse:home_section", data: ["section": "home"], ttl: APICache.TTL.home)
110+
111+
// Simulate mutation invalidation for both prefixes
112+
cache.invalidate(matching: "browse:")
113+
cache.invalidate(matching: "next:")
114+
115+
// Both should be invalidated
116+
XCTAssertNil(cache.get(key: "next:song_abc123"))
117+
XCTAssertNil(cache.get(key: "browse:home_section"))
86118
}
87119

88120
func testCacheEntryIsExpired() {

0 commit comments

Comments
 (0)