mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-15 16:33:02 -03:00
Compare commits
4 Commits
feat/LBX-3
...
feat/LBX-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9678ece1b | ||
|
|
af6d027b06 | ||
|
|
7e78a0f9f1 | ||
|
|
d56cc8695b |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydralauncher",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.1",
|
||||
"description": "Hydra",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Los Broxas",
|
||||
|
||||
@@ -189,7 +189,6 @@
|
||||
"downloader_not_configured": "Available but not configured",
|
||||
"downloader_offline": "Link is offline",
|
||||
"downloader_not_available": "Not available",
|
||||
"recommended": "Recommended",
|
||||
"go_to_settings": "Go to Settings",
|
||||
"select_executable": "Select",
|
||||
"no_executable_selected": "No executable selected",
|
||||
@@ -774,7 +773,10 @@
|
||||
"manual_playtime_tooltip": "This playtime has been manually updated",
|
||||
"all_games": "All Games",
|
||||
"recently_played": "Recently Played",
|
||||
"favorites": "Favorites"
|
||||
"favorites": "Favorites",
|
||||
"disk_usage": "Disk usage",
|
||||
"disk_usage_tooltip": "Installed size on disk",
|
||||
"installer_size_tooltip": "Installer size"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Achievement unlocked",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"game_has_no_executable": "El juego no tiene un ejecutable seleccionado",
|
||||
"sign_in": "Iniciar Sesión",
|
||||
"friends": "Amigos",
|
||||
"notifications": "Notificaciones",
|
||||
"need_help": "¿Necesitás ayuda?",
|
||||
"favorites": "Favoritos",
|
||||
"playable_button_title": "Solo mostrar juegos que podés jugar en este momento",
|
||||
@@ -115,6 +116,7 @@
|
||||
"downloading": "Descargando {{title}}… ({{percentage}} completado) - Restante {{eta}} - {{speed}}",
|
||||
"calculating_eta": "Descargando {{title}}… ({{percentage}} completado) - Comprobando tiempo restante…",
|
||||
"checking_files": "Revisando archivos de {{title}}… ({{percentage}} completado)",
|
||||
"extracting": "Extrayendo {{title}}… ({{percentage}} completado)",
|
||||
"installing_common_redist": "{{log}}…",
|
||||
"installation_complete": "Instalación completada",
|
||||
"installation_complete_message": "Common redistributables instalados correctamente"
|
||||
@@ -173,6 +175,7 @@
|
||||
"repacks_modal_description": "Elegí el repack que querés descargar",
|
||||
"select_folder_hint": "Si querés cambiar la carpeta por defecto, andá a <0>Ajustes</0>",
|
||||
"download_now": "Descargar ahora",
|
||||
"loading": "Cargando...",
|
||||
"no_shop_details": "No se pudieron obtener detalles de la tienda.",
|
||||
"download_options": "Opciones de descarga",
|
||||
"download_path": "Ruta de descarga",
|
||||
@@ -206,6 +209,7 @@
|
||||
"danger_zone_section_description": "Remover este juego de tu librería o los archivos descargados por Hydra",
|
||||
"download_in_progress": "Descarga en progreso",
|
||||
"download_paused": "Descarga pausada",
|
||||
"extracting": "Extrayendo",
|
||||
"last_downloaded_option": "Última opción de descarga",
|
||||
"new_download_option": "Nuevo",
|
||||
"create_steam_shortcut": "Crear atajo de Steam",
|
||||
@@ -400,6 +404,10 @@
|
||||
"completed": "Completado",
|
||||
"removed": "No descargado",
|
||||
"cancel": "Cancelar",
|
||||
"cancel_download": "¿Cancelar descarga?",
|
||||
"cancel_download_description": "¿Estás seguro de que querés cancelar esta descarga? Todos los archivos descargados serán eliminados.",
|
||||
"keep_downloading": "No, seguir descargando",
|
||||
"yes_cancel": "Sí, cancelar",
|
||||
"filter": "Filtrar juegos descargados",
|
||||
"remove": "Remover",
|
||||
"downloading_metadata": "Descargando metadatos…",
|
||||
@@ -420,7 +428,13 @@
|
||||
"resume_seeding": "Continuar sembrando",
|
||||
"options": "Administrar",
|
||||
"extract": "Extraer archivos",
|
||||
"extracting": "Extrayendo archivos…"
|
||||
"extracting": "Extrayendo archivos…",
|
||||
"delete_archive_title": "¿Querés eliminar {{fileName}}?",
|
||||
"delete_archive_description": "El archivo se extrajo exitosamente y ya no es necesario.",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"network": "RED",
|
||||
"peak": "PICO"
|
||||
},
|
||||
"settings": {
|
||||
"downloads_path": "Ruta de descarga",
|
||||
@@ -544,6 +558,7 @@
|
||||
"show_download_speed_in_megabytes": "Mostrar velocidad de descarga en megabytes por segundo",
|
||||
"extract_files_by_default": "Extraer archivos por defecto después de descargar",
|
||||
"enable_steam_achievements": "Habilitar búsqueda de logros de Steam",
|
||||
"enable_new_download_options_badges": "Mostrar badges de nuevas opciones de descarga",
|
||||
"achievement_custom_notification_position": "Posición de notificación de logros",
|
||||
"top-left": "Superior Izquierda",
|
||||
"top-center": "Superior Centro",
|
||||
@@ -570,20 +585,10 @@
|
||||
"debrid_description": "Los servicios Debrid son descargadores premium sin restricciones que te dejan descargar más rápido archivos alojados en servicios de alojamiento siendo que la única limitación es tu velocidad de internet.",
|
||||
"enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego",
|
||||
"autoplay_trailers_on_game_page": "Reproducir trailers automáticamente en la página del juego",
|
||||
"change_achievement_sound": "Cambiar sonido de logro",
|
||||
"download_source_already_exists": "Esta fuente de descarga URL ya existe.",
|
||||
"download_source_failed": "Error",
|
||||
"download_source_matched": "Actualizado",
|
||||
"download_source_matching": "Actualizando",
|
||||
"download_source_no_information": "Sin información disponible",
|
||||
"download_source_pending_matching": "Actualizando pronto",
|
||||
"download_sources_synced_successfully": "Todas las fuentes de descarga están sincronizadas",
|
||||
"failed_add_download_source": "Error al añadir la fuente de descarga. Por favor intentá de nuevo.",
|
||||
"hydra_cloud": "Hydra Cloud",
|
||||
"preview_sound": "Vista previa de sonido",
|
||||
"remove_achievement_sound": "Eliminar sonido de logros",
|
||||
"removed_all_download_sources": "Todas las fuentes de descarga eliminadas",
|
||||
"hide_to_tray_on_game_start": "Ocultar Hydra en la bandeja al iniciar un juego"
|
||||
"hide_to_tray_on_game_start": "Ocultar Hydra en la bandeja al iniciar un juego",
|
||||
"downloads": "Descargas",
|
||||
"use_native_http_downloader": "Usar descargador HTTP nativo (experimental)",
|
||||
"cannot_change_downloader_while_downloading": "No se puede cambiar esta configuración mientras una descarga está en progreso"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Descarga completada",
|
||||
@@ -675,6 +680,7 @@
|
||||
"blocked_users": "Usuarios bloqueados",
|
||||
"unblock": "Desbloquear",
|
||||
"no_friends_added": "No tenés amistades añadidas",
|
||||
"no_friends_yet": "Aún no has agregado ningún amigo",
|
||||
"view_all": "Ver todo",
|
||||
"load_more": "Cargar más",
|
||||
"loading": "Cargando",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"game_has_no_executable": "Jogo não possui executável selecionado",
|
||||
"sign_in": "Login",
|
||||
"friends": "Amigos",
|
||||
"notifications": "Notificações",
|
||||
"need_help": "Precisa de ajuda?",
|
||||
"favorites": "Favoritos",
|
||||
"playable_button_title": "Mostrar apenas jogos que você pode jogar agora",
|
||||
@@ -163,6 +164,7 @@
|
||||
"repacks_modal_description": "Escolha o repack do jogo que deseja baixar",
|
||||
"select_folder_hint": "Para trocar o diretório padrão, acesse a <0>Tela de Ajustes</0>",
|
||||
"download_now": "Iniciar download",
|
||||
"loading": "Carregando...",
|
||||
"no_shop_details": "Não foi possível obter os detalhes da loja.",
|
||||
"download_options": "Opções de download",
|
||||
"download_path": "Diretório de download",
|
||||
@@ -368,6 +370,7 @@
|
||||
"show_translation": "Mostrar tradução",
|
||||
"show_original_translated_from": "Mostrar original (traduzido do {{language}})",
|
||||
"hide_original": "Ocultar original",
|
||||
"vote_failed": "Falha ao registrar seu voto. Por favor, tente novamente.",
|
||||
"rating_count": "Avaliação",
|
||||
"review_from_blocked_user": "Avaliação de usuário bloqueado",
|
||||
"show": "Mostrar",
|
||||
@@ -390,6 +393,10 @@
|
||||
"completed": "Concluído",
|
||||
"removed": "Cancelado",
|
||||
"cancel": "Cancelar",
|
||||
"cancel_download": "Cancelar download?",
|
||||
"cancel_download_description": "Tem certeza de que deseja cancelar este download? Todos os arquivos baixados serão excluídos.",
|
||||
"keep_downloading": "Não, continuar baixando",
|
||||
"yes_cancel": "Sim, cancelar",
|
||||
"filter": "Filtrar jogos baixados",
|
||||
"remove": "Remover",
|
||||
"downloading_metadata": "Baixando metadados…",
|
||||
@@ -463,6 +470,7 @@
|
||||
"download_sources_synced_successfully": "Fontes de download sincronizadas",
|
||||
"removed_download_source": "Fonte removida",
|
||||
"removed_download_sources": "Fontes removidas",
|
||||
"removed_all_download_sources": "Todas as fontes de download removidas",
|
||||
"cancel_button_confirmation_delete_all_sources": "Não",
|
||||
"confirm_button_confirmation_delete_all_sources": "Sim, excluir tudo",
|
||||
"title_confirmation_delete_all_sources": "Remover todas as fontes de download",
|
||||
@@ -488,6 +496,7 @@
|
||||
"blocked_users": "Usuários bloqueados",
|
||||
"user_unblocked": "Usuário desbloqueado",
|
||||
"enable_achievement_notifications": "Quando uma conquista é desbloqueada",
|
||||
"hydra_cloud": "Hydra Cloud",
|
||||
"launch_minimized": "Iniciar o Hydra minimizado",
|
||||
"disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado",
|
||||
"seed_after_download_complete": "Semear após a conclusão do download",
|
||||
@@ -550,6 +559,7 @@
|
||||
"show_download_speed_in_megabytes": "Exibir taxas de download em megabytes por segundo",
|
||||
"extract_files_by_default": "Extrair arquivos automaticamente após o download",
|
||||
"enable_steam_achievements": "Habilitar busca por conquistas da Steam",
|
||||
"enable_new_download_options_badges": "Mostrar badges de novas opções de download",
|
||||
"enable_achievement_custom_notifications": "Habilitar notificações customizadas de conquistas",
|
||||
"top-left": "Superior esquerdo",
|
||||
"top-center": "Superior central",
|
||||
@@ -567,6 +577,9 @@
|
||||
"test_notification": "Testar notificação",
|
||||
"achievement_sound_volume": "Volume do som de conquista",
|
||||
"select_achievement_sound": "Selecionar som de conquista",
|
||||
"change_achievement_sound": "Alterar som de conquista",
|
||||
"remove_achievement_sound": "Remover som de conquista",
|
||||
"preview_sound": "Reproduzir som",
|
||||
"select": "Selecionar",
|
||||
"preview": "Reproduzir",
|
||||
"remove": "Remover",
|
||||
@@ -574,7 +587,10 @@
|
||||
"notification_preview": "Prévia da Notificação de Conquistas",
|
||||
"enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo",
|
||||
"autoplay_trailers_on_game_page": "Reproduzir trailers automaticamente na página do jogo",
|
||||
"hide_to_tray_on_game_start": "Ocultar o Hydra na bandeja ao iniciar um jogo"
|
||||
"hide_to_tray_on_game_start": "Ocultar o Hydra na bandeja ao iniciar um jogo",
|
||||
"downloads": "Downloads",
|
||||
"use_native_http_downloader": "Usar downloader HTTP nativo (experimental)",
|
||||
"cannot_change_downloader_while_downloading": "Não é possível alterar esta configuração enquanto um download estiver em andamento"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Download concluído",
|
||||
@@ -680,6 +696,7 @@
|
||||
"blocked_users": "Usuários bloqueados",
|
||||
"unblock": "Desbloquear",
|
||||
"no_friends_added": "Você ainda não possui amigos adicionados",
|
||||
"no_friends_yet": "Você ainda não adicionou nenhum amigo",
|
||||
"view_all": "Ver todos",
|
||||
"load_more": "Carregar mais",
|
||||
"loading": "Carregando",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"game_has_no_executable": "Файл запуска игры не выбран",
|
||||
"sign_in": "Войти",
|
||||
"friends": "Друзья",
|
||||
"notifications": "Уведомления",
|
||||
"need_help": "Нужна помощь?",
|
||||
"favorites": "Избранное",
|
||||
"playable_button_title": "Показать только установленные игры.",
|
||||
@@ -115,6 +116,7 @@
|
||||
"downloading": "Загрузка {{title}}… ({{percentage}} завершено) - Окончание {{eta}} - {{speed}}",
|
||||
"calculating_eta": "Загрузка {{title}}… ({{percentage}} завершено) - Подсчёт оставшегося времени…",
|
||||
"checking_files": "Проверка файлов {{title}}… ({{percentage}} завершено)",
|
||||
"extracting": "Распаковка {{title}}… ({{percentage}} завершено)",
|
||||
"installing_common_redist": "{{log}}…",
|
||||
"installation_complete": "Установка завершена",
|
||||
"installation_complete_message": "Библиотеки успешно установлены"
|
||||
@@ -173,6 +175,7 @@
|
||||
"repacks_modal_description": "Выберите репак для загрузки",
|
||||
"select_folder_hint": "Чтобы изменить папку загрузок по умолчанию, откройте <0>Настройки</0>",
|
||||
"download_now": "Загрузить сейчас",
|
||||
"loading": "Загрузка...",
|
||||
"no_shop_details": "Не удалось получить описание",
|
||||
"download_options": "Источники",
|
||||
"download_path": "Путь для загрузок",
|
||||
@@ -208,6 +211,7 @@
|
||||
"danger_zone_section_description": "Вы можете удалить эту игру из вашей библиотеки или файлы скачанные из Hydra",
|
||||
"download_in_progress": "Идёт загрузка",
|
||||
"download_paused": "Загрузка приостановлена",
|
||||
"extracting": "Распаковка",
|
||||
"last_downloaded_option": "Последний вариант загрузки",
|
||||
"new_download_option": "Новый",
|
||||
"create_steam_shortcut": "Создать ярлык Steam",
|
||||
@@ -400,6 +404,10 @@
|
||||
"completed": "Завершено",
|
||||
"removed": "Не скачано",
|
||||
"cancel": "Отмена",
|
||||
"cancel_download": "Отменить загрузку?",
|
||||
"cancel_download_description": "Вы уверены, что хотите отменить эту загрузку? Все загруженные файлы будут удалены.",
|
||||
"keep_downloading": "Нет, продолжить загрузку",
|
||||
"yes_cancel": "Да, отменить",
|
||||
"filter": "Поиск загруженных игр",
|
||||
"remove": "Удалить",
|
||||
"downloading_metadata": "Загрузка метаданных…",
|
||||
@@ -420,7 +428,13 @@
|
||||
"resume_seeding": "Продолжить раздачу",
|
||||
"options": "Управлять",
|
||||
"extract": "Распаковать файлы",
|
||||
"extracting": "Распаковка файлов…"
|
||||
"extracting": "Распаковка файлов…",
|
||||
"delete_archive_title": "Хотите удалить {{fileName}}?",
|
||||
"delete_archive_description": "Файл был успешно распакован и больше не нужен.",
|
||||
"yes": "Да",
|
||||
"no": "Нет",
|
||||
"network": "СЕТЬ",
|
||||
"peak": "ПИК"
|
||||
},
|
||||
"settings": {
|
||||
"downloads_path": "Путь загрузок",
|
||||
@@ -556,6 +570,7 @@
|
||||
"show_download_speed_in_megabytes": "Показать скорость загрузки в мегабайтах в секунду",
|
||||
"extract_files_by_default": "Извлекать файлы по умолчанию после загрузки",
|
||||
"enable_steam_achievements": "Включить поиск достижений Steam",
|
||||
"enable_new_download_options_badges": "Показывать значки новых вариантов загрузки",
|
||||
"achievement_custom_notification_position": "Позиция уведомлений достижений",
|
||||
"top-left": "Верхний левый угол",
|
||||
"top-center": "Верхний центр",
|
||||
@@ -573,6 +588,9 @@
|
||||
"test_notification": "Тестовое уведомление",
|
||||
"achievement_sound_volume": "Громкость звука достижения",
|
||||
"select_achievement_sound": "Выбрать звук достижения",
|
||||
"change_achievement_sound": "Изменить звук достижения",
|
||||
"remove_achievement_sound": "Удалить звук достижения",
|
||||
"preview_sound": "Предпросмотр звука",
|
||||
"select": "Выбрать",
|
||||
"preview": "Предпросмотр",
|
||||
"remove": "Удалить",
|
||||
@@ -580,7 +598,10 @@
|
||||
"notification_preview": "Предварительный просмотр уведомления о достижении",
|
||||
"enable_friend_start_game_notifications": "Когда друг начинает играть в игру",
|
||||
"autoplay_trailers_on_game_page": "Автоматически начинать воспроизведение трейлеров на странице игры",
|
||||
"hide_to_tray_on_game_start": "Скрывать Hydra в трей при запуске игры"
|
||||
"hide_to_tray_on_game_start": "Скрывать Hydra в трей при запуске игры",
|
||||
"downloads": "Загрузки",
|
||||
"use_native_http_downloader": "Использовать встроенный HTTP-загрузчик (экспериментально)",
|
||||
"cannot_change_downloader_while_downloading": "Нельзя изменить эту настройку во время загрузки"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Загрузка завершена",
|
||||
@@ -675,6 +696,7 @@
|
||||
"blocked_users": "Заблокированные пользователи",
|
||||
"unblock": "Разблокировать",
|
||||
"no_friends_added": "Вы ещё не добавили ни одного друга",
|
||||
"no_friends_yet": "Вы ещё не добавили ни одного друга",
|
||||
"view_all": "Показать все",
|
||||
"load_more": "Загрузить еще",
|
||||
"loading": "Загрузка",
|
||||
|
||||
39
src/main/events/helpers/get-directory-size.ts
Normal file
39
src/main/events/helpers/get-directory-size.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
export const getDirectorySize = async (dirPath: string): Promise<number> => {
|
||||
let totalSize = 0;
|
||||
|
||||
try {
|
||||
const stat = await fs.promises.stat(dirPath);
|
||||
|
||||
if (stat.isFile()) {
|
||||
return stat.size;
|
||||
}
|
||||
|
||||
if (!stat.isDirectory()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dirPath, entry.name);
|
||||
|
||||
try {
|
||||
if (entry.isDirectory()) {
|
||||
totalSize += await getDirectorySize(fullPath);
|
||||
} else if (entry.isFile()) {
|
||||
const fileStat = await fs.promises.stat(fullPath);
|
||||
totalSize += fileStat.size;
|
||||
}
|
||||
} catch {
|
||||
// Skip files that can't be accessed
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Path doesn't exist or can't be read
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { logger } from "@main/services";
|
||||
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const deleteArchive = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -11,8 +13,33 @@ const deleteArchive = async (
|
||||
if (fs.existsSync(filePath)) {
|
||||
await fs.promises.unlink(filePath);
|
||||
logger.info(`Deleted archive: ${filePath}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the game that has this archive and clear installer size
|
||||
const normalizedPath = path.normalize(filePath);
|
||||
const downloads = await downloadsSublevel.values().all();
|
||||
|
||||
for (const download of downloads) {
|
||||
if (!download.folderName) continue;
|
||||
|
||||
const downloadPath = path.normalize(
|
||||
path.join(download.downloadPath, download.folderName)
|
||||
);
|
||||
|
||||
if (downloadPath === normalizedPath) {
|
||||
const gameKey = levelKeys.game(download.shop, download.objectId);
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
|
||||
if (game) {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
installerSizeInBytes: null,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(`Failed to delete archive: ${filePath}`, err);
|
||||
|
||||
@@ -5,15 +5,15 @@ import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
import { logger } from "@main/services";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { GameShop } from "@types";
|
||||
import { downloadsSublevel, levelKeys } from "@main/level";
|
||||
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const deleteGameFolder = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
): Promise<void> => {
|
||||
const downloadKey = levelKeys.game(shop, objectId);
|
||||
const download = await downloadsSublevel.get(downloadKey);
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
const download = await downloadsSublevel.get(gameKey);
|
||||
|
||||
if (!download?.folderName) return;
|
||||
|
||||
@@ -49,7 +49,16 @@ const deleteGameFolder = async (
|
||||
|
||||
await deleteFile(folderPath, true);
|
||||
await deleteFile(metaPath);
|
||||
await downloadsSublevel.del(downloadKey);
|
||||
await downloadsSublevel.del(gameKey);
|
||||
|
||||
// Clear installer size from game record
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
if (game) {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
installerSizeInBytes: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("deleteGameFolder", deleteGameFolder);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
import type { LibraryGame } from "@types";
|
||||
import { registerEvent } from "../register-event";
|
||||
import {
|
||||
@@ -28,9 +31,40 @@ const getLibrary = async (): Promise<LibraryGame[]> => {
|
||||
achievements?.unlockedAchievements.length ?? 0;
|
||||
}
|
||||
|
||||
// Verify installer still exists, clear if deleted externally
|
||||
let installerSizeInBytes = game.installerSizeInBytes;
|
||||
if (installerSizeInBytes && download?.folderName) {
|
||||
const installerPath = path.join(
|
||||
download.downloadPath,
|
||||
download.folderName
|
||||
);
|
||||
|
||||
if (!fs.existsSync(installerPath)) {
|
||||
installerSizeInBytes = null;
|
||||
gamesSublevel.put(key, { ...game, installerSizeInBytes: null });
|
||||
}
|
||||
}
|
||||
|
||||
// Verify installed folder still exists, clear if deleted externally
|
||||
let installedSizeInBytes = game.installedSizeInBytes;
|
||||
if (installedSizeInBytes && game.executablePath) {
|
||||
const executableDir = path.dirname(game.executablePath);
|
||||
|
||||
if (!fs.existsSync(executableDir)) {
|
||||
installedSizeInBytes = null;
|
||||
gamesSublevel.put(key, {
|
||||
...game,
|
||||
installerSizeInBytes,
|
||||
installedSizeInBytes: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: key,
|
||||
...game,
|
||||
installerSizeInBytes,
|
||||
installedSizeInBytes,
|
||||
download: download ?? null,
|
||||
unlockedAchievementCount,
|
||||
achievementCount: game.achievementCount ?? 0,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import path from "node:path";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { parseExecutablePath } from "../helpers/parse-executable-path";
|
||||
import { getDirectorySize } from "../helpers/get-directory-size";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
@@ -18,12 +21,29 @@ const updateExecutablePath = async (
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
if (!game) return;
|
||||
|
||||
// Update immediately without size so UI responds fast
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
executablePath: parsedPath,
|
||||
installedSizeInBytes: parsedPath ? game.installedSizeInBytes : null,
|
||||
automaticCloudSync:
|
||||
executablePath === null ? false : game.automaticCloudSync,
|
||||
});
|
||||
|
||||
// Calculate size in background and update later
|
||||
if (parsedPath) {
|
||||
const executableDir = path.dirname(parsedPath);
|
||||
|
||||
getDirectorySize(executableDir).then(async (installedSizeInBytes) => {
|
||||
const currentGame = await gamesSublevel.get(gameKey);
|
||||
if (!currentGame) return;
|
||||
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...currentGame,
|
||||
installedSizeInBytes,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("updateExecutablePath", updateExecutablePath);
|
||||
|
||||
@@ -27,6 +27,7 @@ import { GameFilesManager } from "../game-files-manager";
|
||||
import { HydraDebridClient } from "./hydra-debrid";
|
||||
import { BuzzheavierApi, FuckingFastApi } from "@main/services/hosters";
|
||||
import { JsHttpDownloader } from "./js-http-downloader";
|
||||
import { getDirectorySize } from "@main/events/helpers/get-directory-size";
|
||||
|
||||
export class DownloadManager {
|
||||
private static downloadingGameId: string | null = null;
|
||||
@@ -360,6 +361,24 @@ export class DownloadManager {
|
||||
userPreferences?.seedAfterDownloadComplete
|
||||
);
|
||||
|
||||
// Calculate installer size in background
|
||||
if (download.folderName) {
|
||||
const installerPath = path.join(
|
||||
download.downloadPath,
|
||||
download.folderName
|
||||
);
|
||||
|
||||
getDirectorySize(installerPath).then(async (installerSizeInBytes) => {
|
||||
const currentGame = await gamesSublevel.get(gameId);
|
||||
if (!currentGame) return;
|
||||
|
||||
await gamesSublevel.put(gameId, {
|
||||
...currentGame,
|
||||
installerSizeInBytes,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (download.automaticallyExtract) {
|
||||
this.handleExtraction(download, game);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SevenZip, ExtractionProgress } from "./7zip";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { publishExtractionCompleteNotification } from "./notifications";
|
||||
import { logger } from "./logger";
|
||||
import { getDirectorySize } from "@main/events/helpers/get-directory-size";
|
||||
|
||||
const PROGRESS_THROTTLE_MS = 1000;
|
||||
|
||||
@@ -142,6 +143,17 @@ export class GameFilesManager {
|
||||
extractionProgress: 0,
|
||||
});
|
||||
|
||||
// Calculate and store the installed size
|
||||
if (game && download.folderName) {
|
||||
const gamePath = path.join(download.downloadPath, download.folderName);
|
||||
const installedSizeInBytes = await getDirectorySize(gamePath);
|
||||
|
||||
await gamesSublevel.put(this.gameKey, {
|
||||
...game,
|
||||
installedSizeInBytes,
|
||||
});
|
||||
}
|
||||
|
||||
WindowManager.mainWindow?.webContents.send(
|
||||
"on-extraction-complete",
|
||||
this.shop,
|
||||
|
||||
@@ -427,7 +427,7 @@
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
width: fit-content;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&:focus,
|
||||
@@ -509,6 +509,7 @@
|
||||
&__simple-menu-btn {
|
||||
padding: calc(globals.$spacing-unit);
|
||||
min-height: unset;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__simple-action-btn {
|
||||
@@ -516,6 +517,7 @@
|
||||
min-height: unset;
|
||||
gap: calc(globals.$spacing-unit);
|
||||
min-width: 120px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__progress-wrapper {
|
||||
|
||||
@@ -698,14 +698,6 @@ export function DownloadGroup({
|
||||
|
||||
if (game.download?.progress === 1) {
|
||||
const actions = [
|
||||
{
|
||||
label: t("install"),
|
||||
disabled: deleting,
|
||||
onClick: () => {
|
||||
openGameInstaller(game.shop, game.objectId);
|
||||
},
|
||||
icon: <DownloadIcon />,
|
||||
},
|
||||
{
|
||||
label: t("extract"),
|
||||
disabled: game.download.extracting,
|
||||
|
||||
@@ -84,6 +84,45 @@
|
||||
gap: calc(globals.$spacing-unit);
|
||||
}
|
||||
|
||||
&__size-badges {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border: solid 1px rgba(255, 255, 255, 0.15);
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
min-height: 28px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__size-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
&__size-bar-line {
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0.5),
|
||||
rgba(255, 255, 255, 0.8)
|
||||
);
|
||||
}
|
||||
|
||||
&__size-bar-text {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__logo-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { LibraryGame } from "@types";
|
||||
import { useGameCard } from "@renderer/hooks";
|
||||
import { ClockIcon, AlertFillIcon, TrophyIcon } from "@primer/octicons-react";
|
||||
import { formatBytes } from "@renderer/utils";
|
||||
import {
|
||||
ClockIcon,
|
||||
AlertFillIcon,
|
||||
TrophyIcon,
|
||||
DatabaseIcon,
|
||||
FileZipIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./library-game-card-large.scss";
|
||||
|
||||
interface LibraryGameCardLargeProps {
|
||||
@@ -30,9 +38,53 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({
|
||||
game,
|
||||
onContextMenu,
|
||||
}: Readonly<LibraryGameCardLargeProps>) {
|
||||
const { t } = useTranslation("library");
|
||||
const { formatPlayTime, handleCardClick, handleContextMenuClick } =
|
||||
useGameCard(game, onContextMenu);
|
||||
|
||||
const sizeBars = useMemo(() => {
|
||||
const items: {
|
||||
type: "installer" | "installed";
|
||||
bytes: number;
|
||||
formatted: string;
|
||||
icon: typeof FileZipIcon;
|
||||
tooltipKey: string;
|
||||
}[] = [];
|
||||
|
||||
if (game.installerSizeInBytes) {
|
||||
items.push({
|
||||
type: "installer",
|
||||
bytes: game.installerSizeInBytes,
|
||||
formatted: formatBytes(game.installerSizeInBytes),
|
||||
icon: FileZipIcon,
|
||||
tooltipKey: "installer_size_tooltip",
|
||||
});
|
||||
}
|
||||
|
||||
if (game.installedSizeInBytes) {
|
||||
items.push({
|
||||
type: "installed",
|
||||
bytes: game.installedSizeInBytes,
|
||||
formatted: formatBytes(game.installedSizeInBytes),
|
||||
icon: DatabaseIcon,
|
||||
tooltipKey: "disk_usage_tooltip",
|
||||
});
|
||||
}
|
||||
|
||||
if (items.length === 0) return [];
|
||||
|
||||
// Sort by size descending (larger first)
|
||||
items.sort((a, b) => b.bytes - a.bytes);
|
||||
|
||||
// Calculate proportional widths in pixels (max bar is 80px)
|
||||
const maxBytes = items[0].bytes;
|
||||
const maxWidth = 80;
|
||||
return items.map((item) => ({
|
||||
...item,
|
||||
widthPx: Math.round((item.bytes / maxBytes) * maxWidth),
|
||||
}));
|
||||
}, [game.installerSizeInBytes, game.installedSizeInBytes]);
|
||||
|
||||
const backgroundImage = useMemo(
|
||||
() =>
|
||||
getImageWithCustomPriority(
|
||||
@@ -94,6 +146,27 @@ export const LibraryGameCardLarge = memo(function LibraryGameCardLarge({
|
||||
|
||||
<div className="library-game-card-large__overlay">
|
||||
<div className="library-game-card-large__top-section">
|
||||
{sizeBars.length > 0 && (
|
||||
<div className="library-game-card-large__size-badges">
|
||||
{sizeBars.map((bar) => (
|
||||
<div
|
||||
key={bar.type}
|
||||
className="library-game-card-large__size-bar"
|
||||
title={t(bar.tooltipKey)}
|
||||
>
|
||||
<bar.icon size={11} />
|
||||
<div
|
||||
className={`library-game-card-large__size-bar-line library-game-card-large__size-bar-line--${bar.type}`}
|
||||
style={{ width: `${bar.widthPx}px` }}
|
||||
/>
|
||||
<span className="library-game-card-large__size-bar-text">
|
||||
{bar.formatted}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="library-game-card-large__playtime">
|
||||
{game.hasManuallyUpdatedPlaytime ? (
|
||||
<AlertFillIcon
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
&__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
}
|
||||
|
||||
&__section-header {
|
||||
|
||||
18
src/renderer/src/utils/format-bytes.ts
Normal file
18
src/renderer/src/utils/format-bytes.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Converts a number of bytes into a human-readable string with appropriate units
|
||||
* @param bytes - The number of bytes to format
|
||||
* @param decimals - Number of decimal places (default: 2)
|
||||
* @returns Formatted string like "1.5 GB", "256 MB", etc.
|
||||
*/
|
||||
export const formatBytes = (bytes: number, decimals = 2): string => {
|
||||
if (bytes === 0) return "0 B";
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB", "PB"];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
const index = Math.min(i, sizes.length - 1);
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, index)).toFixed(dm))} ${sizes[index]}`;
|
||||
};
|
||||
1
src/renderer/src/utils/index.ts
Normal file
1
src/renderer/src/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./format-bytes";
|
||||
@@ -64,6 +64,8 @@ export interface Game {
|
||||
automaticCloudSync?: boolean;
|
||||
hasManuallyUpdatedPlaytime?: boolean;
|
||||
newDownloadOptionsCount?: number;
|
||||
installedSizeInBytes?: number | null;
|
||||
installerSizeInBytes?: number | null;
|
||||
}
|
||||
|
||||
export interface Download {
|
||||
|
||||
Reference in New Issue
Block a user