import 'package:freezer/api/definitions.dart'; import 'package:freezer/api/paths.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'dart:async'; part 'cache.g.dart'; late Cache cache; class CacheEntryAdapter extends TypeAdapter { @override final int typeId = 20; @override CacheEntry read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return CacheEntry( fields[0], updatedAt: fields[1] as DateTime?, ); } @override void write(BinaryWriter writer, CacheEntry obj) { writer ..writeByte(2) ..writeByte(0) ..write(obj.value) ..writeByte(1) ..write(obj.updatedAt); } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is CacheEntryAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } class CacheEntry { final T value; final DateTime updatedAt; CacheEntry(this.value, {DateTime? updatedAt}) : updatedAt = updatedAt ?? DateTime.now(); } //Cache for miscellaneous things @HiveType(typeId: 22) class Cache { static Future> get _box async => Hive.openLazyBox('metacache', path: await Paths.cacheDir()); //ID's of tracks that are in library @HiveField(0, defaultValue: []) List libraryTracks = []; //Track ID of logged track, to prevent duplicates @HiveField(1) String? loggedTrackId; @HiveField(2) List history = []; //All sorting cached @HiveField(3) List sorts = []; //Sleep timer DateTime? sleepTimerTime; // ignore: cancel_subscriptions StreamSubscription? sleepTimer; //Search history @HiveField(4) List searchHistory = []; //If download threads warning was shown @HiveField(5) bool threadsWarning = false; //Last time update check @HiveField(6) DateTime? lastUpdateCheck; @HiveField(7) String? favoritesPlaylistId; @HiveField(8, defaultValue: false) bool canStreamHQ = false; @HiveField(9, defaultValue: false) bool canStreamLossless = false; bool wakelock = false; @HiveField(10, defaultValue: null) CacheEntry>? favoritePlaylists; @HiveField(11, defaultValue: null) CacheEntry>? favoriteArtists; @HiveField(12, defaultValue: null) CacheEntry>? favoriteAlbums; @HiveField(13, defaultValue: null) CacheEntry>? favoriteTracks; Cache(); //Wrapper to test if track is favorite against cache bool checkTrackFavorite(Track t) { if (t.favorite != null && t.favorite!) return true; if (libraryTracks.isEmpty) return false; return libraryTracks.contains(t.id); } /// Add [item] to the corresponding favorite* cached item void addFavorite(DeezerMediaItem item) { switch (item) { case final Track track: favoriteTracks?.value.add(track); libraryTracks.add(track.id); break; case final Album album: favoriteAlbums?.value[album.id!] = album; break; case final Playlist playlist: favoritePlaylists?.value[playlist.id] = playlist; break; case final Artist artist: favoriteArtists?.value[artist.id] = artist; break; } } //Add to history void addToSearchHistory(DeezerMediaItem item) async { // Remove duplicate int i = searchHistory.indexWhere((e) => e.id == item.id); if (i != -1) { searchHistory.removeAt(i); } searchHistory.add(item); await save(); } //Save, load static Future wipe() async { await (await _box).clear(); } // TODO: improve static Future load([int tryN = 0]) async { final box = await _box; if (tryN > 1) { // recursion error, something's wrong throw Exception("can't load cache"); } if (box.isNotEmpty) { try { return (await box.get(0))!; } catch (e) { // invalidate cache on error await box.clear(); } } // create cache if non-existent or error occurred final c = Cache(); await c.save(); return c; } Future save() async { final box = await _box; await box.clear(); await box.put(0, this); } } @deprecated @HiveType(typeId: 21) enum SearchHistoryItemType { @HiveField(0) track, @HiveField(1) album, @HiveField(2) artist, @HiveField(3) playlist }