Skip to content

Commit

Permalink
Caching of album extra data.
Browse files Browse the repository at this point in the history
  • Loading branch information
Kern, Thomas committed Jan 15, 2024
1 parent abae28c commit 77defa5
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 41 deletions.
89 changes: 52 additions & 37 deletions lib/services/mopidy_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<AlbumInfoExtraData>(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:
Expand Down Expand Up @@ -363,6 +355,7 @@ class MopidyServiceImpl extends MopidyService {

@override
Future<bool> connect(String uri, {int? maxRetries}) async {
_albumDataCache.clear();
_mopidy.disconnect();
_connected = false;
Globals.logger.i("Connecting to $uri");
Expand All @@ -387,18 +380,16 @@ class MopidyServiceImpl extends MopidyService {
var refs = await _mopidy.library.browse(parent?.uri);
// lookup and add album extra info
List<String> uris = refs.map((e) => e.type == Ref.typeAlbum ? e.uri : null).nonNulls.toList();

if (uris.isNotEmpty) {
Map<String, List<Track>> 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);
}
}
}
Expand All @@ -409,6 +400,30 @@ class MopidyServiceImpl extends MopidyService {
});
}

_loadAlbumExtraData(List<String> uris) async {
var notCached = uris.map((uri) => !_albumDataCache.contains(uri) ? uri : null).nonNulls.toList();
if (notCached.isNotEmpty) {
_albumDataCache.putAll(await _getAlbumExtraInfo(notCached));
}
}

Future<Map<String, AlbumInfoExtraData>> _getAlbumExtraInfo(List<String> uris) async {
var result = <String, AlbumInfoExtraData>{};
Map<String, List<Track>> 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<List<T>> flatten<T>(List<T> items, {Ref? playlist}) async {
return waitConnected().then((_) async {
Expand Down Expand Up @@ -739,20 +754,20 @@ class MopidyServiceImpl extends MopidyService {

@override
Future<bool> isMuted() {
if (savedMuteState == null) {
if (_savedMuteState == null) {
return waitConnected().then((_) async {
try {
setBusy(true);
savedMuteState = await _mopidy.mixer.getMute();
_muteChangedNotifier.value = savedMuteState!;
return Future<bool>.value(savedMuteState);
_savedMuteState = await _mopidy.mixer.getMute();
_muteChangedNotifier.value = _savedMuteState!;
return Future<bool>.value(_savedMuteState);
} finally {
setBusy(false);
}
});
} else {
savedMuteState = _muteChangedNotifier.value;
return Future<bool>.value(savedMuteState);
_savedMuteState = _muteChangedNotifier.value;
return Future<bool>.value(_savedMuteState);
}
}

Expand Down Expand Up @@ -782,20 +797,20 @@ class MopidyServiceImpl extends MopidyService {

@override
Future<int> getVolume() {
if (savedVolume == null) {
if (_savedVolume == null) {
return waitConnected().then((_) async {
try {
setBusy(true);
savedVolume = await _mopidy.mixer.getVolume();
_volumeChangedNotifier.value = savedVolume!;
return Future<int>.value(savedVolume);
_savedVolume = await _mopidy.mixer.getVolume();
_volumeChangedNotifier.value = _savedVolume!;
return Future<int>.value(_savedVolume);
} finally {
setBusy(false);
}
});
} else {
savedVolume = _volumeChangedNotifier.value;
return Future<int>.value(savedVolume);
_savedVolume = _volumeChangedNotifier.value;
return Future<int>.value(_savedVolume);
}
}

Expand Down
18 changes: 14 additions & 4 deletions lib/utils/cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class _CacheItem<E> {
late int _age;
final String key;
final E data;

_CacheItem(this.key, this.data) {
_age = _ageCounter++;
}
Expand All @@ -37,30 +38,39 @@ class _CacheItem<E> {
class Cache<E> {
final int maxSize;
final int minSize;

Cache(this.minSize, this.maxSize) {
assert(maxSize > minSize + 2 * ((maxSize * 0.2).toInt()));
}

final _cache = <String, _CacheItem<E>>{};

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++) {
_cache.remove(sortedValues[i].key);
}
}

void clear() {
_cache.clear();
}

void put(String key, E value) {
_cache.update(key, (v) => _CacheItem(key, value),
ifAbsent: () => _CacheItem<E>(key, value));
_cache.update(key, (v) => _CacheItem(key, value), ifAbsent: () => _CacheItem<E>(key, value));
if (_cache.length >= maxSize) {
_shrink();
}
}

void putAll(Map<String, E> data) {
for (var entry in data.entries) {
put(entry.key, entry.value);
}
}

E? get(String key) {
var item = _cache[key];
if (item != null) {
Expand Down

0 comments on commit 77defa5

Please sign in to comment.