diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ceb42c7..9524c4b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,12 @@ jobs: - name: Build with cx_Freeze run: python python_rpc/setup.py build + - name: Copy OpenSSL DLLs + if: matrix.os == 'windows-2022' + run: | + cp hydra-python-rpc/lib/libcrypto-1_1.dll hydra-python-rpc/lib/libcrypto-1_1-x64.dll + cp hydra-python-rpc/lib/libssl-1_1.dll hydra-python-rpc/lib/libssl-1_1-x64.dll + - name: Build Linux if: matrix.os == 'ubuntu-latest' run: | diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index bfc4a379..3977c27d 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -430,6 +430,7 @@ "add_download_source": "Add source", "adding": "Adding…", "failed_add_download_source": "Failed to add download source. Please try again.", + "download_source_already_exists": "This download source URL already exists.", "download_count_zero": "No download options", "download_count_one": "{{countFormatted}} download option", "download_count_other": "{{countFormatted}} download options", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 968483a6..c9e908ac 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -416,6 +416,9 @@ "validate_download_source": "Validar", "remove_download_source": "Remover", "add_download_source": "Adicionar fonte", + "adding": "Adicionando…", + "failed_add_download_source": "Falha ao adicionar fonte de download. Tente novamente.", + "download_source_already_exists": "Esta URL de fonte de download já existe.", "download_count_zero": "Sem downloads na lista", "download_count_one": "{{countFormatted}} download na lista", "download_count_other": "{{countFormatted}} downloads na lista", diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts index d4e65ef3..ee426a82 100644 --- a/src/main/events/download-sources/add-download-source.ts +++ b/src/main/events/download-sources/add-download-source.ts @@ -9,6 +9,13 @@ const addDownloadSource = async ( url: string ) => { try { + const existingSources = await downloadSourcesSublevel.values().all(); + const urlExists = existingSources.some((source) => source.url === url); + + if (urlExists) { + throw new Error("Download source with this URL already exists"); + } + const downloadSource = await HydraApi.post( "/download-sources", { @@ -17,7 +24,7 @@ const addDownloadSource = async ( { needsAuth: false } ); - if (HydraApi.isLoggedIn()) { + if (HydraApi.isLoggedIn() && HydraApi.hasActiveSubscription()) { try { await HydraApi.post("/profile/download-sources", { urls: [url], diff --git a/src/main/events/download-sources/remove-download-source.ts b/src/main/events/download-sources/remove-download-source.ts index 8efe0072..9caeaba5 100644 --- a/src/main/events/download-sources/remove-download-source.ts +++ b/src/main/events/download-sources/remove-download-source.ts @@ -13,7 +13,7 @@ const removeDownloadSource = async ( if (downloadSourceId) params.set("downloadSourceId", downloadSourceId); - if (HydraApi.isLoggedIn()) { + if (HydraApi.isLoggedIn() && HydraApi.hasActiveSubscription()) { void HydraApi.delete(`/profile/download-sources?${params.toString()}`); } diff --git a/src/main/main.ts b/src/main/main.ts index 6e477a18..f2440b9f 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -50,9 +50,14 @@ export const loadState = async () => { DeckyPlugin.checkAndUpdateIfOutdated(); } - await HydraApi.setupApi().then(() => { + await HydraApi.setupApi().then(async () => { uploadGamesBatch(); void migrateDownloadSources(); + + const { syncDownloadSourcesFromApi } = await import( + "./services/user" + ); + void syncDownloadSourcesFromApi(); // WSClient.connect(); }); diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index ffc5756c..12090df3 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -46,7 +46,7 @@ export class HydraApi { return this.userAuth.authToken !== ""; } - private static hasActiveSubscription() { + public static hasActiveSubscription() { const expiresAt = new Date(this.userAuth.subscription?.expiresAt ?? 0); return expiresAt > new Date(); } @@ -105,6 +105,9 @@ export class HydraApi { // WSClient.close(); // WSClient.connect(); + + const { syncDownloadSourcesFromApi } = await import("./user"); + syncDownloadSourcesFromApi(); } } diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 88b39d1b..da4e6848 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -18,3 +18,4 @@ export * from "./library-sync"; export * from "./wine"; export * from "./lock"; export * from "./decky-plugin"; +export * from "./user"; diff --git a/src/main/services/user/index.ts b/src/main/services/user/index.ts new file mode 100644 index 00000000..b5001f7a --- /dev/null +++ b/src/main/services/user/index.ts @@ -0,0 +1,3 @@ +export * from "./get-user-data"; +export * from "./sync-download-sources"; + diff --git a/src/main/services/user/sync-download-sources.ts b/src/main/services/user/sync-download-sources.ts new file mode 100644 index 00000000..c5695d68 --- /dev/null +++ b/src/main/services/user/sync-download-sources.ts @@ -0,0 +1,42 @@ +import { HydraApi, logger } from "../"; +import { downloadSourcesSublevel } from "@main/level"; +import type { DownloadSource } from "@types"; + +export const syncDownloadSourcesFromApi = async () => { + if (!HydraApi.isLoggedIn() || !HydraApi.hasActiveSubscription()) { + return; + } + + try { + const profileSources = await HydraApi.get( + "/profile/download-sources" + ); + + const existingSources = await downloadSourcesSublevel.values().all(); + const existingUrls = new Set(existingSources.map((source) => source.url)); + + for (const downloadSource of profileSources) { + if (!existingUrls.has(downloadSource.url)) { + try { + await downloadSourcesSublevel.put(downloadSource.id, { + ...downloadSource, + isRemote: true, + createdAt: new Date().toISOString(), + }); + + logger.log( + `Synced download source from profile: ${downloadSource.url}` + ); + } catch (error) { + logger.error( + `Failed to sync download source ${downloadSource.url}:`, + error + ); + } + } + } + } catch (error) { + logger.error("Failed to sync download sources from API:", error); + } +}; + diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index d7071391..d96c67a5 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -57,9 +57,13 @@ export function AddDownloadSourceModal({ onAddDownloadSource(); } catch (error) { logger.error("Failed to add download source:", error); + const errorMessage = error instanceof Error && error.message.includes("already exists") + ? t("download_source_already_exists") + : t("failed_add_download_source"); + setError("url", { type: "server", - message: t("failed_add_download_source"), + message: errorMessage, }); } finally { setIsLoading(false);