From 08d1add47b1eb29ecd80a568fad27363778693b0 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Mon, 16 Oct 2023 23:38:57 +0200 Subject: [PATCH] [feature-274] Improve Oculus downloading + add Modal to choose from where to download Beat Saber --- package-lock.json | 8 +- package.json | 2 +- src/main/main.ts | 4 - src/main/models/oculus-downloader.class.ts | 61 +++++++------ src/main/services/bs-local-version.service.ts | 10 ++- .../services/bs-oculus-downloader.service.ts | 88 +++++++++++++++++-- .../choose-store-modal.component.tsx | 53 +++++++++++ .../bs-version-item.component.tsx | 4 +- .../components/nav-bar/nav-bar.component.tsx | 4 +- .../svgs/icons/oculus-icon.component.tsx | 2 +- .../available-versions-list.components.tsx | 38 ++++++-- .../pages/settings-page.component.tsx | 4 +- .../pages/version-viewer.component.tsx | 4 +- .../bs-downgrade/bs-downloader.service.ts | 74 ++++++++++++++++ .../bs-downgrade/oculus-downloader.service.ts | 21 +++++ .../steam-downloader.service.ts} | 28 +++--- src/shared/bs-version.interface.ts | 5 ++ src/shared/helpers/time.helpers.ts | 4 + src/shared/models/bs-store.enum.ts | 4 + 19 files changed, 342 insertions(+), 76 deletions(-) create mode 100644 src/renderer/components/modal/modal-types/bs-downgrade/choose-store-modal.component.tsx create mode 100644 src/renderer/services/bs-downgrade/bs-downloader.service.ts create mode 100644 src/renderer/services/bs-downgrade/oculus-downloader.service.ts rename src/renderer/services/{bs-downloader.service.ts => bs-downgrade/steam-downloader.service.ts} (93%) create mode 100644 src/shared/models/bs-store.enum.ts diff --git a/package-lock.json b/package-lock.json index 3ecb3e5c..b15db640 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "@electron/fuses": "^1.6.2", "@node-steam/vdf": "^2.2.0", "@tippyjs/react": "^4.2.6", - "@types/pako": "^2.0.1", "archiver": "^6.0.1", "color": "^4.2.3", "dateformat": "^5.0.3", @@ -65,6 +64,7 @@ "@types/jest": "^27.5.2", "@types/node": "17.0.23", "@types/node-fetch": "^2.6.3", + "@types/pako": "^2.0.1", "@types/react": "^18.0.33", "@types/react-dom": "^18.0.11", "@types/react-outside-click-handler": "^1.3.1", @@ -2749,7 +2749,8 @@ "node_modules/@types/pako": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.1.tgz", - "integrity": "sha512-fXhui1fHdLrUR0KEyQsBzqdi3Z+MitnRcpI2eeFJyzaRdqO2miX/BDz2Hh0VdkBbrWprgcQ+ItFmbdKYdbMjvg==" + "integrity": "sha512-fXhui1fHdLrUR0KEyQsBzqdi3Z+MitnRcpI2eeFJyzaRdqO2miX/BDz2Hh0VdkBbrWprgcQ+ItFmbdKYdbMjvg==", + "dev": true }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -21326,7 +21327,8 @@ "@types/pako": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.1.tgz", - "integrity": "sha512-fXhui1fHdLrUR0KEyQsBzqdi3Z+MitnRcpI2eeFJyzaRdqO2miX/BDz2Hh0VdkBbrWprgcQ+ItFmbdKYdbMjvg==" + "integrity": "sha512-fXhui1fHdLrUR0KEyQsBzqdi3Z+MitnRcpI2eeFJyzaRdqO2miX/BDz2Hh0VdkBbrWprgcQ+ItFmbdKYdbMjvg==", + "dev": true }, "@types/parse-json": { "version": "4.0.0", diff --git a/package.json b/package.json index 14ac690c..520ef7cc 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,7 @@ "@types/jest": "^27.5.2", "@types/node": "17.0.23", "@types/node-fetch": "^2.6.3", + "@types/pako": "^2.0.1", "@types/react": "^18.0.33", "@types/react-dom": "^18.0.11", "@types/react-outside-click-handler": "^1.3.1", @@ -232,7 +233,6 @@ "@electron/fuses": "^1.6.2", "@node-steam/vdf": "^2.2.0", "@tippyjs/react": "^4.2.6", - "@types/pako": "^2.0.1", "archiver": "^6.0.1", "color": "^4.2.3", "dateformat": "^5.0.3", diff --git a/src/main/main.ts b/src/main/main.ts index 1f9d55d1..efa62415 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -60,10 +60,6 @@ const createWindow = async (window: AppWindow = "launcher.html") => { await installExtensions(); } WindowManagerService.getInstance().openWindow(window); - - setTimeout(() => { // TODO : to remove (test) - BsOculusDownloaderService.getInstance().downloadVersion({} as BSVersion).then(console.log).catch(console.error); - }, 3000); }; const initServicesMustBeInitialized = () => { diff --git a/src/main/models/oculus-downloader.class.ts b/src/main/models/oculus-downloader.class.ts index 9b1ed208..857f1203 100644 --- a/src/main/models/oculus-downloader.class.ts +++ b/src/main/models/oculus-downloader.class.ts @@ -1,10 +1,10 @@ import JSZip from "jszip"; import fetch from "node-fetch"; import { CustomError } from "../../shared/models/exceptions/custom-error.class"; -import { mkdirs, createWriteStream, pathExists } from "fs-extra"; +import { mkdirs, createWriteStream, pathExists, writeFile } from "fs-extra"; import path from "path"; import { inflate } from "pako" -import { Observable, lastValueFrom } from "rxjs"; +import { Observable, lastValueFrom, tap } from "rxjs"; import { Progression, hashFile } from "../helpers/fs.helpers"; export class OculusDownloader { @@ -59,33 +59,32 @@ export class OculusDownloader { return file; } - private verifyIntegrity(manifest: OculusManifest, folder: string): Observable> { - return new Observable>(sub => { + private isFileIntegrityValid(file: OculusFileWithName, folder: string): Promise { + const [fileName, fileData] = file; + const destination = path.join(folder, fileName); + + return pathExists(destination).then(exists => { + if(!exists){ return false; } + return hashFile(destination, "sha256").then(hash => hash === fileData.sha256); + }); + } + + private verifyIntegrity(manifest: OculusManifest, folder: string): Observable> { + return new Observable>(sub => { const files = Object.entries(manifest.files); - const progress: Progression = { current: 0, total: files.length, data: [] }; - const wrongFiles: OculusManifestFile[] = []; + const progress: Progression = { current: 0, total: files.length, data: [] }; + const wrongFiles: OculusFileWithName[] = []; let canceled = false; (async () => { - for(const [fileName, file] of files){ + for(const oculusFile of files){ if(canceled){ return; } - const destination = path.join(folder, fileName); - - if(!pathExists(destination)){ - wrongFiles.push(file); - progress.current++; - sub.next(progress); - continue; - } - - const fileSha256 = await hashFile(destination, "sha256"); - - if(fileSha256 !== file.sha256){ - wrongFiles.push(file); + if(!(await this.isFileIntegrityValid(oculusFile, folder))){ + wrongFiles.push(oculusFile); } progress.current++; @@ -115,6 +114,7 @@ export class OculusDownloader { this.isDownloading = true; const progress: Progression = { current: 0, total: 0 }; + const intallPath = path.join("C:", "test", "test"); (async () => { @@ -124,17 +124,22 @@ export class OculusDownloader { progress.total = files.reduce((acc, file) => { return acc + file[1].size }, 0) subscriber.next(progress); - for(const file of files){ + for(const [filename, file] of files){ if(this.isDownloading === false){ return; } - const destination = path.join("C:", "test", "test", file[0]); - await this.downloadManifestFile(file[1], destination).catch(err => CustomError.throw(err, "DOWNLOAD_FILE_FAILED")); - progress.current += file[1].size; + if(!(await this.isFileIntegrityValid([filename, file], intallPath))){ + const target = path.join(intallPath, filename); + await this.downloadManifestFile(file, target).catch(err => CustomError.throw(err, "DOWNLOAD_FILE_FAILED")); + } + + progress.current += file.size; subscriber.next(progress); } - const integrity = await lastValueFrom(this.verifyIntegrity(manifest, path.join("C:", "test", "test"))).catch(err => CustomError.throw(err, "VERIFY_INTEGRITY_FAILED")); + await writeFile(path.join(intallPath, "type.info"), "oculus"); + + const integrity = await lastValueFrom(this.verifyIntegrity(manifest, intallPath)).catch(err => CustomError.throw(err, "VERIFY_INTEGRITY_FAILED")); if(integrity.data.length > 0){ throw new CustomError("Some files failed to download", "SOME_FILES_FAILED_TO_DOWNLOAD", integrity.data); @@ -148,10 +153,6 @@ export class OculusDownloader { }); } - public async verifyApp(options: OculusDownloaderOptions){ - throw new CustomError("Not implemented", "NOT_IMPLEMENTED"); - } - public stopDownload(){ this.isDownloading = false; } @@ -192,6 +193,8 @@ interface OculusManifestFile { type OculusManifestFileSegment = [number, string, number]; +type OculusFileWithName = [string, OculusManifestFile]; + interface Logger { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; diff --git a/src/main/services/bs-local-version.service.ts b/src/main/services/bs-local-version.service.ts index 80c4b55e..7422b374 100644 --- a/src/main/services/bs-local-version.service.ts +++ b/src/main/services/bs-local-version.service.ts @@ -13,9 +13,10 @@ import { DownloadLinkType } from "shared/models/mods"; import sanitize from "sanitize-filename"; import { copyDirectoryWithJunctions, deleteFolder, getFoldersInFolder, pathExist } from "../helpers/fs.helpers"; import { FolderLinkerService } from "./folder-linker.service"; -import { ReadStream, createReadStream } from "fs-extra"; +import { ReadStream, createReadStream, readFile } from "fs-extra"; import readline from "readline"; import { Observable, Subject } from "rxjs"; +import { BsStore } from "../../shared/models/bs-store.enum"; export class BSLocalVersionService { private static instance: BSLocalVersionService; @@ -109,6 +110,13 @@ export class BSLocalVersionService { folderVersion.ino = folderStats.ino; } + const type: string = await readFile(path.join(bsPath, "type.info"), "utf-8").catch(() => null); + if(type === BsStore.OCULUS){ + folderVersion.downloadedFrom = BsStore.OCULUS; + } else { + folderVersion.downloadedFrom = BsStore.STEAM; + } + const customVersion = this.getCustomVersions().find(customVersion => { return customVersion.BSVersion === folderVersion.BSVersion && customVersion.name === folderVersion.name; }); diff --git a/src/main/services/bs-oculus-downloader.service.ts b/src/main/services/bs-oculus-downloader.service.ts index ff0638e4..b08f71f3 100644 --- a/src/main/services/bs-oculus-downloader.service.ts +++ b/src/main/services/bs-oculus-downloader.service.ts @@ -1,9 +1,12 @@ import { BSVersion } from "../../shared/bs-version.interface"; import { WindowManagerService } from "./window-manager.service"; -import { minToMs } from "../../shared/helpers/time.helpers"; +import { minToMs, msToS } from "../../shared/helpers/time.helpers"; import log from "electron-log"; import { CustomError } from "../../shared/models/exceptions/custom-error.class"; import { OculusDownloader } from "../models/oculus-downloader.class"; +import { Cookie, session } from "electron"; +import { pathExist } from "../helpers/fs.helpers"; +import { BSLocalVersionService } from "./bs-local-version.service"; export class BsOculusDownloaderService { @@ -17,12 +20,14 @@ export class BsOculusDownloaderService { return BsOculusDownloaderService.instance; } - private readonly windows: WindowManagerService; - private readonly oculusDownloader: OculusDownloader; + + private readonly windows: WindowManagerService; + private readonly versions: BSLocalVersionService; private constructor() { this.windows = WindowManagerService.getInstance(); + this.versions = BSLocalVersionService.getInstance(); this.oculusDownloader = new OculusDownloader(); } @@ -58,7 +63,27 @@ export class BsOculusDownloaderService { } - public async getUserToken(): Promise{ + private isCookieValid(cookie: Cookie): boolean { + + if(!cookie){ + return false; + } + + return cookie.expirationDate > msToS(Date.now()); + } + + private async getTokenFromCookie(): Promise { + const cookie = await session.defaultSession.cookies.get({ name: "oc_www_at" }).then(a => a.at(0)); + + if(this.isCookieValid(cookie) && this.isUserTokenValid(cookie.value)){ + return cookie.value; + } + + return undefined; + } + + public async getUserFromMetaAuth(saveToken: boolean): Promise{ + const redirectUrl = "https://developer.oculus.com/manage/"; const loginUrl = `https://auth.oculus.com/login/?redirect_uri=${encodeURIComponent(redirectUrl)}`; const window = await this.windows.openWindow(loginUrl, { frame: true }); @@ -70,7 +95,7 @@ export class BsOculusDownloaderService { reject(new CustomError("Trying to get Oculus user token timed out", "OCULUS_LOGIN_TIMED_OUT")); window.close(); }, minToMs(5)); - + window.webContents.on("did-navigate", async (_, url) => { if(!url.startsWith(redirectUrl)){ return; } @@ -87,23 +112,70 @@ export class BsOculusDownloaderService { reject(new CustomError("Oculus login window closed by user", "OCULUS_LOGIN_WINDOW_CLOSED_BY_USER")); }); }).finally(() => { + + if(!saveToken){ + window.webContents.session.clearStorageData(); + } + if(!window.isDestroyed() && window.isClosable()){ window.close(); } + clearTimeout(timout); }); return promise; } + /** + * DUPLICATION FROM BS-INSTALLER.SERVICE (TODO : need to be refactored) + * @param path + * @returns + */ + private async getPathNotAleardyExist(path: string): Promise { + let destPath = path; + let folderExist = await pathExist(destPath); + let i = 0; + + while (folderExist) { + i++; + destPath = `${path} (${i})`; + folderExist = await pathExist(destPath); + } + + return destPath; + } + public async downloadVersion(version: BSVersion){ - const token = await this.getUserToken(); - console.log(token); - const s = this.oculusDownloader.downloadApp({ accessToken: token, binaryId: "5074476459318759", destination: "" }).subscribe({ + // await this.clearTokenCookie(); + const token = await this.getUserFromMetaAuth(true); + const dest = await this.getPathNotAleardyExist(await this.versions.getVersionPath(version)); + + const s = this.oculusDownloader.downloadApp({ accessToken: token, binaryId: "1387243574708751", destination: dest }).subscribe({ next: a => console.log(a), error: a => console.error(a), complete: () => console.log("complete") }); } + public async autoDownloadVersion(version: BSVersion){ + const token = await this.getTokenFromCookie(); + + if(!token){ + throw new CustomError("No token has been found while try to auto download Beat Saber from Oculus", "TOKEN_NEEDED"); + } + + const dest = await this.getPathNotAleardyExist(await this.versions.getVersionPath(version)); + + const s = this.oculusDownloader.downloadApp({ accessToken: token, binaryId: "1387243574708751", destination: dest }).subscribe({ + next: a => console.log(a), + error: a => console.error(a), + complete: () => console.log("complete") + }); + } + + public clearTokenCookie(): Promise{ + return session.defaultSession.clearStorageData({ storages: ["cookies"], origin: ".oculus.com" }) + } + } \ No newline at end of file diff --git a/src/renderer/components/modal/modal-types/bs-downgrade/choose-store-modal.component.tsx b/src/renderer/components/modal/modal-types/bs-downgrade/choose-store-modal.component.tsx new file mode 100644 index 00000000..20c94103 --- /dev/null +++ b/src/renderer/components/modal/modal-types/bs-downgrade/choose-store-modal.component.tsx @@ -0,0 +1,53 @@ +import { ModalComponent, ModalExitCode } from "../../../../services/modale.service"; +import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component"; +import { SteamIcon } from "renderer/components/svgs/icons/steam-icon.component"; +import { useTranslation } from "renderer/hooks/use-translation.hook"; +import { BsStore } from "shared/models/bs-store.enum"; +import { useState } from "react"; +import tailwindConfig from "../../../../../../tailwind.config"; +import Color from "color"; + +export const ChooseStore: ModalComponent = ({ resolver }) => { + + const t = useTranslation(); + + const [oculusHover, setOculusHover] = useState(false); + const [steamHover, setSteamHover] = useState(false); + + const chooseStore = (store: BsStore) => { + resolver({ exitCode: ModalExitCode.COMPLETED, data: store }); + } + + const isDarkMode = document.documentElement.classList.contains("dark"); + + const bg = (() => { + if(isDarkMode){ + return { + bright: new Color(tailwindConfig.theme.colors["main-color"][1], "hex").hex(), + dim: new Color(tailwindConfig.theme.colors["main-color"][1], "hex").darken(.2).hex() + } + } + + return { + bright: new Color(tailwindConfig.theme.colors["main-color"][1], "hex").hex(), + dim: new Color(tailwindConfig.theme.colors["main-color"][1], "hex").darken(.2).hex() + } + })(); + + return ( +
+

{t("which platform ?")}

+

Select the platfrom where you own Beat Saber or from which you want to download it.

+
+
setOculusHover(true)} onMouseLeave={() => setOculusHover(false)} onClick={() => chooseStore(BsStore.OCULUS)} style={{backgroundColor: oculusHover ? bg.dim : bg.bright}}> + +

Oculus PC

+
+
setSteamHover(true)} onMouseLeave={() => setSteamHover(false)} onClick={() => chooseStore(BsStore.STEAM)} style={{backgroundColor: steamHover ? bg.dim : bg.bright}}> + +

Steam

+
+
+
+ ); +}; diff --git a/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx b/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx index 22fff626..9cebcda9 100644 --- a/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx +++ b/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx @@ -1,6 +1,6 @@ import { BSVersion } from "shared/bs-version.interface"; import { Link, useLocation } from "react-router-dom"; -import { BsDownloaderService } from "renderer/services/bs-downloader.service"; +import { SteamDownloaderService } from "renderer/services/bs-downgrade/steam-downloader.service"; import { useState } from "react"; import { distinctUntilChanged, map, of, Subscription, switchMap } from "rxjs"; import { BSLauncherService, LaunchMods } from "renderer/services/bs-launcher.service"; @@ -17,7 +17,7 @@ import equal from "fast-deep-equal"; import { useOnUpdate } from "renderer/hooks/use-on-update.hook"; export function BsVersionItem(props: { version: BSVersion }) { - const downloaderService = useService(BsDownloaderService); + const downloaderService = useService(SteamDownloaderService); const verionManagerService = useService(BSVersionManagerService); const launcherService = useService(BSLauncherService); const configService = useService(ConfigurationService); diff --git a/src/renderer/components/nav-bar/nav-bar.component.tsx b/src/renderer/components/nav-bar/nav-bar.component.tsx index 42ee5658..f4d673bf 100644 --- a/src/renderer/components/nav-bar/nav-bar.component.tsx +++ b/src/renderer/components/nav-bar/nav-bar.component.tsx @@ -11,13 +11,13 @@ import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; import Tippy from "@tippyjs/react"; import { useTranslation } from "renderer/hooks/use-translation.hook"; import { useService } from "renderer/hooks/use-service.hook"; -import { BsDownloaderService } from "renderer/services/bs-downloader.service"; +import { SteamDownloaderService } from "renderer/services/bs-downgrade/steam-downloader.service"; import { distinctUntilChanged } from "rxjs"; import equal from "fast-deep-equal"; export function NavBar() { const versionManager = useService(BSVersionManagerService); - const versionDownloader = useService(BsDownloaderService); + const versionDownloader = useService(SteamDownloaderService); const downloadingVersion = useObservable(versionDownloader.currentBsVersionDownload$.pipe(distinctUntilChanged(equal))); const installedVersions = useObservable(versionManager.installedVersions$); diff --git a/src/renderer/components/svgs/icons/oculus-icon.component.tsx b/src/renderer/components/svgs/icons/oculus-icon.component.tsx index 34e134cb..ce911748 100644 --- a/src/renderer/components/svgs/icons/oculus-icon.component.tsx +++ b/src/renderer/components/svgs/icons/oculus-icon.component.tsx @@ -2,7 +2,7 @@ import { CSSProperties } from "react"; export function OculusIcon(props: { className?: string; style?: CSSProperties }) { return ( - + void }>(null); export function AvailableVersionsList() { - const bsDownloader = useService(BsDownloaderService); + + const steamDownloader = useService(SteamDownloaderService); const versionManager = useService(BSVersionManagerService); const progressBar = useService(ProgressBarService); const modal = useService(ModalService); const ipc = useService(IpcService); - const installer = useService(BsDownloaderService); const notification = useService(NotificationService); + const config = useService(ConfigurationService); + const bsDownloader = useService(BsDownloaderService); const [selectedVersion, setSelectedVersion] = useState(null); const contextValue = useMemo(() => ({ selectedVersion, setSelectedVersion }), [selectedVersion]); - const downloading = useObservable(bsDownloader.currentBsVersionDownload$.pipe(map(v => !!v))); + const downloading = useObservable(steamDownloader.currentBsVersionDownload$.pipe(map(v => !!v))); const t = useTranslation(); - const startDownload = () => { - bsDownloader.downloadBsVersion(selectedVersion) + const downloadFromSteam = (version: BSVersion) => { + return steamDownloader.downloadBsVersion(version) .catch(() => {}) .finally(() => setSelectedVersion(null)); + } + + const downloadFromOculus = (version: BSVersion) => { + return undefined; // TODO + } + + const startDownload = () => { + + return bsDownloader.downloadVersion(selectedVersion); + + if(config.get("last-downloaded-from") === "steam"){ + return downloadFromSteam(selectedVersion); + } + + if(config.get("last-downloaded-from") === "oculus"){ + return downloadFromOculus(selectedVersion); + } + + + }; const importVersion = async () => { @@ -63,7 +87,7 @@ export function AvailableVersionsList() { const toImport = folderRes.filePaths.at(0); - const imported = await installer.importVersion(toImport); + const imported = await steamDownloader.importVersion(toImport); if (imported) { versionManager.askInstalledVersions(); diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx index 7b29906b..0334a79a 100644 --- a/src/renderer/pages/settings-page.component.tsx +++ b/src/renderer/pages/settings-page.component.tsx @@ -6,7 +6,7 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component"; import { BsmIconType } from "renderer/components/svgs/bsm-icon.component"; import { DefaultConfigKey, ThemeConfig } from "renderer/config/default-configuration.config"; import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; -import { BsDownloaderService } from "renderer/services/bs-downloader.service"; +import { SteamDownloaderService } from "renderer/services/bs-downgrade/steam-downloader.service"; import { ConfigurationService } from "renderer/services/configuration.service"; import { I18nService } from "renderer/services/i18n.service"; import { IpcService } from "renderer/services/ipc.service"; @@ -43,7 +43,7 @@ export function SettingsPage() { const themeService = useService(ThemeService); const ipcService = useService(IpcService); const modalService = useService(ModalService); - const downloaderService = useService(BsDownloaderService); + const downloaderService = useService(SteamDownloaderService); const progressBarService = useService(ProgressBarService); const notificationService = useService(NotificationService); const i18nService = useService(I18nService); diff --git a/src/renderer/pages/version-viewer.component.tsx b/src/renderer/pages/version-viewer.component.tsx index 340aa6cf..deac6514 100644 --- a/src/renderer/pages/version-viewer.component.tsx +++ b/src/renderer/pages/version-viewer.component.tsx @@ -8,7 +8,7 @@ import { BSUninstallerService } from "../services/bs-uninstaller.service"; import { BSVersionManagerService } from "../services/bs-version-manager.service"; import { ModalExitCode, ModalService } from "../services/modale.service"; import DefautVersionImage from "../../../assets/images/default-version-img.jpg"; -import { BsDownloaderService } from "renderer/services/bs-downloader.service"; +import { SteamDownloaderService } from "renderer/services/bs-downgrade/steam-downloader.service"; import { IpcService } from "renderer/services/ipc.service"; import { LaunchSlide } from "renderer/components/version-viewer/slides/launch/launch-slide.component"; import { ModsSlide } from "renderer/components/version-viewer/slides/mods/mods-slide.component"; @@ -27,7 +27,7 @@ export function VersionViewer() { const bsUninstallerService = useService(BSUninstallerService); const bsVersionManagerService = useService(BSVersionManagerService); const modalService = useService(ModalService); - const bsDownloaderService = useService(BsDownloaderService); + const bsDownloaderService = useService(SteamDownloaderService); const ipcService = useService(IpcService); const bsLauncher = useService(BSLauncherService); const notification = useService(NotificationService); diff --git a/src/renderer/services/bs-downgrade/bs-downloader.service.ts b/src/renderer/services/bs-downgrade/bs-downloader.service.ts new file mode 100644 index 00000000..891eb6b5 --- /dev/null +++ b/src/renderer/services/bs-downgrade/bs-downloader.service.ts @@ -0,0 +1,74 @@ +import { BsStore } from "shared/models/bs-store.enum"; +import { ConfigurationService } from "../configuration.service"; +import { BehaviorSubject } from "rxjs"; +import { BSVersion } from "shared/bs-version.interface"; +import { ModalExitCode, ModalService } from "../modale.service"; +import { ChooseStore } from "renderer/components/modal/modal-types/bs-downgrade/choose-store-modal.component"; +import { SteamDownloaderService } from "./steam-downloader.service"; +import { OculusDownloaderService } from "./oculus-downloader.service"; +import { CustomError } from "shared/models/exceptions/custom-error.class"; + +export class BsDownloaderService { + + private static instance: BsDownloaderService; + + public static getInstance(): BsDownloaderService { + if (!BsDownloaderService.instance) { + BsDownloaderService.instance = new BsDownloaderService(); + } + + return BsDownloaderService.instance; + } + + private readonly config: ConfigurationService; + private readonly modals: ModalService; + private readonly steamDownloader: SteamDownloaderService; + private readonly oculusDownloader: OculusDownloaderService; // TODO : create oculus downloader + + private readonly downloadingVersion$ = new BehaviorSubject(null); // <= TODO : will replace obs in SteamDownloaderService + + private constructor(){ + this.config = ConfigurationService.getInstance(); + this.modals = ModalService.getInstance(); + } + + public getLastStoreDownloadedFrom(): BsStore | undefined { + const lastStore = this.config.get("lastStoreDownloadedFrom"); + + if(!lastStore){ + return undefined; + } + + return lastStore as BsStore; + } + + private async chooseStoreToDownloadFrom(): Promise { + const res = await this.modals.openModal(ChooseStore); + + if(res.exitCode !== ModalExitCode.COMPLETED){ + return undefined; + } + + return res.data; + } + + public async downloadVersion(version: BSVersion): Promise { + const store = this.getLastStoreDownloadedFrom() ?? await this.chooseStoreToDownloadFrom(); + + if(store === BsStore.STEAM){ + return this.steamDownloader.downloadBsVersion(version); + } + + if(store === BsStore.OCULUS){ + return this.oculusDownloader.downloadBsVersion(version); + } + + return undefined + } + + public importVersion(): void { + // TODO : open Modal + // Will replace method in SteamDownloaderService + } + +} \ No newline at end of file diff --git a/src/renderer/services/bs-downgrade/oculus-downloader.service.ts b/src/renderer/services/bs-downgrade/oculus-downloader.service.ts new file mode 100644 index 00000000..6f6c4133 --- /dev/null +++ b/src/renderer/services/bs-downgrade/oculus-downloader.service.ts @@ -0,0 +1,21 @@ +import { BSVersion } from "shared/bs-version.interface"; + +export class OculusDownloaderService { + + private static instance: OculusDownloaderService; + + public static getInstance(): OculusDownloaderService { + if (!OculusDownloaderService.instance) { + OculusDownloaderService.instance = new OculusDownloaderService(); + } + + return OculusDownloaderService.instance; + } + + private constructor(){} + + public async downloadBsVersion(version: BSVersion): Promise { + return version; + } + +} \ No newline at end of file diff --git a/src/renderer/services/bs-downloader.service.ts b/src/renderer/services/bs-downgrade/steam-downloader.service.ts similarity index 93% rename from src/renderer/services/bs-downloader.service.ts rename to src/renderer/services/bs-downgrade/steam-downloader.service.ts index bb3fff53..e2b5ad50 100644 --- a/src/renderer/services/bs-downloader.service.ts +++ b/src/renderer/services/bs-downgrade/steam-downloader.service.ts @@ -2,20 +2,20 @@ import { DownloadInfo } from "main/services/bs-installer.service"; import { BehaviorSubject, Observable, ReplaySubject, Subscription, lastValueFrom, throwError } from "rxjs"; import { filter, map, share, take, tap, throttleTime } from "rxjs/operators"; import { BSVersion } from "shared/bs-version.interface"; -import { AuthUserService } from "./auth-user.service"; -import { BSVersionManagerService } from "./bs-version-manager.service"; -import { IpcService } from "./ipc.service"; -import { ModalExitCode, ModalService } from "./modale.service"; -import { NotificationService } from "./notification.service"; -import { ProgressBarService } from "./progress-bar.service"; +import { AuthUserService } from "../auth-user.service"; +import { BSVersionManagerService } from "../bs-version-manager.service"; +import { IpcService } from "../ipc.service"; +import { ModalExitCode, ModalService } from "../modale.service"; +import { NotificationService } from "../notification.service"; +import { ProgressBarService } from "../progress-bar.service"; import { LoginModal } from "renderer/components/modal/modal-types/login-modal.component"; import { GuardModal } from "renderer/components/modal/modal-types/guard-modal.component"; -import { LinkOpenerService } from "./link-opener.service"; -import { DepotDownloaderErrorEvent, DepotDownloaderEvent, DepotDownloaderEventType, DepotDownloaderInfoEvent, DepotDownloaderWarningEvent } from "../../shared/models/depot-downloader.model"; +import { LinkOpenerService } from "../link-opener.service"; +import { DepotDownloaderErrorEvent, DepotDownloaderEvent, DepotDownloaderEventType, DepotDownloaderInfoEvent, DepotDownloaderWarningEvent } from "../../../shared/models/depot-downloader.model"; import { SteamMobileApproveModal } from "renderer/components/modal/modal-types/steam-mobile-approve-modal.component"; -export class BsDownloaderService { - private static instance: BsDownloaderService; +export class SteamDownloaderService { + private static instance: SteamDownloaderService; private readonly modalService: ModalService; private readonly ipcService: IpcService; @@ -29,11 +29,11 @@ export class BsDownloaderService { public readonly currentBsVersionDownload$ = new BehaviorSubject(null); public readonly downloadProgress$ = new BehaviorSubject(0); - public static getInstance(): BsDownloaderService { - if (!BsDownloaderService.instance) { - BsDownloaderService.instance = new BsDownloaderService(); + public static getInstance(): SteamDownloaderService { + if (!SteamDownloaderService.instance) { + SteamDownloaderService.instance = new SteamDownloaderService(); } - return BsDownloaderService.instance; + return SteamDownloaderService.instance; } private constructor() { diff --git a/src/shared/bs-version.interface.ts b/src/shared/bs-version.interface.ts index 5b732bb6..0a6dde4b 100644 --- a/src/shared/bs-version.interface.ts +++ b/src/shared/bs-version.interface.ts @@ -1,3 +1,5 @@ +import { BsStore } from "./models/bs-store.enum"; + export interface PartialBSVersion { BSVersion: string, name?: string @@ -13,4 +15,7 @@ export interface BSVersion extends PartialBSVersion { steam?: boolean; oculus?: boolean; color?: string; + downloadedFrom?: BsStore; } + + diff --git a/src/shared/helpers/time.helpers.ts b/src/shared/helpers/time.helpers.ts index e0ca656b..8284d7af 100644 --- a/src/shared/helpers/time.helpers.ts +++ b/src/shared/helpers/time.helpers.ts @@ -16,3 +16,7 @@ export function hourToMin(hours: number): number { export function hourToS(hours: number): number { return hours * minToS(MINUTES_IN_HOUR); } + +export function msToS(milliseconds: number): number { + return milliseconds / 1000; +} diff --git a/src/shared/models/bs-store.enum.ts b/src/shared/models/bs-store.enum.ts new file mode 100644 index 00000000..d0bdaa9b --- /dev/null +++ b/src/shared/models/bs-store.enum.ts @@ -0,0 +1,4 @@ +export enum BsStore { + STEAM = "steam", + OCULUS = "oculus", +} \ No newline at end of file