mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[bugfix] fix playlist maps with malformed hash doesnt get loaded + improve playlist maps reading reliability
This commit is contained in:
@@ -7,7 +7,7 @@ import { RequestService } from "../request.service";
|
||||
import { LocalMapsManagerService } from "./maps/local-maps-manager.service";
|
||||
import log from "electron-log";
|
||||
import { WindowManagerService } from "../window-manager.service";
|
||||
import { BPList, DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface";
|
||||
import { BPList, DownloadPlaylistProgressionData, PlaylistSong } from "shared/models/playlists/playlist.interface";
|
||||
import { readFileSync, Stats } from "fs";
|
||||
import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service";
|
||||
import { copy, ensureDir, pathExists, pathExistsSync, realpath, writeFileSync } from "fs-extra";
|
||||
@@ -25,6 +25,8 @@ import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
import recursiveReadDir from "recursive-readdir";
|
||||
import { BsvMapDetail, SongDetails } from "shared/models/maps";
|
||||
import { findHashInString } from "shared/helpers/string.helpers";
|
||||
|
||||
export class LocalPlaylistsManagerService {
|
||||
private static instance: LocalPlaylistsManagerService;
|
||||
@@ -138,10 +140,14 @@ export class LocalPlaylistsManagerService {
|
||||
|
||||
const bpList: BPList = isLocalFile ? JSON.parse(readFileSync(source).toString()) : await this.request.getJSON<BPList>(source);
|
||||
|
||||
if(!bpList?.playlistTitle){
|
||||
if(!bpList?.playlistTitle) {
|
||||
throw new Error(`Invalid playlist file ${source}`);
|
||||
}
|
||||
|
||||
bpList.songs = (bpList.songs ?? []).map(s => s.hash ? (
|
||||
{ ...s, hash: findHashInString(s.hash) ?? s.hash }
|
||||
) : s).filter(Boolean);
|
||||
|
||||
return bpList;
|
||||
}
|
||||
|
||||
@@ -190,6 +196,25 @@ export class LocalPlaylistsManagerService {
|
||||
});
|
||||
}
|
||||
|
||||
private getSongDetailsFromPlaylistSong(song: PlaylistSong): SongDetails | undefined {
|
||||
let songDetails: SongDetails;
|
||||
|
||||
const songHash = findHashInString(song.hash);
|
||||
if(songHash){
|
||||
songDetails = this.songDetails.getSongDetails(song.hash);
|
||||
}
|
||||
|
||||
if(song.key && !songDetails){
|
||||
songDetails = this.songDetails.getSongDetailsById(song.key);
|
||||
}
|
||||
|
||||
const levelIdHash = findHashInString(song.levelid);
|
||||
if(levelIdHash && !songDetails){
|
||||
songDetails = this.songDetails.getSongDetails(levelIdHash);
|
||||
}
|
||||
return songDetails;
|
||||
}
|
||||
|
||||
public getLocalBPListDetails(localBPList: LocalBPList): LocalBPListsDetails {
|
||||
|
||||
const tryExtractPlaylistId = (url: string) => {
|
||||
@@ -204,23 +229,23 @@ export class LocalPlaylistsManagerService {
|
||||
id: localBPList.customData?.syncURL ? tryExtractPlaylistId(localBPList.customData.syncURL) : undefined
|
||||
}
|
||||
|
||||
const songsDetails = localBPList.songs?.map(s => {
|
||||
if(s.hash){
|
||||
return this.songDetails.getSongDetails(s.hash);
|
||||
}
|
||||
if(s.key){
|
||||
return this.songDetails.getSongDetailsById(s.key);
|
||||
}
|
||||
return undefined;
|
||||
}).filter(Boolean);
|
||||
const mappers = new Set<number>();
|
||||
|
||||
if(songsDetails?.length){
|
||||
bpListDetails.duration = songsDetails.reduce((acc, song) => acc + song.duration, 0);
|
||||
bpListDetails.nbMappers = new Set(songsDetails.map(s => s.uploader.id)).size;
|
||||
bpListDetails.minNps = Math.min(...songsDetails.map(s => Math.min(...s.difficulties.map(d => d.nps || 0))));
|
||||
bpListDetails.maxNps = Math.max(...songsDetails.map(s => Math.max(...s.difficulties.map(d => d.nps || 0))));
|
||||
for(const song of localBPList.songs){
|
||||
const songDetails = this.getSongDetailsFromPlaylistSong(song);
|
||||
|
||||
if(!songDetails) { continue; }
|
||||
|
||||
bpListDetails.duration += songDetails?.duration ?? 0;
|
||||
mappers.add(songDetails.uploader?.id);
|
||||
bpListDetails.minNps = Math.min(bpListDetails?.minNps ?? 0, Math.min(...songDetails.difficulties?.map(d => d?.nps || 0) ?? [0]));
|
||||
bpListDetails.maxNps = Math.max(bpListDetails?.maxNps ?? 0, Math.max(...songDetails.difficulties?.map(d => d?.nps || 0) ?? [0]));
|
||||
|
||||
song.songDetails = songDetails;
|
||||
}
|
||||
|
||||
bpListDetails.nbMappers = mappers.size;
|
||||
|
||||
return bpListDetails;
|
||||
}
|
||||
|
||||
@@ -276,7 +301,28 @@ export class LocalPlaylistsManagerService {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [ mapDetail ] = await this.bsaver.getMapDetailsFromHashs([song.hash]);
|
||||
const mapDetail = await (async () => {
|
||||
let mapDetail: BsvMapDetail;
|
||||
|
||||
const mapHash = findHashInString(song?.hash);
|
||||
if(mapHash) {
|
||||
mapDetail = (await this.bsaver.getMapDetailsFromHashs([findHashInString(mapHash)])).at(0);
|
||||
}
|
||||
|
||||
if(song.key && !mapDetail) {
|
||||
mapDetail = await this.bsaver.getMapDetailsById(song.key);
|
||||
}
|
||||
|
||||
const levelIdHash = findHashInString(song?.levelid);
|
||||
if(levelIdHash && !mapDetail) {
|
||||
mapDetail = (await this.bsaver.getMapDetailsFromHashs([levelIdHash])).at(0);
|
||||
}
|
||||
|
||||
return mapDetail;
|
||||
})().catch(e => {
|
||||
log.error(e);
|
||||
return undefined as BsvMapDetail;
|
||||
});
|
||||
|
||||
if(!mapDetail) {
|
||||
continue;
|
||||
|
||||
@@ -23,14 +23,15 @@ export class BeatSaverService {
|
||||
}
|
||||
|
||||
public async getMapDetailsFromHashs(hashs: string[]): Promise<BsvMapDetail[]> {
|
||||
const filtredHashs = hashs.map(h => h.toLowerCase()).filter(hash => !Array.from(this.cachedMapsDetails.keys()).includes(hash));
|
||||
const filtredHashs = hashs.map(h => h.toLowerCase()).filter(hash => !this.cachedMapsDetails.has(hash));
|
||||
const chunkHash = splitIntoChunk(filtredHashs, 50);
|
||||
|
||||
const mapDetails = Array.from(this.cachedMapsDetails.entries()).reduce((res, [hash, details]) => {
|
||||
if (hashs.includes(hash)) {
|
||||
res.push(details);
|
||||
const mapDetails = hashs.reduce((acc, hash) => {
|
||||
const detail = this.cachedMapsDetails.get(hash.toLowerCase());
|
||||
if (detail) {
|
||||
acc.push(detail);
|
||||
}
|
||||
return res;
|
||||
return acc;
|
||||
}, [] as BsvMapDetail[]);
|
||||
|
||||
await Promise.allSettled(
|
||||
|
||||
+3
-2
@@ -282,8 +282,9 @@ export const LocalPlaylistsListPanel = forwardRef<LocalPlaylistsListRef, Props>(
|
||||
|
||||
let map: BsmLocalMap;
|
||||
|
||||
if(playlistSong.hash){
|
||||
map = maps.find(m => m.hash.toLowerCase() === playlistSong.hash.toLowerCase());
|
||||
if(playlistSong.hash || playlistSong?.songDetails?.hash){
|
||||
const hash = (playlistSong.hash || playlistSong.songDetails.hash).toLowerCase();
|
||||
map = maps.find(m => m.hash.toLowerCase() === hash);
|
||||
}
|
||||
else if(playlistSong.key){
|
||||
map = maps.find(m => m?.songDetails?.id === playlistSong.key);
|
||||
|
||||
+2
-1
@@ -36,6 +36,7 @@ import { DraggableVirtualScroll } from "renderer/components/shared/virtual-scrol
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import BeatWaiting from "../../../../../../../assets/images/apngs/beat-waiting.png";
|
||||
import BeatConflict from "../../../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { findHashInString } from "shared/helpers/string.helpers";
|
||||
|
||||
type Props = {
|
||||
maps$: Observable<BsmLocalMap[]>;
|
||||
@@ -115,7 +116,7 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
|
||||
const maps = await lastValueFrom(maps$.pipe(take(1)));
|
||||
|
||||
const playlistMapsRes = (playlist?.songs ?? []).reduce((acc, song) => {
|
||||
const songHash = song.hash?.toLowerCase();
|
||||
const songHash = song?.hash?.toLowerCase() ?? song?.songDetails?.hash?.toLowerCase() ?? findHashInString(song.levelid)?.toLowerCase();
|
||||
const map = maps.find(map => map.hash === songHash);
|
||||
|
||||
if(map){
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
const HashAlgorithmsLengths = {
|
||||
sha1: 40,
|
||||
} as const;
|
||||
|
||||
export function findHashInString(str: string, algorithm: keyof typeof HashAlgorithmsLengths = 'sha1'): string | undefined {
|
||||
|
||||
if(!str) { return undefined; }
|
||||
|
||||
const hashLength = HashAlgorithmsLengths[algorithm];
|
||||
const regex = new RegExp(`[a-fA-F0-9]{${hashLength}}`, "g");
|
||||
const match = str.match(regex);
|
||||
return match ? match[0] : undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user