[feature-75] rework fontend functions to import maps

This commit is contained in:
MathieuG-P
2024-10-16 20:35:34 +02:00
parent 4e6948693d
commit 9b8d6ddda0
3 changed files with 76 additions and 108 deletions
@@ -14,7 +14,7 @@ import { BsmButton } from "../shared/bsm-button.component";
import { MapIcon } from "../svgs/icons/map-icon.component";
import { PlaylistIcon } from "../svgs/icons/playlist-icon.component";
import { useObservable } from "renderer/hooks/use-observable.hook";
import { BehaviorSubject, of } from "rxjs";
import { BehaviorSubject, lastValueFrom, of } from "rxjs";
import { LocalPlaylistsListPanel, LocalPlaylistsListRef } from "./playlists/local-playlists-list-panel.component";
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
@@ -25,6 +25,7 @@ import { LocalPlaylistFilter, LocalPlaylistFilterPanel } from "./playlists/local
import { noop } from "shared/helpers/function.helpers";
import { Dropzone } from "../shared/dropzone.component";
import { NotificationService } from "renderer/services/notification.service";
import { logRenderError } from "renderer";
type Props = {
readonly version?: BSVersion;
@@ -122,7 +123,8 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
return;
}
await mapsManager.importMaps(paths, version);
const import$ = mapsManager.importMaps(paths, version);
return lastValueFrom(import$).catch(logRenderError);
}
const dropDownItems = ((): DropDownItem[] => {
@@ -5,7 +5,7 @@ import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
import { Subscription, BehaviorSubject, lastValueFrom } from "rxjs";
import { MapFilter } from "shared/models/maps/beat-saver.model";
import { MapsDownloaderService } from "renderer/services/maps-downloader.service";
import { last, tap } from "rxjs/operators";
import { bufferTime, last, tap } from "rxjs/operators";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
import { BsmImage } from "../../shared/bsm-image.component";
@@ -104,46 +104,28 @@ export const LocalMapsListPanel = forwardRef<LocalMapsListPanelRef, Props>(({ ve
useEffect(() => {
let sub: Subscription;
const subs: Subscription[] = [];
if(isActiveOnce){
sub = mapsDownloader.lastDownloadedMap$.subscribe({ next: ({map, version: targetVersion}) => {
subs.push(mapsDownloader.lastDownloadedMap$.subscribe({ next: ({map, version: targetVersion}) => {
if (!equal(targetVersion, version)) {
return;
}
setMaps((maps$.value ? [map, ...maps$.value] : [map]));
}});
mapsManager.addImportListener(importListener);
}}));
subs.push(mapsManager.$onMapImported(version).pipe(bufferTime(500)).subscribe({ next: newMaps => {
const maps = (maps$.value ?? []).filter(map => !newMaps.some(newMap => newMap.path === map.path));
setMaps([...newMaps, ...maps]);
}}));
}
return () => {
sub?.unsubscribe();
mapsManager.removeImportListener(importListener);
subs.forEach(s => s.unsubscribe());
}
}, [isActiveOnce, version])
const importListener = (importMaps: BsmLocalMap[], targetVersion?: BSVersion) => {
if (!equal(targetVersion, version)) {
return;
}
if (!maps$.value) {
setMaps(importMaps);
return;
}
const mapsCopy = [ ...maps$.value ];
for (const importMap of importMaps) {
const index = mapsCopy.findIndex(map => map.hash === importMap.hash);
if (index > -1) {
mapsCopy.splice(index, 1);
}
}
setMaps([...importMaps, ...mapsCopy]);
};
const loadMaps = () => {
setMaps(null);
loadPercent$.next(0);
+62 -78
View File
@@ -9,12 +9,13 @@ import { DeleteMapsModal } from "renderer/components/modal/modal-types/delete-ma
import { ProgressBarService } from "./progress-bar.service";
import { NotificationService } from "./notification.service";
import { ConfigurationService } from "./configuration.service";
import { map, last, catchError, tap } from "rxjs/operators";
import { map, last, catchError, filter, distinctUntilChanged } from "rxjs/operators";
import { ProgressionInterface } from "shared/models/progress-bar";
import { FolderLinkState, VersionFolderLinkerService } from "./version-folder-linker.service";
import { SongDetails } from "shared/models/maps";
import { Progression } from "main/helpers/fs.helpers";
import { DeleteDuplicateMapsModal } from "renderer/components/modal/modal-types/delete-duplicate-maps-modal.component";
import equal from "fast-deep-equal";
export class MapsManagerService {
private static instance: MapsManagerService;
@@ -29,8 +30,6 @@ export class MapsManagerService {
public static readonly REMEMBER_CHOICE_DELETE_MAP_KEY = "not-confirm-delete-map";
public static readonly RELATIVE_MAPS_FOLDER = window.electron.path.join("Beat Saber_Data", "CustomLevels");
private readonly IMPORT_BATCH_SIZE = 8;
private readonly ipcService: IpcService;
private readonly modal: ModalService;
private readonly progressBar: ProgressBarService;
@@ -41,7 +40,7 @@ export class MapsManagerService {
private readonly lastLinkedVersion$: Subject<BSVersion> = new Subject();
private readonly lastUnlinkedVersion$: Subject<BSVersion> = new Subject();
private importListeners = new Set<((maps: BsmLocalMap[], version?: BSVersion) => void)>();
private readonly lastImportedMap$: Subject<{ version: BSVersion, map: BsmLocalMap }> = new Subject();
private constructor() {
this.ipcService = IpcService.getInstance();
@@ -200,73 +199,62 @@ export class MapsManagerService {
})
}
public async importMaps(paths: string[], version?: BSVersion): Promise<void> {
try {
if (!this.progressBar.require()) {
return;
}
const importObserver$ = this.ipcService.sendV2(
"bs-maps.import-maps",
{ paths, version }
);
this.progressBar.show(importObserver$.pipe(
catchError(() => of()),
map(progress => ({
progression: (progress.current / progress.total) * 100,
label: progress.data?.songDetails?.name
} as ProgressionInterface))
));
// Processing
let importCount = 0;
let importTotal = 0;
const mapBatch: BsmLocalMap[] = []; // For batching
await lastValueFrom(importObserver$.pipe(
tap(progress => {
if (!progress.data) {
importTotal = progress.total;
return;
}
++importCount;
mapBatch.push(progress.data);
if (mapBatch.length >= this.IMPORT_BATCH_SIZE) {
const currentBatch = mapBatch.splice(0, this.IMPORT_BATCH_SIZE);
this.importListeners.forEach(
listeners => listeners(currentBatch, version)
);
}
}),
));
if (mapBatch.length > 0) {
this.importListeners.forEach(listeners => listeners(mapBatch, version));
}
// Done processing
if (importCount === importTotal) {
this.notifications.notifySuccess({
title: "notifications.maps.import-map.titles.success",
desc: "notifications.maps.import-map.msgs.success",
});
} else if (importCount > 0) {
this.notifications.notifySuccess({
title: "notifications.maps.import-map.titles.success",
desc: "notifications.maps.import-map.msgs.some-success",
});
}
} catch (error: any) {
this.notifications.notifyError({
title: "notifications.maps.import-map.titles.error",
desc: ["invalid-zip"].includes(error?.code)
? `notifications.maps.import-map.msgs.${error.code}`
: "misc.unknown"
});
} finally {
this.progressBar.hide();
public importMaps(paths: string[], version?: BSVersion): Observable<Progression<BsmLocalMap>> {
if(!this.progressBar.require()){
return throwError(() => new Error("Another operation is already in progress (importMaps)"));
}
return new Observable<Progression<BsmLocalMap>>(obs => {
const subs: Subscription[] = [];
(async () => {
const import$ = this.ipcService.sendV2("bs-maps.import-maps", { paths, version });
this.progressBar.show(import$.pipe(
catchError(() => of()),
map(progress => ({
progression: (progress.current / progress.total) * 100,
label: progress.data?.songDetails?.name
} as ProgressionInterface))
));
subs.push(import$.subscribe(obs));
subs.push(import$.pipe(catchError(() => of()), map(progress => progress?.data), filter(Boolean), distinctUntilChanged((a, b) => equal(a, b))).subscribe({ next: map => {
this.lastImportedMap$.next({ version, map });
}}));
return lastValueFrom(import$);
})()
.then(progress => {
if (progress.current === progress.total) {
this.notifications.notifySuccess({
title: "notifications.maps.import-map.titles.success",
desc: "notifications.maps.import-map.msgs.success",
});
} else if (progress.current > 0) {
this.notifications.notifySuccess({
title: "notifications.maps.import-map.titles.success",
desc: "notifications.maps.import-map.msgs.some-success",
});
}
}).catch(err => {
this.notifications.notifyError({
title: "notifications.maps.import-map.titles.error",
desc: ["invalid-zip"].includes(err?.code)
? `notifications.maps.import-map.msgs.${err.code}`
: "misc.unknown"
});
obs.error(err);
})
.finally(() => obs.complete());
return () => {
if(subs.length){
this.progressBar.hide();
subs.forEach(s => s.unsubscribe());
}
}
});
}
public getMapsInfoFromHashs(hashs: string[]): Observable<SongDetails[]> {
@@ -285,14 +273,6 @@ export class MapsManagerService {
return lastValueFrom(this.ipcService.sendV2("unregister-maps-deep-link"));
}
public addImportListener(listener: (maps: BsmLocalMap[], version?: BSVersion) => void): void {
this.importListeners.add(listener);
}
public removeImportListener(listener: (maps: BsmLocalMap[], version?: BSVersion) => void): void {
this.importListeners.delete(listener);
}
public get versionLinked$(): Observable<BSVersion> {
return this.lastLinkedVersion$.asObservable();
}
@@ -301,6 +281,10 @@ export class MapsManagerService {
return this.lastUnlinkedVersion$.asObservable();
}
public $onMapImported(version: BSVersion): Observable<BsmLocalMap> {
return this.lastImportedMap$.pipe(filter(newMap => equal(newMap.version, version)), map(m => m.map));
}
public $mapsFolderLinkState(version: BSVersion): Observable<FolderLinkState> {
return this.linker.$folderLinkedState(version, MapsManagerService.RELATIVE_MAPS_FOLDER);
}