mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-29 15:28:48 +02:00
fix(queue): restore pre-refactor guarantees found by commit audit
- _replaceSafFileVia: op now registers produced files via addCleanup the moment they exist, so converted temp files are deleted even when a later step throws (old code's outer-variable finally) - native worker history: recompute format from the final file path at history-write time and gate bitrate on lossy output again - the extracted helper was recording pre-conversion format/bitrate (FLAC conversions logged as m4a with nulled bit depth on SAF) - drop dead ignore_for_file header in the finalization part
This commit is contained in:
@@ -2836,7 +2836,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
|||||||
uri: currentFilePath,
|
uri: currentFilePath,
|
||||||
treeUri: settings.downloadTreeUri,
|
treeUri: settings.downloadTreeUri,
|
||||||
relativeDir: effectiveOutputDir,
|
relativeDir: effectiveOutputDir,
|
||||||
op: (tempPath) async {
|
op: (tempPath, addCleanup) async {
|
||||||
opStarted = true;
|
opStarted = true;
|
||||||
updateItemStatus(
|
updateItemStatus(
|
||||||
item.id,
|
item.id,
|
||||||
@@ -2853,6 +2853,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
|||||||
convertFailed = true;
|
convertFailed = true;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
addCleanup(convertedPath);
|
||||||
_log.i(
|
_log.i(
|
||||||
'Successfully converted M4A to $format (temp): $convertedPath',
|
'Successfully converted M4A to $format (temp): $convertedPath',
|
||||||
);
|
);
|
||||||
@@ -2917,7 +2918,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
|||||||
uri: currentFilePath,
|
uri: currentFilePath,
|
||||||
treeUri: settings.downloadTreeUri,
|
treeUri: settings.downloadTreeUri,
|
||||||
relativeDir: effectiveOutputDir,
|
relativeDir: effectiveOutputDir,
|
||||||
op: (tempPath) async {
|
op: (tempPath, _) async {
|
||||||
opStarted = true;
|
opStarted = true;
|
||||||
if (metadataEmbeddingEnabled) {
|
if (metadataEmbeddingEnabled) {
|
||||||
updateItemStatus(
|
updateItemStatus(
|
||||||
@@ -2967,7 +2968,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
|||||||
uri: currentFilePath,
|
uri: currentFilePath,
|
||||||
treeUri: settings.downloadTreeUri,
|
treeUri: settings.downloadTreeUri,
|
||||||
relativeDir: effectiveOutputDir,
|
relativeDir: effectiveOutputDir,
|
||||||
op: (tempPath) async {
|
op: (tempPath, addCleanup) async {
|
||||||
final length = await File(tempPath).length();
|
final length = await File(tempPath).length();
|
||||||
if (length < 1024) {
|
if (length < 1024) {
|
||||||
_log.w(
|
_log.w(
|
||||||
@@ -3041,6 +3042,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
|||||||
branch = 'convertFailed';
|
branch = 'convertFailed';
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
addCleanup(flacPath);
|
||||||
_log.d('Converted to FLAC (temp): $flacPath');
|
_log.d('Converted to FLAC (temp): $flacPath');
|
||||||
_log.d(
|
_log.d(
|
||||||
'Embedding metadata and cover to converted FLAC...',
|
'Embedding metadata and cover to converted FLAC...',
|
||||||
@@ -3368,7 +3370,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
|||||||
uri: currentFilePath,
|
uri: currentFilePath,
|
||||||
treeUri: settings.downloadTreeUri,
|
treeUri: settings.downloadTreeUri,
|
||||||
relativeDir: effectiveOutputDir,
|
relativeDir: effectiveOutputDir,
|
||||||
op: (tempPath) async {
|
op: (tempPath, _) async {
|
||||||
opStarted = true;
|
opStarted = true;
|
||||||
updateItemStatus(
|
updateItemStatus(
|
||||||
item.id,
|
item.id,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
|
||||||
part of 'download_queue_provider.dart';
|
part of 'download_queue_provider.dart';
|
||||||
|
|
||||||
/// Result of [DownloadQueueNotifier._finalizeDecryption]. [failStage] is
|
/// Result of [DownloadQueueNotifier._finalizeDecryption]. [failStage] is
|
||||||
@@ -199,14 +198,20 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
|
|||||||
required String uri,
|
required String uri,
|
||||||
required String treeUri,
|
required String treeUri,
|
||||||
required String relativeDir,
|
required String relativeDir,
|
||||||
required Future<(String path, String fileName)?> Function(String tempPath)
|
required Future<(String path, String fileName)?> Function(
|
||||||
|
String tempPath,
|
||||||
|
void Function(String path) addCleanup,
|
||||||
|
)
|
||||||
op,
|
op,
|
||||||
}) async {
|
}) async {
|
||||||
final tempPath = await _copySafToTemp(uri);
|
final tempPath = await _copySafToTemp(uri);
|
||||||
if (tempPath == null) return null;
|
if (tempPath == null) return null;
|
||||||
|
// Files op produces are registered here the moment they exist, so they
|
||||||
|
// are cleaned up even if op throws before returning.
|
||||||
|
final producedTemps = <String>{};
|
||||||
String? outPath;
|
String? outPath;
|
||||||
try {
|
try {
|
||||||
final produced = await op(tempPath);
|
final produced = await op(tempPath, producedTemps.add);
|
||||||
if (produced == null) return null;
|
if (produced == null) return null;
|
||||||
outPath = produced.$1;
|
outPath = produced.$1;
|
||||||
final fileName = produced.$2;
|
final fileName = produced.$2;
|
||||||
@@ -228,9 +233,13 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
|
|||||||
try {
|
try {
|
||||||
await File(tempPath).delete();
|
await File(tempPath).delete();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
if (outPath != null && outPath != tempPath) {
|
if (outPath != null) {
|
||||||
|
producedTemps.add(outPath);
|
||||||
|
}
|
||||||
|
for (final path in producedTemps) {
|
||||||
|
if (path == tempPath) continue;
|
||||||
try {
|
try {
|
||||||
await File(outPath).delete();
|
await File(path).delete();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +285,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
|
|||||||
uri: filePath,
|
uri: filePath,
|
||||||
treeUri: downloadTreeUri,
|
treeUri: downloadTreeUri,
|
||||||
relativeDir: safRelativeDir,
|
relativeDir: safRelativeDir,
|
||||||
op: (tempPath) async {
|
op: (tempPath, addCleanup) async {
|
||||||
opStarted = true;
|
opStarted = true;
|
||||||
final decryptedTempPath = await FFmpegService.decryptWithDescriptor(
|
final decryptedTempPath = await FFmpegService.decryptWithDescriptor(
|
||||||
inputPath: tempPath,
|
inputPath: tempPath,
|
||||||
@@ -287,6 +296,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
|
|||||||
failStage = DownloadQueueNotifier._decryptStageDecrypt;
|
failStage = DownloadQueueNotifier._decryptStageDecrypt;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
addCleanup(decryptedTempPath);
|
||||||
if (repairAc4) {
|
if (repairAc4) {
|
||||||
try {
|
try {
|
||||||
await PlatformBridge.ensureAC4Config(decryptedTempPath, tempPath);
|
await PlatformBridge.ensureAC4Config(decryptedTempPath, tempPath);
|
||||||
@@ -416,7 +426,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
|
|||||||
uri: filePath,
|
uri: filePath,
|
||||||
treeUri: treeUri,
|
treeUri: treeUri,
|
||||||
relativeDir: context.safRelativeDir ?? '',
|
relativeDir: context.safRelativeDir ?? '',
|
||||||
op: (tempPath) async {
|
op: (tempPath, addCleanup) async {
|
||||||
final convertedPath = await FFmpegService.convertM4aToLossy(
|
final convertedPath = await FFmpegService.convertM4aToLossy(
|
||||||
tempPath,
|
tempPath,
|
||||||
format: format,
|
format: format,
|
||||||
@@ -424,6 +434,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
|
|||||||
deleteOriginal: false,
|
deleteOriginal: false,
|
||||||
);
|
);
|
||||||
if (convertedPath == null) return null;
|
if (convertedPath == null) return null;
|
||||||
|
addCleanup(convertedPath);
|
||||||
await embedConvertedMetadata(convertedPath);
|
await embedConvertedMetadata(convertedPath);
|
||||||
return (convertedPath, newFileName);
|
return (convertedPath, newFileName);
|
||||||
},
|
},
|
||||||
@@ -540,7 +551,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
|
|||||||
uri: filePath,
|
uri: filePath,
|
||||||
treeUri: treeUri,
|
treeUri: treeUri,
|
||||||
relativeDir: context.safRelativeDir ?? '',
|
relativeDir: context.safRelativeDir ?? '',
|
||||||
op: (tempPath) async {
|
op: (tempPath, addCleanup) async {
|
||||||
final codec = await FFmpegService.probePrimaryAudioCodec(tempPath);
|
final codec = await FFmpegService.probePrimaryAudioCodec(tempPath);
|
||||||
final isAlreadyNativeFlac =
|
final isAlreadyNativeFlac =
|
||||||
codec == 'flac' && await FFmpegService.isNativeFlacFile(tempPath);
|
codec == 'flac' && await FFmpegService.isNativeFlacFile(tempPath);
|
||||||
@@ -571,6 +582,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
|
|||||||
if (flacPath == null) {
|
if (flacPath == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
addCleanup(flacPath);
|
||||||
await embedFlacMetadata(flacPath);
|
await embedFlacMetadata(flacPath);
|
||||||
final rawFileName =
|
final rawFileName =
|
||||||
(result['file_name'] as String?) ??
|
(result['file_name'] as String?) ??
|
||||||
|
|||||||
@@ -1047,8 +1047,16 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
|
|||||||
|
|
||||||
final resultSafFileName = result['file_name'] as String?;
|
final resultSafFileName = result['file_name'] as String?;
|
||||||
final lowerFilePath = filePath.toLowerCase();
|
final lowerFilePath = filePath.toLowerCase();
|
||||||
|
// Recompute from the FINAL file path/result: the HIGH and container
|
||||||
|
// conversions above may have changed the format since actualFormat was
|
||||||
|
// derived from the pre-conversion output.
|
||||||
|
final historyFormat =
|
||||||
|
normalizeAudioFormatValue(
|
||||||
|
result['audio_codec']?.toString() ?? result['format']?.toString(),
|
||||||
|
) ??
|
||||||
|
normalizeAudioFormatValue(audioFormatForPath(filePath));
|
||||||
final isLossyOutput =
|
final isLossyOutput =
|
||||||
isLossyAudioFormat(actualFormat) ||
|
isLossyAudioFormat(historyFormat) ||
|
||||||
lowerFilePath.endsWith('.mp3') ||
|
lowerFilePath.endsWith('.mp3') ||
|
||||||
lowerFilePath.endsWith('.opus') ||
|
lowerFilePath.endsWith('.opus') ||
|
||||||
lowerFilePath.endsWith('.ogg');
|
lowerFilePath.endsWith('.ogg');
|
||||||
@@ -1072,8 +1080,8 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
|
|||||||
: context.safFileName,
|
: context.safFileName,
|
||||||
bitDepth: isLossyOutput ? null : actualBitDepth,
|
bitDepth: isLossyOutput ? null : actualBitDepth,
|
||||||
sampleRate: isLossyOutput ? null : actualSampleRate,
|
sampleRate: isLossyOutput ? null : actualSampleRate,
|
||||||
bitrate: actualBitrate,
|
bitrate: isLossyOutput ? actualBitrate : null,
|
||||||
format: actualFormat,
|
format: historyFormat,
|
||||||
genre: normalizeOptionalString(result['genre'] as String?),
|
genre: normalizeOptionalString(result['genre'] as String?),
|
||||||
label: normalizeOptionalString(result['label'] as String?),
|
label: normalizeOptionalString(result['label'] as String?),
|
||||||
copyright: normalizeOptionalString(
|
copyright: normalizeOptionalString(
|
||||||
|
|||||||
Reference in New Issue
Block a user