From 4bbfe723810de550c4cf59ec47e00cef3670980c Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:22:35 +0200 Subject: [PATCH] [chore] updating naming sheme of downloaded maps to avoid duplication when downloading maps from other tools + updating naming sheme of downloading playlists + updating download models due to function changes --- .../local-maps-manager.service.ts | 8 ++- .../local-models-manager.service.ts | 6 +- .../local-playlists-manager.service.ts | 12 ++-- src/main/services/request.service.ts | 72 +++++++++++++++---- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/main/services/additional-content/local-maps-manager.service.ts b/src/main/services/additional-content/local-maps-manager.service.ts index 6c3c444d..7383bf11 100644 --- a/src/main/services/additional-content/local-maps-manager.service.ts +++ b/src/main/services/additional-content/local-maps-manager.service.ts @@ -141,9 +141,11 @@ export class LocalMapsManagerService { const fileName = `${path.basename(zipUrl, ".zip")}-${crypto.randomUUID()}.zip`; const tempPath = this.utils.getTempPath(); await ensureFolderExist(this.utils.getTempPath()); - const dest = path.join(tempPath, fileName); - const zipPath = (await lastValueFrom(this.reqService.downloadFile(zipUrl, dest))).data; + const zipPath = (await lastValueFrom(this.reqService.downloadFile(zipUrl, { + destFolder: tempPath, + filename: fileName + }))).data; const zip = new StreamZip.async({ file: zipPath }); return { zip, zipPath }; @@ -263,7 +265,7 @@ export class LocalMapsManagerService { } const zipUrl = map.versions.at(0).downloadURL; - const mapFolderName = sanitize(`${map.id}-${map.name}`); + const mapFolderName = sanitize(`${map.id} (${map.metadata.songName} - ${map.metadata.levelAuthorName})`); const mapsFolder = await this.getMapsFolderPath(version); const mapPath = path.join(mapsFolder, mapFolderName); diff --git a/src/main/services/additional-content/local-models-manager.service.ts b/src/main/services/additional-content/local-models-manager.service.ts index 7365f28f..d9d82be5 100644 --- a/src/main/services/additional-content/local-models-manager.service.ts +++ b/src/main/services/additional-content/local-models-manager.service.ts @@ -87,12 +87,14 @@ export class LocalModelsManagerService { (async () => { const modelFolder = await this.getModelFolderPath(model.type, version); - const modelDest = path.join(modelFolder, sanitize(path.basename(model.download))); const url = model.download.split("/"); url[url.length - 1] = encodeURIComponent(url[url.length - 1]); - const download$ = this.request.downloadFile(url.join("/"), modelDest); + const download$ = this.request.downloadFile(url.join("/"), { + destFolder: modelFolder, + filename: sanitize(path.basename(model.download)) + }); subs.push(download$.subscribe({ next: value => subscriber.next({ ...value, data: undefined }), error: e => subscriber.error(e) })); diff --git a/src/main/services/additional-content/local-playlists-manager.service.ts b/src/main/services/additional-content/local-playlists-manager.service.ts index 22881e54..e6259c03 100644 --- a/src/main/services/additional-content/local-playlists-manager.service.ts +++ b/src/main/services/additional-content/local-playlists-manager.service.ts @@ -77,9 +77,13 @@ export class LocalPlaylistsManagerService { if (isLocalFile) { return copyFile(bslistSource, destFile).then(() => destFile); } - return lastValueFrom(this.request.downloadFile(bslistSource, destFile)).then(res => res.data); + return lastValueFrom(this.request.downloadFile(bslistSource, { + destFolder: playlistFolder, + filename, + preferContentDisposition: true, + })).then(res => res.data); } - + private async readPlaylistFile(path: string): Promise { if (!(await pathExist(path))) { @@ -102,8 +106,8 @@ export class LocalPlaylistsManagerService { const bpListFilePath = await this.installBPListFile(bpListUrl, version); const bpList = await this.readPlaylistFile(bpListFilePath); - - const progress: Progression = { + + const progress: Progression = { total: bpList.songs.length, current: 0, data: { diff --git a/src/main/services/request.service.ts b/src/main/services/request.service.ts index 2c7f6741..78cba41b 100644 --- a/src/main/services/request.service.ts +++ b/src/main/services/request.service.ts @@ -1,11 +1,14 @@ import { Agent, RequestOptions, get } from "https"; -import { createWriteStream, unlink } from "fs"; -import { Progression } from "main/helpers/fs.helpers"; +import { createWriteStream } from "fs"; +import { Progression, unlinkPath } from "../helpers/fs.helpers"; import { Observable, shareReplay, tap } from "rxjs"; import log from "electron-log"; import fetch, { RequestInfo, RequestInit } from "node-fetch"; import { app } from "electron"; import os from "os"; +import path from "path"; +import { tryit } from "../../shared/helpers/error.helpers"; +import sanitize from "sanitize-filename"; export class RequestService { private static instance: RequestService; @@ -56,22 +59,65 @@ export class RequestService { } } - public downloadFile(url: string, dest: string): Observable> { + private getFilenameFromContentDisposition(disposition: string): string | null { + + if(!disposition) { + return null; + } + + const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i; + const asciiFilenameRegex = /^filename=(["']?)(.*?[^\\])\1(?:; ?|$)/i; + + const utf8Match = utf8FilenameRegex.exec(disposition); + if (utf8Match) { + return decodeURIComponent(utf8Match[1]); + } + + const filenameStart = disposition.toLowerCase().indexOf('filename='); + if (filenameStart >= 0) { + const partialDisposition = disposition.slice(filenameStart); + const asciiMatch = asciiFilenameRegex.exec(partialDisposition); + if (asciiMatch && asciiMatch[2]) { + return asciiMatch[2]; + } + } + + return null; + } + + public downloadFile(url: string, opt: { destFolder: string, filename: string, preferContentDisposition?: boolean }): Observable> { return new Observable>(subscriber => { const progress: Progression = { current: 0, total: 0 }; - const file = createWriteStream(dest); - - file.on("close", () => { - progress.data = dest; - subscriber.next(progress); - subscriber.complete(); - }); - file.on("error", err => unlink(dest, () => subscriber.error(err))); - const req = get(url, this.requestOptionsFromDefaultInit(), res => { + + const filePath = (() => { + if(opt.preferContentDisposition && res.headers["content-disposition"]) { + const filename = this.getFilenameFromContentDisposition(res.headers["content-disposition"]); + return path.join(opt.destFolder, sanitize(filename || opt.filename)); + } + return path.join(opt.destFolder, opt.filename); + })(); + + const file = createWriteStream(filePath); + + file.on("close", () => { + subscriber.next(progress); + subscriber.complete(); + }); + file.on("error", async err => { + log.error(err); + const res = await tryit(() => unlinkPath(filePath)); + if(res.error) { + log.error(res.error); + } + }); + + progress.data = filePath; progress.total = parseInt(res.headers?.["content-length"] || "0", 10); + subscriber.next(progress); + res.on("data", chunk => { progress.current += chunk.length; subscriber.next(progress); @@ -83,7 +129,7 @@ export class RequestService { req.on("error", err => { subscriber.error(err); }); - }).pipe(tap({ error: e => log.error(e, url, dest) }), shareReplay(1)); + }).pipe(tap({ error: e => log.error(e, url) }), shareReplay(1)); } public downloadBuffer(url: string): Observable> {