[feature-274] improve oculus downloading process

This commit is contained in:
MathieuG-P
2023-10-24 02:17:46 +02:00
parent b576a33293
commit a17da3a83f
5 changed files with 74 additions and 40 deletions
+1
View File
@@ -167,5 +167,6 @@ export function hashFile(filePath: string, algorithm = "sha256"): Promise<string
export interface Progression<T = unknown> {
total: number;
current: number;
diff?: number;
data?: T;
}
+64 -29
View File
@@ -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, writeFile } from "fs-extra";
import { mkdirs, createWriteStream, pathExists, writeFile, WriteStream } from "fs-extra";
import path from "path";
import { inflate } from "pako"
import { Observable, ReplaySubject, lastValueFrom, share, tap } from "rxjs";
import { EMPTY, Observable, ReplaySubject, catchError, filter, from, lastValueFrom, map, mergeMap, scan, share, tap } from "rxjs";
import { Progression, hashFile } from "../helpers/fs.helpers";
export class OculusDownloader {
@@ -38,25 +38,49 @@ export class OculusDownloader {
return manifestFile.async("text").then(JSON.parse).catch(err => CustomError.throw(err, "PARSE_MANIFEST_FILE_FAILED"));
}
private async downloadManifestFile(file: OculusManifestFile, destination: string): Promise<OculusManifestFile> {
private downloadManifestFile(file: OculusManifestFile, destination: string): Observable<Progression<OculusManifestFile>> {
const downloadSegment = async (segment: OculusManifestFileSegment): Promise<ArrayBuffer> => {
const segmentUrl = this.getDownloadSegmentUrl(this.options.accessToken, this.options.binaryId, segment[1]);
const response = await fetch(segmentUrl);
return response.arrayBuffer();
}
await mkdirs(path.dirname(destination));
const writeStream = createWriteStream(destination);
for (const segment of file.segments) {
const arrBuffer = await downloadSegment(segment);
await writeStream.write(inflate(arrBuffer));
}
writeStream.close();
return file;
const totalSegmentSize = file.segments.reduce((acc, segment) => acc + segment[2], 0);
const progress: Progression<OculusManifestFile> = { current: 0, total: totalSegmentSize, diff: 0, data: file };
return new Observable<Progression<OculusManifestFile>>(sub => {
let canceled = false;
let writeStream: WriteStream;
(async () => {
await mkdirs(path.dirname(destination));
writeStream = createWriteStream(destination);
for (const segment of file.segments) {
if(canceled || !writeStream.writable){ return; }
const arrBuffer = await downloadSegment(segment);
const inflated = inflate(arrBuffer);
await writeStream.write(inflated);
progress.current += inflated.byteLength;
progress.diff = inflated.byteLength;
sub.next(progress);
}
})().catch(err => sub.error(err)).finally(() => {
sub.complete();
writeStream?.end();
});
return () => {
canceled = true;
writeStream?.end();
}
});
}
private isFileIntegrityValid(file: OculusFileWithName, folder: string): Promise<boolean> {
@@ -114,7 +138,6 @@ export class OculusDownloader {
this.isDownloading = true;
const progress: Progression = { current: 0, total: 0 };
const intallPath = path.join("C:", "test", "test");
(async () => {
@@ -124,22 +147,34 @@ export class OculusDownloader {
progress.total = files.reduce((acc, file) => { return acc + file[1].size }, 0)
subscriber.next(progress);
for(const [filename, file] of files){
const filesDownloadObservable = from(files).pipe(
filter(() => this.isDownloading),
mergeMap(([filename, file]) => (
from(this.isFileIntegrityValid([filename, file], options.destination)).pipe( map(isValid => ({ filename, file, isValid })))
)),
filter(({ isValid }) => !isValid),
mergeMap(({ filename, file }) => {
const target = path.join(options.destination, filename);
return this.downloadManifestFile(file, target).pipe(
catchError(err => {
this.options.logger?.error(err);
return EMPTY;
})
);
}, 10),
scan((acc, curr) => acc + curr.diff, 0),
);
if(this.isDownloading === false){ return; }
await lastValueFrom(filesDownloadObservable.pipe(tap({
next: download => {
progress.current = download;
subscriber.next(progress);
},
})));
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"));
}
await writeFile(path.join(options.destination, "type.info"), "oculus");
progress.current += file.size;
subscriber.next(progress);
}
await writeFile(path.join(intallPath, "type.info"), "oculus");
const integrity = await lastValueFrom(this.verifyIntegrity(manifest, intallPath)).catch(err => CustomError.throw(err, "VERIFY_INTEGRITY_FAILED"));
const integrity = await lastValueFrom(this.verifyIntegrity(manifest, options.destination)).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,7 +183,7 @@ export class OculusDownloader {
})().then(() => subscriber.complete()).catch(err => subscriber.error(err));
return () => {
console.log("C FINI MAIN")
console.log("C FINI");
this.isDownloading = false;
}
}).pipe(share({connector: () => new ReplaySubject(1)}));
@@ -83,7 +83,7 @@ export class BsOculusDownloaderService {
return undefined;
}
private async getUserTokenFromMetaAuth(saveToken: boolean): Promise<string>{
private async getUserTokenFromMetaAuth(keepToken: boolean): Promise<string>{
const redirectUrl = "https://developer.oculus.com/manage/";
const loginUrl = `https://auth.oculus.com/login/?redirect_uri=${encodeURIComponent(redirectUrl)}`;
@@ -115,8 +115,8 @@ export class BsOculusDownloaderService {
}).finally(() => {
clearTimeout(timout);
if(!saveToken){
if(!keepToken){
this.clearTokenCookie();
}
@@ -157,7 +157,7 @@ export class BsOculusDownloaderService {
const token = await this.getUserTokenFromMetaAuth(downloadInfo.stay);
const dest = await this.getPathNotAleardyExist(await this.versions.getVersionPath(downloadInfo.version));
sub = this.oculusDownloader.downloadApp({ accessToken: token, binaryId: "1387243574708751", destination: dest }).subscribe(obs);
sub = this.oculusDownloader.downloadApp({ accessToken: token, binaryId: downloadInfo.version.OculusBinaryId, destination: dest }).subscribe(obs);
})().catch(err => obs.error(err));
return () => {
@@ -183,7 +183,7 @@ export class BsOculusDownloaderService {
const dest = await this.getPathNotAleardyExist(await this.versions.getVersionPath(version));
sub = this.oculusDownloader.downloadApp({ accessToken: token, binaryId: "1387243574708751", destination: dest }).subscribe(obs);
sub = this.oculusDownloader.downloadApp({ accessToken: token, binaryId: version.OculusBinaryId, destination: dest }).subscribe(obs);
})().catch(err => obs.error(err));
return () => {
@@ -56,7 +56,7 @@ export class BsDownloaderService {
public async downloadVersion(version: BSVersion, from: BsStore): Promise<BSVersion | void> {
if(from === BsStore.STEAM){
if(from === BsStore.STEAM || !version.OculusBinaryId){
return this.steamDownloader.downloadBsVersion(version);
}
@@ -66,17 +66,15 @@ export class OculusDownloaderService {
return (async () => {
const autoDownload = await lastValueFrom(this.tryAutoDownload(version)).then(() => true).catch(() => false);
console.log("LAAAA");
if(autoDownload){
return autoDownload;
}
const [confirm, stay] = await this.modals.openModal(LoginToMetaModal).then(res => [res.exitCode === ModalExitCode.COMPLETED, res.data]);
const [completed, stay] = await this.modals.openModal(LoginToMetaModal).then(res => [res.exitCode === ModalExitCode.COMPLETED, res.data]);
if(!confirm){
return confirm;
if(!completed){
return completed;
}
return lastValueFrom(this.doDownloadBsVersion({ version, stay })).then(() => true);