import { BehaviorSubject, Observable, Subscription, distinctUntilChanged, filter, lastValueFrom, map, shareReplay, startWith, timer } from "rxjs"; import { BSVersion } from "shared/bs-version.interface"; import { BsmLocalModel } from "shared/models/models/bsm-local-model.interface"; import { MSModel, MSModelType } from "shared/models/models/model-saber.model"; import { ModalResponse, ModalService } from "../modale.service"; import { IpcService } from "../ipc.service"; import { DownloadModelsModal } from "renderer/components/modal/modal-types/models/download-models-modal.component"; import { ProgressBarService } from "../progress-bar.service"; import { ProgressionInterface } from "shared/models/progress-bar"; import equal from "fast-deep-equal"; export class ModelsDownloaderService { private static instance: ModelsDownloaderService; public static getInstance(): ModelsDownloaderService { if (!ModelsDownloaderService.instance) { ModelsDownloaderService.instance = new ModelsDownloaderService(); } return ModelsDownloaderService.instance; } private readonly ipc: IpcService; private readonly modal: ModalService; private readonly progress: ProgressBarService; private readonly lastDownload$ = new BehaviorSubject(null); private readonly queue$ = new BehaviorSubject([]); private readonly _currentDownload$ = this.queue$.pipe( map(queue => queue.at(0)), distinctUntilChanged(), shareReplay(1) ); private constructor() { this.ipc = IpcService.getInstance(); this.modal = ModalService.getInstance(); this.progress = ProgressBarService.getInstance(); this._currentDownload$.pipe(filter(v => !!v)).subscribe(model => this.downloadModel(model)); this._currentDownload$.pipe(filter(v => !v)).subscribe(() => this.lastDownload$.next(null)); } private async downloadModel(download: ModelDownload) { const download$ = this.ipc.sendV2("download-model", download); if (!this.progress.isVisible) { const progress$: Observable = download$.pipe( map(progress => { return { progression: progress.total ? (progress.current / progress.total) * 100 : 0, label: download.model.name, }; }), startWith({ progression: 0.1, label: download.model.name }) ); this.progress.show(progress$); } const downloaded = await lastValueFrom(download$); this.lastDownload$.next(downloaded.data); this.progress.hide(); this.queue$.next(this.queue$.value.filter(m => m.model.hash !== download.model.hash)); } public addModelToDownload(model: ModelDownload): void { if (this.queue$.value.some(m => equal(m, model))) { return null; } const queue = this.queue$.value; queue.push(model); this.queue$.next([...queue]); } public removeFromDownloadQueue(download: ModelDownload) { const queue = this.queue$.value; const index = queue.findIndex(m => m.model.hash === download.model.hash); if (index === -1) { return; } queue.splice(index, 1); this.queue$.next([...queue]); } public onModelsDownloaded(cb: (model: BsmLocalModel) => void): Subscription { return this.lastDownload$.pipe(filter(download => !!download)).subscribe(cb); } public isDownloading$(download: ModelDownload): Observable { return this._currentDownload$.pipe( map(current => { if (!current) { return false; } return current.model.hash === download.model.hash && current.version === download.version; }), distinctUntilChanged() ); } public currentDownload$(): Observable { return this._currentDownload$; } public isPending$(download: ModelDownload): Observable { return this.queue$.pipe( map(queue => queue.at(0).model.hash !== download.model.hash && queue.at(0).version !== download.version && queue.some(d => d.model.hash === download.model.hash && d.version === download.version)), distinctUntilChanged() ); } public getQueue$(): Observable { return this.queue$.asObservable(); } public openDownloadModelsModal(version: BSVersion, type?: MSModelType, owned?: BsmLocalModel[]): Promise> { return this.modal.openModal(DownloadModelsModal, {data: { version, type, owned }}); } public async oneClickInstallModel(model: MSModel): Promise { this.progress.showFake(0.04); const res = await lastValueFrom(this.ipc.sendV2("one-click-install-model", model)).then(() => true).catch(() => false); this.progress.complete(); await lastValueFrom(timer(500)); this.progress.hide(); return res; } } export type ModelDownload = { model: MSModel; version: BSVersion; };