fix: fixing download sources import status

This commit is contained in:
Chubby Granny Chaser
2025-10-27 04:12:19 +00:00
parent 2a90faeb42
commit 2835207d79
10 changed files with 124 additions and 16 deletions

29
.cursorrules Normal file
View File

@@ -0,0 +1,29 @@
# Hydra Project Rules
## Logging
- **Always use `logger` instead of `console` for logging** in both main and renderer processes
- In main process: `import { logger } from "@main/services";`
- In renderer process: `import { logger } from "@renderer/logger";`
- Replace all instances of:
- `console.log()` → `logger.log()`
- `console.error()` → `logger.error()`
- `console.warn()` → `logger.warn()`
- `console.info()` → `logger.info()`
- `console.debug()` → `logger.debug()`
- Do not use `console` for any logging purposes
## Internationalization (i18n)
- All user-facing strings must be translated using i18next
- Use the `useTranslation` hook in React components: `const { t } = useTranslation("namespace");`
- Add new translation keys to `src/locales/en/translation.json`
- Never hardcode English strings in the UI code
- Placeholder text in form fields must also be translated
## Code Style
- Use ESLint and Prettier for code formatting
- Follow TypeScript strict mode conventions
- Use async/await instead of promises when possible
- Prefer named exports over default exports for utilities and services

View File

@@ -428,6 +428,8 @@
"validate_download_source": "Validate", "validate_download_source": "Validate",
"remove_download_source": "Remove", "remove_download_source": "Remove",
"add_download_source": "Add source", "add_download_source": "Add source",
"adding": "Adding…",
"failed_add_download_source": "Failed to add download source. Please try again.",
"download_count_zero": "No download options", "download_count_zero": "No download options",
"download_count_one": "{{countFormatted}} download option", "download_count_one": "{{countFormatted}} download option",
"download_count_other": "{{countFormatted}} download options", "download_count_other": "{{countFormatted}} download options",
@@ -435,9 +437,16 @@
"add_download_source_description": "Insert the URL of the .json file", "add_download_source_description": "Insert the URL of the .json file",
"download_source_up_to_date": "Up-to-date", "download_source_up_to_date": "Up-to-date",
"download_source_errored": "Errored", "download_source_errored": "Errored",
"download_source_pending_matching": "Updating soon",
"download_source_matched": "Up to date",
"download_source_matching": "Updating",
"download_source_failed": "Error",
"download_source_no_information": "No information available",
"sync_download_sources": "Sync sources", "sync_download_sources": "Sync sources",
"removed_download_source": "Download source removed", "removed_download_source": "Download source removed",
"removed_download_sources": "Download sources removed", "removed_download_sources": "Download sources removed",
"removed_all_download_sources": "All download sources removed",
"download_sources_synced_successfully": "All download sources are synced",
"cancel_button_confirmation_delete_all_sources": "No", "cancel_button_confirmation_delete_all_sources": "No",
"confirm_button_confirmation_delete_all_sources": "Yes, delete everything", "confirm_button_confirmation_delete_all_sources": "Yes, delete everything",
"title_confirmation_delete_all_sources": "Delete all download sources", "title_confirmation_delete_all_sources": "Delete all download sources",
@@ -468,6 +477,7 @@
"seed_after_download_complete": "Seed after download complete", "seed_after_download_complete": "Seed after download complete",
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them", "show_hidden_achievement_description": "Show hidden achievements description before unlocking them",
"account": "Account", "account": "Account",
"hydra_cloud": "Hydra Cloud",
"no_users_blocked": "You have no blocked users", "no_users_blocked": "You have no blocked users",
"subscription_active_until": "Your Hydra Cloud is active until {{date}}", "subscription_active_until": "Your Hydra Cloud is active until {{date}}",
"manage_subscription": "Manage subscription", "manage_subscription": "Manage subscription",

View File

@@ -423,7 +423,13 @@
"add_download_source_description": "Insira a URL contendo o arquivo .json", "add_download_source_description": "Insira a URL contendo o arquivo .json",
"download_source_up_to_date": "Sincronizada", "download_source_up_to_date": "Sincronizada",
"download_source_errored": "Falhou", "download_source_errored": "Falhou",
"download_source_pending_matching": "Importando em breve",
"download_source_matched": "Sincronizada",
"download_source_matching": "Sincronizando",
"download_source_failed": "Erro",
"download_source_no_information": "Sem informações",
"sync_download_sources": "Sincronizar", "sync_download_sources": "Sincronizar",
"download_sources_synced_successfully": "Fontes de download sincronizadas",
"removed_download_source": "Fonte removida", "removed_download_source": "Fonte removida",
"removed_download_sources": "Fontes removidas", "removed_download_sources": "Fontes removidas",
"cancel_button_confirmation_delete_all_sources": "Não", "cancel_button_confirmation_delete_all_sources": "Não",

View File

@@ -252,7 +252,13 @@
"add_download_source_description": "Insere o URL que contém o ficheiro .json", "add_download_source_description": "Insere o URL que contém o ficheiro .json",
"download_source_up_to_date": "Sincronizada", "download_source_up_to_date": "Sincronizada",
"download_source_errored": "Falhou", "download_source_errored": "Falhou",
"download_source_pending_matching": "A atualizar em breve",
"download_source_matched": "Atualizado",
"download_source_matching": "A atualizar",
"download_source_failed": "Erro",
"download_source_no_information": "Sem informações",
"sync_download_sources": "Sincronizar", "sync_download_sources": "Sincronizar",
"download_sources_synced_successfully": "Fontes de download sincronizadas",
"removed_download_source": "Fonte removida", "removed_download_source": "Fonte removida",
"cancel_button_confirmation_delete_all_sources": "Não", "cancel_button_confirmation_delete_all_sources": "Não",
"confirm_button_confirmation_delete_all_sources": "Sim, apague tudo", "confirm_button_confirmation_delete_all_sources": "Sim, apague tudo",

View File

@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { Button, Modal, TextField } from "@renderer/components"; import { Button, Modal, TextField } from "@renderer/components";
import { settingsContext } from "@renderer/context"; import { settingsContext } from "@renderer/context";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { logger } from "@renderer/logger";
import * as yup from "yup"; import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
@@ -55,10 +56,10 @@ export function AddDownloadSourceModal({
onClose(); onClose();
onAddDownloadSource(); onAddDownloadSource();
} catch (error) { } catch (error) {
console.error("Failed to add download source:", error); logger.error("Failed to add download source:", error);
setError("url", { setError("url", {
type: "server", type: "server",
message: "Failed to add download source. Please try again.", message: t("failed_add_download_source"),
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);

View File

@@ -201,7 +201,7 @@ export function SettingsAccount() {
</section> </section>
<section className="settings-account__section"> <section className="settings-account__section">
<h3>Hydra Cloud</h3> <h3>{t("hydra_cloud")}</h3>
<div className="settings-account__subscription-info"> <div className="settings-account__subscription-info">
{getHydraCloudSectionContent().description} {getHydraCloudSectionContent().description}
</div> </div>

View File

@@ -1,5 +1,14 @@
@use "../../scss/globals.scss"; @use "../../scss/globals.scss";
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.settings-download-sources { .settings-download-sources {
&__list { &__list {
padding: 0; padding: 0;
@@ -22,6 +31,17 @@
&--syncing { &--syncing {
opacity: globals.$disabled-opacity; opacity: globals.$disabled-opacity;
} }
&--pending {
opacity: 0.6;
}
}
&__spinner {
animation: spin 1s linear infinite;
margin-right: calc(globals.$spacing-unit / 2);
width: 12px;
height: 12px;
} }
&__item-header { &__item-header {

View File

@@ -22,6 +22,7 @@ import { settingsContext } from "@renderer/context";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { setFilters, clearFilters } from "@renderer/features"; import { setFilters, clearFilters } from "@renderer/features";
import "./settings-download-sources.scss"; import "./settings-download-sources.scss";
import { logger } from "@renderer/logger";
export function SettingsDownloadSources() { export function SettingsDownloadSources() {
const [ const [
@@ -58,6 +59,30 @@ export function SettingsDownloadSources() {
fetchDownloadSources(); fetchDownloadSources();
}, []); }, []);
useEffect(() => {
const hasPendingOrMatchingSource = downloadSources.some(
(source) =>
source.status === DownloadSourceStatus.PendingMatching ||
source.status === DownloadSourceStatus.Matching
);
if (!hasPendingOrMatchingSource || !downloadSources.length) {
return;
}
const intervalId = setInterval(async () => {
try {
await window.electron.syncDownloadSources();
const sources = await window.electron.getDownloadSources();
setDownloadSources(sources);
} catch (error) {
logger.error("Failed to fetch download sources:", error);
}
}, 5000);
return () => clearInterval(intervalId);
}, [downloadSources]);
const handleRemoveSource = async (downloadSource: DownloadSource) => { const handleRemoveSource = async (downloadSource: DownloadSource) => {
setIsRemovingDownloadSource(true); setIsRemovingDownloadSource(true);
@@ -67,7 +92,7 @@ export function SettingsDownloadSources() {
setDownloadSources(sources as DownloadSource[]); setDownloadSources(sources as DownloadSource[]);
showSuccessToast(t("removed_download_source")); showSuccessToast(t("removed_download_source"));
} catch (error) { } catch (error) {
console.error("Failed to remove download source:", error); logger.error("Failed to remove download source:", error);
} finally { } finally {
setIsRemovingDownloadSource(false); setIsRemovingDownloadSource(false);
} }
@@ -82,7 +107,7 @@ export function SettingsDownloadSources() {
setDownloadSources(sources as DownloadSource[]); setDownloadSources(sources as DownloadSource[]);
showSuccessToast(t("removed_all_download_sources")); showSuccessToast(t("removed_all_download_sources"));
} catch (error) { } catch (error) {
console.error("Failed to remove all download sources:", error); logger.error("Failed to remove all download sources:", error);
} finally { } finally {
setIsRemovingDownloadSource(false); setIsRemovingDownloadSource(false);
setShowConfirmationDeleteAllSourcesModal(false); setShowConfirmationDeleteAllSourcesModal(false);
@@ -94,7 +119,7 @@ export function SettingsDownloadSources() {
const sources = await window.electron.getDownloadSources(); const sources = await window.electron.getDownloadSources();
setDownloadSources(sources as DownloadSource[]); setDownloadSources(sources as DownloadSource[]);
} catch (error) { } catch (error) {
console.error("Failed to refresh download sources:", error); logger.error("Failed to refresh download sources:", error);
} }
}; };
@@ -127,7 +152,7 @@ export function SettingsDownloadSources() {
const navigateToCatalogue = (fingerprint?: string) => { const navigateToCatalogue = (fingerprint?: string) => {
if (!fingerprint) { if (!fingerprint) {
console.error("Cannot navigate: fingerprint is undefined"); logger.error("Cannot navigate: fingerprint is undefined");
return; return;
} }
@@ -202,16 +227,25 @@ export function SettingsDownloadSources() {
<ul className="settings-download-sources__list"> <ul className="settings-download-sources__list">
{downloadSources.map((downloadSource) => { {downloadSources.map((downloadSource) => {
const isPendingOrMatching =
downloadSource.status === DownloadSourceStatus.PendingMatching ||
downloadSource.status === DownloadSourceStatus.Matching;
return ( return (
<li <li
key={downloadSource.id} key={downloadSource.id}
className={`settings-download-sources__item ${isSyncingDownloadSources ? "settings-download-sources__item--syncing" : ""}`} className={`settings-download-sources__item ${isSyncingDownloadSources ? "settings-download-sources__item--syncing" : ""} ${isPendingOrMatching ? "settings-download-sources__item--pending" : ""}`}
> >
<div className="settings-download-sources__item-header"> <div className="settings-download-sources__item-header">
<h2>{downloadSource.name}</h2> <h2>{downloadSource.name}</h2>
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<Badge>{statusTitle[downloadSource.status]}</Badge> <Badge>
{isPendingOrMatching && (
<SyncIcon className="settings-download-sources__spinner" />
)}
{statusTitle[downloadSource.status]}
</Badge>
</div> </div>
<button <button
@@ -223,11 +257,13 @@ export function SettingsDownloadSources() {
} }
> >
<small> <small>
{t("download_count", { {isPendingOrMatching
count: downloadSource.downloadCount, ? t("download_source_no_information")
countFormatted: : t("download_count", {
downloadSource.downloadCount.toLocaleString(), count: downloadSource.downloadCount,
})} countFormatted:
downloadSource.downloadCount.toLocaleString(),
})}
</small> </small>
</button> </button>
</div> </div>

View File

@@ -133,7 +133,7 @@ export function SettingsRealDebrid() {
{t("save_changes")} {t("save_changes")}
</Button> </Button>
} }
placeholder="API Token" placeholder={t("api_token")}
hint={ hint={
<Trans i18nKey="debrid_api_token_hint" ns="settings"> <Trans i18nKey="debrid_api_token_hint" ns="settings">
<Link to={REAL_DEBRID_API_TOKEN_URL} /> <Link to={REAL_DEBRID_API_TOKEN_URL} />

View File

@@ -116,7 +116,7 @@ export function SettingsTorBox() {
onChange={(event) => onChange={(event) =>
setForm({ ...form, torBoxApiToken: event.target.value }) setForm({ ...form, torBoxApiToken: event.target.value })
} }
placeholder="API Token" placeholder={t("api_token")}
rightContent={ rightContent={
<Button type="submit" disabled={isButtonDisabled}> <Button type="submit" disabled={isButtonDisabled}>
{t("save_changes")} {t("save_changes")}