huge maps deletion and export speed improvement + optimize map component update rate

This commit is contained in:
MathieuG-P
2023-02-17 01:26:16 +01:00
parent a7e81cfa8a
commit 64280509b0
7 changed files with 58 additions and 71 deletions
+2 -2
View File
@@ -50,11 +50,11 @@ ipcMain.on("unlink-version-maps", async (event, request: IpcRequest<{version: BS
});
ipcMain.on("delete-maps", async (event, request: IpcRequest<{version: BSVersion, maps: BsmLocalMap[]}>) => {
ipcMain.on("delete-maps", async (event, request: IpcRequest<{maps: BsmLocalMap[]}>) => {
const utils = UtilsService.getInstance();
const maps = LocalMapsManagerService.getInstance();
maps.deleteMaps(request.args.maps, request.args.version).then(() => {
maps.deleteMaps(request.args.maps).then(() => {
utils.ipcSend<void>(request.responceChannel, {success: true});
}).catch(err => {
utils.ipcSend<void>(request.responceChannel, {success: false, error: err});
@@ -100,7 +100,7 @@ export class LocalMapsManagerService {
const hash = await this.computeMapHash(mapPath, rawInfoString);
return {rawInfo, coverUrl, songUrl, hash};
return {rawInfo, coverUrl, songUrl, hash, path: mapPath};
}
private async downloadMapZip(zipUrl: string): Promise<{zip: StreamZip.StreamZipAsync, zipPath: string}>{
@@ -115,28 +115,6 @@ export class LocalMapsManagerService {
return {zip, zipPath};
}
private async getAbsoluteFolderOfMaps(maps: BsmLocalMap[], version: BSVersion): Promise<string[]>{
const mapsFolder = await this.getMapsFolderPath(version);
const res: string[] = [];
const mapHashs = maps.map(map => map.hash);
const mapsFolders = readdirSync(mapsFolder, {withFileTypes: true});
for(const content of mapsFolders){
if(!content.isDirectory()){ continue; }
const mapFolderPath = path.join(mapsFolder, content.name);
const { hash } = await this.loadMapInfoFromPath(mapFolderPath);
if(mapHashs.includes(hash)){
res.push(mapFolderPath);
}
}
return res;
}
private openOneClickDownloadMapWindow(mapId: string, isHash = false): void{
ipcMain.once("one-click-map-info", async (event, req: IpcRequest<void>) => {
@@ -201,16 +179,15 @@ export class LocalMapsManagerService {
}
public async deleteMaps(maps: BsmLocalMap[], verion?: BSVersion){
public async deleteMaps(maps: BsmLocalMap[]){
const mapsFolders = await this.getAbsoluteFolderOfMaps(maps, verion);
const mapsFolders = maps.map(map => map.path);
const mapsHashsToDelete = maps.map(map => map.hash);
for(const folder of mapsFolders){
const { hash } = await this.loadMapInfoFromPath(folder);
if(mapsHashsToDelete.includes(hash)){
await this.utils.deleteFolder(folder);
}
if(!mapsHashsToDelete.includes(hash)){ continue; }
await this.utils.deleteFolder(folder);
}
}
@@ -257,7 +234,7 @@ export class LocalMapsManagerService {
}
else{
const mapsFolders = await this.getAbsoluteFolderOfMaps(maps, version);
const mapsFolders = maps.map(map => map.path);
for(const folder of mapsFolders){
archive.directory(folder, path.basename(folder));
@@ -6,7 +6,6 @@ import { Subscription } from "rxjs"
import { MapFilter } from "shared/models/maps/beat-saver.model"
import { useInView } from "framer-motion"
import { MapsDownloaderService } from "renderer/services/maps-downloader.service"
import { useTranslation } from "renderer/hooks/use-translation.hook"
import { VariableSizeList } from "react-window"
import { MapsRow } from "./maps-row.component"
import { BehaviorSubject } from "rxjs"
@@ -28,20 +27,19 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
const isVisible = useInView(ref, {once: true});
const [maps, setMaps] = useState<BsmLocalMap[]>(null);
const [subs] = useState<Subscription[]>([]);
const [selectedMaps, setSelectedMaps] = useState([]);
const [selectedMaps$] = useState<BehaviorSubject<BsmLocalMap[]>>(new BehaviorSubject([]));
const [itemPerRow, setItemPerRow] = useState(2);
const [listHeight, setListHeight] = useState(0);
const t = useTranslation();
useImperativeHandle(forwardRef ,()=>({
deleteMaps(){
const mapsToDelete = selectedMaps.length === 0 ? maps : selectedMaps
mapsManager.deleteMaps(mapsToDelete, version).finally(loadMaps);
const mapsToDelete = selectedMaps$.value.length === 0 ? maps : selectedMaps$.value
mapsManager.deleteMaps(mapsToDelete, version).then(res => res && removeMapsFromList(mapsToDelete));
},
exportMaps(){
mapsManager.exportMaps(version, selectedMaps)
mapsManager.exportMaps(version, selectedMaps$.value)
}
}), [selectedMaps, maps, version]);
}), [selectedMaps$.value, maps, version]);
useEffect(() => {
@@ -91,23 +89,34 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
subs.push(mapsManager.getMaps(version).subscribe(localMaps => setMaps(() => [...localMaps])));
}
const handleDelete = useCallback((map: BsmLocalMap) => {
mapsManager.deleteMaps([map], version).then(res => res && loadMaps())
}, [version]);
const removeMapsFromList = (mapsToRemove: BsmLocalMap[]) => {
const filtredMaps = maps.filter(map => !mapsToRemove.some(toDeleteMaps => map.hash === toDeleteMaps.hash));
setMaps(() => filtredMaps);
};
const handleDelete = useCallback((map: BsmLocalMap) => {
mapsManager.deleteMaps([map], version).then(res => res && removeMapsFromList([map]));
}, [version, maps]);
const onMapSelected = useCallback((map: BsmLocalMap) => {
const maps = [...selectedMaps];
if(maps.some(selectedMap => selectedMap.hash === map.hash)){
const i = maps.findIndex(selectedMap => selectedMap.hash === map.hash);
maps.splice(i, 1);
console.log(map);
const mapsCopy = [...selectedMaps$.value];
if(mapsCopy.some(selectedMap => selectedMap.hash === map.hash)){
const i = mapsCopy.findIndex(selectedMap => selectedMap.hash === map.hash);
mapsCopy.splice(i, 1);
}
else{
maps.push(map);
mapsCopy.push(map);
}
setSelectedMaps(() => maps);
}, [selectedMaps]);
console.log(mapsCopy);
selectedMaps$.next(mapsCopy);
}, [selectedMaps$.value]);
console.log(selectedMaps$.value);
const isMapFitFilter = (map: BsmLocalMap): boolean => {
@@ -250,8 +259,8 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
return (
<div ref={ref} className={className}>
<VariableSizeList className="p-0 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900" width={"100%"} height={listHeight} itemSize={() => 108} itemCount={preppedMaps.length} itemData={preppedMaps} layout="vertical" style={{scrollbarGutter: "stable both-edges"}}>
{(props) => <MapsRow maps={props.data[props.index]} style={props.style}/>}
<VariableSizeList className="p-0 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900" width={"100%"} height={listHeight} itemSize={() => 108} itemCount={preppedMaps.length} itemData={preppedMaps} layout="vertical" style={{scrollbarGutter: "stable both-edges"}} itemKey={(i, data) => data[i].map(map => map.hash).join()}>
{(props) => <MapsRow maps={props.data[props.index]} style={props.style} selectedMaps$={selectedMaps$} onMapSelect={onMapSelected} onMapDelete={handleDelete}/>}
</VariableSizeList>
</div>
)
@@ -236,8 +236,4 @@ export const MapItem = memo(({hash, title, autor, songAutor, coverUrl, songUrl,
</div>
</motion.li>
)
}, areEqual)
function areEqual(prevProps: MapItemProps, nextProps: MapItemProps): boolean {
return equal(prevProps, nextProps);
}
}, equal);
@@ -1,22 +1,26 @@
import { CSSProperties } from "react"
import { CSSProperties, memo } from "react"
import { useObservable } from "renderer/hooks/use-observable.hook"
import { distinctUntilChanged, map } from "rxjs/operators"
import { BehaviorSubject } from "rxjs"
import { BsvMapCharacteristic } from "shared/models/maps/beat-saver.model"
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"
import { ParsedMapDiff, MapItem } from "./map-item.component"
import equal from "fast-deep-equal/es6"
type Props = {
maps: BsmLocalMap[],
style?: CSSProperties
style?: CSSProperties,
onMapDelete: (map: BsmLocalMap) => void,
onMapSelect: (map: BsmLocalMap) => void,
selectedMaps$: BehaviorSubject<BsmLocalMap[]>
}
export function MapsRow({maps, style}: Props) {
export const MapsRow = memo(({maps, style, selectedMaps$, onMapSelect, onMapDelete}: Props) => {
const handleDelete = () => {
}
const onMapSelected = () => {
}
const selectedMaps = useObservable<BsmLocalMap[]>(selectedMaps$.pipe(
map(selectedMaps => selectedMaps.filter(selected => maps.some(map => map.hash === selected.hash))),
distinctUntilChanged(equal)
), []);
const extractMapDiffs = (map: BsmLocalMap): Map<BsvMapCharacteristic, ParsedMapDiff[]> => {
const res = new Map<BsvMapCharacteristic, ParsedMapDiff[]>();
@@ -53,10 +57,10 @@ export function MapsRow({maps, style}: Props) {
songAutor={map.rawInfo._songAuthorName}
bpm={map.rawInfo._beatsPerMinute}
duration={map.bsaverInfo?.metadata?.duration}
selected={false}
selected={selectedMaps.some(selected => selected.hash === map.hash)}
diffs={extractMapDiffs(map)} mapId={map.bsaverInfo?.id} ranked={map.bsaverInfo?.ranked} autorId={map.bsaverInfo?.uploader?.id} likes={map.bsaverInfo?.stats?.upvotes} createdAt={map.bsaverInfo?.createdAt}
onDelete={handleDelete}
onSelected={onMapSelected}
onDelete={onMapDelete}
onSelected={onMapSelect}
callBackParam={map}
/>;
}
@@ -66,4 +70,4 @@ export function MapsRow({maps, style}: Props) {
{maps && maps.map(renderMapItem)}
</ul>
)
}
}, equal);
@@ -133,7 +133,7 @@ export class MapsManagerService {
this.progressBar.showFake(.008);
}
const res = await this.ipcService.send<void, {version: BSVersion, maps: BsmLocalMap[]}>("delete-maps", {args: {version, maps}});
const res = await this.ipcService.send<void, {maps: BsmLocalMap[]}>("delete-maps", {args: {maps}});
if(showProgressBar){
this.progressBar.hide(true);
@@ -7,4 +7,5 @@ export interface BsmLocalMap {
songUrl: string,
rawInfo: RawMapInfoData,
bsaverInfo?: BsvMapDetail
path: string
}