Skip to content

Commit

Permalink
Merge pull request #14 from GravityTwoG/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
GravityTwoG authored Apr 2, 2024
2 parents 13c1393 + 3ce2848 commit 99b4d3f
Show file tree
Hide file tree
Showing 73 changed files with 1,449 additions and 775 deletions.
43 changes: 25 additions & 18 deletions src/@types/electron-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,38 +26,45 @@ interface Window {

showFolderDialog: () => Promise<ElectronApiResponse<FolderInfo>>;

getSavePaths: (
getStatePaths: (
paths: import("../types").GamePath[]
) => Promise<ElectronApiResponse<import("../types").GamePath[]>>;

getFolderInfo: (
folderPath: string
) => Promise<ElectronApiResponse<FolderInfo>>;

uploadSave: (
folder: {
path: string;
name: string;
},
game: import("../types").Game
) => Promise<
onGetSyncedStates: (callback: () => void) => void;

sendSyncedStates: (
args: import("../types").GameState[]
) => Promise<ElectronApiResponse<void>>;

uploadState: (folder: {
gameId?: string;
localPath: string;
name: string;
isPublic: boolean;
}) => Promise<
ElectronApiResponse<{
buffer: Buffer;
gameStateValues: {
gameStateParameterId: string;
value: string;
}[];
gameStateValues: { value: string; gameStateParameterId: string }[];
}>
>;

onGetSyncedSaves: (callback: () => void) => void;
reuploadState: (state: import("../types").GameState) => Promise<
ElectronApiResponse<{
buffer: Buffer;
gameStateValues: { value: string; gameStateParameterId: string }[];
}>
>;

sendSyncedSaves: (
args: import("../types").GameState[]
downloadState: (
gameState: import("../types").GameState
) => Promise<ElectronApiResponse<void>>;

downloadState: (gameState: import("../types").GameState) => Promise<void>;

downloadStateAs: (gameState: import("../types").GameState) => Promise<void>;
downloadStateAs: (
gameState: import("../types").GameState
) => Promise<ElectronApiResponse<void>>;
};
}
43 changes: 36 additions & 7 deletions src/Application.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import path from "path";
import { BrowserWindow, Menu, Tray, app, nativeImage, Event } from "electron";
import {
BrowserWindow,
Menu,
Tray,
app,
nativeImage,
Event,
ipcMain,
} from "electron";
import electronDl from "electron-dl";

import { setupIPC } from "./backend/electron-api";
import { SyncManager } from "./backend/SyncManager";
import { syncManager } from "./backend";
import { syncManager, electronAPI } from "./backend";

const protocolName = "cloud-saves";
const clientProtocol = `${protocolName}://`;
Expand All @@ -22,7 +29,7 @@ export class Application {
electronDl();

this.syncManager.init(() => {
this.mainWindow?.webContents.send("getSyncedSaves");
this.mainWindow?.webContents.send("getSyncedStates");
});

const gotTheLock = app.requestSingleInstanceLock();
Expand Down Expand Up @@ -67,15 +74,14 @@ export class Application {
app.on("ready", () => {
this.mainWindow = this.createWindow();
this.createTray();

setupIPC();
this.setupIPC();

app.on("activate", () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
this.mainWindow = this.createWindow();
setupIPC();
this.setupIPC();
}
});
});
Expand All @@ -95,6 +101,29 @@ export class Application {
});
}

private setupIPC() {
function registerHandler<T extends unknown[], R>(
name: string,
handler: (...args: T) => R
) {
ipcMain.handle(name, (_, ...args: unknown[]) => handler(...(args as T)));
}

registerHandler("showFolderDialog", electronAPI.showFolderDialog);

registerHandler("getStatePaths", electronAPI.getStatePaths);

registerHandler("getFolderInfo", electronAPI.getFolderInfo);

registerHandler("uploadState", electronAPI.uploadState);

registerHandler("reuploadState", electronAPI.reuploadState);

registerHandler("downloadState", electronAPI.downloadState);

registerHandler("downloadStateAs", electronAPI.downloadStateAs);
}

private registerProtocolClient() {
if (process.defaultApp) {
if (process.argv.length >= 2) {
Expand Down
142 changes: 125 additions & 17 deletions src/backend/StatesManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import fs from "fs/promises";
import path from "path";
import os from "os";
import AdmZip from "adm-zip";
import { session } from "electron";

import { Game, GameState } from "@/types";
import { GameAPI, GameFromServer } from "@/client/api/GameAPI";
import { ValueExtractor } from "./game-state-parameters/ValueExtractor";
import { moveFolder } from "./fs/moveFolder";
import { downloadToFolder } from "./fs/downloadToFolder";
Expand All @@ -16,39 +18,145 @@ export class StatesManager {
this.valueExtractor = valueExtractor;
}

async uploadState(folder: { path: string; name: string }, game?: Game) {
async uploadState(folder: {
gameId?: string;
localPath: string;
name: string;
isPublic: boolean;
}) {
const gameStateData = await this.getState(folder);
const formData = this.mapToGameStateData(
{
...folder,
gameId: folder.gameId || "",
},
gameStateData
);

await fetch(`${import.meta.env.VITE_API_BASE_URL}/game-saves`, {
method: "POST",
headers: {
Cookie: await this.buildCookieHeader(),
},
body: formData,
});

return gameStateData;
}

async reuploadState(gameState: GameState) {
const gameStateData = await this.getState(gameState);
const formData = this.mapToGameStateData(gameState, gameStateData);

const response2 = await fetch(
`${import.meta.env.VITE_API_BASE_URL}/game-saves/${gameState.id}`,
{
method: "PATCH",
headers: {
Cookie: await this.buildCookieHeader(),
},
body: formData,
}
);

console.log(response2.status);
console.log(response2.statusText);
return gameStateData;
}

async downloadState(gameState: GameState) {
const tempPath = os.tmpdir();
const archivePath = path.join(tempPath, "cloud-saves");
const filename = `${gameState.name}-archive.zip`;
const filePath = path.join(archivePath, filename);

await downloadToFolder(gameState.archiveURL, archivePath, filename);

const extractedFolderPath = await extractZIP(filePath);

// move extracted folder to game states folder
await moveFolder(extractedFolderPath, gameState.localPath);
}

private mapToGameStateData = (
gameState: {
gameId: string;
localPath: string;
name: string;
isPublic: boolean;
},
response: {
buffer: Buffer;
gameStateValues: { value: string; gameStateParameterId: string }[];
}
) => {
const formData = new FormData();
formData.append("archive", new Blob([response.buffer]));
formData.append(
"gameStateData",
JSON.stringify({
gameId: gameState.gameId,
name: gameState.name,
localPath: gameState.localPath,
isPublic: gameState.isPublic,
gameStateValues: response.gameStateValues.map((value) => ({
value: value.value,
gameStateParameterId: value.gameStateParameterId,
})),
})
);

return formData;
};

// returns gameStateValues and buffer with gameState archive
private async getState(folder: {
gameId?: string;
localPath: string;
name: string;
}) {
const zip = new AdmZip();

const isDirectory = (await fs.lstat(folder.path)).isDirectory();
const isDirectory = (await fs.lstat(folder.localPath)).isDirectory();

if (isDirectory) {
await zip.addLocalFolderPromise(folder.path, {});
await zip.addLocalFolderPromise(folder.localPath, {});
} else {
zip.addLocalFile(folder.path);
zip.addLocalFile(folder.localPath);
}
// await zip.writeZipPromise(`${path}.zip`);

const game = folder.gameId ? await this.getGame(folder.gameId) : undefined;
const gameStateValues = game
? await this.valueExtractor.extract(folder, game)
? await this.valueExtractor.extract(folder.localPath, game)
: [];

const buffer = zip.toBuffer();
return {
buffer,
buffer: zip.toBuffer(),
gameStateValues,
};
}

async downloadState(gameState: GameState) {
const tempPath = os.tmpdir();
const archivePath = path.join(tempPath, "cloud-saves");
const filename = `${gameState.name}-archive.zip`;
const filePath = path.join(archivePath, filename);
private async getGame(gameId: string): Promise<Game> {
const response = await fetch(
`${import.meta.env.VITE_API_BASE_URL}/games/${gameId}`,
{
headers: {
Cookie: await this.buildCookieHeader(),
},
}
);

await downloadToFolder(gameState.archiveURL, archivePath, filename);
if (!response.ok) {
throw new Error(response.statusText);
}

const extractedFolderPath = await extractZIP(filePath);
const game = (await response.json()) as GameFromServer;

// move extracted folder to game folder
await moveFolder(extractedFolderPath, gameState.localPath);
return GameAPI.mapGameFromServer(game);
}

private async buildCookieHeader() {
const cookies = await session.defaultSession.cookies.get({});
return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join(";");
}
}
Loading

0 comments on commit 99b4d3f

Please sign in to comment.