packages/main/src/controllers/download.ts
import { NuclearMeta, IpcEvents } from '@nuclear/core';
import { IpcMessageEvent, DownloadItem } from 'electron';
import { inject } from 'inversify';
import { ipcController, ipcEvent } from '../utils/decorators';
import { getTrackArtist, getTrackTitle } from '../utils/tracks';
import Download from '../services/download';
import Logger, { $mainLogger } from '../services/logger';
import Window from '../services/window';
import Platform from '../services/platform';
interface DownloadRef {
uuid: string;
ref: DownloadItem;
}
@ipcController()
class DownloadIpcCtrl {
private downloadItems: DownloadRef[] = [];
constructor(
@inject(Download) private download: Download,
@inject($mainLogger) private logger: Logger,
@inject(Window) private window: Window,
@inject(Platform) private platform: Platform
) { }
removeInvalidCharacters(filename: string): string {
let invalidChars: string[];
if (this.platform.isMac()) {
invalidChars = [':', '/'];
} else if (this.platform.isLinux()) {
invalidChars = ['/'];
} else { // Windows and other platforms
invalidChars = ['<', '>', ':', '"', '/', '\\', '|', '?', '*'];
}
const sanitizedFilename = filename.split('').filter((char) => !invalidChars.includes(char)).join('');
return sanitizedFilename;
}
/**
* Start a download using the download service
*/
@ipcEvent(IpcEvents.DOWNLOAD_START)
async onStartDownload(event: IpcMessageEvent, data: NuclearMeta) {
try {
const { uuid } = data;
const downloadRef = this.downloadItems.find((item => item.uuid === uuid));
if (downloadRef) {
if (downloadRef.ref.canResume()) {
downloadRef.ref.resume();
return;
}
this.downloadItems = this.downloadItems.filter((item => item.uuid === uuid));
}
const artistName = getTrackArtist(data);
const title = getTrackTitle(data);
// .replace(/[/\\?%*:|"<>]/g, '-') or equivalent invalid characters based on platform
const filename = this.removeInvalidCharacters(`${artistName} - ${title}`);
this.logger.log(`Start Download: ${filename}`);
await this.download.start({
query: {
artist: artistName,
track: title
},
filename,
onStart: (item) => {
this.downloadItems = this.downloadItems.filter((item) => item.uuid !== uuid);
this.downloadItems.push({ uuid, ref: item });
},
onProgress: (progress) => {
if (progress.transferredBytes === progress.totalBytes) {
this.window.send(IpcEvents.DOWNLOAD_FINISHED, uuid);
this.downloadItems = this.downloadItems.filter((item) => item.uuid !== uuid);
}
this.window.send(IpcEvents.DOWNLOAD_PROGRESS, {
uuid,
progress: progress.percent
});
}
});
this.logger.log(`Download success: ${artistName} - ${title}`);
} catch (error) {
this.window.send(IpcEvents.DOWNLOAD_ERROR, { uuid: data.uuid, error });
throw error;
}
}
@ipcEvent(IpcEvents.DOWNLOAD_REMOVED)
async onDownloadRemoved(event: IpcMessageEvent, data: NuclearMeta) {
try {
const { uuid } = data;
this.downloadItems = this.downloadItems.filter((item => item.uuid === uuid));
} catch (error) {
this.window.send(IpcEvents.DOWNLOAD_ERROR, { uuid: data.uuid, error });
throw error;
}
}
@ipcEvent(IpcEvents.DOWNLOAD_PAUSE)
async onPauseDownload(event: IpcMessageEvent, data: NuclearMeta) {
try {
const { uuid } = data;
const downloadRef = this.downloadItems.find((item => item.uuid === uuid));
if (downloadRef) {
downloadRef.ref.pause();
}
} catch (error) {
this.window.send(IpcEvents.DOWNLOAD_ERROR, { uuid: data.uuid, error });
throw error;
}
}
}
export default DownloadIpcCtrl;