Skip to content

Commit

Permalink
feat: missing tracks page
Browse files Browse the repository at this point in the history
closes #117
  • Loading branch information
MSOB7YY committed Mar 9, 2024
1 parent 4c4104e commit 00bd918
Show file tree
Hide file tree
Showing 19 changed files with 673 additions and 54 deletions.
6 changes: 5 additions & 1 deletion lib/controller/backup_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter_archive/flutter_archive.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';

import 'package:namida/controller/history_controller.dart';
import 'package:namida/controller/indexer_controller.dart';
import 'package:namida/controller/navigator_controller.dart';
import 'package:namida/controller/playlist_controller.dart';
Expand All @@ -16,6 +17,7 @@ import 'package:namida/core/extensions.dart';
import 'package:namida/core/translations/language.dart';
import 'package:namida/main.dart';
import 'package:namida/youtube/controller/youtube_controller.dart';
import 'package:namida/youtube/controller/youtube_history_controller.dart';
import 'package:namida/youtube/controller/youtube_playlist_controller.dart';

class BackupController {
Expand Down Expand Up @@ -235,13 +237,15 @@ class BackupController {

QueueController.inst.prepareAllQueuesFile();

PlaylistController.inst.prepareAllPlaylists();
VideoController.inst.initialize();

PlaylistController.inst.prepareAllPlaylists();
HistoryController.inst.prepareHistoryFile();
await PlaylistController.inst.prepareDefaultPlaylistsFile();
// await QueueController.inst.prepareLatestQueue();

YoutubePlaylistController.inst.prepareAllPlaylists();
YoutubeHistoryController.inst.prepareHistoryFile();
await YoutubePlaylistController.inst.prepareDefaultPlaylistsFile();
YoutubeController.inst.fillBackupInfoMap(); // for history videos info.
}
Expand Down
67 changes: 54 additions & 13 deletions lib/controller/edit_delete_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,36 +83,77 @@ class EditDeleteController {
}
}

Future<void> updateTrackPathInEveryPartOfNamidaBulk(Map<String, String> oldNewPath) async {
final newtrlist = await Indexer.inst.convertPathToTrack(oldNewPath.values);
if (newtrlist.isEmpty) return;
final oldNewMap = <Track, Track>{for (final on in oldNewPath.entries) Track(on.key): Track(on.value)};

// -- Player Queue
Player.inst.replaceAllTracksInQueueBulk(oldNewMap); // no need to await

// -- History
final daysToSave = <int>[];
final allHistory = HistoryController.inst.historyMap.value.entries.toList();

final oldNewTrack = oldNewMap;
for (final oldNewTrack in oldNewTrack.entries) {
allHistory.loop((entry, index) {
final day = entry.key;
final trs = entry.value;
trs.replaceWhere(
(e) => e.track == oldNewTrack.key.track,
(old) => TrackWithDate(
dateAdded: old.dateAdded,
track: oldNewTrack.value,
source: old.source,
),
onMatch: () => daysToSave.add(day),
);
});
}
await Future.wait([
HistoryController.inst.saveHistoryToStorage(daysToSave).then((value) => HistoryController.inst.updateMostPlayedPlaylist()),
QueueController.inst.replaceTrackInAllQueues(oldNewTrack), // -- Queues
PlaylistController.inst.replaceTrackInAllPlaylistsBulk(oldNewTrack), // -- Playlists
]);
// -- Selected Tracks
if (SelectedTracksController.inst.selectedTracks.isNotEmpty) {
for (final oldNewTrack in oldNewTrack.entries) {
SelectedTracksController.inst.replaceThisTrack(oldNewTrack.key, oldNewTrack.value);
}
}
}

Future<void> updateTrackPathInEveryPartOfNamida(Track oldTrack, String newPath) async {
final newtrlist = await Indexer.inst.convertPathToTrack([newPath]);
if (newtrlist.isEmpty) return;
final newTrack = newtrlist.first;
await Future.wait([
// --- Queues ---
QueueController.inst.replaceTrackInAllQueues(oldTrack, newTrack),

// --- Player Queue ---
Player.inst.replaceAllTracksInQueue(oldTrack, newTrack),

// --- Playlists & Favourites---
PlaylistController.inst.replaceTrackInAllPlaylists(oldTrack, newTrack),

// --- History---
HistoryController.inst.replaceAllTracksInsideHistory(oldTrack, newTrack),
QueueController.inst.replaceTrackInAllQueues({oldTrack: newTrack}), // Queues
Player.inst.replaceAllTracksInQueueBulk({oldTrack: newTrack}), // Player Queue
PlaylistController.inst.replaceTrackInAllPlaylists(oldTrack, newTrack), // Playlists & Favourites
HistoryController.inst.replaceAllTracksInsideHistory(oldTrack, newTrack), // History
]);
// --- Selected Tracks ---
SelectedTracksController.inst.replaceThisTrack(oldTrack, newTrack);
if (SelectedTracksController.inst.selectedTracks.isNotEmpty) {
SelectedTracksController.inst.replaceThisTrack(oldTrack, newTrack);
}
}

Future<void> updateDirectoryInEveryPartOfNamida(String oldDir, String newDir, {Iterable<String>? forThesePathsOnly, bool ensureNewFileExists = false}) async {
settings.save(directoriesToScan: [newDir]);
final pathSeparator = Platform.pathSeparator;
if (!oldDir.endsWith(pathSeparator)) oldDir += pathSeparator;
if (!newDir.endsWith(pathSeparator)) newDir += pathSeparator;
await Future.wait([
PlaylistController.inst.replaceTracksDirectory(oldDir, newDir, forThesePathsOnly: forThesePathsOnly, ensureNewFileExists: ensureNewFileExists),
QueueController.inst.replaceTracksDirectoryInQueues(oldDir, newDir, forThesePathsOnly: forThesePathsOnly, ensureNewFileExists: ensureNewFileExists),
Player.inst.replaceTracksDirectoryInQueue(oldDir, newDir, forThesePathsOnly: forThesePathsOnly, ensureNewFileExists: ensureNewFileExists),
HistoryController.inst.replaceTracksDirectoryInHistory(oldDir, newDir, forThesePathsOnly: forThesePathsOnly, ensureNewFileExists: ensureNewFileExists),
]);
SelectedTracksController.inst.replaceTrackDirectory(oldDir, newDir, forThesePathsOnly: forThesePathsOnly, ensureNewFileExists: ensureNewFileExists);
if (SelectedTracksController.inst.selectedTracks.isNotEmpty) {
SelectedTracksController.inst.replaceTrackDirectory(oldDir, newDir, forThesePathsOnly: forThesePathsOnly, ensureNewFileExists: ensureNewFileExists);
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions lib/controller/generators_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class NamidaGenerator extends NamidaGeneratorBase<TrackWithDate, Track> {
@override
HistoryManager<TrackWithDate, Track> get historyController => HistoryController.inst;

Set<String> getHighMatcheFilesFromFilename(Iterable<String> files, String filename) {
static Iterable<String> getHighMatcheFilesFromFilename(Iterable<String> files, String filename) {
return files.where(
(element) {
final trackFilename = filename;
Expand All @@ -26,7 +26,7 @@ class NamidaGenerator extends NamidaGeneratorBase<TrackWithDate, Track> {
final matching2 = fileSystemFilenameCleaned.contains(trackTitle.split('(').first) && fileSystemFilenameCleaned.contains(trackArtist);
return matching1 || matching2;
},
).toSet();
);
}

Iterable<Track> getRandomTracks({Track? exclude, int? min, int? max}) {
Expand Down
38 changes: 32 additions & 6 deletions lib/controller/indexer_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -665,17 +665,43 @@ class Indexer {

Future<List<Track>> convertPathToTrack(Iterable<String> tracksPathPre) async {
final List<Track> finalTracks = <Track>[];
final tracksPath = tracksPathPre.toList();
final tracksToExtract = <String>[];

final orderLookup = <String, int>{};
int index = 0;
for (final path in tracksPathPre) {
final trInLib = path.toTrackOrNull();
if (trInLib != null) {
finalTracks.add(trInLib);
} else {
tracksToExtract.add(path);
}
orderLookup[path] = index;
index++;
}

await tracksPath.loopFuture((tp, index) async {
final trako = await tp.toTrackExtOrExtract();
if (trako != null) finalTracks.add(trako.toTrack());
});
if (tracksToExtract.isNotEmpty) {
TrackExtended? extractFunction(FAudioModel item) => _convertTagToTrack(
trackPath: item.tags.path,
trackInfo: item,
tryExtractingFromFilename: true,
onMinDurTrigger: () => null,
onMinSizeTrigger: () => null,
onError: (_) => null,
);

final stream = await FAudioTaggerController.inst.extractMetadataAsStream(paths: tracksToExtract);
await for (final item in stream) {
finalTracks.add(Track(item.tags.path));
final trext = extractFunction(item);
if (trext != null) _addTrackToLists(trext, true, item.tags.artwork);
}
}

_addTheseTracksToAlbumGenreArtistEtc(finalTracks);
await _sortAndSaveTracks();

finalTracks.sortBy((e) => tracksPath.indexOf(e.path));
finalTracks.sortBy((e) => orderLookup[e.path] ?? 0);
return finalTracks;
}

Expand Down
4 changes: 4 additions & 0 deletions lib/controller/player_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ class Player {
await _audioHandler.replaceAllItemsInQueue(oldTrack, newTrack);
}

Future<void> replaceAllTracksInQueueBulk(Map<Playable, Playable> oldNewTrack) async {
await _audioHandler.replaceAllItemsInQueueBulk(oldNewTrack);
}

Future<void> replaceTracksDirectoryInQueue(String oldDir, String newDir, {Iterable<String>? forThesePathsOnly, bool ensureNewFileExists = false}) async {
String getNewPath(String old) => old.replaceFirst(oldDir, newDir);
if (currentQueue.isNotEmpty) {
Expand Down
28 changes: 21 additions & 7 deletions lib/controller/playlist_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import 'package:playlist_manager/playlist_manager.dart';

import 'package:namida/class/track.dart';
import 'package:namida/controller/generators_controller.dart';
import 'package:namida/controller/history_controller.dart';
import 'package:namida/controller/indexer_controller.dart';
import 'package:namida/controller/navigator_controller.dart';
import 'package:namida/controller/search_sort_controller.dart';
Expand Down Expand Up @@ -225,6 +224,23 @@ class PlaylistController extends PlaylistManager<TrackWithDate> {
);
}

Future<void> replaceTrackInAllPlaylistsBulk(Map<Track, Track> oldNewTrack) async {
final fnList = <MapEntry<bool Function(TrackWithDate e), TrackWithDate Function(TrackWithDate old)>>[];
for (final entry in oldNewTrack.entries) {
fnList.add(
MapEntry(
(e) => e.track == entry.key,
(old) => TrackWithDate(
dateAdded: old.dateAdded,
track: entry.value,
source: old.source,
),
),
);
}
await replaceTheseTracksInPlaylistsBulk(fnList);
}

/// Returns number of generated tracks.
int generateRandomPlaylist() {
final rt = NamidaGenerator.inst.getRandomTracks();
Expand Down Expand Up @@ -271,6 +287,9 @@ class PlaylistController extends PlaylistManager<TrackWithDate> {
return listy;
}

final _m3uPlaylistsCompleter = Completer<bool>();
Future<bool> get waitForM3UPlaylistsLoad => _m3uPlaylistsCompleter.future;

Future<void> prepareM3UPlaylists({Set<String> forPaths = const {}}) async {
final allAvailableDirectories = await Indexer.inst.getAvailableDirectories(strictNoMedia: false);

Expand Down Expand Up @@ -304,6 +323,7 @@ class PlaylistController extends PlaylistManager<TrackWithDate> {
PlaylistController.inst.addNewPlaylist(plName, tracks: trs, m3uPath: m3uPath, creationDate: creationDate);
}
_pathsM3ULookup = infoMap;
if (!_m3uPlaylistsCompleter.isCompleted) _m3uPlaylistsCompleter.complete(true);
}

/// saves each track m3u info for writing back
Expand Down Expand Up @@ -518,12 +538,6 @@ class PlaylistController extends PlaylistManager<TrackWithDate> {
return await _prepareFavouritesFile.thready(favouritePlaylistPath);
}

@override
Future<void> prepareDefaultPlaylistsFile() async {
HistoryController.inst.prepareHistoryFile();
await super.prepareDefaultPlaylistsFile();
}

static Future<Playlist?> _prepareFavouritesFile(String path) async {
try {
final response = File(path).readAsJsonSync();
Expand Down
14 changes: 8 additions & 6 deletions lib/controller/queue_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,17 @@ class QueueController {
}
}

Future<void> replaceTrackInAllQueues(Track oldTrack, Track newTrack) async {
Future<void> replaceTrackInAllQueues(Map<Track, Track> oldNewTrack) async {
final queuesToSave = <Queue>[];
queuesMap.value.entries.toList().loop((entry, index) {
final q = entry.value;
q.tracks.replaceItems(
oldTrack,
newTrack,
onMatch: () => queuesToSave.add(q),
);
for (final e in oldNewTrack.entries) {
q.tracks.replaceItems(
e.key,
e.value,
onMatch: () => queuesToSave.add(q),
);
}
});
for (final q in queuesToSave) {
_updateMap(q);
Expand Down
1 change: 1 addition & 0 deletions lib/core/dimensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Dimensions {
route == RouteType.SETTINGS_page || // bcz no search
route == RouteType.SETTINGS_subpage || // bcz no search
route == RouteType.YOUTUBE_PLAYLIST_DOWNLOAD_SUBPAGE || // bcz has fab
route == RouteType.SUBPAGE_INDEXER_UPDATE_MISSING_TRACKS || // bcz has fab
((fab == FABType.shuffle || fab == FABType.play) && SelectedTracksController.inst.currentAllTracks.isEmpty) ||
(settings.selectedLibraryTab.value == LibraryTab.tracks && LibraryTab.tracks.isBarVisible == false);
return shouldHide;
Expand Down
1 change: 1 addition & 0 deletions lib/core/enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ enum RouteType {
SUBPAGE_historyTracks,
SUBPAGE_mostPlayedTracks,
SUBPAGE_queueTracks,
SUBPAGE_INDEXER_UPDATE_MISSING_TRACKS,

// ----- Subpages -----
SETTINGS_page,
Expand Down
4 changes: 3 additions & 1 deletion lib/core/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,9 @@ extension ThreadOpener<M, R> on ComputeCallback<M, R> {
/// Executes function on a separate thread using compute().
/// Must be `static` or `global` function.
Future<R> thready(M parameter) async {
WidgetsFlutterBinding.ensureInitialized();
try {
WidgetsFlutterBinding.ensureInitialized();
} catch (_) {}
return await compute(this, parameter);
}
}
Expand Down
4 changes: 4 additions & 0 deletions lib/core/namida_converter_ext.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import 'package:namida/ui/pages/settings_page.dart';
import 'package:namida/ui/pages/subpages/album_tracks_subpage.dart';
import 'package:namida/ui/pages/subpages/artist_tracks_subpage.dart';
import 'package:namida/ui/pages/subpages/genre_tracks_subpage.dart';
import 'package:namida/ui/pages/subpages/indexer_missing_tracks_subpage.dart';
import 'package:namida/ui/pages/subpages/playlist_tracks_subpage.dart';
import 'package:namida/ui/pages/subpages/queue_tracks_subpage.dart';
import 'package:namida/ui/pages/tracks_page.dart';
Expand Down Expand Up @@ -749,6 +750,9 @@ extension WidgetsPagess on Widget {
route = RouteType.SUBPAGE_queueTracks;
name = (this as QueueTracksPage).queue.date.toString();
break;
case IndexerMissingTracksSubpage:
route = RouteType.SUBPAGE_INDEXER_UPDATE_MISSING_TRACKS;
break;

// ----- Search Results -----
case AlbumSearchResultsPage:
Expand Down
1 change: 1 addition & 0 deletions lib/core/translations/keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ abstract class LanguageKeys {
String get MINIPLAYER_CUSTOMIZATION => _getKey('MINIPLAYER_CUSTOMIZATION');
String get MINUTES => _getKey('MINUTES');
String get MISSING_ENTRIES => _getKey('MISSING_ENTRIES');
String get MISSING_TRACKS => _getKey('MISSING_TRACKS');
String get MIXES => _getKey('MIXES');
String get MONTH => _getKey('MONTH');
String get MONTHS => _getKey('MONTHS');
Expand Down
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'package:namida/controller/backup_controller.dart';
import 'package:namida/controller/connectivity.dart';
import 'package:namida/controller/current_color.dart';
import 'package:namida/controller/folders_controller.dart';
import 'package:namida/controller/history_controller.dart';
import 'package:namida/controller/indexer_controller.dart';
import 'package:namida/controller/namida_channel.dart';
import 'package:namida/controller/navigator_controller.dart';
Expand All @@ -43,6 +44,7 @@ import 'package:namida/packages/scroll_physics_modified.dart';
import 'package:namida/ui/widgets/custom_widgets.dart';
import 'package:namida/ui/widgets/video_widget.dart';
import 'package:namida/youtube/controller/youtube_controller.dart';
import 'package:namida/youtube/controller/youtube_history_controller.dart';
import 'package:namida/youtube/controller/youtube_playlist_controller.dart';
import 'package:namida/youtube/controller/youtube_subscriptions_controller.dart';

Expand Down Expand Up @@ -121,6 +123,8 @@ void main() async {

FlutterNativeSplash.remove();

HistoryController.inst.prepareHistoryFile();
YoutubeHistoryController.inst.prepareHistoryFile();
await Future.wait([
PlaylistController.inst.prepareDefaultPlaylistsFile(),
if (!shouldShowOnBoarding) QueueController.inst.prepareLatestQueue(),
Expand Down
4 changes: 2 additions & 2 deletions lib/ui/dialogs/general_popup_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ Future<void> showGeneralPopupDialog(
final paths = files.mapped((e) => e.path);
paths.sortBy((e) => e);

final highMatchesFiles = NamidaGenerator.inst.getHighMatcheFilesFromFilename(paths, tracks.first.path.getFilename);
final highMatchesFiles = NamidaGenerator.getHighMatcheFilesFromFilename(paths, tracks.first.path.getFilename).toSet();

/// Searching
final txtc = TextEditingController();
Expand Down Expand Up @@ -798,7 +798,7 @@ Future<void> showGeneralPopupDialog(
}

/// firstly checks if a file exists in current library
final firstHighMatchesFiles = NamidaGenerator.inst.getHighMatcheFilesFromFilename(Indexer.inst.allAudioFiles, tracks.first.path.getFilename);
final firstHighMatchesFiles = NamidaGenerator.getHighMatcheFilesFromFilename(Indexer.inst.allAudioFiles, tracks.first.path.getFilename).toSet();
if (firstHighMatchesFiles.isNotEmpty) {
await openDialog(
CustomBlurryDialog(
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/dialogs/track_advanced_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ void showTrackAdvancedDialog({
),

// -- Updating directory path option, only for tracks whithin the same parent directory.
if (tracksUniqued.every((element) => element.track.path.startsWith(firstTracksDirectoryPath)))
if (!isSingle && tracksUniqued.every((element) => element.track.path.startsWith(firstTracksDirectoryPath)))
UpdateDirectoryPathListTile(
colorScheme: colorScheme,
oldPath: firstTracksDirectoryPath,
Expand Down
Loading

0 comments on commit 00bd918

Please sign in to comment.