From 77defa5558b050678e69834b538f239148394017 Mon Sep 17 00:00:00 2001 From: "Kern, Thomas" Date: Tue, 16 Jan 2024 00:02:34 +0100 Subject: [PATCH] Caching of album extra data. --- lib/services/mopidy_service.dart | 89 +++++++++++++++++++------------- lib/utils/cache.dart | 18 +++++-- 2 files changed, 66 insertions(+), 41 deletions(-) diff --git a/lib/services/mopidy_service.dart b/lib/services/mopidy_service.dart index e73564f..01041b8 100644 --- a/lib/services/mopidy_service.dart +++ b/lib/services/mopidy_service.dart @@ -24,6 +24,7 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart' show BuildContext, ValueNotifier; +import 'package:mopicon/utils/cache.dart'; import 'package:mopicon/components/error_snackbar.dart'; import 'package:mopicon/extensions/mopidy_utils.dart'; import 'package:mopicon/common/selected_item_positions.dart'; @@ -245,24 +246,15 @@ class MopidyServiceImpl extends MopidyService { int _busyLevel = 0; // cached current volume - int? savedVolume; + int? _savedVolume; // cached current mute state - bool? savedMuteState; + bool? _savedMuteState; + + // cached album extra info + final _albumDataCache = Cache(1000, 2000); MopidyServiceImpl() : _mopidy = Mopidy(logger: Globals.logger, backoffDelayMin: 500, backoffDelayMax: 2000) { - /* - SystemChannels.lifecycle.setMessageHandler((msg) async { - if (msg == 'AppLifecycleState.resumed') { - if (_lastConnectedUri == _currentUri) { - print("RESUMED"); - Globals.logger.i("Reonnecting to $_lastConnectedUri"); - connect(_currentUri); - } - } - return Future.value(null); - }); -*/ _mopidy.clientState$.listen((value) { switch (value.state) { case ClientState.online: @@ -363,6 +355,7 @@ class MopidyServiceImpl extends MopidyService { @override Future connect(String uri, {int? maxRetries}) async { + _albumDataCache.clear(); _mopidy.disconnect(); _connected = false; Globals.logger.i("Connecting to $uri"); @@ -387,18 +380,16 @@ class MopidyServiceImpl extends MopidyService { var refs = await _mopidy.library.browse(parent?.uri); // lookup and add album extra info List uris = refs.map((e) => e.type == Ref.typeAlbum ? e.uri : null).nonNulls.toList(); - if (uris.isNotEmpty) { - Map> trackMap = await _mopidy.library.lookup(uris); - if (trackMap.isNotEmpty) { - for (var ref in refs) { - var tracks = trackMap[ref.uri]; - if (tracks != null && tracks.isNotEmpty) { - Album? album = tracks.first.album; - if (album != null) { - ref.extraData = AlbumInfoExtraData(album); - } - } + // warm up cache + _loadAlbumExtraData(uris); + for (var ref in refs) { + var info = _albumDataCache.get(ref.uri); + // cache miss + info = info ?? (await _getAlbumExtraInfo([ref.uri]))[ref.uri]; + if (info != null) { + ref.extraData = info; + _albumDataCache.put(ref.uri, ref.extraData); } } } @@ -409,6 +400,30 @@ class MopidyServiceImpl extends MopidyService { }); } + _loadAlbumExtraData(List uris) async { + var notCached = uris.map((uri) => !_albumDataCache.contains(uri) ? uri : null).nonNulls.toList(); + if (notCached.isNotEmpty) { + _albumDataCache.putAll(await _getAlbumExtraInfo(notCached)); + } + } + + Future> _getAlbumExtraInfo(List uris) async { + var result = {}; + Map> trackMap = await _mopidy.library.lookup(uris); + if (trackMap.isNotEmpty) { + for (var uri in uris) { + var tracks = trackMap[uri]; + if (tracks != null && tracks.isNotEmpty) { + Album? album = tracks.first.album; + if (album != null) { + result[uri] = AlbumInfoExtraData(album); + } + } + } + } + return result; + } + @override Future> flatten(List items, {Ref? playlist}) async { return waitConnected().then((_) async { @@ -739,20 +754,20 @@ class MopidyServiceImpl extends MopidyService { @override Future isMuted() { - if (savedMuteState == null) { + if (_savedMuteState == null) { return waitConnected().then((_) async { try { setBusy(true); - savedMuteState = await _mopidy.mixer.getMute(); - _muteChangedNotifier.value = savedMuteState!; - return Future.value(savedMuteState); + _savedMuteState = await _mopidy.mixer.getMute(); + _muteChangedNotifier.value = _savedMuteState!; + return Future.value(_savedMuteState); } finally { setBusy(false); } }); } else { - savedMuteState = _muteChangedNotifier.value; - return Future.value(savedMuteState); + _savedMuteState = _muteChangedNotifier.value; + return Future.value(_savedMuteState); } } @@ -782,20 +797,20 @@ class MopidyServiceImpl extends MopidyService { @override Future getVolume() { - if (savedVolume == null) { + if (_savedVolume == null) { return waitConnected().then((_) async { try { setBusy(true); - savedVolume = await _mopidy.mixer.getVolume(); - _volumeChangedNotifier.value = savedVolume!; - return Future.value(savedVolume); + _savedVolume = await _mopidy.mixer.getVolume(); + _volumeChangedNotifier.value = _savedVolume!; + return Future.value(_savedVolume); } finally { setBusy(false); } }); } else { - savedVolume = _volumeChangedNotifier.value; - return Future.value(savedVolume); + _savedVolume = _volumeChangedNotifier.value; + return Future.value(_savedVolume); } } diff --git a/lib/utils/cache.dart b/lib/utils/cache.dart index 32b15fb..8cb70df 100644 --- a/lib/utils/cache.dart +++ b/lib/utils/cache.dart @@ -26,6 +26,7 @@ class _CacheItem { late int _age; final String key; final E data; + _CacheItem(this.key, this.data) { _age = _ageCounter++; } @@ -37,6 +38,7 @@ class _CacheItem { class Cache { final int maxSize; final int minSize; + Cache(this.minSize, this.maxSize) { assert(maxSize > minSize + 2 * ((maxSize * 0.2).toInt())); } @@ -44,8 +46,7 @@ class Cache { final _cache = >{}; void _shrink() { - var sortedValues = _cache.values.toList() - ..sort((e1, e2) => e1._age.compareTo(e2._age)); + var sortedValues = _cache.values.toList()..sort((e1, e2) => e1._age.compareTo(e2._age)); int nr = (maxSize * 0.2).toInt(); // shrink by 20% // remove nr items which were accessed least for (int i = 0; i < nr; i++) { @@ -53,14 +54,23 @@ class Cache { } } + void clear() { + _cache.clear(); + } + void put(String key, E value) { - _cache.update(key, (v) => _CacheItem(key, value), - ifAbsent: () => _CacheItem(key, value)); + _cache.update(key, (v) => _CacheItem(key, value), ifAbsent: () => _CacheItem(key, value)); if (_cache.length >= maxSize) { _shrink(); } } + void putAll(Map data) { + for (var entry in data.entries) { + put(entry.key, entry.value); + } + } + E? get(String key) { var item = _cache[key]; if (item != null) {