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:
zarzet
2026-07-13 06:21:42 +07:00
parent 2d52f3251e
commit fb7d101019
3 changed files with 37 additions and 15 deletions
+6 -4
View File
@@ -2836,7 +2836,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
uri: currentFilePath,
treeUri: settings.downloadTreeUri,
relativeDir: effectiveOutputDir,
op: (tempPath) async {
op: (tempPath, addCleanup) async {
opStarted = true;
updateItemStatus(
item.id,
@@ -2853,6 +2853,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
convertFailed = true;
return null;
}
addCleanup(convertedPath);
_log.i(
'Successfully converted M4A to $format (temp): $convertedPath',
);
@@ -2917,7 +2918,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
uri: currentFilePath,
treeUri: settings.downloadTreeUri,
relativeDir: effectiveOutputDir,
op: (tempPath) async {
op: (tempPath, _) async {
opStarted = true;
if (metadataEmbeddingEnabled) {
updateItemStatus(
@@ -2967,7 +2968,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
uri: currentFilePath,
treeUri: settings.downloadTreeUri,
relativeDir: effectiveOutputDir,
op: (tempPath) async {
op: (tempPath, addCleanup) async {
final length = await File(tempPath).length();
if (length < 1024) {
_log.w(
@@ -3041,6 +3042,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
branch = 'convertFailed';
return null;
}
addCleanup(flacPath);
_log.d('Converted to FLAC (temp): $flacPath');
_log.d(
'Embedding metadata and cover to converted FLAC...',
@@ -3368,7 +3370,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
uri: currentFilePath,
treeUri: settings.downloadTreeUri,
relativeDir: effectiveOutputDir,
op: (tempPath) async {
op: (tempPath, _) async {
opStarted = true;
updateItemStatus(
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';
/// Result of [DownloadQueueNotifier._finalizeDecryption]. [failStage] is
@@ -199,14 +198,20 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
required String uri,
required String treeUri,
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,
}) async {
final tempPath = await _copySafToTemp(uri);
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;
try {
final produced = await op(tempPath);
final produced = await op(tempPath, producedTemps.add);
if (produced == null) return null;
outPath = produced.$1;
final fileName = produced.$2;
@@ -228,9 +233,13 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
try {
await File(tempPath).delete();
} catch (_) {}
if (outPath != null && outPath != tempPath) {
if (outPath != null) {
producedTemps.add(outPath);
}
for (final path in producedTemps) {
if (path == tempPath) continue;
try {
await File(outPath).delete();
await File(path).delete();
} catch (_) {}
}
}
@@ -276,7 +285,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
uri: filePath,
treeUri: downloadTreeUri,
relativeDir: safRelativeDir,
op: (tempPath) async {
op: (tempPath, addCleanup) async {
opStarted = true;
final decryptedTempPath = await FFmpegService.decryptWithDescriptor(
inputPath: tempPath,
@@ -287,6 +296,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
failStage = DownloadQueueNotifier._decryptStageDecrypt;
return null;
}
addCleanup(decryptedTempPath);
if (repairAc4) {
try {
await PlatformBridge.ensureAC4Config(decryptedTempPath, tempPath);
@@ -416,7 +426,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
uri: filePath,
treeUri: treeUri,
relativeDir: context.safRelativeDir ?? '',
op: (tempPath) async {
op: (tempPath, addCleanup) async {
final convertedPath = await FFmpegService.convertM4aToLossy(
tempPath,
format: format,
@@ -424,6 +434,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
deleteOriginal: false,
);
if (convertedPath == null) return null;
addCleanup(convertedPath);
await embedConvertedMetadata(convertedPath);
return (convertedPath, newFileName);
},
@@ -540,7 +551,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
uri: filePath,
treeUri: treeUri,
relativeDir: context.safRelativeDir ?? '',
op: (tempPath) async {
op: (tempPath, addCleanup) async {
final codec = await FFmpegService.probePrimaryAudioCodec(tempPath);
final isAlreadyNativeFlac =
codec == 'flac' && await FFmpegService.isNativeFlacFile(tempPath);
@@ -571,6 +582,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
if (flacPath == null) {
return null;
}
addCleanup(flacPath);
await embedFlacMetadata(flacPath);
final rawFileName =
(result['file_name'] as String?) ??
@@ -1047,8 +1047,16 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
final resultSafFileName = result['file_name'] as String?;
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 =
isLossyAudioFormat(actualFormat) ||
isLossyAudioFormat(historyFormat) ||
lowerFilePath.endsWith('.mp3') ||
lowerFilePath.endsWith('.opus') ||
lowerFilePath.endsWith('.ogg');
@@ -1072,8 +1080,8 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
: context.safFileName,
bitDepth: isLossyOutput ? null : actualBitDepth,
sampleRate: isLossyOutput ? null : actualSampleRate,
bitrate: actualBitrate,
format: actualFormat,
bitrate: isLossyOutput ? actualBitrate : null,
format: historyFormat,
genre: normalizeOptionalString(result['genre'] as String?),
label: normalizeOptionalString(result['label'] as String?),
copyright: normalizeOptionalString(