perf: optimize cache cleanup and reduce unnecessary widget rebuilds

This commit is contained in:
zarzet
2026-01-31 14:29:14 +07:00
parent 24c2fd6a15
commit 99281df5fb
3 changed files with 62 additions and 14 deletions
+52 -9
View File
@@ -17,6 +17,9 @@ type TrackIDCache struct {
cache map[string]*TrackIDCacheEntry
mu sync.RWMutex
ttl time.Duration
// Cleanup is triggered on writes at a fixed interval to avoid unbounded growth.
lastCleanup time.Time
cleanupInterval time.Duration
}
var (
@@ -27,8 +30,9 @@ var (
func GetTrackIDCache() *TrackIDCache {
trackIDCacheOnce.Do(func() {
globalTrackIDCache = &TrackIDCache{
cache: make(map[string]*TrackIDCacheEntry),
ttl: 30 * time.Minute,
cache: make(map[string]*TrackIDCacheEntry),
ttl: 30 * time.Minute,
cleanupInterval: 5 * time.Minute,
}
})
return globalTrackIDCache
@@ -36,13 +40,34 @@ func GetTrackIDCache() *TrackIDCache {
func (c *TrackIDCache) Get(isrc string) *TrackIDCacheEntry {
c.mu.RLock()
defer c.mu.RUnlock()
entry, exists := c.cache[isrc]
if !exists || time.Now().After(entry.ExpiresAt) {
if !exists {
c.mu.RUnlock()
return nil
}
return entry
expired := time.Now().After(entry.ExpiresAt)
c.mu.RUnlock()
if !expired {
return entry
}
// Lazily delete expired entry.
c.mu.Lock()
entry, exists = c.cache[isrc]
if exists && time.Now().After(entry.ExpiresAt) {
delete(c.cache, isrc)
}
c.mu.Unlock()
return nil
}
func (c *TrackIDCache) pruneExpiredLocked(now time.Time) {
for key, entry := range c.cache {
if now.After(entry.ExpiresAt) {
delete(c.cache, key)
}
}
}
func (c *TrackIDCache) SetTidal(isrc string, trackID int64) {
@@ -55,7 +80,13 @@ func (c *TrackIDCache) SetTidal(isrc string, trackID int64) {
c.cache[isrc] = entry
}
entry.TidalTrackID = trackID
entry.ExpiresAt = time.Now().Add(c.ttl)
now := time.Now()
entry.ExpiresAt = now.Add(c.ttl)
if c.cleanupInterval > 0 && (c.lastCleanup.IsZero() || now.Sub(c.lastCleanup) >= c.cleanupInterval) {
c.pruneExpiredLocked(now)
c.lastCleanup = now
}
}
func (c *TrackIDCache) SetQobuz(isrc string, trackID int64) {
@@ -68,7 +99,13 @@ func (c *TrackIDCache) SetQobuz(isrc string, trackID int64) {
c.cache[isrc] = entry
}
entry.QobuzTrackID = trackID
entry.ExpiresAt = time.Now().Add(c.ttl)
now := time.Now()
entry.ExpiresAt = now.Add(c.ttl)
if c.cleanupInterval > 0 && (c.lastCleanup.IsZero() || now.Sub(c.lastCleanup) >= c.cleanupInterval) {
c.pruneExpiredLocked(now)
c.lastCleanup = now
}
}
func (c *TrackIDCache) SetAmazon(isrc string, trackID string) {
@@ -81,7 +118,13 @@ func (c *TrackIDCache) SetAmazon(isrc string, trackID string) {
c.cache[isrc] = entry
}
entry.AmazonTrackID = trackID
entry.ExpiresAt = time.Now().Add(c.ttl)
now := time.Now()
entry.ExpiresAt = now.Add(c.ttl)
if c.cleanupInterval > 0 && (c.lastCleanup.IsZero() || now.Sub(c.lastCleanup) >= c.cleanupInterval) {
c.pruneExpiredLocked(now)
c.lastCleanup = now
}
}
func (c *TrackIDCache) Clear() {
+2 -1
View File
@@ -43,6 +43,8 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> {
void initState() {
super.initState();
_initializeExtensions();
// Trigger history provider initialization without subscribing to updates.
ref.read(downloadHistoryProvider);
}
Future<void> _initializeExtensions() async {
@@ -62,7 +64,6 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> {
@override
Widget build(BuildContext context) {
ref.watch(downloadHistoryProvider);
return widget.child;
}
}
+8 -4
View File
@@ -2430,12 +2430,16 @@ class _TrackItemWithStatus extends ConsumerWidget {
double thumbWidth = 56;
double thumbHeight = 56;
final trackState = ref.watch(trackProvider);
final extensionId = track.source ?? trackState.searchExtensionId;
final searchExtensionId =
ref.watch(trackProvider.select((s) => s.searchExtensionId));
final extensionId = track.source ?? searchExtensionId;
if (extensionId != null && extensionId.isNotEmpty) {
final extState = ref.watch(extensionProvider);
final extension = extState.extensions.where((e) => e.id == extensionId).firstOrNull;
final extension = ref.watch(
extensionProvider.select(
(s) => s.extensions.where((e) => e.id == extensionId).firstOrNull,
),
);
if (extension?.searchBehavior != null) {
final size = extension!.searchBehavior!.getThumbnailSize(defaultSize: 56);
thumbWidth = size.$1;