mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-02 17:18:36 +02:00
fix(library): retain records when deletion fails
This commit is contained in:
@@ -157,9 +157,8 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
|
||||
deleteItem: (id) async {
|
||||
final item = tracksById[id];
|
||||
if (item == null) return false;
|
||||
try {
|
||||
await deleteFile(item.filePath);
|
||||
} catch (_) {}
|
||||
final deleted = await deleteFile(item.filePath);
|
||||
if (!deleted) return false;
|
||||
historyNotifier.removeFromHistory(id);
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -130,9 +130,8 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
|
||||
final item = tracksById[id];
|
||||
if (item == null) return false;
|
||||
if (!isCueVirtualPath(item.filePath)) {
|
||||
try {
|
||||
await deleteFile(item.filePath);
|
||||
} catch (_) {}
|
||||
final deleted = await deleteFile(item.filePath);
|
||||
if (!deleted) return false;
|
||||
}
|
||||
await libraryNotifier.removeItem(id);
|
||||
return true;
|
||||
|
||||
@@ -476,10 +476,9 @@ extension _QueueTabSelectionActions on _QueueTabState {
|
||||
for (final id in _selectedIds) {
|
||||
final item = itemsById[id];
|
||||
if (item != null) {
|
||||
try {
|
||||
final cleanPath = _cleanFilePath(item.filePath);
|
||||
await deleteFile(cleanPath);
|
||||
} catch (_) {}
|
||||
final cleanPath = _cleanFilePath(item.filePath);
|
||||
final fileDeleted = await deleteFile(cleanPath);
|
||||
if (!fileDeleted) continue;
|
||||
|
||||
if (item.source == LibraryItemSource.downloaded) {
|
||||
historyNotifier.removeFromHistory(item.historyItem!.id);
|
||||
|
||||
@@ -120,33 +120,42 @@ extension _TrackMetadataFileActions on _TrackMetadataScreenState {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var fileDeleted = true;
|
||||
if (_isLocalItem) {
|
||||
if (_isCueVirtualTrack && _localLibraryItem != null) {
|
||||
await ref
|
||||
.read(localLibraryProvider.notifier)
|
||||
.removeItem(_localLibraryItem!.id);
|
||||
} else {
|
||||
try {
|
||||
await deleteFile(cleanFilePath);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to delete file: $e');
|
||||
}
|
||||
if (_localLibraryItem != null) {
|
||||
fileDeleted = await deleteFile(cleanFilePath);
|
||||
if (fileDeleted && _localLibraryItem != null) {
|
||||
await ref
|
||||
.read(localLibraryProvider.notifier)
|
||||
.removeItem(_localLibraryItem!.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await deleteFile(cleanFilePath);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to delete file: $e');
|
||||
fileDeleted = await deleteFile(cleanFilePath);
|
||||
if (fileDeleted) {
|
||||
ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.removeFromHistory(_downloadItem!.id);
|
||||
}
|
||||
}
|
||||
|
||||
ref
|
||||
.read(downloadHistoryProvider.notifier)
|
||||
.removeFromHistory(_downloadItem!.id);
|
||||
if (!fileDeleted) {
|
||||
if (screenContext.mounted) {
|
||||
ScaffoldMessenger.of(screenContext).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
screenContext.l10n.snackbarError(
|
||||
screenContext.l10n.snackbarFailedToWriteStorage,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogContext.mounted) {
|
||||
|
||||
@@ -272,21 +272,43 @@ Future<bool> fileExists(String? path) async {
|
||||
return File(realPath).exists();
|
||||
}
|
||||
|
||||
Future<void> deleteFile(String? path) async {
|
||||
if (path == null || path.isEmpty) return;
|
||||
/// Deletes [path] and reports whether the file is confirmed absent afterward.
|
||||
///
|
||||
/// SAF providers are allowed to reject a delete request by returning `false`.
|
||||
/// Callers that also remove a Library row must only do so when this returns
|
||||
/// `true`, otherwise the app would hide a file that still exists on storage.
|
||||
Future<bool> deleteFile(String? path) async {
|
||||
if (path == null || path.isEmpty) return false;
|
||||
// CUE virtual paths should NOT be deleted through this function —
|
||||
// deleting album.cue would remove ALL tracks. Callers should handle
|
||||
// CUE deletion specially (e.g. only delete when all tracks are removed).
|
||||
if (isCueVirtualPath(path)) return;
|
||||
if (isCueVirtualPath(path)) return false;
|
||||
if (isContentUri(path)) {
|
||||
await PlatformBridge.safDelete(path);
|
||||
await musicPlayerHandler?.onSourceDeleted(path);
|
||||
return;
|
||||
try {
|
||||
final deleted = await PlatformBridge.safDelete(path);
|
||||
final confirmedAbsent = deleted || !await PlatformBridge.safExists(path);
|
||||
if (confirmedAbsent) {
|
||||
await musicPlayerHandler?.onSourceDeleted(path);
|
||||
}
|
||||
return confirmedAbsent;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final file = File(path);
|
||||
try {
|
||||
await File(path).delete();
|
||||
} catch (_) {}
|
||||
await musicPlayerHandler?.onSourceDeleted(path);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
final confirmedAbsent = !await file.exists();
|
||||
if (confirmedAbsent) {
|
||||
await musicPlayerHandler?.onSourceDeleted(path);
|
||||
}
|
||||
return confirmedAbsent;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<FileAccessStat?> fileStat(String? path) async {
|
||||
|
||||
@@ -74,7 +74,9 @@ class _DuplicateReviewSheetState extends ConsumerState<DuplicateReviewSheet> {
|
||||
if (bitDepth > 0 && sampleRate > 0) {
|
||||
final khz = sampleRate / 1000;
|
||||
final khzText = khz % 1 == 0 ? khz.toInt().toString() : khz.toString();
|
||||
return format.isEmpty ? '$bitDepth/$khzText' : '$bitDepth/$khzText $format';
|
||||
return format.isEmpty
|
||||
? '$bitDepth/$khzText'
|
||||
: '$bitDepth/$khzText $format';
|
||||
}
|
||||
final bitrate = entry.bitrate ?? 0;
|
||||
if (bitrate > 0) {
|
||||
@@ -112,11 +114,10 @@ class _DuplicateReviewSheetState extends ConsumerState<DuplicateReviewSheet> {
|
||||
final historyNotifier = ref.read(downloadHistoryProvider.notifier);
|
||||
var deleted = 0;
|
||||
for (final entry in entries) {
|
||||
try {
|
||||
await deleteFile(
|
||||
DownloadedEmbeddedCoverResolver.cleanFilePath(entry.filePath),
|
||||
);
|
||||
} catch (_) {}
|
||||
final fileDeleted = await deleteFile(
|
||||
DownloadedEmbeddedCoverResolver.cleanFilePath(entry.filePath),
|
||||
);
|
||||
if (!fileDeleted) continue;
|
||||
if (entry.source == 'downloaded') {
|
||||
historyNotifier.removeFromHistory(entry.id);
|
||||
} else {
|
||||
@@ -200,8 +201,9 @@ class _DuplicateReviewSheetState extends ConsumerState<DuplicateReviewSheet> {
|
||||
padding: const EdgeInsets.fromLTRB(24, 8, 24, 32),
|
||||
child: Text(
|
||||
context.l10n.duplicatesEmpty,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(color: colorScheme.onSurfaceVariant),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user