mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +03:00
Merge pull request #902 from hydralauncher/feat/show-current-game-user-profile
feat: show current game user profile
This commit is contained in:
commit
cc9d254a32
@ -1,3 +1,4 @@
|
|||||||
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
|
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
|
||||||
MAIN_VITE_API_URL=API_URL
|
MAIN_VITE_API_URL=API_URL
|
||||||
MAIN_VITE_SENTRY_DSN=YOUR_SENTRY_DSN
|
MAIN_VITE_SENTRY_DSN=YOUR_SENTRY_DSN
|
||||||
|
SENTRY_AUTH_TOKEN=
|
||||||
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -37,8 +37,6 @@ jobs:
|
|||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: yarn build:linux
|
run: yarn build:linux
|
||||||
env:
|
env:
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
|
||||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
|
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
|
||||||
@ -48,8 +46,6 @@ jobs:
|
|||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
run: yarn build:win
|
run: yarn build:win
|
||||||
env:
|
env:
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
|
||||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
|
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -39,8 +39,6 @@ jobs:
|
|||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: yarn build:linux
|
run: yarn build:linux
|
||||||
env:
|
env:
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
|
||||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
|
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
|
||||||
@ -50,8 +48,6 @@ jobs:
|
|||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
run: yarn build:win
|
run: yarn build:win
|
||||||
env:
|
env:
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
|
||||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
|
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
|
||||||
|
@ -5,3 +5,4 @@ pnpm-lock.yaml
|
|||||||
LICENSE.md
|
LICENSE.md
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
tsconfig.*.json
|
tsconfig.*.json
|
||||||
|
src/main/migrations
|
||||||
|
@ -8,30 +8,22 @@ import {
|
|||||||
UserPreferences,
|
UserPreferences,
|
||||||
UserAuth,
|
UserAuth,
|
||||||
} from "@main/entity";
|
} from "@main/entity";
|
||||||
import type { BetterSqlite3ConnectionOptions } from "typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions";
|
|
||||||
|
|
||||||
import { databasePath } from "./constants";
|
import { databasePath } from "./constants";
|
||||||
import migrations from "./migrations";
|
import * as migrations from "./migrations";
|
||||||
|
|
||||||
export const createDataSource = (
|
export const dataSource = new DataSource({
|
||||||
options: Partial<BetterSqlite3ConnectionOptions>
|
type: "better-sqlite3",
|
||||||
) =>
|
entities: [
|
||||||
new DataSource({
|
Game,
|
||||||
type: "better-sqlite3",
|
Repack,
|
||||||
entities: [
|
UserPreferences,
|
||||||
Game,
|
GameShopCache,
|
||||||
Repack,
|
DownloadSource,
|
||||||
UserPreferences,
|
DownloadQueue,
|
||||||
GameShopCache,
|
UserAuth,
|
||||||
DownloadSource,
|
],
|
||||||
DownloadQueue,
|
synchronize: true,
|
||||||
UserAuth,
|
database: databasePath,
|
||||||
],
|
|
||||||
synchronize: true,
|
|
||||||
database: databasePath,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const dataSource = createDataSource({
|
|
||||||
migrations,
|
migrations,
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,7 @@ const removeRemoveGameFromLibrary = async (gameId: number) => {
|
|||||||
const game = await gameRepository.findOne({ where: { id: gameId } });
|
const game = await gameRepository.findOne({ where: { id: gameId } });
|
||||||
|
|
||||||
if (game?.remoteId) {
|
if (game?.remoteId) {
|
||||||
HydraApi.delete(`/games/${game.remoteId}`).catch(() => {});
|
HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ const blockUser = async (
|
|||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
userId: string
|
userId: string
|
||||||
) => {
|
) => {
|
||||||
await HydraApi.post(`/user/${userId}/block`);
|
await HydraApi.post(`/users/${userId}/block`);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("blockUser", blockUser);
|
registerEvent("blockUser", blockUser);
|
||||||
|
@ -14,7 +14,7 @@ export const getUserFriends = async (
|
|||||||
return HydraApi.get(`/profile/friends`, { take, skip });
|
return HydraApi.get(`/profile/friends`, { take, skip });
|
||||||
}
|
}
|
||||||
|
|
||||||
return HydraApi.get(`/user/${userId}/friends`, { take, skip });
|
return HydraApi.get(`/users/${userId}/friends`, { take, skip });
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserFriendsEvent = async (
|
const getUserFriendsEvent = async (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { HydraApi } from "@main/services";
|
import { HydraApi } from "@main/services";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
import { steamGamesWorker } from "@main/workers";
|
||||||
import { UserProfile } from "@types";
|
import { GameRunning, UserGame, UserProfile } from "@types";
|
||||||
import { convertSteamGameToCatalogueEntry } from "../helpers/search-games";
|
import { convertSteamGameToCatalogueEntry } from "../helpers/search-games";
|
||||||
import { getSteamAppAsset } from "@main/helpers";
|
import { getSteamAppAsset } from "@main/helpers";
|
||||||
import { getUserFriends } from "./get-user-friends";
|
import { getUserFriends } from "./get-user-friends";
|
||||||
@ -12,7 +12,7 @@ const getUser = async (
|
|||||||
): Promise<UserProfile | null> => {
|
): Promise<UserProfile | null> => {
|
||||||
try {
|
try {
|
||||||
const [profile, friends] = await Promise.all([
|
const [profile, friends] = await Promise.all([
|
||||||
HydraApi.get(`/user/${userId}`),
|
HydraApi.get(`/users/${userId}`),
|
||||||
getUserFriends(userId, 12, 0).catch(() => {
|
getUserFriends(userId, 12, 0).catch(() => {
|
||||||
return { totalFriends: 0, friends: [] };
|
return { totalFriends: 0, friends: [] };
|
||||||
}),
|
}),
|
||||||
@ -20,48 +20,57 @@ const getUser = async (
|
|||||||
|
|
||||||
const recentGames = await Promise.all(
|
const recentGames = await Promise.all(
|
||||||
profile.recentGames.map(async (game) => {
|
profile.recentGames.map(async (game) => {
|
||||||
const steamGame = await steamGamesWorker.run(Number(game.objectId), {
|
return getSteamUserGame(game);
|
||||||
name: "getById",
|
|
||||||
});
|
|
||||||
const iconUrl = steamGame?.clientIcon
|
|
||||||
? getSteamAppAsset("icon", game.objectId, steamGame.clientIcon)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...game,
|
|
||||||
...convertSteamGameToCatalogueEntry(steamGame),
|
|
||||||
iconUrl,
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const libraryGames = await Promise.all(
|
const libraryGames = await Promise.all(
|
||||||
profile.libraryGames.map(async (game) => {
|
profile.libraryGames.map(async (game) => {
|
||||||
const steamGame = await steamGamesWorker.run(Number(game.objectId), {
|
return getSteamUserGame(game);
|
||||||
name: "getById",
|
|
||||||
});
|
|
||||||
const iconUrl = steamGame?.clientIcon
|
|
||||||
? getSteamAppAsset("icon", game.objectId, steamGame.clientIcon)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...game,
|
|
||||||
...convertSteamGameToCatalogueEntry(steamGame),
|
|
||||||
iconUrl,
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const currentGame = await getGameRunning(profile.currentGame);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...profile,
|
...profile,
|
||||||
libraryGames,
|
libraryGames,
|
||||||
recentGames,
|
recentGames,
|
||||||
friends: friends.friends,
|
friends: friends.friends,
|
||||||
totalFriends: friends.totalFriends,
|
totalFriends: friends.totalFriends,
|
||||||
|
currentGame,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getGameRunning = async (currentGame): Promise<GameRunning | null> => {
|
||||||
|
if (!currentGame) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gameRunning = await getSteamUserGame(currentGame);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...gameRunning,
|
||||||
|
sessionDurationInMillis: currentGame.sessionDurationInSeconds * 1000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSteamUserGame = async (game): Promise<UserGame> => {
|
||||||
|
const steamGame = await steamGamesWorker.run(Number(game.objectId), {
|
||||||
|
name: "getById",
|
||||||
|
});
|
||||||
|
const iconUrl = steamGame?.clientIcon
|
||||||
|
? getSteamAppAsset("icon", game.objectId, steamGame.clientIcon)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...game,
|
||||||
|
...convertSteamGameToCatalogueEntry(steamGame),
|
||||||
|
iconUrl,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
registerEvent("getUser", getUser);
|
registerEvent("getUser", getUser);
|
||||||
|
@ -5,7 +5,7 @@ const unblockUser = async (
|
|||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
userId: string
|
userId: string
|
||||||
) => {
|
) => {
|
||||||
await HydraApi.post(`/user/${userId}/unblock`);
|
await HydraApi.post(`/users/${userId}/unblock`);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("unblockUser", unblockUser);
|
registerEvent("unblockUser", unblockUser);
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class FixRepackUploadDate1715900413313 implements MigrationInterface {
|
|
||||||
public async up(_: QueryRunner): Promise<void> {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(_: QueryRunner): Promise<void> {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
import { Game } from "@main/entity";
|
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class AlterLastTimePlayedToDatime1716776027208
|
|
||||||
implements MigrationInterface
|
|
||||||
{
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
// 2024-05-27 02:08:17
|
|
||||||
// Mon, 27 May 2024 02:08:17 GMT
|
|
||||||
const updateLastTimePlayedValues = `
|
|
||||||
UPDATE game SET lastTimePlayed = (SELECT
|
|
||||||
SUBSTR(lastTimePlayed, 13, 4) || '-' || -- Year
|
|
||||||
CASE SUBSTR(lastTimePlayed, 9, 3)
|
|
||||||
WHEN 'Jan' THEN '01'
|
|
||||||
WHEN 'Feb' THEN '02'
|
|
||||||
WHEN 'Mar' THEN '03'
|
|
||||||
WHEN 'Apr' THEN '04'
|
|
||||||
WHEN 'May' THEN '05'
|
|
||||||
WHEN 'Jun' THEN '06'
|
|
||||||
WHEN 'Jul' THEN '07'
|
|
||||||
WHEN 'Aug' THEN '08'
|
|
||||||
WHEN 'Sep' THEN '09'
|
|
||||||
WHEN 'Oct' THEN '10'
|
|
||||||
WHEN 'Nov' THEN '11'
|
|
||||||
WHEN 'Dec' THEN '12'
|
|
||||||
END || '-' || -- Month
|
|
||||||
SUBSTR(lastTimePlayed, 6, 2) || ' ' || -- Day
|
|
||||||
SUBSTR(lastTimePlayed, 18, 8) -- hh:mm:ss;
|
|
||||||
FROM game)
|
|
||||||
WHERE lastTimePlayed IS NOT NULL;
|
|
||||||
`;
|
|
||||||
|
|
||||||
await queryRunner.query(updateLastTimePlayedValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
const queryBuilder = queryRunner.manager.createQueryBuilder(Game, "game");
|
|
||||||
|
|
||||||
const result = await queryBuilder.getMany();
|
|
||||||
|
|
||||||
for (const game of result) {
|
|
||||||
if (!game.lastTimePlayed) continue;
|
|
||||||
await queryRunner.query(
|
|
||||||
`UPDATE game set lastTimePlayed = ? WHERE id = ?;`,
|
|
||||||
[game.lastTimePlayed.toUTCString(), game.id]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
50
src/main/migrations/1724081695967-Hydra_2_0_3.ts
Normal file
50
src/main/migrations/1724081695967-Hydra_2_0_3.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class Hydra2031724081695967 implements MigrationInterface {
|
||||||
|
name = 'Hydra2031724081695967'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "game" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "objectID" text NOT NULL, "remoteId" text, "title" text NOT NULL, "iconUrl" text, "folderName" text, "downloadPath" text, "executablePath" text, "playTimeInMilliseconds" integer NOT NULL DEFAULT (0), "shop" text NOT NULL, "status" text, "downloader" integer NOT NULL DEFAULT (1), "progress" float NOT NULL DEFAULT (0), "bytesDownloaded" integer NOT NULL DEFAULT (0), "lastTimePlayed" datetime, "fileSize" float NOT NULL DEFAULT (0), "uri" text, "isDeleted" boolean NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "repackId" integer, CONSTRAINT "UQ_04293f46e8db3deaec8dfb69264" UNIQUE ("objectID"), CONSTRAINT "UQ_6dac8c3148e141251a4864e94d4" UNIQUE ("remoteId"), CONSTRAINT "REL_0c1d6445ad047d9bbd256f961f" UNIQUE ("repackId"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "download_source" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "url" text, "name" text NOT NULL, "etag" text, "downloadCount" integer NOT NULL DEFAULT (0), "status" text NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_aec2879321a87e9bb2ed477981a" UNIQUE ("url"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "user_preferences" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "downloadsPath" text, "language" text NOT NULL DEFAULT ('en'), "realDebridApiToken" text, "downloadNotificationsEnabled" boolean NOT NULL DEFAULT (0), "repackUpdatesNotificationsEnabled" boolean NOT NULL DEFAULT (0), "preferQuitInsteadOfHiding" boolean NOT NULL DEFAULT (0), "runAtStartup" boolean NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "game_shop_cache" ("objectID" text PRIMARY KEY NOT NULL, "shop" text NOT NULL, "serializedData" text, "howLongToBeatSerializedData" text, "language" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "download_queue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "gameId" integer, CONSTRAINT "REL_aed852c94d9ded617a7a07f541" UNIQUE ("gameId"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "user_auth" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "userId" text NOT NULL DEFAULT (''), "displayName" text NOT NULL DEFAULT (''), "profileImageUrl" text, "accessToken" text NOT NULL DEFAULT (''), "refreshToken" text NOT NULL DEFAULT (''), "tokenExpirationTimestamp" integer NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "temporary_game" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "objectID" text NOT NULL, "remoteId" text, "title" text NOT NULL, "iconUrl" text, "folderName" text, "downloadPath" text, "executablePath" text, "playTimeInMilliseconds" integer NOT NULL DEFAULT (0), "shop" text NOT NULL, "status" text, "downloader" integer NOT NULL DEFAULT (1), "progress" float NOT NULL DEFAULT (0), "bytesDownloaded" integer NOT NULL DEFAULT (0), "lastTimePlayed" datetime, "fileSize" float NOT NULL DEFAULT (0), "uri" text, "isDeleted" boolean NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "repackId" integer, CONSTRAINT "UQ_04293f46e8db3deaec8dfb69264" UNIQUE ("objectID"), CONSTRAINT "UQ_6dac8c3148e141251a4864e94d4" UNIQUE ("remoteId"), CONSTRAINT "REL_0c1d6445ad047d9bbd256f961f" UNIQUE ("repackId"), CONSTRAINT "FK_0c1d6445ad047d9bbd256f961f6" FOREIGN KEY ("repackId") REFERENCES "repack" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||||
|
await queryRunner.query(`INSERT INTO "temporary_game"("id", "objectID", "remoteId", "title", "iconUrl", "folderName", "downloadPath", "executablePath", "playTimeInMilliseconds", "shop", "status", "downloader", "progress", "bytesDownloaded", "lastTimePlayed", "fileSize", "uri", "isDeleted", "createdAt", "updatedAt", "repackId") SELECT "id", "objectID", "remoteId", "title", "iconUrl", "folderName", "downloadPath", "executablePath", "playTimeInMilliseconds", "shop", "status", "downloader", "progress", "bytesDownloaded", "lastTimePlayed", "fileSize", "uri", "isDeleted", "createdAt", "updatedAt", "repackId" FROM "game"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "game"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "temporary_game" RENAME TO "game"`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "temporary_repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"), CONSTRAINT "FK_13f131029be1dd361fd3cd9c2a6" FOREIGN KEY ("downloadSourceId") REFERENCES "download_source" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
|
||||||
|
await queryRunner.query(`INSERT INTO "temporary_repack"("id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "repack"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "repack"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "temporary_repack" RENAME TO "repack"`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "temporary_download_queue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "gameId" integer, CONSTRAINT "REL_aed852c94d9ded617a7a07f541" UNIQUE ("gameId"), CONSTRAINT "FK_aed852c94d9ded617a7a07f5415" FOREIGN KEY ("gameId") REFERENCES "game" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||||
|
await queryRunner.query(`INSERT INTO "temporary_download_queue"("id", "createdAt", "updatedAt", "gameId") SELECT "id", "createdAt", "updatedAt", "gameId" FROM "download_queue"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "download_queue"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "temporary_download_queue" RENAME TO "download_queue"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "download_queue" RENAME TO "temporary_download_queue"`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "download_queue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "gameId" integer, CONSTRAINT "REL_aed852c94d9ded617a7a07f541" UNIQUE ("gameId"))`);
|
||||||
|
await queryRunner.query(`INSERT INTO "download_queue"("id", "createdAt", "updatedAt", "gameId") SELECT "id", "createdAt", "updatedAt", "gameId" FROM "temporary_download_queue"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "temporary_download_queue"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "repack" RENAME TO "temporary_repack"`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"))`);
|
||||||
|
await queryRunner.query(`INSERT INTO "repack"("id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "temporary_repack"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "temporary_repack"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "game" RENAME TO "temporary_game"`);
|
||||||
|
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "game" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "objectID" text NOT NULL, "remoteId" text, "title" text NOT NULL, "iconUrl" text, "folderName" text, "downloadPath" text, "executablePath" text, "playTimeInMilliseconds" integer NOT NULL DEFAULT (0), "shop" text NOT NULL, "status" text, "downloader" integer NOT NULL DEFAULT (1), "progress" float NOT NULL DEFAULT (0), "bytesDownloaded" integer NOT NULL DEFAULT (0), "lastTimePlayed" datetime, "fileSize" float NOT NULL DEFAULT (0), "uri" text, "isDeleted" boolean NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "repackId" integer, CONSTRAINT "UQ_04293f46e8db3deaec8dfb69264" UNIQUE ("objectID"), CONSTRAINT "UQ_6dac8c3148e141251a4864e94d4" UNIQUE ("remoteId"), CONSTRAINT "REL_0c1d6445ad047d9bbd256f961f" UNIQUE ("repackId"))`);
|
||||||
|
await queryRunner.query(`INSERT INTO "game"("id", "objectID", "remoteId", "title", "iconUrl", "folderName", "downloadPath", "executablePath", "playTimeInMilliseconds", "shop", "status", "downloader", "progress", "bytesDownloaded", "lastTimePlayed", "fileSize", "uri", "isDeleted", "createdAt", "updatedAt", "repackId") SELECT "id", "objectID", "remoteId", "title", "iconUrl", "folderName", "downloadPath", "executablePath", "playTimeInMilliseconds", "shop", "status", "downloader", "progress", "bytesDownloaded", "lastTimePlayed", "fileSize", "uri", "isDeleted", "createdAt", "updatedAt", "repackId" FROM "temporary_game"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "temporary_game"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "user_auth"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "download_queue"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "game_shop_cache"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "user_preferences"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "repack"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "download_source"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "game"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/main/migrations/1724081984535-DowloadsRefactor.ts
Normal file
20
src/main/migrations/1724081984535-DowloadsRefactor.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class DowloadsRefactor1724081984535 implements MigrationInterface {
|
||||||
|
name = 'DowloadsRefactor1724081984535'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE "temporary_repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, "uris" text NOT NULL DEFAULT ('[]'), CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"), CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "FK_13f131029be1dd361fd3cd9c2a6" FOREIGN KEY ("downloadSourceId") REFERENCES "download_source" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
|
||||||
|
await queryRunner.query(`INSERT INTO "temporary_repack"("id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "repack"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "repack"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "temporary_repack" RENAME TO "repack"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "repack" RENAME TO "temporary_repack"`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "repack" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" text NOT NULL, "magnet" text NOT NULL, "page" integer, "repacker" text NOT NULL, "fileSize" text NOT NULL, "uploadDate" datetime NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "downloadSourceId" integer, CONSTRAINT "UQ_5e8d57798643e693bced32095d2" UNIQUE ("magnet"), CONSTRAINT "UQ_a46a68496754a4d429ddf9d48ec" UNIQUE ("title"), CONSTRAINT "FK_13f131029be1dd361fd3cd9c2a6" FOREIGN KEY ("downloadSourceId") REFERENCES "download_source" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
|
||||||
|
await queryRunner.query(`INSERT INTO "repack"("id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "page", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "temporary_repack"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "temporary_repack"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,2 @@
|
|||||||
import { FixRepackUploadDate1715900413313 } from "./1715900413313-fix_repack_uploadDate";
|
export * from "./1724081695967-Hydra_2_0_3";
|
||||||
import { AlterLastTimePlayedToDatime1716776027208 } from "./1716776027208-alter_lastTimePlayed_to_datime";
|
export * from "./1724081984535-DowloadsRefactor";
|
||||||
|
|
||||||
export default [
|
|
||||||
FixRepackUploadDate1715900413313,
|
|
||||||
AlterLastTimePlayedToDatime1716776027208,
|
|
||||||
];
|
|
||||||
|
@ -77,54 +77,54 @@ export class HydraApi {
|
|||||||
baseURL: import.meta.env.MAIN_VITE_API_URL,
|
baseURL: import.meta.env.MAIN_VITE_API_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
// this.instance.interceptors.request.use(
|
this.instance.interceptors.request.use(
|
||||||
// (request) => {
|
(request) => {
|
||||||
// logger.log(" ---- REQUEST -----");
|
logger.log(" ---- REQUEST -----");
|
||||||
// logger.log(request.method, request.url, request.params, request.data);
|
logger.log(request.method, request.url, request.params, request.data);
|
||||||
// return request;
|
return request;
|
||||||
// },
|
},
|
||||||
// (error) => {
|
(error) => {
|
||||||
// logger.error("request error", error);
|
logger.error("request error", error);
|
||||||
// return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
|
|
||||||
// this.instance.interceptors.response.use(
|
this.instance.interceptors.response.use(
|
||||||
// (response) => {
|
(response) => {
|
||||||
// logger.log(" ---- RESPONSE -----");
|
logger.log(" ---- RESPONSE -----");
|
||||||
// logger.log(
|
logger.log(
|
||||||
// response.status,
|
response.status,
|
||||||
// response.config.method,
|
response.config.method,
|
||||||
// response.config.url,
|
response.config.url,
|
||||||
// response.data
|
response.data
|
||||||
// );
|
);
|
||||||
// return response;
|
return response;
|
||||||
// },
|
},
|
||||||
// (error) => {
|
(error) => {
|
||||||
// logger.error(" ---- RESPONSE ERROR -----");
|
logger.error(" ---- RESPONSE ERROR -----");
|
||||||
|
|
||||||
// const { config } = error;
|
const { config } = error;
|
||||||
|
|
||||||
// logger.error(
|
logger.error(
|
||||||
// config.method,
|
config.method,
|
||||||
// config.baseURL,
|
config.baseURL,
|
||||||
// config.url,
|
config.url,
|
||||||
// config.headers,
|
config.headers,
|
||||||
// config.data
|
config.data
|
||||||
// );
|
);
|
||||||
|
|
||||||
// if (error.response) {
|
if (error.response) {
|
||||||
// logger.error("Response", error.response.status, error.response.data);
|
logger.error("Response", error.response.status, error.response.data);
|
||||||
// } else if (error.request) {
|
} else if (error.request) {
|
||||||
// logger.error("Request", error.request);
|
logger.error("Request", error.request);
|
||||||
// } else {
|
} else {
|
||||||
// logger.error("Error", error.message);
|
logger.error("Error", error.message);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// logger.error(" ----- END RESPONSE ERROR -------");
|
logger.error(" ----- END RESPONSE ERROR -------");
|
||||||
// return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
|
|
||||||
const userAuth = await userAuthRepository.findOne({
|
const userAuth = await userAuthRepository.findOne({
|
||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
|
@ -3,7 +3,7 @@ import { HydraApi } from "../hydra-api";
|
|||||||
import { gameRepository } from "@main/repository";
|
import { gameRepository } from "@main/repository";
|
||||||
|
|
||||||
export const createGame = async (game: Game) => {
|
export const createGame = async (game: Game) => {
|
||||||
HydraApi.post(`/games`, {
|
HydraApi.post(`/profile/games`, {
|
||||||
objectId: game.objectID,
|
objectId: game.objectID,
|
||||||
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),
|
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),
|
||||||
shop: game.shop,
|
shop: game.shop,
|
||||||
|
@ -4,7 +4,7 @@ import { steamGamesWorker } from "@main/workers";
|
|||||||
import { getSteamAppAsset } from "@main/helpers";
|
import { getSteamAppAsset } from "@main/helpers";
|
||||||
|
|
||||||
export const mergeWithRemoteGames = async () => {
|
export const mergeWithRemoteGames = async () => {
|
||||||
return HydraApi.get("/games")
|
return HydraApi.get("/profile/games")
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
for (const game of response) {
|
for (const game of response) {
|
||||||
const localGame = await gameRepository.findOne({
|
const localGame = await gameRepository.findOne({
|
||||||
|
@ -6,7 +6,7 @@ export const updateGamePlaytime = async (
|
|||||||
deltaInMillis: number,
|
deltaInMillis: number,
|
||||||
lastTimePlayed: Date
|
lastTimePlayed: Date
|
||||||
) => {
|
) => {
|
||||||
HydraApi.put(`/games/${game.remoteId}`, {
|
HydraApi.put(`/profile/games/${game.remoteId}`, {
|
||||||
playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000),
|
playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000),
|
||||||
lastTimePlayed,
|
lastTimePlayed,
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
@ -14,7 +14,7 @@ export const uploadGamesBatch = async () => {
|
|||||||
|
|
||||||
for (const chunk of gamesChunks) {
|
for (const chunk of gamesChunks) {
|
||||||
await HydraApi.post(
|
await HydraApi.post(
|
||||||
"/games/batch",
|
"/profile/games/batch",
|
||||||
chunk.map((game) => {
|
chunk.map((game) => {
|
||||||
return {
|
return {
|
||||||
objectId: game.objectID,
|
objectId: game.objectID,
|
||||||
|
@ -10,6 +10,6 @@ export const startMainLoop = async () => {
|
|||||||
DownloadManager.watchDownloads(),
|
DownloadManager.watchDownloads(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await sleep(500);
|
await sleep(1000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,12 +4,16 @@ import { WindowManager } from "./window-manager";
|
|||||||
import { createGame, updateGamePlaytime } from "./library-sync";
|
import { createGame, updateGamePlaytime } from "./library-sync";
|
||||||
import { GameRunning } from "@types";
|
import { GameRunning } from "@types";
|
||||||
import { PythonInstance } from "./download";
|
import { PythonInstance } from "./download";
|
||||||
|
import { Game } from "@main/entity";
|
||||||
|
|
||||||
export const gamesPlaytime = new Map<
|
export const gamesPlaytime = new Map<
|
||||||
number,
|
number,
|
||||||
{ lastTick: number; firstTick: number }
|
{ lastTick: number; firstTick: number; lastSyncTick: number }
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
const TICKS_TO_UPDATE_API = 120;
|
||||||
|
let currentTick = 1;
|
||||||
|
|
||||||
export const watchProcesses = async () => {
|
export const watchProcesses = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
where: {
|
where: {
|
||||||
@ -30,48 +34,17 @@ export const watchProcesses = async () => {
|
|||||||
|
|
||||||
if (gameProcess) {
|
if (gameProcess) {
|
||||||
if (gamesPlaytime.has(game.id)) {
|
if (gamesPlaytime.has(game.id)) {
|
||||||
const gamePlaytime = gamesPlaytime.get(game.id)!;
|
onTickGame(game);
|
||||||
|
|
||||||
const zero = gamePlaytime.lastTick;
|
|
||||||
const delta = performance.now() - zero;
|
|
||||||
|
|
||||||
await gameRepository.update(game.id, {
|
|
||||||
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
|
||||||
lastTimePlayed: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
gamesPlaytime.set(game.id, {
|
|
||||||
...gamePlaytime,
|
|
||||||
lastTick: performance.now(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (game.remoteId) {
|
onOpenGame(game);
|
||||||
updateGamePlaytime(game, 0, new Date());
|
|
||||||
} else {
|
|
||||||
createGame({ ...game, lastTimePlayed: new Date() });
|
|
||||||
}
|
|
||||||
|
|
||||||
gamesPlaytime.set(game.id, {
|
|
||||||
lastTick: performance.now(),
|
|
||||||
firstTick: performance.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (gamesPlaytime.has(game.id)) {
|
} else if (gamesPlaytime.has(game.id)) {
|
||||||
const gamePlaytime = gamesPlaytime.get(game.id)!;
|
onCloseGame(game);
|
||||||
gamesPlaytime.delete(game.id);
|
|
||||||
|
|
||||||
if (game.remoteId) {
|
|
||||||
updateGamePlaytime(
|
|
||||||
game,
|
|
||||||
performance.now() - gamePlaytime.firstTick,
|
|
||||||
game.lastTimePlayed!
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
createGame(game);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentTick++;
|
||||||
|
|
||||||
if (WindowManager.mainWindow) {
|
if (WindowManager.mainWindow) {
|
||||||
const gamesRunning = Array.from(gamesPlaytime.entries()).map((entry) => {
|
const gamesRunning = Array.from(gamesPlaytime.entries()).map((entry) => {
|
||||||
return {
|
return {
|
||||||
@ -86,3 +59,68 @@ export const watchProcesses = async () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function onOpenGame(game: Game) {
|
||||||
|
const now = performance.now();
|
||||||
|
|
||||||
|
gamesPlaytime.set(game.id, {
|
||||||
|
lastTick: now,
|
||||||
|
firstTick: now,
|
||||||
|
lastSyncTick: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (game.remoteId) {
|
||||||
|
updateGamePlaytime(game, 0, new Date());
|
||||||
|
} else {
|
||||||
|
createGame({ ...game, lastTimePlayed: new Date() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTickGame(game: Game) {
|
||||||
|
const now = performance.now();
|
||||||
|
const gamePlaytime = gamesPlaytime.get(game.id)!;
|
||||||
|
|
||||||
|
const delta = now - gamePlaytime.lastTick;
|
||||||
|
|
||||||
|
gameRepository.update(game.id, {
|
||||||
|
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
||||||
|
lastTimePlayed: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
gamesPlaytime.set(game.id, {
|
||||||
|
...gamePlaytime,
|
||||||
|
lastTick: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentTick % TICKS_TO_UPDATE_API === 0) {
|
||||||
|
if (game.remoteId) {
|
||||||
|
updateGamePlaytime(
|
||||||
|
game,
|
||||||
|
now - gamePlaytime.lastSyncTick,
|
||||||
|
game.lastTimePlayed!
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
createGame(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
gamesPlaytime.set(game.id, {
|
||||||
|
...gamePlaytime,
|
||||||
|
lastSyncTick: now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCloseGame = (game: Game) => {
|
||||||
|
const gamePlaytime = gamesPlaytime.get(game.id)!;
|
||||||
|
gamesPlaytime.delete(game.id);
|
||||||
|
|
||||||
|
if (game.remoteId) {
|
||||||
|
updateGamePlaytime(
|
||||||
|
game,
|
||||||
|
performance.now() - gamePlaytime.firstTick,
|
||||||
|
game.lastTimePlayed!
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
createGame(game);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -78,7 +78,7 @@ export function SidebarProfile() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{userDetails && gameRunning && (
|
{userDetails && gameRunning?.iconUrl && (
|
||||||
<img
|
<img
|
||||||
alt={gameRunning.title}
|
alt={gameRunning.title}
|
||||||
width={24}
|
width={24}
|
||||||
|
@ -62,9 +62,9 @@ export const UserFriendModalList = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listContainer.current?.addEventListener("scroll", handleScroll);
|
const container = listContainer.current;
|
||||||
return () =>
|
container?.addEventListener("scroll", handleScroll);
|
||||||
listContainer.current?.removeEventListener("scroll", handleScroll);
|
return () => container?.removeEventListener("scroll", handleScroll);
|
||||||
}, [isLoading]);
|
}, [isLoading]);
|
||||||
|
|
||||||
const reloadList = () => {
|
const reloadList = () => {
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
import { FriendRequestAction, UserGame, UserProfile } from "@types";
|
import {
|
||||||
|
FriendRequestAction,
|
||||||
|
GameRunning,
|
||||||
|
UserGame,
|
||||||
|
UserProfile,
|
||||||
|
} from "@types";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import * as styles from "./user.css";
|
import * as styles from "./user.css";
|
||||||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||||
@ -44,7 +49,6 @@ export function UserContent({
|
|||||||
updateUserProfile,
|
updateUserProfile,
|
||||||
}: ProfileContentProps) {
|
}: ProfileContentProps) {
|
||||||
const { t, i18n } = useTranslation("user_profile");
|
const { t, i18n } = useTranslation("user_profile");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
userDetails,
|
userDetails,
|
||||||
profileBackground,
|
profileBackground,
|
||||||
@ -64,6 +68,7 @@ export function UserContent({
|
|||||||
useState(false);
|
useState(false);
|
||||||
const [showSignOutModal, setShowSignOutModal] = useState(false);
|
const [showSignOutModal, setShowSignOutModal] = useState(false);
|
||||||
const [showUserBlockModal, setShowUserBlockModal] = useState(false);
|
const [showUserBlockModal, setShowUserBlockModal] = useState(false);
|
||||||
|
const [currentGame, setCurrentGame] = useState<GameRunning | null>(null);
|
||||||
|
|
||||||
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
||||||
|
|
||||||
@ -113,6 +118,15 @@ export function UserContent({
|
|||||||
|
|
||||||
const isMe = userDetails?.id == userProfile.id;
|
const isMe = userDetails?.id == userProfile.id;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMe && gameRunning) {
|
||||||
|
setCurrentGame(gameRunning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentGame(userProfile.currentGame);
|
||||||
|
}, [gameRunning, isMe, userProfile.currentGame]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMe) fetchFriendRequests();
|
if (isMe) fetchFriendRequests();
|
||||||
}, [isMe, fetchFriendRequests]);
|
}, [isMe, fetchFriendRequests]);
|
||||||
@ -284,10 +298,10 @@ export function UserContent({
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{gameRunning && isMe && (
|
{currentGame && (
|
||||||
<img
|
<img
|
||||||
src={steamUrlBuilder.libraryHero(gameRunning.objectID)}
|
src={steamUrlBuilder.libraryHero(currentGame.objectID)}
|
||||||
alt={gameRunning.title}
|
alt={currentGame.title}
|
||||||
className={styles.profileBackground}
|
className={styles.profileBackground}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -315,7 +329,7 @@ export function UserContent({
|
|||||||
|
|
||||||
<div className={styles.profileInformation}>
|
<div className={styles.profileInformation}>
|
||||||
<h2 style={{ fontWeight: "bold" }}>{userProfile.displayName}</h2>
|
<h2 style={{ fontWeight: "bold" }}>{userProfile.displayName}</h2>
|
||||||
{isMe && gameRunning && (
|
{currentGame && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -331,14 +345,14 @@ export function UserContent({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link to={buildGameDetailsPath(gameRunning)}>
|
<Link to={buildGameDetailsPath(currentGame)}>
|
||||||
{gameRunning.title}
|
{currentGame.title}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<small>
|
<small>
|
||||||
{t("playing_for", {
|
{t("playing_for", {
|
||||||
amount: formatDiffInMillis(
|
amount: formatDiffInMillis(
|
||||||
gameRunning.sessionDurationInMillis,
|
currentGame.sessionDurationInMillis,
|
||||||
new Date()
|
new Date()
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
|
@ -51,9 +51,9 @@ export const UserEditProfileBlockList = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listContainer.current?.addEventListener("scroll", handleScroll);
|
const container = listContainer.current;
|
||||||
return () =>
|
container?.addEventListener("scroll", handleScroll);
|
||||||
listContainer.current?.removeEventListener("scroll", handleScroll);
|
return () => container?.removeEventListener("scroll", handleScroll);
|
||||||
}, [isLoading]);
|
}, [isLoading]);
|
||||||
|
|
||||||
const reloadList = () => {
|
const reloadList = () => {
|
||||||
|
@ -141,9 +141,9 @@ export interface Game {
|
|||||||
export type LibraryGame = Omit<Game, "repacks">;
|
export type LibraryGame = Omit<Game, "repacks">;
|
||||||
|
|
||||||
export interface GameRunning {
|
export interface GameRunning {
|
||||||
id: number;
|
id?: number;
|
||||||
title: string;
|
title: string;
|
||||||
iconUrl: string;
|
iconUrl: string | null;
|
||||||
objectID: string;
|
objectID: string;
|
||||||
shop: GameShop;
|
shop: GameShop;
|
||||||
sessionDurationInMillis: number;
|
sessionDurationInMillis: number;
|
||||||
@ -318,6 +318,7 @@ export interface UserProfile {
|
|||||||
friends: UserFriend[];
|
friends: UserFriend[];
|
||||||
totalFriends: number;
|
totalFriends: number;
|
||||||
relation: UserRelation | null;
|
relation: UserRelation | null;
|
||||||
|
currentGame: GameRunning | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateProfileProps {
|
export interface UpdateProfileProps {
|
||||||
|
Loading…
Reference in New Issue
Block a user