fix(download): resolve missing album folders before publishing

This commit is contained in:
zarzet
2026-09-07 16:13:41 +07:00
parent fed88fc3a9
commit c48406f5d0
15 changed files with 383 additions and 13 deletions
@@ -863,6 +863,11 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
coverUrl: settings.embedMetadata ? (track.coverUrl ?? '') : '',
coverMaxDimension: settings.embeddedCoverMaxDimension,
outputDir: outputDir,
albumFolderTemplate: _unresolvedAlbumFolderTemplate(
track,
item,
settings,
),
filenameFormat: filenameFormat,
quality: quality,
embedMetadata: settings.embedMetadata,
@@ -25,6 +25,23 @@ class _NativeWorkerRequestContext {
this.safFileName,
this.qualityVariantCollisionOnly = false,
});
_NativeWorkerRequestContext withResolvedFolder(Map<String, dynamic> result) {
final directory = result['saf_relative_dir'];
if (storageMode != 'saf' || directory is! String) return this;
return _NativeWorkerRequestContext(
item: item,
requestJson: requestJson,
outputDir: directory,
quality: quality,
storageMode: storageMode,
outputExt: outputExt,
downloadTreeUri: downloadTreeUri,
safRelativeDir: directory,
safFileName: safFileName,
qualityVariantCollisionOnly: qualityVariantCollisionOnly,
);
}
}
class _NativeWorkerStartupTimeout implements Exception {
@@ -854,7 +871,7 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
final isSafMode = _isSafMode(settings);
final rawOutputDir = isSafMode
? await _buildRelativeOutputDir(
? _buildRelativeOutputDir(
item.track,
settings.folderOrganization,
separateSingles: settings.separateSingles,
@@ -1212,6 +1229,7 @@ extension _DownloadQueueNativeWorker on DownloadQueueNotifier {
Map<String, dynamic> result,
AppSettings settings,
) async {
context = context.withResolvedFolder(result);
final item = context.item;
var filePath = result['file_path'] as String?;
if (filePath == null || filePath.isEmpty) {
@@ -234,7 +234,7 @@ extension _DownloadQueuePaths on DownloadQueueNotifier {
}
}
final relativeDir = await _buildRelativeOutputDir(
final relativeDir = _buildRelativeOutputDir(
track,
folderOrganization,
separateSingles: separateSingles,
@@ -391,7 +391,7 @@ extension _DownloadQueuePaths on DownloadQueueNotifier {
return artist;
}
Future<String> _buildRelativeOutputDir(
String _buildRelativeOutputDir(
Track track,
String folderOrganization, {
bool separateSingles = false,
@@ -401,7 +401,7 @@ extension _DownloadQueuePaths on DownloadQueueNotifier {
bool usePrimaryArtistOnly = false,
bool filterContributingArtistsInAlbumArtist = false,
String? playlistName,
}) async {
}) {
final playlistPrefix =
createPlaylistFolder &&
folderOrganization != 'playlist' &&
@@ -503,6 +503,31 @@ extension _DownloadQueuePaths on DownloadQueueNotifier {
return '$prefix/$suffix';
}
String _unresolvedAlbumFolderTemplate(
Track track,
DownloadItem item,
AppSettings settings,
) {
if (track.albumName.trim().isNotEmpty) return '';
String folderFor(Track value) => _buildRelativeOutputDir(
value,
settings.folderOrganization,
separateSingles: settings.separateSingles,
albumFolderStructure: settings.albumFolderStructure,
createPlaylistFolder: settings.createPlaylistFolder,
useAlbumArtistForFolders: settings.useAlbumArtistForFolders,
usePrimaryArtistOnly: settings.usePrimaryArtistOnly,
filterContributingArtistsInAlbumArtist:
settings.filterContributingArtistsInAlbumArtist,
playlistName: item.playlistName,
);
final planned = folderFor(track);
final marked = folderFor(track.copyWith(albumName: '{album}'));
if (planned == marked) return '';
final leaf = marked.split('/').last;
return leaf.contains('{album}') ? leaf : '';
}
String? _extensionPreferredOutputExt(String service) {
final normalizedService = service.trim().toLowerCase();
if (normalizedService.isEmpty) return null;
@@ -233,6 +233,12 @@ class _DownloadRun {
}
if (result['success'] == true) {
if (effectiveSafMode && result['saf_relative_dir'] is String) {
effectiveOutputDir = n._sanitizeSafRelativeDir(
result['saf_relative_dir'] as String,
);
_log.d('Resolved output dir: $effectiveOutputDir');
}
if (!await _handleDownloadSuccess()) return;
} else {
if (!await _handleBackendFailure()) return;
@@ -394,7 +400,7 @@ class _DownloadRun {
if (quality == 'DEFAULT') quality = n.state.audioQuality;
final isSafMode = n._isSafMode(settings);
final relativeOutputDir = isSafMode
? await n._buildRelativeOutputDir(
? n._buildRelativeOutputDir(
trackToDownload,
settings.folderOrganization,
separateSingles: settings.separateSingles,
@@ -14,6 +14,7 @@ class DownloadRequestPayload {
final String coverUrl;
final int coverMaxDimension;
final String outputDir;
final String albumFolderTemplate;
final String filenameFormat;
final String quality;
final bool embedMetadata;
@@ -76,6 +77,7 @@ class DownloadRequestPayload {
this.coverUrl = '',
this.coverMaxDimension = 0,
required this.outputDir,
this.albumFolderTemplate = '',
required this.filenameFormat,
this.quality = 'LOSSLESS',
this.embedMetadata = true,
@@ -140,6 +142,8 @@ class DownloadRequestPayload {
'cover_url': coverUrl,
'cover_max_dimension': coverMaxDimension,
'output_dir': outputDir,
if (albumFolderTemplate.isNotEmpty)
'album_folder_template': albumFolderTemplate,
'filename_format': filenameFormat,
'quality': quality,
'embed_metadata': embedMetadata,
@@ -208,6 +212,7 @@ class DownloadRequestPayload {
coverUrl: coverUrl,
coverMaxDimension: coverMaxDimension,
outputDir: outputDir,
albumFolderTemplate: albumFolderTemplate,
filenameFormat: filenameFormat,
quality: quality,
embedMetadata: embedMetadata,