feat(downloads): implement SequentialFileWriter for individual file downloads
This commit is contained in:
parent
25c338fac3
commit
a4d92b0759
2 changed files with 37 additions and 53 deletions
|
|
@ -41,6 +41,35 @@ export interface IBulkDownloadWriter {
|
||||||
write(files: AsyncIterable<WriterEntry>): Promise<void>;
|
write(files: AsyncIterable<WriterEntry>): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers individual downloads for each file entry, one after another.
|
||||||
|
*/
|
||||||
|
export class SequentialFileWriter implements IBulkDownloadWriter {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
async write(files: AsyncIterable<WriterEntry>): Promise<void> {
|
||||||
|
for await (const file of files) {
|
||||||
|
const name = file.name?.split('/').pop();
|
||||||
|
const ext = name?.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
console.warn('No name for file entry.', file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['m3u', 'm3u8', 'cue', 'jpg', 'png', 'nfo', 'json'].includes(ext)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.input instanceof Blob) {
|
||||||
|
triggerDownload(file.input, name);
|
||||||
|
} else {
|
||||||
|
triggerDownload(new Blob([file.input as BlobPart]), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Streams a ZIP archive to a file via the File System Access API.
|
* Streams a ZIP archive to a file via the File System Access API.
|
||||||
* Prompts the user to choose a save location with showSaveFilePicker.
|
* Prompts the user to choose a save location with showSaveFilePicker.
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,13 @@ import { AbortError } from './errorTypes.ts';
|
||||||
import { lyricsSettings, bulkDownloadSettings, playlistSettings } from './storage.js';
|
import { lyricsSettings, bulkDownloadSettings, playlistSettings } from './storage.js';
|
||||||
import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js';
|
import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js';
|
||||||
import { triggerDownload } from './download-utils.ts';
|
import { triggerDownload } from './download-utils.ts';
|
||||||
import { ZipStreamWriter, ZipBlobWriter, ZipNeutralinoWriter, FolderPickerWriter } from './bulk-download-writer.ts';
|
import {
|
||||||
|
ZipStreamWriter,
|
||||||
|
ZipBlobWriter,
|
||||||
|
ZipNeutralinoWriter,
|
||||||
|
FolderPickerWriter,
|
||||||
|
SequentialFileWriter,
|
||||||
|
} from './bulk-download-writer.ts';
|
||||||
import { FfmpegProgress } from './ffmpeg.types.js';
|
import { FfmpegProgress } from './ffmpeg.types.js';
|
||||||
import { DownloadProgress, ProgressMessage, SegmentedDownloadProgress } from './progressEvents.js';
|
import { DownloadProgress, ProgressMessage, SegmentedDownloadProgress } from './progressEvents.js';
|
||||||
|
|
||||||
|
|
@ -318,44 +324,6 @@ async function downloadTrackBlob(track, quality, api, signal = null, onProgress
|
||||||
return { blob, extension };
|
return { blob, extension };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bulkDownloadSequentially(tracks, api, quality, lyricsManager, notification, coverBlob = null) {
|
|
||||||
const { abortController } = bulkDownloadTasks.get(notification);
|
|
||||||
const signal = abortController.signal;
|
|
||||||
|
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
|
||||||
if (signal.aborted) break;
|
|
||||||
const track = tracks[i];
|
|
||||||
const trackTitle = getTrackTitle(track);
|
|
||||||
|
|
||||||
updateBulkDownloadProgress(notification, i, tracks.length, trackTitle);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { blob, extension } = await downloadTrackBlob(track, quality, api, signal, null);
|
|
||||||
const filename = buildTrackFilename(track, quality, extension);
|
|
||||||
triggerDownload(blob, filename);
|
|
||||||
|
|
||||||
if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) {
|
|
||||||
try {
|
|
||||||
const lyricsData = await lyricsManager.fetchLyrics(track.id, track);
|
|
||||||
if (lyricsData) {
|
|
||||||
const lrcContent = lyricsManager.generateLRCContent(lyricsData, track);
|
|
||||||
if (lrcContent) {
|
|
||||||
const lrcFilename = filename.replace(/\.[^.]+$/, '.lrc');
|
|
||||||
const lrcBlob = new Blob([lrcContent], { type: 'text/plain' });
|
|
||||||
triggerDownload(lrcBlob, lrcFilename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Silent fail for lyrics
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (err.name === 'AbortError') throw err;
|
|
||||||
console.error(`Failed to download track ${trackTitle}:`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function bulkDownload(
|
async function bulkDownload(
|
||||||
tracks,
|
tracks,
|
||||||
folderName,
|
folderName,
|
||||||
|
|
@ -548,7 +516,7 @@ async function createBulkWriter(folderName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (method === 'individual') {
|
if (method === 'individual') {
|
||||||
return null;
|
return new SequentialFileWriter();
|
||||||
}
|
}
|
||||||
// method === 'zip' (or folder picker unavailable as fallback)
|
// method === 'zip' (or folder picker unavailable as fallback)
|
||||||
if (!forceZipBlob && hasFileSystemAccess) {
|
if (!forceZipBlob && hasFileSystemAccess) {
|
||||||
|
|
@ -586,9 +554,6 @@ async function startBulkDownload(
|
||||||
type,
|
type,
|
||||||
metadata
|
metadata
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// Individual sequential downloads
|
|
||||||
await bulkDownloadSequentially(tracks, api, quality, lyricsManager, notification);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
completeBulkDownload(notification, true);
|
completeBulkDownload(notification, true);
|
||||||
|
|
@ -803,16 +768,6 @@ export async function downloadDiscography(artist, selectedReleases, api, quality
|
||||||
|
|
||||||
if (writer) {
|
if (writer) {
|
||||||
await writer.write(yieldDiscography());
|
await writer.write(yieldDiscography());
|
||||||
} else {
|
|
||||||
// Individual sequential downloads for discography
|
|
||||||
for (let albumIndex = 0; albumIndex < selectedReleases.length; albumIndex++) {
|
|
||||||
if (signal.aborted) break;
|
|
||||||
const album = selectedReleases[albumIndex];
|
|
||||||
updateBulkDownloadProgress(notification, albumIndex, selectedReleases.length, album.title);
|
|
||||||
const { tracks: rawTracks } = await api.getAlbum(album.id);
|
|
||||||
const tracks = await annotateTracksWithDiscInfo(rawTracks, api);
|
|
||||||
await bulkDownloadSequentially(tracks, api, quality, lyricsManager, notification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
completeBulkDownload(notification, true);
|
completeBulkDownload(notification, true);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue