refactor(download): remove concurrent download option

The download API only permits one request at a time, so parallel
downloads are removed to avoid wasted/blocked API calls. Downloads
now always run sequentially (one track at a time).

- Drop concurrentDownloads from AppSettings + JSON serialization
- Remove setConcurrentDownloads and the settings UI (1-5 chips + warning)
- Strip optionsConcurrent* l10n keys from all ARBs and regenerate
- Rework queue worker into _processQueueSequential (single active download)
- Update marketing copy and adjust tests
This commit is contained in:
zarzet
2026-06-06 21:58:45 +07:00
parent 3a536ad348
commit d736e5aafe
42 changed files with 9 additions and 889 deletions
-24
View File
@@ -494,30 +494,6 @@ abstract class AppLocalizations {
/// **'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.'**
String get optionsArtistTagModeSplitVorbisSubtitle;
/// Number of parallel downloads
///
/// In en, this message translates to:
/// **'Concurrent Downloads'**
String get optionsConcurrentDownloads;
/// Download one at a time
///
/// In en, this message translates to:
/// **'Sequential (1 at a time)'**
String get optionsConcurrentSequential;
/// Multiple parallel downloads
///
/// In en, this message translates to:
/// **'{count} parallel downloads'**
String optionsConcurrentParallel(int count);
/// Warning about rate limits
///
/// In en, this message translates to:
/// **'Parallel downloads may trigger rate limiting'**
String get optionsConcurrentWarning;
/// Show/hide store tab
///
/// In en, this message translates to:
-15
View File
@@ -206,21 +206,6 @@ class AppLocalizationsAr extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@override
String get optionsConcurrentSequential => 'Sequential (1 at a time)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallel downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Repo';
-15
View File
@@ -211,21 +211,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Schreibe einen Künstler Tag pro Künstler für FLAC und Opus; MP3 und M4A bleiben beigetreten.';
@override
String get optionsConcurrentDownloads => 'Parallele Downloads';
@override
String get optionsConcurrentSequential => 'Sequentiell (1 gleichzeitig)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallele Downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallele Downloads können Ratenlimitierung auslösen';
@override
String get optionsExtensionStore => 'Erweiterungs-Repo';
-15
View File
@@ -206,21 +206,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@override
String get optionsConcurrentSequential => 'Sequential (1 at a time)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallel downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Repo';
-30
View File
@@ -206,21 +206,6 @@ class AppLocalizationsEs extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@override
String get optionsConcurrentSequential => 'Sequential (1 at a time)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallel downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Store';
@@ -4448,21 +4433,6 @@ class AppLocalizationsEsEs extends AppLocalizationsEs {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Escribe una etiqueta de artista por artista para FLAC y OPUS; MP3 y M4A se mantienen agrupados.';
@override
String get optionsConcurrentDownloads => 'Descargas simultáneas';
@override
String get optionsConcurrentSequential => 'Secuencial (1 a la vez)';
@override
String optionsConcurrentParallel(int count) {
return '$count descargas en paralelo';
}
@override
String get optionsConcurrentWarning =>
'Las descargas paralelas pueden activar la limitación de velocidad';
@override
String get optionsExtensionStore => 'Extensión Repo';
-15
View File
@@ -213,21 +213,6 @@ class AppLocalizationsFr extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Créez une balise « artiste » par artiste pour les fichiers FLAC et Opus ; les fichiers MP3 et M4A restent regroupés.';
@override
String get optionsConcurrentDownloads => 'Téléchargements simultanés';
@override
String get optionsConcurrentSequential => 'Séquentiel (1 à la fois)';
@override
String optionsConcurrentParallel(int count) {
return '$count téléchargements simultanés';
}
@override
String get optionsConcurrentWarning =>
'Les téléchargements simultanés peuvent déclencher une limitation du débit';
@override
String get optionsExtensionStore => 'Référentiel d\'extensions';
-15
View File
@@ -206,21 +206,6 @@ class AppLocalizationsHi extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@override
String get optionsConcurrentSequential => 'Sequential (1 at a time)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallel downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Repo';
-15
View File
@@ -209,21 +209,6 @@ class AppLocalizationsId extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Tulis satu tag artis per artis untuk FLAC dan Opus; MP3 dan M4A tetap tergabung.';
@override
String get optionsConcurrentDownloads => 'Unduhan Bersamaan';
@override
String get optionsConcurrentSequential => 'Berurutan (1 per waktu)';
@override
String optionsConcurrentParallel(int count) {
return '$count unduhan paralel';
}
@override
String get optionsConcurrentWarning =>
'Unduhan paralel dapat memicu pembatasan rate';
@override
String get optionsExtensionStore => 'Extension Repo';
-15
View File
@@ -205,21 +205,6 @@ class AppLocalizationsJa extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => '同時ダウンロード';
@override
String get optionsConcurrentSequential => 'Sequential (1 at a time)';
@override
String optionsConcurrentParallel(int count) {
return '$count 件の分割ダウンロード';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Repo';
-14
View File
@@ -202,20 +202,6 @@ class AppLocalizationsKo extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => '동시 다운로드';
@override
String get optionsConcurrentSequential => '순차 다운로드 (한 번에 하나)';
@override
String optionsConcurrentParallel(int count) {
return '$count개 동시 다운로드';
}
@override
String get optionsConcurrentWarning => '동시에 다수의 음반을 다운로드하면 속도 제한이 발생할 수 있습니다';
@override
String get optionsExtensionStore => 'Extension Repo';
-15
View File
@@ -206,21 +206,6 @@ class AppLocalizationsNl extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@override
String get optionsConcurrentSequential => 'Sequentiële (1 per keer)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallel downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloaden kan leiden tot rate-limiting';
@override
String get optionsExtensionStore => 'Extension Repo';
-30
View File
@@ -206,21 +206,6 @@ class AppLocalizationsPt extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@override
String get optionsConcurrentSequential => 'Sequential (1 at a time)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallel downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Store';
@@ -4447,21 +4432,6 @@ class AppLocalizationsPtPt extends AppLocalizationsPt {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Downloads Simultâneos';
@override
String get optionsConcurrentSequential => 'Sequencial (1 por vez)';
@override
String optionsConcurrentParallel(int count) {
return '$count downloads paralelos';
}
@override
String get optionsConcurrentWarning =>
'Downloads simultâneos podem causar um limite da taxa (ratelimit)';
@override
String get optionsExtensionStore => 'Extension Repo';
-15
View File
@@ -211,21 +211,6 @@ class AppLocalizationsRu extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Одновременные загрузки';
@override
String get optionsConcurrentSequential => 'Последовательно (1 за раз)';
@override
String optionsConcurrentParallel(int count) {
return '$count параллельных загрузок';
}
@override
String get optionsConcurrentWarning =>
'Параллельные загрузки могут вызвать ограничение скорости';
@override
String get optionsExtensionStore => 'Репозиторий расширения';
-15
View File
@@ -211,21 +211,6 @@ class AppLocalizationsTr extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'FLAC ve Opus için her sanatçıya ayrı bir etiket yazın; MP3 ve M4A birleşik kalır.';
@override
String get optionsConcurrentDownloads => 'Eş Zamanlı İndirmeler';
@override
String get optionsConcurrentSequential => 'Sıralı (Birer birer)';
@override
String optionsConcurrentParallel(int count) {
return 'Aynı anda $count indirme';
}
@override
String get optionsConcurrentWarning =>
'Aynı anda birden fazla indirme sınırlamaya takılabilir';
@override
String get optionsExtensionStore => 'Eklenti Deposu';
-15
View File
@@ -212,21 +212,6 @@ class AppLocalizationsUk extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Для FLAC та Opus на кожного виконавця додати окремий тег виконавця; MP3 та M4A залишаються об’єднаними.';
@override
String get optionsConcurrentDownloads => 'Кількість одночасних завантажень';
@override
String get optionsConcurrentSequential => 'Послідовно (по одному за раз)';
@override
String optionsConcurrentParallel(int count) {
return '$count паралельних завантажень';
}
@override
String get optionsConcurrentWarning =>
'Паралельні завантаження можуть призвести до обмеження швидкості';
@override
String get optionsExtensionStore => 'Репозиторій розширень';
-44
View File
@@ -206,21 +206,6 @@ class AppLocalizationsZh extends AppLocalizations {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@override
String get optionsConcurrentSequential => 'Sequential (1 at a time)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallel downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Store';
@@ -4434,20 +4419,6 @@ class AppLocalizationsZhCn extends AppLocalizationsZh {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => '并行下载数';
@override
String get optionsConcurrentSequential => '按顺序下载(一次一首)';
@override
String optionsConcurrentParallel(int count) {
return '同时下载 $count';
}
@override
String get optionsConcurrentWarning => '并行下载可能会触发速率限制';
@override
String get optionsExtensionStore => 'Extension Repo';
@@ -8670,21 +8641,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
String get optionsArtistTagModeSplitVorbisSubtitle =>
'Write one artist tag per artist for FLAC and Opus; MP3 and M4A stay joined.';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@override
String get optionsConcurrentSequential => 'Sequential (1 at a time)';
@override
String optionsConcurrentParallel(int count) {
return '$count parallel downloads';
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Repo';
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Concurrent Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequential (1 at a time)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallel downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Parallele Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequentiell (1 gleichzeitig)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallele Downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallele Downloads können Ratenlimitierung auslösen",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Erweiterungs-Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Concurrent Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequential (1 at a time)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallel downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -182,27 +182,6 @@
"@optionsMaxQualityCoverSubtitle": {
"description": "Subtitle for max quality cover"
},
"optionsConcurrentDownloads": "Concurrent Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequential (1 at a time)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallel downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Store",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Descargas simultáneas",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Secuencial (1 a la vez)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} descargas en paralelo",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Las descargas paralelas pueden activar la limitación de velocidad",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extensión Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Téléchargements simultanés",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Séquentiel (1 à la fois)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} téléchargements simultanés",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Les téléchargements simultanés peuvent déclencher une limitation du débit",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Référentiel d'extensions",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Concurrent Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequential (1 at a time)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallel downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -206,27 +206,6 @@
"@optionsMaxQualityCoverSubtitle": {
"description": "Subtitle for max quality cover"
},
"optionsConcurrentDownloads": "Unduhan Bersamaan",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Berurutan (1 per waktu)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} unduhan paralel",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Unduhan paralel dapat memicu pembatasan rate",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -190,27 +190,6 @@
"@optionsMaxQualityCoverSubtitle": {
"description": "Subtitle for max quality cover"
},
"optionsConcurrentDownloads": "同時ダウンロード",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequential (1 at a time)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} 件の分割ダウンロード",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "동시 다운로드",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "순차 다운로드 (한 번에 하나)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count}개 동시 다운로드",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "동시에 다수의 음반을 다운로드하면 속도 제한이 발생할 수 있습니다",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Concurrent Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequentiële (1 per keer)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallel downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloaden kan leiden tot rate-limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -182,27 +182,6 @@
"@optionsMaxQualityCoverSubtitle": {
"description": "Subtitle for max quality cover"
},
"optionsConcurrentDownloads": "Concurrent Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequential (1 at a time)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallel downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Store",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Downloads Simultâneos",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequencial (1 por vez)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} downloads paralelos",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Downloads simultâneos podem causar um limite da taxa (ratelimit)",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Одновременные загрузки",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Последовательно (1 за раз)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} параллельных загрузок",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Параллельные загрузки могут вызвать ограничение скорости",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Репозиторий расширения",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -190,27 +190,6 @@
"@optionsMaxQualityCoverSubtitle": {
"description": "Subtitle for max quality cover"
},
"optionsConcurrentDownloads": "Eş Zamanlı İndirmeler",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sıralı (Birer birer)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "Aynı anda {count} indirme",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Aynı anda birden fazla indirme sınırlamaya takılabilir",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Eklenti Deposu",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Кількість одночасних завантажень",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Послідовно (по одному за раз)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} паралельних завантажень",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Паралельні завантаження можуть призвести до обмеження швидкості",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Репозиторій розширень",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -182,27 +182,6 @@
"@optionsMaxQualityCoverSubtitle": {
"description": "Subtitle for max quality cover"
},
"optionsConcurrentDownloads": "Concurrent Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequential (1 at a time)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallel downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Store",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "并行下载数",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "按顺序下载(一次一首)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "同时下载 {count} 首",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "并行下载可能会触发速率限制",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-21
View File
@@ -250,27 +250,6 @@
"@optionsArtistTagModeSplitVorbisSubtitle": {
"description": "Subtitle for split Vorbis artist tag mode"
},
"optionsConcurrentDownloads": "Concurrent Downloads",
"@optionsConcurrentDownloads": {
"description": "Number of parallel downloads"
},
"optionsConcurrentSequential": "Sequential (1 at a time)",
"@optionsConcurrentSequential": {
"description": "Download one at a time"
},
"optionsConcurrentParallel": "{count} parallel downloads",
"@optionsConcurrentParallel": {
"description": "Multiple parallel downloads",
"placeholders": {
"count": {
"type": "int"
}
}
},
"optionsConcurrentWarning": "Parallel downloads may trigger rate limiting",
"@optionsConcurrentWarning": {
"description": "Warning about rate limits"
},
"optionsExtensionStore": "Extension Repo",
"@optionsExtensionStore": {
"description": "Show/hide store tab"
-4
View File
@@ -22,7 +22,6 @@ class AppSettings {
final bool embedReplayGain; // Calculate and embed ReplayGain tags
final bool maxQualityCover;
final bool isFirstLaunch;
final int concurrentDownloads;
final bool checkForUpdates;
final String updateChannel;
final bool hasSearchedBefore;
@@ -108,7 +107,6 @@ class AppSettings {
this.embedReplayGain = false,
this.maxQualityCover = true,
this.isFirstLaunch = true,
this.concurrentDownloads = 1,
this.checkForUpdates = true,
this.updateChannel = 'stable',
this.hasSearchedBefore = false,
@@ -171,7 +169,6 @@ class AppSettings {
bool? embedReplayGain,
bool? maxQualityCover,
bool? isFirstLaunch,
int? concurrentDownloads,
bool? checkForUpdates,
String? updateChannel,
bool? hasSearchedBefore,
@@ -237,7 +234,6 @@ class AppSettings {
embedReplayGain: embedReplayGain ?? this.embedReplayGain,
maxQualityCover: maxQualityCover ?? this.maxQualityCover,
isFirstLaunch: isFirstLaunch ?? this.isFirstLaunch,
concurrentDownloads: concurrentDownloads ?? this.concurrentDownloads,
checkForUpdates: checkForUpdates ?? this.checkForUpdates,
updateChannel: updateChannel ?? this.updateChannel,
hasSearchedBefore: hasSearchedBefore ?? this.hasSearchedBefore,
-2
View File
@@ -21,7 +21,6 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
embedReplayGain: json['embedReplayGain'] as bool? ?? false,
maxQualityCover: json['maxQualityCover'] as bool? ?? true,
isFirstLaunch: json['isFirstLaunch'] as bool? ?? true,
concurrentDownloads: (json['concurrentDownloads'] as num?)?.toInt() ?? 1,
checkForUpdates: json['checkForUpdates'] as bool? ?? true,
updateChannel: json['updateChannel'] as String? ?? 'stable',
hasSearchedBefore: json['hasSearchedBefore'] as bool? ?? false,
@@ -102,7 +101,6 @@ Map<String, dynamic> _$AppSettingsToJson(
'embedReplayGain': instance.embedReplayGain,
'maxQualityCover': instance.maxQualityCover,
'isFirstLaunch': instance.isFirstLaunch,
'concurrentDownloads': instance.concurrentDownloads,
'checkForUpdates': instance.checkForUpdates,
'updateChannel': instance.updateChannel,
'hasSearchedBefore': instance.hasSearchedBefore,
+8 -29
View File
@@ -1767,7 +1767,6 @@ class DownloadQueueState {
final String singleFilenameFormat;
final String audioQuality;
final bool autoFallback;
final int concurrentDownloads;
const DownloadQueueState({
this.items = const [],
@@ -1780,7 +1779,6 @@ class DownloadQueueState {
this.singleFilenameFormat = '{title} - {artist}',
this.audioQuality = 'LOSSLESS',
this.autoFallback = true,
this.concurrentDownloads = 1,
});
DownloadQueueState copyWith({
@@ -1794,7 +1792,6 @@ class DownloadQueueState {
String? singleFilenameFormat,
String? audioQuality,
bool? autoFallback,
int? concurrentDownloads,
}) {
final resolvedItems = items ?? this.items;
return DownloadQueueState(
@@ -1814,7 +1811,6 @@ class DownloadQueueState {
singleFilenameFormat: singleFilenameFormat ?? this.singleFilenameFormat,
audioQuality: audioQuality ?? this.audioQuality,
autoFallback: autoFallback ?? this.autoFallback,
concurrentDownloads: concurrentDownloads ?? this.concurrentDownloads,
);
}
@@ -1962,14 +1958,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
@override
DownloadQueueState build() {
ref.listen<AppSettings>(settingsProvider, (previous, next) {
final previousConcurrent =
previous?.concurrentDownloads ?? state.concurrentDownloads;
updateSettings(next);
if (previousConcurrent != next.concurrentDownloads) {
_log.i(
'Concurrent downloads updated: $previousConcurrent -> ${next.concurrentDownloads}',
);
}
if (previous?.downloadNetworkMode != next.downloadNetworkMode) {
_handleDownloadNetworkModeChanged(next.downloadNetworkMode);
}
@@ -3601,7 +3590,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
void updateSettings(AppSettings settings) {
final concurrentDownloads = settings.concurrentDownloads.clamp(1, 5);
state = state.copyWith(
outputDir: settings.downloadDirectory.isNotEmpty
? settings.downloadDirectory
@@ -3610,7 +3598,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
singleFilenameFormat: settings.singleFilenameFormat,
audioQuality: settings.audioQuality,
autoFallback: settings.autoFallback,
concurrentDownloads: concurrentDownloads,
);
}
@@ -6781,9 +6768,8 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
_log.d('Concurrent downloads: ${state.concurrentDownloads}');
try {
await _processQueueParallel();
await _processQueueSequential();
} finally {
if (iosDownloadBookmarkActive) {
await PlatformBridge.stopAccessingIosBookmark();
@@ -6859,19 +6845,18 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
Future<void> _processQueueParallel() async {
Future<void> _processQueueSequential() async {
final activeDownloads = <String, Future<void>>{};
var lastLoggedMaxConcurrent = -1;
_startMultiProgressPolling();
while (true) {
if (state.isPaused) {
if (activeDownloads.isEmpty) {
_log.d('Queue is paused and no active downloads remain');
_log.d('Queue is paused and no active download remains');
break;
}
_log.d('Queue is paused, waiting for active downloads...');
_log.d('Queue is paused, waiting for active download...');
await Future.any([
Future.wait(activeDownloads.values),
Future<void>.delayed(_queueSchedulingInterval),
@@ -6879,12 +6864,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
continue;
}
final maxConcurrent = max(1, state.concurrentDownloads);
if (lastLoggedMaxConcurrent != maxConcurrent) {
_log.d('Parallel worker max concurrency now: $maxConcurrent');
lastLoggedMaxConcurrent = maxConcurrent;
}
final queuedItems = state.items
.where(
(item) =>
@@ -6898,7 +6877,9 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
break;
}
while (activeDownloads.length < maxConcurrent &&
// One download at a time: only start the next item once the current
// download has finished, to stay within the API's single-request limit.
if (activeDownloads.isEmpty &&
queuedItems.isNotEmpty &&
!state.isPaused) {
final item = queuedItems.removeAt(0);
@@ -6911,9 +6892,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
});
activeDownloads[item.id] = future;
_log.d(
'Started parallel download: ${item.track.name} (${activeDownloads.length}/$maxConcurrent active)',
);
_log.d('Started download: ${item.track.name}');
}
if (activeDownloads.isNotEmpty) {
-6
View File
@@ -392,12 +392,6 @@ class SettingsNotifier extends Notifier<AppSettings> {
_saveSettings();
}
void setConcurrentDownloads(int count) {
final clamped = count.clamp(1, 5);
state = state.copyWith(concurrentDownloads: clamped);
_saveSettings();
}
void setCheckForUpdates(bool enabled) {
state = state.copyWith(checkForUpdates: enabled);
_saveSettings();
@@ -178,12 +178,6 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
SliverToBoxAdapter(
child: SettingsGroup(
children: [
_ConcurrentDownloadsItem(
currentValue: settings.concurrentDownloads,
onChanged: (v) => ref
.read(settingsProvider.notifier)
.setConcurrentDownloads(v),
),
SettingsItem(
icon: Icons.wifi,
title: context.l10n.settingsDownloadNetwork,
@@ -1015,139 +1009,6 @@ class _ServiceChip extends StatelessWidget {
}
}
class _ConcurrentDownloadsItem extends StatelessWidget {
final int currentValue;
final ValueChanged<int> onChanged;
const _ConcurrentDownloadsItem({
required this.currentValue,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.download_for_offline,
color: colorScheme.onSurfaceVariant,
size: 24,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.optionsConcurrentDownloads,
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 2),
Text(
currentValue == 1
? context.l10n.optionsConcurrentSequential
: context.l10n.optionsConcurrentParallel(
currentValue,
),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
for (final n in [1, 2, 3, 4, 5]) ...[
if (n > 1) const SizedBox(width: 8),
_ConcurrentChip(
label: '$n',
isSelected: currentValue == n,
onTap: () => onChanged(n),
),
],
],
),
const SizedBox(height: 12),
Row(
children: [
Icon(
Icons.warning_amber_rounded,
size: 16,
color: colorScheme.error,
),
const SizedBox(width: 8),
Expanded(
child: Text(
context.l10n.optionsConcurrentWarning,
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: colorScheme.error),
),
),
],
),
],
),
);
}
}
class _ConcurrentChip extends StatelessWidget {
final String label;
final bool isSelected;
final VoidCallback onTap;
const _ConcurrentChip({
required this.label,
required this.isSelected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
final unselectedColor = isDark
? Color.alphaBlend(
Colors.white.withValues(alpha: 0.05),
colorScheme.surface,
)
: colorScheme.surfaceContainerHigh;
return Expanded(
child: Material(
color: isSelected ? colorScheme.primaryContainer : unselectedColor,
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Center(
child: Text(
label,
style: TextStyle(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected
? colorScheme.onPrimaryContainer
: colorScheme.onSurfaceVariant,
),
),
),
),
),
),
);
}
}
class _MetadataSourceSelector extends ConsumerWidget {
const _MetadataSourceSelector();
+1 -1
View File
@@ -454,7 +454,7 @@
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="currentColor" d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12zm-6-1l-4-4.8h3V5h2v4.2h3L14 14z"/></svg>
</div>
<h3>Batch & Playlist Download</h3>
<p>Download entire albums and playlists at once. Smart queue management with concurrent downloads.</p>
<p>Download entire albums and playlists at once. Smart queue management that downloads one track at a time.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
-2
View File
@@ -202,7 +202,6 @@ void main() {
final updated = settings.copyWith(
defaultService: 'tidal',
concurrentDownloads: 4,
embedReplayGain: true,
lyricsProviders: ['apple_music'],
lyricsAppleElrcWordSync: true,
@@ -213,7 +212,6 @@ void main() {
);
expect(updated.defaultService, 'tidal');
expect(updated.concurrentDownloads, 4);
expect(updated.embedReplayGain, isTrue);
expect(updated.lyricsProviders, ['apple_music']);
expect(updated.lyricsAppleElrcWordSync, isTrue);