Merge branch 'master' into feature/fileSizes+OversizeWarning

This commit is contained in:
Unifox
2025-02-21 04:33:33 -07:00
committed by GitHub
29 changed files with 2919 additions and 24 deletions
+2 -1
View File
@@ -1,4 +1,4 @@
import { CopyOptions, MoveOptions, RmOptions, copy, createReadStream, ensureDir, move, pathExists, pathExistsSync, realpath, rm, stat, symlink, unlink, unlinkSync } from "fs-extra";
import { CopyOptions, MoveOptions, RmOptions, copy, createReadStream, ensureDir, move, pathExists, pathExistsSync, realpath, rm, stat, symlink, unlink, unlinkSync, rmSync } from "fs-extra";
import { access, mkdir, readdir, lstat, readlink } from "fs/promises";
import path from "path";
import { Observable, concatMap, from } from "rxjs";
@@ -71,6 +71,7 @@ export function deleteFolderSync(folderPath: string, options?: RmOptions) {
try {
options = options || { recursive: true, force: true };
log.info("Deleting folder", `"${folderPath}"`, options);
rmSync(folderPath, options);
} catch (error: any) {
log.error("Could not delete folder", `"${folderPath}"`);
throw CustomError.fromError(error, "generic.fs.delete-folder");
+5
View File
@@ -1,6 +1,7 @@
import { BsModsManagerService } from "../services/mods/bs-mods-manager.service";
import { IpcService } from "../services/ipc.service";
import { from } from "rxjs";
import { BeatModsApiService } from "main/services/mods/beat-mods-api.service";
const ipc = IpcService.getInstance();
@@ -34,3 +35,7 @@ ipc.on("bs-mods.uninstall-all-mods", (args, reply) => {
reply(modsManager.uninstallAllMods(args));
});
ipc.on("bs-mods.beatmods-up", (_, reply) => {
const beatMods = BeatModsApiService.getInstance();
reply(from(beatMods.isUp()));
})
+11 -1
View File
@@ -164,6 +164,16 @@ function convertDateToDateString(date: Date): string {
return `${date.getFullYear()}-${month}-${day}`;
}
function getReadableTime(date: Date): string {
return `${
date.getHours().toString().padStart(2, "0")
}-${
date.getMinutes().toString().padStart(2, "0")
}-${
date.getSeconds().toString().padStart(2, "0")
}`;
}
function initLogger(){
log.transports.file.level = "info";
@@ -178,7 +188,7 @@ function initLogger(){
filepath = path.join(
app.getPath("logs"), nowString,
`${now.getTime()}-v${app.getVersion()}.log`
`${nowString}_${getReadableTime(now)}_v${app.getVersion()}.log`
);
currentDateString = nowString;
return filepath;
+16 -3
View File
@@ -1,3 +1,4 @@
import log from "electron-log";
import fetch from "node-fetch";
import { CustomError } from "../../shared/models/exceptions/custom-error.class";
import { mkdirs, createWriteStream, pathExists, WriteStream } from "fs-extra";
@@ -100,8 +101,13 @@ export class OculusDownloader {
}
private async isFileIntegrityValid(file: OculusFileWithName, folder: string): Promise<boolean> {
const [fileName, fileData] = file;
const destination = path.join(folder, fileName);
const [filename, fileData] = file;
const destination = path.join(
folder,
process.platform === "win32"
? filename
: filename.replaceAll("\\", "/")
);
return pathExists(destination).then(exists => {
if(!exists){ return false; }
@@ -176,7 +182,14 @@ export class OculusDownloader {
return from([res])
}
const target = path.join(options.destination, filename);
const target = path.join(
options.destination,
process.platform === "win32"
? filename
: filename.replaceAll("\\", "/")
);
log.info("Downloaded file", `"${filename}"`, "to", `"${target}"`);
return this.downloadManifestFile(file, target).pipe(
catchError(err => {
this.options.logger?.error(err);
@@ -107,7 +107,7 @@ export class BsOculusDownloaderService {
let downloadVersion: BSVersion
return of(downloadInfo.token).pipe(
switchMap(token => {
switchMap(async token => {
isOculusTokenValid(token, log.info); // Log token validity
if(!downloadInfo.isVerification){
return this.createDownloadVersion(downloadInfo.bsVersion).then(({version, dest}) => ({token, version, dest}))
+3 -3
View File
@@ -1,6 +1,6 @@
import { InstallationLocationService } from "./installation-location.service";
import log from "electron-log";
import { deleteFile, deleteFileSync, deleteFolder, deleteFolderSync, ensureFolderExist, moveFolderContent, pathExist } from "../helpers/fs.helpers";
import { deleteFileSync, deleteFolder, deleteFolderSync, ensureFolderExist, moveFolderContent, pathExist } from "../helpers/fs.helpers";
import { lstat, symlink } from "fs/promises";
import path from "path";
import { copy, mkdirSync, readlink, symlinkSync } from "fs-extra";
@@ -113,7 +113,7 @@ export class FolderLinkerService {
if (isTargetedToSharedPath) {
return;
}
await deleteFile(folderPath);
await deleteFolder(folderPath);
log.info(`Linking ${folderPath} to ${sharedPath}; type: ${this.linkingType}`);
return symlink(sharedPath, folderPath, this.getLinkingType());
@@ -141,7 +141,7 @@ export class FolderLinkerService {
if (!(await this.isFolderSymlink(folderPath))) {
return;
}
await deleteFile(folderPath);
await deleteFolder(folderPath);
const sharedPath = await this.getSharedFolder(folderPath, options?.intermediateFolder);
@@ -26,6 +26,17 @@ export class BeatModsApiService {
this.requestService = RequestService.getInstance();
}
public async isUp(): Promise<boolean> {
try {
// The data in status can be dropped
await this.requestService.getJSON<{}>(`${this.MODS_REPO_API_URL}/status`);
return true;
} catch (error) {
log.error("Could not connect to beatmods", error);
return false;
}
}
private getVersionModsUrl(version: BSVersion): string {
const platform: BbmPlatform = version.oculus || version.metadata?.store === BsStore.OCULUS ? BbmPlatform.OculusPC : BbmPlatform.SteamPC;
return `${this.MODS_REPO_API_URL}/mods?status=verified&gameVersion=${version.BSVersion}&gameName=BeatSaber&platform=${platform}`;
@@ -16,6 +16,7 @@ import { MODEL_TYPE_FOLDERS } from "shared/models/models/constants";
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
import { MapsManagerService } from "renderer/services/maps-manager.service";
import { map } from "rxjs";
import { DISCORD_URL } from "shared/constants";
export const LinkContentModal: ModalComponent<void, {version: BSVersion, contentType: "maps"|"playlists"|"avatars"|"sabers"|"platforms"|"blocks"}> = ({options: { data: { version, contentType } }, resolver }) => {
const { text: t, element: te } = useTranslationV2();
@@ -97,7 +98,7 @@ export const LinkContentModal: ModalComponent<void, {version: BSVersion, content
<p className="max-w-sm w-full italic my-2 text-warning-400">{t("modals.link-contents.warning", {contentType: t(`misc.${contentType}`).toLowerCase()})}</p>
<div className="flex justify-center items-center gap-3 *:underline *:text-sm *:text-neutral-200">
<BsmLink href="https://en.qrwp.org/Symbolic_link">{t("modals.link-contents.what-is-a-symbolic-link")}</BsmLink>
<BsmLink href="https://discord.gg/uSqbHVpKdV">{t("modals.link-contents.i-need-help")}</BsmLink>
<BsmLink href={DISCORD_URL}>{t("modals.link-contents.i-need-help")}</BsmLink>
</div>
<div className="grid grid-flow-col grid-cols-2 gap-2 mt-4 h-8">
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all flex justify-center items-center" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
@@ -16,6 +16,7 @@ import { MODEL_TYPE_FOLDERS } from "shared/models/models/constants";
import { useConstant } from "renderer/hooks/use-constant.hook";
import { MapsManagerService } from "renderer/services/maps-manager.service";
import { BsmLink } from "renderer/components/shared/bsm-link.component";
import { DISCORD_URL } from "shared/constants";
export const UnlinkContentsModal: ModalComponent<boolean, {version: BSVersion, contentType: "maps"|"playlists"|"avatars"|"sabers"|"platforms"|"blocks"}> = ({options: { data: { version, contentType } }, resolver }) => {
const { text: t, element: te } = useTranslationV2();
@@ -86,7 +87,7 @@ export const UnlinkContentsModal: ModalComponent<boolean, {version: BSVersion, c
</p>
<div className="flex justify-center items-center gap-3 *:underline *:text-sm *:text-neutral-200">
<BsmLink href="https://en.qrwp.org/Symbolic_link">{t("modals.link-contents.what-is-a-symbolic-link")}</BsmLink>
<BsmLink href="https://discord.gg/uSqbHVpKdV">{t("modals.link-contents.i-need-help")}</BsmLink>
<BsmLink href={DISCORD_URL}>{t("modals.link-contents.i-need-help")}</BsmLink>
</div>
<Tippy content={t("modals.unlink-contents.do-not-copy-contents-tip", { contentType: t(`misc.${contentType}`).toLowerCase() })} theme="default">
<div className="relative h-5 flex my-3 items-center w-fit">
@@ -9,7 +9,7 @@ import BeatWaitingImg from "../../../../../../assets/images/apngs/beat-waiting.p
import BeatConflictImg from "../../../../../../assets/images/apngs/beat-conflict.png";
import { useObservable } from "renderer/hooks/use-observable.hook";
import { lastValueFrom } from "rxjs";
import { useTranslation, useTranslationV2 } from "renderer/hooks/use-translation.hook";
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
import { LinkOpenerService } from "renderer/services/link-opener.service";
import { ModalExitCode, ModalService } from "renderer/services/modale.service";
import { ModsDisclaimerModal } from "renderer/components/modal/modal-types/mods-disclaimer-modal.component";
@@ -23,6 +23,8 @@ import Tippy from "@tippyjs/react";
import { ProgressBarService } from "renderer/services/progress-bar.service";
import { Dropzone } from "renderer/components/shared/dropzone.component";
import { ModsGridStatus } from "shared/models/mods/mod-ipc.model";
import { BsmLink } from "renderer/components/shared/bsm-link.component";
import { DISCORD_URL, GITHUB_URL } from "shared/constants";
export type ModsSlideRef = {
loadMods: () => Promise<void>;
@@ -33,7 +35,7 @@ type Props = { version: BSVersion; isActive?: boolean, onDisclamerDecline: () =>
export const ModsSlide = forwardRef<ModsSlideRef, Props>(({ version, isActive, onDisclamerDecline }, forwaredRef) => {
const ACCEPTED_DISCLAIMER_KEY = "accepted-mods-disclaimer";
const { text: t } = useTranslationV2();
const { text: t, element: te } = useTranslationV2();
const modsManager = useService(BsModsManagerService);
const configService = useService(ConfigurationService);
@@ -266,12 +268,32 @@ export const ModsSlide = forwardRef<ModsSlideRef, Props>(({ version, isActive, o
}
}, [modsAvailable]);
const renderStatus = () => {
if (gridStatus === ModsGridStatus.BEATMODS_DOWN) {
return <ModStatus image={BeatConflictImg}>
<span className="text-xl text-center px-2 mt-3 italic">
{te("pages.version-viewer.mods.status.beatmods-down", {links: (<>
<BsmLink className="text-blue-500 underline" href={DISCORD_URL}>
Discord
</BsmLink>
/
<BsmLink className="text-blue-500 underline" href={GITHUB_URL}>
GitHub
</BsmLink>
</>)})}
</span>
</ModStatus>
}
return <ModStatus text={`pages.version-viewer.mods.status.${gridStatus}`} image={BeatConflictImg} />;
}
const renderContent = () => {
if (!isOnline) {
return <ModStatus text="pages.version-viewer.mods.no-internet" image={BeatConflictImg} />;
}
if (gridStatus !== ModsGridStatus.OK) {
return <ModStatus text={`pages.version-viewer.mods.status.${gridStatus}`} image={BeatConflictImg} />;
return renderStatus();
}
if (!modsAvailable) {
return <ModStatus text="pages.version-viewer.mods.loading-mods" image={BeatWaitingImg} spin />;
@@ -342,13 +364,13 @@ export const ModsSlide = forwardRef<ModsSlideRef, Props>(({ version, isActive, o
);
});
function ModStatus({ text, image, spin = false, children }: { text: string; image: string; spin?: boolean, children?: ReactNode}) {
const t = useTranslation();
function ModStatus({ text, image, spin = false, children }: { text?: string; image: string; spin?: boolean, children?: ReactNode}) {
const { text: t } = useTranslationV2();
return (
<div className="w-full h-full flex flex-col items-center justify-center text-gray-800 dark:text-gray-200">
<img className={`w-32 h-32 ${spin ? "spin-loading" : ""}`} src={image} alt=" " />
<span className="text-xl mt-3 italic text-center">{t(text)}</span>
{text && <span className="text-xl text-center px-2 mt-3 italic">{t(text)}</span>}
{children}
</div>
);
@@ -47,6 +47,7 @@ import { tryit } from "shared/helpers/error.helpers";
import { InstallationLocationService } from "renderer/services/installation-location.service";
import { AutoUpdaterService } from "renderer/services/auto-updater.service";
import { OculusDownloaderService } from "renderer/services/bs-version-download/oculus-downloader.service";
import { DISCORD_URL } from "shared/constants";
export function SettingsPage() {
@@ -240,7 +241,7 @@ export function SettingsPage() {
const openGithub = () => linkOpener.open("https://github.com/Zagrios/bs-manager");
const openReportBug = () => linkOpener.open("https://github.com/Zagrios/bs-manager/issues/new?assignees=Zagrios&labels=bug&template=-bug--bug-report.md&title=%5BBUG%5D+%3A+");
const openRequestFeatures = () => linkOpener.open("https://github.com/Zagrios/bs-manager/issues/new?assignees=Zagrios&labels=enhancement&template=-feat---feature-request.md&title=%5BFEAT.%5D+%3A+");
const openDiscord = () => linkOpener.open("https://discord.gg/uSqbHVpKdV");
const openDiscord = () => linkOpener.open(DISCORD_URL);
const openTwitter = () => linkOpener.open("https://twitter.com/BSManager_");
const openLogs = () => lastValueFrom(ipcService.sendV2("open-logs"));
@@ -507,7 +508,8 @@ export function SettingsPage() {
<SettingContainer className="mt-3" description="pages.settings.discord.description">
<div className="flex gap-2">
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="Discord" icon="discord" iconClassName="p-0.5 mr-1" color="#5865f2" onClick={openDiscord} />
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="Twitter" icon="twitter" iconClassName="p-0.5 mr-1" color="#000" onClick={openTwitter} />
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="Twitter" icon="twitter" iconClassName="p-0.5 mr-1" color="#171717" onClick={openTwitter} />
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="GitHub" icon="github" iconClassName="p-0.5 mr-1 h-full w-full" color="#171717" onClick={openGithub} />
</div>
</SettingContainer>
<SettingContainer className="pt-3" description="pages.settings.contribution.description">
@@ -518,7 +520,6 @@ export function SettingsPage() {
</div>
<div className="flex px-2 gap-2">
<BsmButton onClick={openLogs} className="shrink-0 whitespace-nowrap px-2 font-bold italic text-sm rounded-md" text="pages.settings.contribution.buttons.open-logs" withBar={false} />
<BsmButton onClick={openGithub} className="shrink-0 px-2 rounded-md" icon="github" title="GitHub" withBar={false} />
</div>
</div>
</SettingContainer>
@@ -218,6 +218,11 @@ export class BsModsManagerService {
}
}
const beatModsUp = await lastValueFrom(this.ipcService.sendV2("bs-mods.beatmods-up")).catch(() => false);
if (!beatModsUp) {
return ModsGridStatus.BEATMODS_DOWN;
}
return ModsGridStatus.OK;
}
+4
View File
@@ -0,0 +1,4 @@
export const DISCORD_URL = "https://discord.gg/uSqbHVpKdV";
export const GITHUB_URL = "https://github.com/Zagrios/bs-manager";
+1
View File
@@ -88,6 +88,7 @@ export interface IpcChannelMapping {
"bs-mods.install-mods": { request: { mods: BbmFullMod[]; version: BSVersion }, response: Progression };
"bs-mods.uninstall-mods": { request: { mods: BbmFullMod[]; version: BSVersion }, response: Progression };
"bs-mods.uninstall-all-mods": { request: BSVersion, response: Progression };
"bs-mods.beatmods-up": { request: void, response: boolean };
/* ** bs-playlist-ipcs ** */
"one-click-install-playlist": { request: string, response: Progression<DownloadPlaylistProgressionData> };
+1
View File
@@ -16,6 +16,7 @@ export interface UninstallModsResult {
export enum ModsGridStatus {
OK = "",
NO_WINEPREFIX = "no-wineprefix",
BEATMODS_DOWN = "beatmods-down",
UNKNOWN = "unknown"
}