Skip to content

Commit

Permalink
perf: migrate track stats to a db
Browse files Browse the repository at this point in the history
  • Loading branch information
MSOB7YY committed Sep 23, 2024
1 parent 884f887 commit 2ef6cd7
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 50 deletions.
50 changes: 40 additions & 10 deletions lib/class/track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,29 +97,58 @@ class TrackStats {
required this.lastPositionInMs,
});

static List<String>? _parseList(dynamic listJson) {
if (listJson is List && listJson.isNotEmpty) {
return listJson.cast<String>();
}
return null;
}

static List<String>? _cleanList(List<String> current) {
if (current.isEmpty || (current.length == 1 && current[0].isEmpty)) return null;
return current;
}

factory TrackStats.fromJson(Map<String, dynamic> json) {
final track = Track.fromJson(json['track'] ?? '', isVideo: json['v'] == true);
return TrackStats.fromJsonWithoutTrack(track, json);
}

factory TrackStats.fromJsonWithoutTrack(Track track, Map<String, dynamic> json) {
return TrackStats(
track: Track.fromJson(json['track'] ?? '', isVideo: json['v'] == true),
track: track,
rating: json['rating'] ?? 0,
tags: (json['tags'] as List?)?.cast<String>() ?? [],
moods: (json['moods'] as List?)?.cast<String>() ?? [],
tags: _parseList(json['tags']) ?? [],
moods: _parseList(json['moods']) ?? [],
lastPositionInMs: json['lastPositionInMs'] ?? 0,
);
}

Map<String, dynamic> toJson() {
return {
'track': track.path,
'rating': rating,
'tags': tags,
'moods': moods,
'lastPositionInMs': lastPositionInMs,
...?toJsonWithoutTrack(),
};
}

Map<String, dynamic>? toJsonWithoutTrack() {
final tagsFinal = _cleanList(tags);
final moodsFinal = _cleanList(moods);
final map = {
if (rating > 0) 'rating': rating,
if (tagsFinal != null) 'tags': tagsFinal,
if (moodsFinal != null) 'moods': moodsFinal,
if (lastPositionInMs > 0) 'lastPositionInMs': lastPositionInMs,
if (track is Video) 'v': true,
};
if (map.isEmpty) return null;
return map;
}

@override
String toString() => '${track.toString()}, rating: $rating, tags: $tags, moods: $moods, lastPositionInMs: $lastPositionInMs';
String toString() {
return 'TrackStats(track: $track, rating: $rating, tags: $tags, moods: $moods, lastPositionInMs: $lastPositionInMs)';
}
}

abstract class Playable<T extends Object> {
Expand Down Expand Up @@ -291,6 +320,7 @@ class TrackExtended {
Map<String, dynamic> json, {
required ArtistsSplitConfig artistsSplitConfig,
required GenresSplitConfig genresSplitConfig,
required GeneralSplitConfig generalSplitConfig,
}) {
return TrackExtended(
title: json['title'] ?? '',
Expand All @@ -310,7 +340,7 @@ class TrackExtended {
originalMood: json['originalMood'] ?? '',
moodList: Indexer.splitGeneral(
json['originalMood'],
config: genresSplitConfig,
config: generalSplitConfig,
),
composer: json['composer'] ?? '',
trackNo: json['trackNo'] ?? 0,
Expand All @@ -335,7 +365,7 @@ class TrackExtended {
originalTags: json['originalTags'],
tagsList: Indexer.splitGeneral(
json['originalTags'],
config: genresSplitConfig,
config: generalSplitConfig,
),
gainData: json['gainData'] == null ? null : ReplayGainData.fromMap(json['gainData']),
isVideo: json['v'] ?? false,
Expand Down
3 changes: 2 additions & 1 deletion lib/controller/backup_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ class BackupController {
if (diff > interval) {
final itemsToBackup = [
AppPaths.TRACKS,
AppPaths.TRACKS_STATS,
AppPaths.TRACKS_STATS_OLD,
AppPaths.TRACKS_STATS_DB_INFO.file.path,
AppPaths.TOTAL_LISTEN_TIME,
AppPaths.VIDEOS_CACHE,
AppPaths.VIDEOS_LOCAL,
Expand Down
97 changes: 68 additions & 29 deletions lib/controller/indexer_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter/services.dart';

import 'package:audio_service/audio_service.dart';
import 'package:intl/intl.dart';
import 'package:namico_db_wrapper/namico_db_wrapper.dart';
import 'package:on_audio_query/on_audio_query.dart';

import 'package:namida/class/faudiomodel.dart';
Expand Down Expand Up @@ -38,6 +39,8 @@ class Indexer<T extends Track> {
bool get _defaultUseMediaStore => settings.useMediaStore.value;
bool get _includeVideosAsTracks => true; // TODO: settings.includeVideosAsTracks.value

late final _trackStatsDBManager = DBWrapper.openFromInfo(fileInfo: AppPaths.TRACKS_STATS_DB_INFO, createIfNotExist: true);

final isIndexing = false.obs;

final allAudioFiles = <String>{}.obs;
Expand Down Expand Up @@ -167,7 +170,7 @@ class Indexer<T extends Track> {

/// Only awaits if the track file exists, otherwise it will get into normally and start indexing.
if (File(AppPaths.TRACKS).existsAndValidSync()) {
readTrackData();
_readTrackData();
_afterIndexing();
}

Expand All @@ -178,6 +181,36 @@ class Indexer<T extends Track> {
}
}

void rebuildTracksAfterSplitConfigChanges() {
final splitConfig = _createSplitConfig();
final keysList = allTracksMappedByPath.keys.toList();
for (int i = 0; i < keysList.length; i++) {
var trPath = keysList[i];
final oldtr = allTracksMappedByPath[trPath]!;
allTracksMappedByPath[trPath] = oldtr.copyWith(
artistsList: Indexer.splitArtist(
title: oldtr.title,
originalArtist: oldtr.originalArtist,
config: splitConfig.artistsConfig,
),
genresList: Indexer.splitGenre(
oldtr.originalGenre,
config: splitConfig.genresConfig,
),
moodList: Indexer.splitGeneral(
oldtr.originalMood,
config: splitConfig.generalConfig,
),
tagsList: Indexer.splitGeneral(
oldtr.originalTags,
config: splitConfig.generalConfig,
),
);
}

tracksInfoList.refresh();
}

Future<void> refreshLibraryAndCheckForDiff({
Set<String>? currentFiles,
bool forceReIndex = false,
Expand Down Expand Up @@ -1022,19 +1055,43 @@ class Indexer<T extends Track> {
lastPositionInMs: lastPositionInMs,
);
trackStatsMap[track] = newStats;

await _saveTrackStatsFileToStorage();
_trackStatsDBManager.put(track.path, newStats.toJsonWithoutTrack());
return newStats;
}

Future<void> _saveTrackStatsFileToStorage() async {
await File(AppPaths.TRACKS_STATS).writeAsJson(trackStatsMap.values.map((e) => e.toJson()).toList());
}

void readTrackData() {
void _readTrackData() {
// reading stats file containing track rating etc.
final statsResult = _readTracksStatsCompute(AppPaths.TRACKS_STATS);
trackStatsMap.value = statsResult;
try {
_trackStatsDBManager.loadEverythingKeyed(
(key, value) {
final track = Track.fromJson(key, isVideo: value['v'] == true);
final stats = TrackStats.fromJsonWithoutTrack(track, value);
trackStatsMap.value[stats.track] = stats;
},
);

// -- migrating json to db
final statsJsonFile = File(AppPaths.TRACKS_STATS_OLD);
if (statsJsonFile.existsSync()) {
final list = statsJsonFile.readAsJsonSync() as List?;
if (list != null) {
for (int i = 0; i < list.length; i++) {
try {
final item = list[i];
final trst = TrackStats.fromJson(item);
if (trackStatsMap.value[trst.track] == null) {
final jsonDetails = trst.toJsonWithoutTrack();
if (jsonDetails != null) {
trackStatsMap.value[trst.track] = trst;
_trackStatsDBManager.put(trst.track.path, trst.toJsonWithoutTrack());
}
}
} catch (_) {}
}
}
statsJsonFile.deleteSync();
}
} catch (_) {}

tracksInfoList.clear(); // clearing for cases which refreshing library is required (like after changing separators)

Expand All @@ -1049,23 +1106,6 @@ class Indexer<T extends Track> {
printy("All Tracks Length From File: ${tracksInfoList.length}");
}

static Map<Track, TrackStats> _readTracksStatsCompute(String path) {
final map = <Track, TrackStats>{};
final list = File(path).readAsJsonSync() as List?;
if (list != null) {
for (int i = 0; i < list.length; i++) {
try {
final item = list[i];
final trst = TrackStats.fromJson(item);
map[trst.track] = trst;
} catch (e) {
continue;
}
}
}
return map;
}

static (Map<String, TrackExtended>, Map<String, List<Track>>, List<Track>) _readTracksFileCompute(SplitArtistGenreConfigsWrapper config) {
final map = <String, TrackExtended>{};
final idsMap = <String, List<Track>>{};
Expand All @@ -1079,6 +1119,7 @@ class Indexer<T extends Track> {
item,
artistsSplitConfig: config.artistsConfig,
genresSplitConfig: config.genresConfig,
generalSplitConfig: config.generalConfig,
);
final track = trExt.asTrack();
map[track.path] = trExt;
Expand All @@ -1092,8 +1133,6 @@ class Indexer<T extends Track> {
return (map, idsMap, allTracks);
}

/// [addArtistsFromTitle] extracts feat artists.
/// Defaults to [settings.extractFeatArtistFromTitle]
static List<String> splitArtist({
required String? title,
required String? originalArtist,
Expand Down
2 changes: 1 addition & 1 deletion lib/controller/settings_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class _SettingsController with SettingsFileWriter {
final heatmapListensView = false.obs;
final RxList<String> backupItemslist = [
AppPaths.TRACKS,
AppPaths.TRACKS_STATS,
AppPaths.TRACKS_STATS_DB_INFO.file.path,
AppPaths.TOTAL_LISTEN_TIME,
AppPaths.VIDEOS_CACHE,
AppPaths.VIDEOS_LOCAL,
Expand Down
4 changes: 3 additions & 1 deletion lib/core/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter/services.dart';

import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter_udid/flutter_udid.dart';
import 'package:namico_db_wrapper/namico_db_wrapper.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher_string.dart';

Expand Down Expand Up @@ -218,7 +219,8 @@ class AppPaths {
static final SETTINGS_YOUTUBE = '$_USER_DATA/namida_settings_youtube.json';
static final SETTINGS_EXTRA = '$_USER_DATA/namida_settings_extra.json';
static final TRACKS = '$_USER_DATA/tracks.json';
static final TRACKS_STATS = '$_USER_DATA/tracks_stats.json';
static final TRACKS_STATS_OLD = '$_USER_DATA/tracks_stats.json';
static final TRACKS_STATS_DB_INFO = DbWrapperFileInfo(directory: _USER_DATA, dbName: 'tracks_stats');
static final VIDEOS_LOCAL = '$_USER_DATA/local_videos.json';
static final VIDEOS_CACHE = '$_USER_DATA/cache_videos.json';
static final LATEST_QUEUE = '$_USER_DATA/latest_queue.json';
Expand Down
6 changes: 4 additions & 2 deletions lib/ui/widgets/settings/backup_restore_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ class BackupAndRestore extends SettingSubpageProvider {
AppPaths.SETTINGS_YOUTUBE,
AppPaths.SETTINGS_EXTRA,
AppPaths.TRACKS,
AppPaths.TRACKS_STATS,
AppPaths.TRACKS_STATS_OLD,
AppPaths.TRACKS_STATS_DB_INFO.file.path,
AppPaths.VIDEOS_LOCAL,
AppPaths.VIDEOS_CACHE,
AppPaths.LATEST_QUEUE,
Expand Down Expand Up @@ -371,7 +372,8 @@ class BackupAndRestore extends SettingSubpageProvider {
icon: Broken.box_1,
items: [
AppPaths.TRACKS,
AppPaths.TRACKS_STATS,
AppPaths.TRACKS_STATS_OLD,
AppPaths.TRACKS_STATS_DB_INFO.file.path,
AppPaths.TOTAL_LISTEN_TIME,
AppPaths.VIDEOS_CACHE,
AppPaths.VIDEOS_LOCAL,
Expand Down
7 changes: 2 additions & 5 deletions lib/ui/widgets/settings/indexer_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ class IndexerSettings extends SettingSubpageProvider {
subtitle: "${lang.EXTRACT_FEAT_ARTIST_SUBTITLE} ${lang.INSTANTLY_APPLIES}.",
onChanged: (isTrue) async {
settings.save(extractFeatArtistFromTitle: !isTrue);
Indexer.inst.prepareTracksFile();
Indexer.inst.rebuildTracksAfterSplitConfigChanges();
},
value: settings.extractFeatArtistFromTitle.valueR,
),
Expand Down Expand Up @@ -438,9 +438,6 @@ class IndexerSettings extends SettingSubpageProvider {
NamidaNavigator.inst.closeDialog();
settings.removeFromList(albumIdentifiersAll: AlbumIdentifier.values);
settings.save(albumIdentifiers: tempList.value);

Indexer.inst.prepareTracksFile();

_showReindexingPrompt(title: lang.ALBUM_IDENTIFIERS, body: lang.REQUIRES_CLEARING_IMAGE_CACHE_AND_RE_INDEXING);
},
);
Expand Down Expand Up @@ -694,7 +691,7 @@ class IndexerSettings extends SettingSubpageProvider {
? null
: () async {
updatingLibrary.value = true;
Indexer.inst.prepareTracksFile();
Indexer.inst.rebuildTracksAfterSplitConfigChanges();
},
durationInMs: 200,
dialog: CustomBlurryDialog(
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: namida
description: A Beautiful and Feature-rich Music Player, With YouTube & Video Support Built in Flutter
publish_to: "none"
version: 4.4.57-beta+240923004
version: 4.4.6-beta+240923004

environment:
sdk: ">=3.4.0 <4.0.0"
Expand Down

0 comments on commit 2ef6cd7

Please sign in to comment.