From df54f4eeb65c543fb70004a8ca3082f30e85443f Mon Sep 17 00:00:00 2001 From: john liddell Date: Tue, 6 Oct 2020 22:04:19 -0500 Subject: [PATCH 01/12] Add localizations provided by @Pansa Remove the windows menu bar buttons for mac. --- wowup-electron/src/app/app.component.ts | 2 + .../titlebar/titlebar.component.html | 7 +- .../titlebar/titlebar.component.scss | 4 + .../app/services/electron/electron.service.ts | 5 + wowup-electron/src/assets/i18n/de.json | 122 +++++++++++++++++ wowup-electron/src/assets/i18n/es.json | 122 +++++++++++++++++ wowup-electron/src/assets/i18n/fr.json | 122 +++++++++++++++++ wowup-electron/src/assets/i18n/it.json | 122 +++++++++++++++++ wowup-electron/src/assets/i18n/pt.json | 123 ++++++++++++++++++ wowup-electron/src/assets/i18n/ru.json | 122 +++++++++++++++++ wowup-electron/src/assets/i18n/zh.json | 122 +++++++++++++++++ 11 files changed, 871 insertions(+), 2 deletions(-) create mode 100644 wowup-electron/src/assets/i18n/de.json create mode 100644 wowup-electron/src/assets/i18n/es.json create mode 100644 wowup-electron/src/assets/i18n/fr.json create mode 100644 wowup-electron/src/assets/i18n/it.json create mode 100644 wowup-electron/src/assets/i18n/pt.json create mode 100644 wowup-electron/src/assets/i18n/ru.json create mode 100644 wowup-electron/src/assets/i18n/zh.json diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index e289f878..24a117cb 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -23,6 +23,8 @@ export class AppComponent implements AfterViewInit { private _dialog: MatDialog ) { this.translate.setDefaultLang('en'); + + this.translate.use(this.electronService.locale); } ngAfterViewInit(): void { diff --git a/wowup-electron/src/app/components/titlebar/titlebar.component.html b/wowup-electron/src/app/components/titlebar/titlebar.component.html index 15010270..d50386fb 100644 --- a/wowup-electron/src/app/components/titlebar/titlebar.component.html +++ b/wowup-electron/src/app/components/titlebar/titlebar.component.html @@ -6,7 +6,10 @@
WowUp.io
-
+
+ bug_report +
+
bug_report
@@ -21,4 +24,4 @@
-
\ No newline at end of file + diff --git a/wowup-electron/src/app/components/titlebar/titlebar.component.scss b/wowup-electron/src/app/components/titlebar/titlebar.component.scss index d0d7d0fb..352fb572 100644 --- a/wowup-electron/src/app/components/titlebar/titlebar.component.scss +++ b/wowup-electron/src/app/components/titlebar/titlebar.component.scss @@ -5,6 +5,10 @@ flex-direction: row; align-items: center; position: relative; + + .debug-button { + height: 22px; + } } .titlebar-mac { height: 22px; diff --git a/wowup-electron/src/app/services/electron/electron.service.ts b/wowup-electron/src/app/services/electron/electron.service.ts index 4e77b987..239a92ca 100644 --- a/wowup-electron/src/app/services/electron/electron.service.ts +++ b/wowup-electron/src/app/services/electron/electron.service.ts @@ -30,6 +30,10 @@ export class ElectronService { return !!(window && window.process && window.process.type); } + get locale(): string { + return this.remote.app.getLocale().split('-')[0]; + } + constructor() { // Conditional imports if (!this.isElectron) { @@ -58,6 +62,7 @@ export class ElectronService { this.remote.getCurrentWindow().on('unmaximize', () => { this._windowMaximizedSrc.next(false); }); + } minimizeWindow() { diff --git a/wowup-electron/src/assets/i18n/de.json b/wowup-electron/src/assets/i18n/de.json new file mode 100644 index 00000000..c6da1648 --- /dev/null +++ b/wowup-electron/src/assets/i18n/de.json @@ -0,0 +1,122 @@ +{ + "PAGES": { + "ABOUT": { + "CHANGE_LOG_SECTION_LABEL": "Log ändern", + "TITLE": "WowUp.io", + "WEBSITE_LINK_LABEL": "Schau dir die Webseite an!" + }, + "GET_ADDONS": { + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "REFRESH_BUTTON": "Aktualisieren", + "INSTALL_FROM_URL_BUTTON": "Von URL installieren", + "SEARCH_LABEL": "Suchen", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "AUTHOR_COLUMN_HEADER": "Autor", + "PROVIDER_COLUMN_HEADER": "Anbieter", + "STATUS_COLUMN_HEADER": "Status" + } + }, + "HOME": { + "TITLE": "App funktioniert !", + "GO_TO_DETAIL": "Zum Detail gehen", + "MY_ADDONS_TAB_TITLE": "Meine Addons", + "GET_ADDONS_TAB_TITLE": "Addons abrufen", + "ABOUT_TAB_TITLE": "Über", + "OPTIONS_TAB_TITLE": "Optionen" + }, + "MY_ADDONS": { + "CHECK_UPDATES_BUTTON": "Updates prüfen", + "CHECK_UPDATES_BUTTON_TOOLTIP": "Nach neuesten Addon-Updates suchen", + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "RESCAN_FOLDERS_BUTTON": "Ordner erneut scannen", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Scannen Sie Ihren Client-Ordner nach installierten Addons", + "UPDATE_ALL_BUTTON": "Alle aktualisieren", + "UPDATE_ALL_BUTTON_TOOLTIP": "Alle Addons für diesen Client aktualisieren", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "ADDON_INSTALL_BUTTON": "Installieren", + "ADDON_UPDATE_BUTTON": "Aktualisieren", + "AUTHOR_COLUMN_HEADER": "Autor", + "AUTO_UPDATE_ICON_TOOLTIP": "Auto-Update aktiviert", + "GAME_VERSION_COLUMN_HEADER": "Spielversion", + "LATEST_VERSION_COLUMN_HEADER": "Neueste Version", + "PROVIDER_COLUMN_HEADER": "Anbieter", + "STATUS_COLUMN_HEADER": "Status" + }, + "ADDON_CONTEXT_MENU": { + "IGNORE_ADDON_BUTTON": "Ignorieren", + "AUTO_UPDATE_ADDON_BUTTON": "Automatisches Aktualisieren", + "CHANNEL_SUBMENT_TITLE": "Kanal", + "REINSTALL_ADDON_BUTTON": "Neu installieren", + "REMOVE_ADDON_BUTTON": "Entfernen", + "STABLE_ADDON_CHANNEL": "Stall", + "BETA_ADDON_CHANNEL": "Beta", + "ALPHA_ADDON_CHANNEL": "Alpha" + }, + "COLUMNS_CONTEXT_MENU": { + "TITLE": "Spalten anzeigen" + }, + "UPDATE_ALL_CONTEXT_MENU": { + "UPDATE_RETAIL_CLASSIC_BUTTON": "Retail/Classic aktualisieren", + "UPDATE_ALL_CLIENTS_BUTTON": "Alle Clients aktualisieren" + } + }, + "OPTIONS": { + "APPLICATION": { + "MINIMIZE_ON_CLOSE_LABEL": "Minimieren bei Schliessen", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "Beim Schließen des WowUp-Fensters auf das Systemabschnitt minimieren.", + "TELEMETRY_DESCRIPTION": "Helfen Sie WowUp zu verbessern, indem Sie anonyme Installationsdaten und/oder Fehler senden.", + "TELEMETRY_LABEL": "Telemetrie", + "TITLE": "Applikation" + }, + "DEBUG": { + "DEBUG_DATA_BUTTON": "Debug-Daten dumpen", + "DEBUG_DATA_DESCRIPTION": "Protokollieren Sie Debug-Daten, um mögliche Probleme zu diagnostizieren. Dies finden Sie in Ihrer aktuellen Protokolldatei für Neugierde.", + "DEBUG_DATA_LABEL": "Debug-Daten", + "LOG_FILES_BUTTON": "Log-Dateien anzeigen", + "LOG_FILES_DESCRIPTION": "Öffnen Sie den Ordner, der Ihre letzten Logdateien enthält.", + "LOG_FILES_LABEL": "Log-Dateien", + "TITLE": "Debuggen" + }, + "WOW": { + "AUTO_UPDATE_DESCRIPTION": "Neu installierte Addons werden standardmäßig auf Auto-Update gesetzt", + "AUTO_UPDATE_LABEL": "Auto-Update", + "TITLE": "World of Warcraft", + "DEFAULT_ADDON_CHANNEL_LABEL": "Standard-Addon-Kanal", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Addon-Kanal", + "RESCAN_CLIENTS_BUTTON": "Neu scannen", + "RESCAN_CLIENTS_LABEL": "Installierte World of Warcraft Produkte erneut durchsuchen" + } + } + }, + "DIALOGS": { + "ADDON_DETAILS": { + "VIEW_IN_BROWSER_BUTTON": "Im Browser anzeigen" + }, + "ALERT": { + "POSITIVE_BUTTON": "Okay" + }, + "CONFIRM": { + "NEGATIVE_BUTTON": "Nein", + "POSITIVE_BUTTON": "Ja" + }, + "INSTALL_FROM_URL": { + "ADDON_URL_INPUT_LABEL": "Addon URL", + "ADDON_URL_INPUT_PLACEHOLDER": "Ex. GitHub or WowInterface URL", + "CLOSE_BUTTON": "Schließen", + "IMPORT_BUTTON": "Importieren", + "INSTALL_BUTTON": "Installieren", + "INSTALL_SUCCESS_LABEL": "Installiert!", + "TITLE": "Installieren Sie die Addon-URL", + "DESCRIPTION": "Wenn Sie ein Addon direkt von einer URL installieren möchten, fügen Sie es unten ein, um loszulegen.", + "SUPPORTED_SOURCES": "Unterstützt WowInterface und GitHub*" + }, + "TELEMETRY": { + "DESCRIPTION": "Hilf mir, WowUp zu verbessern, indem du anonyme Installationsdateien und/oder Fehler schickst?", + "NEGATIVE_BUTTON": "Nein Danke", + "POSITIVE_BUTTON": "Sicher!", + "TITLE": "WowUp Telemetrie" + } + } +} diff --git a/wowup-electron/src/assets/i18n/es.json b/wowup-electron/src/assets/i18n/es.json new file mode 100644 index 00000000..d4ca50d3 --- /dev/null +++ b/wowup-electron/src/assets/i18n/es.json @@ -0,0 +1,122 @@ +{ + "PAGES": { + "ABOUT": { + "CHANGE_LOG_SECTION_LABEL": "Registro de Cambios", + "TITLE": "WowUp.io", + "WEBSITE_LINK_LABEL": "Echa un vistazo a la página web!" + }, + "GET_ADDONS": { + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "REFRESH_BUTTON": "Actualizar", + "INSTALL_FROM_URL_BUTTON": "Instalar desde URL", + "SEARCH_LABEL": "Buscar", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "AUTHOR_COLUMN_HEADER": "Autor", + "PROVIDER_COLUMN_HEADER": "Proveedor", + "STATUS_COLUMN_HEADER": "Estado" + } + }, + "HOME": { + "TITLE": "¡La aplicación funciona!", + "GO_TO_DETAIL": "Ir a Detalle", + "MY_ADDONS_TAB_TITLE": "Mis Addons", + "GET_ADDONS_TAB_TITLE": "Obtener Addons", + "ABOUT_TAB_TITLE": "Acerca de", + "OPTIONS_TAB_TITLE": "Opciones" + }, + "MY_ADDONS": { + "CHECK_UPDATES_BUTTON": "Verificar Actualizaciones", + "CHECK_UPDATES_BUTTON_TOOLTIP": "Buscar últimas actualizaciones de addon", + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "RESCAN_FOLDERS_BUTTON": "Volver a escanear carpetas", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Busca por addons instalados", + "UPDATE_ALL_BUTTON": "Actualizar todo", + "UPDATE_ALL_BUTTON_TOOLTIP": "Actualizar todos los addons para este cliente", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "ADDON_INSTALL_BUTTON": "Instalar", + "ADDON_UPDATE_BUTTON": "Actualizar", + "AUTHOR_COLUMN_HEADER": "Autor", + "AUTO_UPDATE_ICON_TOOLTIP": "Actualización automática habilitada", + "GAME_VERSION_COLUMN_HEADER": "Versión del juego", + "LATEST_VERSION_COLUMN_HEADER": "Última Versión", + "PROVIDER_COLUMN_HEADER": "Proveedor", + "STATUS_COLUMN_HEADER": "Situación" + }, + "ADDON_CONTEXT_MENU": { + "IGNORE_ADDON_BUTTON": "Ignorar", + "AUTO_UPDATE_ADDON_BUTTON": "Actualización automática", + "CHANNEL_SUBMENT_TITLE": "Canal", + "REINSTALL_ADDON_BUTTON": "Reinstalar", + "REMOVE_ADDON_BUTTON": "Eliminar", + "STABLE_ADDON_CHANNEL": "Estable", + "BETA_ADDON_CHANNEL": "Beta", + "ALPHA_ADDON_CHANNEL": "Alfa" + }, + "COLUMNS_CONTEXT_MENU": { + "TITLE": "Mostrar Columna" + }, + "UPDATE_ALL_CONTEXT_MENU": { + "UPDATE_RETAIL_CLASSIC_BUTTON": "Actualizar Retail/Clásico", + "UPDATE_ALL_CLIENTS_BUTTON": "Actualizar Todos los Clientes" + } + }, + "OPTIONS": { + "APPLICATION": { + "MINIMIZE_ON_CLOSE_LABEL": "Minimizar al Cerrar", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "Al cerrar la ventana de WowUp, minimízala a la bandeja del sistema.", + "TELEMETRY_DESCRIPTION": "Ayude a mejorar WowUp enviando datos y / o errores de instalación de forma anónima.", + "TELEMETRY_LABEL": "Telemetría", + "TITLE": "Aplicación" + }, + "DEBUG": { + "DEBUG_DATA_BUTTON": "Eliminar registro de depuración de datos", + "DEBUG_DATA_DESCRIPTION": "Registra datos de depuración y ayuda a diagnosticar problemas potenciales. Solo por curiosidad, esto se puede encontrar en su último archivo de registro.", + "DEBUG_DATA_LABEL": "Datos de depuración", + "LOG_FILES_BUTTON": "Mostrar Archivos de Registro", + "LOG_FILES_DESCRIPTION": "Abra la carpeta que contiene sus últimos archivos de registro.", + "LOG_FILES_LABEL": "Archivos de Registro", + "TITLE": "Depuración" + }, + "WOW": { + "AUTO_UPDATE_DESCRIPTION": "Los addons recién instalados se configurarán para actualizarse automáticamente de forma predeterminada", + "AUTO_UPDATE_LABEL": "Actualización automática", + "TITLE": "World of Warcraft", + "DEFAULT_ADDON_CHANNEL_LABEL": "Canal de Addon Estándar", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Canal de Addon", + "RESCAN_CLIENTS_BUTTON": "Volver a escanear", + "RESCAN_CLIENTS_LABEL": "Volver a escanear los productos de World of Warcraft instalados" + } + } + }, + "DIALOGS": { + "ADDON_DETAILS": { + "VIEW_IN_BROWSER_BUTTON": "Ver en el navegador" + }, + "ALERT": { + "POSITIVE_BUTTON": "Aceptar" + }, + "CONFIRM": { + "NEGATIVE_BUTTON": "No", + "POSITIVE_BUTTON": "Si" + }, + "INSTALL_FROM_URL": { + "ADDON_URL_INPUT_LABEL": "Addon URL", + "ADDON_URL_INPUT_PLACEHOLDER": "Por ejemplo, URL de GitHub o WowInterface", + "CLOSE_BUTTON": "Cerrar", + "IMPORT_BUTTON": "Importar", + "INSTALL_BUTTON": "Instalar", + "INSTALL_SUCCESS_LABEL": "Instalado!", + "TITLE": "Instalar desde URL", + "DESCRIPTION": "Si desea instalar un addon directamente desde una URL, péguelo a continuación para comenzar.", + "SUPPORTED_SOURCES": "Soporta WowInterface y GitHub*" + }, + "TELEMETRY": { + "DESCRIPTION": "¿Ayudarme a mejorar WowUp enviando datos y / o errores de instalación de forma anónima?", + "NEGATIVE_BUTTON": "No, gracias", + "POSITIVE_BUTTON": "¡Seguro!", + "TITLE": "Telemetría WowUp" + } + } +} diff --git a/wowup-electron/src/assets/i18n/fr.json b/wowup-electron/src/assets/i18n/fr.json new file mode 100644 index 00000000..f5989414 --- /dev/null +++ b/wowup-electron/src/assets/i18n/fr.json @@ -0,0 +1,122 @@ +{ + "PAGES": { + "ABOUT": { + "CHANGE_LOG_SECTION_LABEL": "Journal des modifications", + "TITLE": "WowUp.io", + "WEBSITE_LINK_LABEL": "Découvrez le site web!" + }, + "GET_ADDONS": { + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "REFRESH_BUTTON": "Rafraîchir", + "INSTALL_FROM_URL_BUTTON": "Installer depuis l'URL", + "SEARCH_LABEL": "Chercher", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "AUTHOR_COLUMN_HEADER": "Auteur", + "PROVIDER_COLUMN_HEADER": "Fournisseur", + "STATUS_COLUMN_HEADER": "Statut" + } + }, + "HOME": { + "TITLE": "L'application fonctionne !", + "GO_TO_DETAIL": "Aller au détail", + "MY_ADDONS_TAB_TITLE": "Mes Addons", + "GET_ADDONS_TAB_TITLE": "Obtenir des Addons", + "ABOUT_TAB_TITLE": "À propos de", + "OPTIONS_TAB_TITLE": "Options" + }, + "MY_ADDONS": { + "CHECK_UPDATES_BUTTON": "Vérifier les mises à jour", + "CHECK_UPDATES_BUTTON_TOOLTIP": "Vérifier les dernières mises à jour des modules complémentaires", + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "RESCAN_FOLDERS_BUTTON": "Re-scanner les dossiers", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Scannez votre dossier client pour trouver des extensions installées", + "UPDATE_ALL_BUTTON": "Tout mettre à jour", + "UPDATE_ALL_BUTTON_TOOLTIP": "Mettre à jour tous les addons pour ce client", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "ADDON_INSTALL_BUTTON": "Installer", + "ADDON_UPDATE_BUTTON": "Mise à jour", + "AUTHOR_COLUMN_HEADER": "Auteur", + "AUTO_UPDATE_ICON_TOOLTIP": "Mise à jour automatique activée", + "GAME_VERSION_COLUMN_HEADER": "Version du jeu", + "LATEST_VERSION_COLUMN_HEADER": "Dernière version", + "PROVIDER_COLUMN_HEADER": "Fournisseur", + "STATUS_COLUMN_HEADER": "Statut" + }, + "ADDON_CONTEXT_MENU": { + "IGNORE_ADDON_BUTTON": "Ignorer", + "AUTO_UPDATE_ADDON_BUTTON": "Mise à jour automatique", + "CHANNEL_SUBMENT_TITLE": "Chaîne", + "REINSTALL_ADDON_BUTTON": "Réinstaller", + "REMOVE_ADDON_BUTTON": "Retirer", + "STABLE_ADDON_CHANNEL": "Écurie", + "BETA_ADDON_CHANNEL": "Bêta", + "ALPHA_ADDON_CHANNEL": "Alphabétisation" + }, + "COLUMNS_CONTEXT_MENU": { + "TITLE": "Afficher les colonnes" + }, + "UPDATE_ALL_CONTEXT_MENU": { + "UPDATE_RETAIL_CLASSIC_BUTTON": "Mise à jour Retail/Classique", + "UPDATE_ALL_CLIENTS_BUTTON": "Mettre à jour tous les clients" + } + }, + "OPTIONS": { + "APPLICATION": { + "MINIMIZE_ON_CLOSE_LABEL": "Minimiser à la fermeture", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "Lorsque vous fermez la fenêtre WowUp, minimisez dans la barre d'état système.", + "TELEMETRY_DESCRIPTION": "Aidez à améliorer WowUp en envoyant des données d'installation et/ou des erreurs anonymes.", + "TELEMETRY_LABEL": "Télémétrie", + "TITLE": "Application" + }, + "DEBUG": { + "DEBUG_DATA_BUTTON": "Dump des données de débogage", + "DEBUG_DATA_DESCRIPTION": "Log les données de débogage pour aider à diagnostiquer les problèmes potentiels. Cela peut être trouvé dans votre dernier fichier journal pour les curieux.", + "DEBUG_DATA_LABEL": "Déboguer les données", + "LOG_FILES_BUTTON": "Afficher les fichiers de log", + "LOG_FILES_DESCRIPTION": "Ouvrez le dossier qui contient vos derniers fichiers journaux.", + "LOG_FILES_LABEL": "Fichiers de log", + "TITLE": "Debug" + }, + "WOW": { + "AUTO_UPDATE_DESCRIPTION": "Les extensions nouvellement installées seront mises à jour automatiquement par défaut", + "AUTO_UPDATE_LABEL": "Mise à jour automatique", + "TITLE": "World of Warcraft", + "DEFAULT_ADDON_CHANNEL_LABEL": "Canal d'extension par défaut", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Canal d'Addon", + "RESCAN_CLIENTS_BUTTON": "Re-scanner", + "RESCAN_CLIENTS_LABEL": "Rescanner les produits de World of Warcraft installés" + } + } + }, + "DIALOGS": { + "ADDON_DETAILS": { + "VIEW_IN_BROWSER_BUTTON": "Voir dans le navigateur" + }, + "ALERT": { + "POSITIVE_BUTTON": "Ok" + }, + "CONFIRM": { + "NEGATIVE_BUTTON": "Non", + "POSITIVE_BUTTON": "Oui" + }, + "INSTALL_FROM_URL": { + "ADDON_URL_INPUT_LABEL": "Addon URL", + "ADDON_URL_INPUT_PLACEHOLDER": "Ex. GitHub or WowInterface URL", + "CLOSE_BUTTON": "Clôturer", + "IMPORT_BUTTON": "Importation", + "INSTALL_BUTTON": "Installer", + "INSTALL_SUCCESS_LABEL": "Installé !", + "TITLE": "Install Addon URL", + "DESCRIPTION": "Si vous voulez installer un addon directement à partir d'une URL collez le ci-dessous pour commencer.", + "SUPPORTED_SOURCES": "Supporte WowInterface et GitHub*" + }, + "TELEMETRY": { + "DESCRIPTION": "Aidez-moi à améliorer WowUp en envoyant des applications anonymes installant des données et/ou des erreurs?", + "NEGATIVE_BUTTON": "Non Merci", + "POSITIVE_BUTTON": "Bien sûr!", + "TITLE": "Télémétrie WowUp" + } + } +} diff --git a/wowup-electron/src/assets/i18n/it.json b/wowup-electron/src/assets/i18n/it.json new file mode 100644 index 00000000..6bfd1340 --- /dev/null +++ b/wowup-electron/src/assets/i18n/it.json @@ -0,0 +1,122 @@ +{ + "PAGES": { + "ABOUT": { + "CHANGE_LOG_SECTION_LABEL": "Registro Delle Modifiche", + "TITLE": "WowUp.io", + "WEBSITE_LINK_LABEL": "Dai un'occhiata al sito!" + }, + "GET_ADDONS": { + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "REFRESH_BUTTON": "Aggiorna", + "INSTALL_FROM_URL_BUTTON": "Installa da URL", + "SEARCH_LABEL": "Cerca", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "AUTHOR_COLUMN_HEADER": "Autore", + "PROVIDER_COLUMN_HEADER": "Provveditore", + "STATUS_COLUMN_HEADER": "Stato" + } + }, + "HOME": { + "TITLE": "L'app funziona !", + "GO_TO_DETAIL": "Vai ai dettagli", + "MY_ADDONS_TAB_TITLE": "I Miei Addons", + "GET_ADDONS_TAB_TITLE": "Ottieni Addons", + "ABOUT_TAB_TITLE": "Informazioni", + "OPTIONS_TAB_TITLE": "Opzioni" + }, + "MY_ADDONS": { + "CHECK_UPDATES_BUTTON": "Controlla Aggiornamenti", + "CHECK_UPDATES_BUTTON_TOOLTIP": "Controlla gli ultimi aggiornamenti dei componenti aggiuntivi", + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "RESCAN_FOLDERS_BUTTON": "Ri-Scansiona Cartelle", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Scansiona la cartella client per gli addons installati", + "UPDATE_ALL_BUTTON": "Aggiorna Tutto", + "UPDATE_ALL_BUTTON_TOOLTIP": "Aggiorna tutti gli addons per questo client", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "ADDON_INSTALL_BUTTON": "Installa", + "ADDON_UPDATE_BUTTON": "Aggiorna", + "AUTHOR_COLUMN_HEADER": "Autore", + "AUTO_UPDATE_ICON_TOOLTIP": "Aggiornamento automatico abilitato", + "GAME_VERSION_COLUMN_HEADER": "Versione Del Gioco", + "LATEST_VERSION_COLUMN_HEADER": "Ultima Versione", + "PROVIDER_COLUMN_HEADER": "Provveditore", + "STATUS_COLUMN_HEADER": "Stato" + }, + "ADDON_CONTEXT_MENU": { + "IGNORE_ADDON_BUTTON": "Ignora", + "AUTO_UPDATE_ADDON_BUTTON": "Aggiornamento Automatico", + "CHANNEL_SUBMENT_TITLE": "Canale", + "REINSTALL_ADDON_BUTTON": "Reinstalla", + "REMOVE_ADDON_BUTTON": "Rimuovi", + "STABLE_ADDON_CHANNEL": "Stabile", + "BETA_ADDON_CHANNEL": "Beta", + "ALPHA_ADDON_CHANNEL": "Alfa" + }, + "COLUMNS_CONTEXT_MENU": { + "TITLE": "Mostra Colonne" + }, + "UPDATE_ALL_CONTEXT_MENU": { + "UPDATE_RETAIL_CLASSIC_BUTTON": "Aggiorna Retail/Classic", + "UPDATE_ALL_CLIENTS_BUTTON": "Aggiorna Tutti I Clienti" + } + }, + "OPTIONS": { + "APPLICATION": { + "MINIMIZE_ON_CLOSE_LABEL": "Minimizza alla chiusura", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "Quando si chiude la finestra WowUp minimizzare nel vassoio di sistema.", + "TELEMETRY_DESCRIPTION": "Aiuta a migliorare WowUp inviando dati di installazione e/o errori anonimi.", + "TELEMETRY_LABEL": "Telemetria", + "TITLE": "Applicazione" + }, + "DEBUG": { + "DEBUG_DATA_BUTTON": "Dump Dati Di Debug", + "DEBUG_DATA_DESCRIPTION": "Registra i dati di debug per aiutare a diagnosticare potenziali problemi. Questo può essere trovato nel tuo ultimo file di log per i curiosi.", + "DEBUG_DATA_LABEL": "Dati Di Debug", + "LOG_FILES_BUTTON": "Mostra File Di Log", + "LOG_FILES_DESCRIPTION": "Aprire la cartella che contiene gli ultimi due file di registro.", + "LOG_FILES_LABEL": "File Di Log", + "TITLE": "Debug" + }, + "WOW": { + "AUTO_UPDATE_DESCRIPTION": "Gli addons di nuova installazione saranno impostati per l'aggiornamento automatico di default", + "AUTO_UPDATE_LABEL": "Aggiornamento Automatico", + "TITLE": "World of Warcraft", + "DEFAULT_ADDON_CHANNEL_LABEL": "Canale Addon Predefinito", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Canale Addon", + "RESCAN_CLIENTS_BUTTON": "Riscansiona", + "RESCAN_CLIENTS_LABEL": "Rescan i prodotti World of Warcraft installati" + } + } + }, + "DIALOGS": { + "ADDON_DETAILS": { + "VIEW_IN_BROWSER_BUTTON": "Visualizza nel browser" + }, + "ALERT": { + "POSITIVE_BUTTON": "Ok" + }, + "CONFIRM": { + "NEGATIVE_BUTTON": "No", + "POSITIVE_BUTTON": "Sì" + }, + "INSTALL_FROM_URL": { + "ADDON_URL_INPUT_LABEL": "Addon URL", + "ADDON_URL_INPUT_PLACEHOLDER": "URL di esempio GitHub o WowInterface", + "CLOSE_BUTTON": "Chiudi", + "IMPORT_BUTTON": "Importa", + "INSTALL_BUTTON": "Installa", + "INSTALL_SUCCESS_LABEL": "Installato!", + "TITLE": "Installa l'URL del componente aggiuntivo", + "DESCRIPTION": "Se si desidera installare un addon direttamente da un URL incollarlo qui sotto per iniziare.", + "SUPPORTED_SOURCES": "Supporta WowInterface e GitHub*" + }, + "TELEMETRY": { + "DESCRIPTION": "Aiutami a migliorare WowUp inviando dati e/o errori di installazione anonimi dell'app?", + "NEGATIVE_BUTTON": "No Grazie", + "POSITIVE_BUTTON": "Certo!", + "TITLE": "Telemetria WowUp" + } + } +} diff --git a/wowup-electron/src/assets/i18n/pt.json b/wowup-electron/src/assets/i18n/pt.json new file mode 100644 index 00000000..bb374a65 --- /dev/null +++ b/wowup-electron/src/assets/i18n/pt.json @@ -0,0 +1,123 @@ +{ + "PAGES": { + "ABOUT": { + "CHANGE_LOG_SECTION_LABEL": "Registro de Alterações", + "TITLE": "WowUp.io", + "WEBSITE_LINK_LABEL": "Conheça o nosso site!" + }, + "GET_ADDONS": { + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "REFRESH_BUTTON": "Atualizar", + "INSTALL_FROM_URL_BUTTON": "Instalar pela URL", + "SEARCH_LABEL": "Procurar", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "AUTHOR_COLUMN_HEADER": "Autor", + "PROVIDER_COLUMN_HEADER": "Provedor", + "STATUS_COLUMN_HEADER": "Estado" + } + }, + "HOME": { + "TITLE": "App funciona!", + "GO_TO_DETAIL": "Ir para Detalhes", + "MY_ADDONS_TAB_TITLE": "Meus Addons", + "GET_ADDONS_TAB_TITLE": "Obtenha Addons", + "ABOUT_TAB_TITLE": "Sobre", + "OPTIONS_TAB_TITLE": "Opções" + }, + "MY_ADDONS": { + "CHECK_UPDATES_BUTTON": "Verificar Atualizações", + "CHECK_UPDATES_BUTTON_TOOLTIP": "Verificar atualizações recentes", + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "RESCAN_FOLDERS_BUTTON": "Re-escanear pastas", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Procura por Addons instalados", + "UPDATE_ALL_BUTTON": "Actualizar todos", + "UPDATE_ALL_BUTTON_TOOLTIP": "Atualizar todos os Addons", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "ADDON_INSTALL_BUTTON": "Instalar", + "ADDON_UPDATE_BUTTON": "Atualizar", + "AUTHOR_COLUMN_HEADER": "Autor", + "AUTO_UPDATE_ICON_TOOLTIP": "Atualização automática habilitada", + "GAME_VERSION_COLUMN_HEADER": "Versão do Jogo", + "LATEST_VERSION_COLUMN_HEADER": "Ultima versão", + "PROVIDER_COLUMN_HEADER": "Provedor", + "STATUS_COLUMN_HEADER": "Estado" + }, + "ADDON_CONTEXT_MENU": { + "IGNORE_ADDON_BUTTON": "Ignorar", + "AUTO_UPDATE_ADDON_BUTTON": "Atualização Automática", + "CHANNEL_SUBMENT_TITLE": "Canal", + "REINSTALL_ADDON_BUTTON": "Reinstalar", + "REMOVE_ADDON_BUTTON": "Remover", + "STABLE_ADDON_CHANNEL": "Estável", + "BETA_ADDON_CHANNEL": "Beta", + "ALPHA_ADDON_CHANNEL": "Alfa" + }, + "COLUMNS_CONTEXT_MENU": { + "TITLE": "Exibir Colunas" + }, + "UPDATE_ALL_CONTEXT_MENU": { + "UPDATE_RETAIL_CLASSIC_BUTTON": "Atualizar Retail/Clássico", + "UPDATE_ALL_CLIENTS_BUTTON": "Atualizar todos os clientes" + } + }, + "OPTIONS": { + "APPLICATION": { + "MINIMIZE_ON_CLOSE_LABEL": "Minimizar ao Fechar", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "Ao fechar a janela do WowUp, minimize para a bandeja do sistema.", + "TELEMETRY_DESCRIPTION": "Ajude a melhorar o WowUp enviando dados e/ou erros de instalação anônimamente.", + "TELEMETRY_LABEL": "Telemetria", + "TITLE": "Aplicativo" + }, + "DEBUG": { + "DEBUG_DATA_BUTTON": "Esvaziar log de depuração de dados", + "DEBUG_DATA_DESCRIPTION": "Registra os dados de depuração e ajuda a diagnosticar problemas potenciais. Apenas por o curiosidade, isso pode ser encontrado em seu último arquivo de registro.", + "DEBUG_DATA_LABEL": "Depurar Dados", + "LOG_FILES_BUTTON": "Mostrar Arquivos de Registro", + "LOG_FILES_DESCRIPTION": "Abre a pasta que contém seus últimos arquivos de registro.", + "LOG_FILES_LABEL": "Arquivos de Registro", + "TITLE": "Depurar" + }, + "WOW": { + "AUTO_UPDATE_DESCRIPTION": "Addons recém-instalados serão definidos para atualizar automáticamente por padrão", + "AUTO_UPDATE_LABEL": "Atualização Automática", + "TITLE": "World of Warcraft", + "DEFAULT_ADDON_CHANNEL_LABEL": "Canal de Addon Padrão", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Canal de Addon", + "RESCAN_CLIENTS_BUTTON": "Re-escanear", + "RESCAN_CLIENTS_LABEL": "Reescanear World of Warcraft instalados" + } + } + }, + "DIALOGS": { + "ADDON_DETAILS": { + "VIEW_IN_BROWSER_BUTTON": "Visualizar no navegador" + }, + "ALERT": { + "POSITIVE_BUTTON": "Ok" + }, + "CONFIRM": { + "NEGATIVE_BUTTON": "Não", + "POSITIVE_BUTTON": "Sim" + }, + "INSTALL_FROM_URL": { + "ADDON_URL_INPUT_LABEL": "Addon URL", + "ADDON_URL_INPUT_PLACEHOLDER": "Ex. GitHub ou WowInterface URL", + "CLOSE_BUTTON": "Fechar", + "IMPORT_BUTTON": "Importar", + "INSTALL_BUTTON": "Instalar", + "INSTALL_SUCCESS_LABEL": "Instalado!", + "TITLE": "Instalar Addon pela URL", + "DESCRIPTION": "Se você deseja instalar um addon diretamente de uma URL, cole-a abaixo para iniciar.", + "SUPPORTED_SOURCES": "Suporta WowInterface e GitHub*" + }, + "TELEMETRY": { + "DESCRIPTION": "Ajude-nos a melhorar o WowUp enviando dados e/ou erros de instalação do aplicativo anônimamente?", + "NEGATIVE_BUTTON": "Não obrigado", + "POSITIVE_BUTTON": "Claro!", + "TITLE": "Telemetria do WowUp" + } + } + } + \ No newline at end of file diff --git a/wowup-electron/src/assets/i18n/ru.json b/wowup-electron/src/assets/i18n/ru.json new file mode 100644 index 00000000..dbf05986 --- /dev/null +++ b/wowup-electron/src/assets/i18n/ru.json @@ -0,0 +1,122 @@ +{ + "PAGES": { + "ABOUT": { + "CHANGE_LOG_SECTION_LABEL": "Журнал изменений", + "TITLE": "WowUp.io", + "WEBSITE_LINK_LABEL": "Посмотрите на сайт!" + }, + "GET_ADDONS": { + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "REFRESH_BUTTON": "Обновить", + "INSTALL_FROM_URL_BUTTON": "Установить из URL", + "SEARCH_LABEL": "Искать", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "AUTHOR_COLUMN_HEADER": "Автор", + "PROVIDER_COLUMN_HEADER": "Поставщик", + "STATUS_COLUMN_HEADER": "Статус" + } + }, + "HOME": { + "TITLE": "Приложение работает !", + "GO_TO_DETAIL": "Детали", + "MY_ADDONS_TAB_TITLE": "Мои аддоны", + "GET_ADDONS_TAB_TITLE": "Получить аддоны", + "ABOUT_TAB_TITLE": "О программе", + "OPTIONS_TAB_TITLE": "Варианты" + }, + "MY_ADDONS": { + "CHECK_UPDATES_BUTTON": "Проверить обновления", + "CHECK_UPDATES_BUTTON_TOOLTIP": "Проверить наличие последних обновлений аддона", + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "RESCAN_FOLDERS_BUTTON": "Пересканировать папки", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Сканирование клиентской папки для установленных аддонов", + "UPDATE_ALL_BUTTON": "Обновить все", + "UPDATE_ALL_BUTTON_TOOLTIP": "Обновить все аддоны для этого клиента", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "ADDON_INSTALL_BUTTON": "Установить", + "ADDON_UPDATE_BUTTON": "Обновить", + "AUTHOR_COLUMN_HEADER": "Автор", + "AUTO_UPDATE_ICON_TOOLTIP": "Автообновление включено", + "GAME_VERSION_COLUMN_HEADER": "Версия игры", + "LATEST_VERSION_COLUMN_HEADER": "Последняя версия", + "PROVIDER_COLUMN_HEADER": "Поставщик", + "STATUS_COLUMN_HEADER": "Статус" + }, + "ADDON_CONTEXT_MENU": { + "IGNORE_ADDON_BUTTON": "Пропустить", + "AUTO_UPDATE_ADDON_BUTTON": "Автообновление", + "CHANNEL_SUBMENT_TITLE": "Канал", + "REINSTALL_ADDON_BUTTON": "Переустановить", + "REMOVE_ADDON_BUTTON": "Удалить", + "STABLE_ADDON_CHANNEL": "Конюшня", + "BETA_ADDON_CHANNEL": "Бета", + "ALPHA_ADDON_CHANNEL": "Альфа" + }, + "COLUMNS_CONTEXT_MENU": { + "TITLE": "Показать колонки" + }, + "UPDATE_ALL_CONTEXT_MENU": { + "UPDATE_RETAIL_CLASSIC_BUTTON": "Обновить Retail/Classic", + "UPDATE_ALL_CLIENTS_BUTTON": "Обновить всех клиентов" + } + }, + "OPTIONS": { + "APPLICATION": { + "MINIMIZE_ON_CLOSE_LABEL": "Свернуть при закрытии", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "При закрытии окна WowUp сворачиваем в системный трей.", + "TELEMETRY_DESCRIPTION": "Помогите улучшить WowUp, отправив анонимные данные об установке и/или ошибках.", + "TELEMETRY_LABEL": "Телеметрия", + "TITLE": "Приложение" + }, + "DEBUG": { + "DEBUG_DATA_BUTTON": "Дамп отладочных данных", + "DEBUG_DATA_DESCRIPTION": "Записывать отладочные данные, чтобы помочь в диагностике потенциальных проблем. Это можно найти в последнем файле журнала для любопытства.", + "DEBUG_DATA_LABEL": "Отладка данных", + "LOG_FILES_BUTTON": "Показать лог-файлы", + "LOG_FILES_DESCRIPTION": "Откройте папку, содержащую последние несколько лог файлов.", + "LOG_FILES_LABEL": "Файлы логов", + "TITLE": "Debug" + }, + "WOW": { + "AUTO_UPDATE_DESCRIPTION": "Новые установленные дополнения будут автоматически обновляться по умолчанию", + "AUTO_UPDATE_LABEL": "Автообновление", + "TITLE": "World of Warcraft", + "DEFAULT_ADDON_CHANNEL_LABEL": "Канал аддона по умолчанию", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Канал дополнения", + "RESCAN_CLIENTS_BUTTON": "Пересканировать", + "RESCAN_CLIENTS_LABEL": "Пересканируйте установленные продукты World of Warcraft" + } + } + }, + "DIALOGS": { + "ADDON_DETAILS": { + "VIEW_IN_BROWSER_BUTTON": "Просмотр в браузере" + }, + "ALERT": { + "POSITIVE_BUTTON": "Окей" + }, + "CONFIRM": { + "NEGATIVE_BUTTON": "Нет", + "POSITIVE_BUTTON": "Да" + }, + "INSTALL_FROM_URL": { + "ADDON_URL_INPUT_LABEL": "Addon URL", + "ADDON_URL_INPUT_PLACEHOLDER": "Пример URL-адреса GitHub или WowInterface", + "CLOSE_BUTTON": "Закрыть", + "IMPORT_BUTTON": "Импорт", + "INSTALL_BUTTON": "Установить", + "INSTALL_SUCCESS_LABEL": "Установлено!", + "TITLE": "Install Addon URL", + "DESCRIPTION": "Если вы хотите установить аддон непосредственно с URL, вставьте его ниже, чтобы начать.", + "SUPPORTED_SOURCES": "Поддерживает WowInterface и GitHub*" + }, + "TELEMETRY": { + "DESCRIPTION": "Помогите мне улучшить WowUp, отправив анонимные данные и/или ошибки в приложении?", + "NEGATIVE_BUTTON": "Нет, спасибо", + "POSITIVE_BUTTON": "Конечно!", + "TITLE": "WowUp Телеметрия" + } + } +} diff --git a/wowup-electron/src/assets/i18n/zh.json b/wowup-electron/src/assets/i18n/zh.json new file mode 100644 index 00000000..6a0ecd70 --- /dev/null +++ b/wowup-electron/src/assets/i18n/zh.json @@ -0,0 +1,122 @@ +{ + "PAGES": { + "ABOUT": { + "CHANGE_LOG_SECTION_LABEL": "更改日志", + "TITLE": "WowUp.io", + "WEBSITE_LINK_LABEL": "查看网站!" + }, + "GET_ADDONS": { + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "REFRESH_BUTTON": "刷新", + "INSTALL_FROM_URL_BUTTON": "从 URL 安装", + "SEARCH_LABEL": "搜索", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "AUTHOR_COLUMN_HEADER": "作者", + "PROVIDER_COLUMN_HEADER": "提供商", + "STATUS_COLUMN_HEADER": "状态" + } + }, + "HOME": { + "TITLE": "应用程序正常工作!", + "GO_TO_DETAIL": "转到详细信息", + "MY_ADDONS_TAB_TITLE": "我的附加组件", + "GET_ADDONS_TAB_TITLE": "获取 Addons", + "ABOUT_TAB_TITLE": "关于的", + "OPTIONS_TAB_TITLE": "备选方案" + }, + "MY_ADDONS": { + "CHECK_UPDATES_BUTTON": "检查更新", + "CHECK_UPDATES_BUTTON_TOOLTIP": "检查最新的插件更新", + "CLIENT_TYPE_SELECT_LABEL": "战术世界", + "RESCAN_FOLDERS_BUTTON": "重新扫描文件夹", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "扫描已安装附加组件的客户端文件夹", + "UPDATE_ALL_BUTTON": "全部更新", + "UPDATE_ALL_BUTTON_TOOLTIP": "更新此客户端的所有插件", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "ADDON_INSTALL_BUTTON": "安装", + "ADDON_UPDATE_BUTTON": "更新", + "AUTHOR_COLUMN_HEADER": "作者", + "AUTO_UPDATE_ICON_TOOLTIP": "自动更新已启用", + "GAME_VERSION_COLUMN_HEADER": "游戏版本", + "LATEST_VERSION_COLUMN_HEADER": "最新版本", + "PROVIDER_COLUMN_HEADER": "提供商", + "STATUS_COLUMN_HEADER": "状态" + }, + "ADDON_CONTEXT_MENU": { + "IGNORE_ADDON_BUTTON": "忽略", + "AUTO_UPDATE_ADDON_BUTTON": "自动更新", + "CHANNEL_SUBMENT_TITLE": "频道", + "REINSTALL_ADDON_BUTTON": "重新安装", + "REMOVE_ADDON_BUTTON": "删除", + "STABLE_ADDON_CHANNEL": "稳定", + "BETA_ADDON_CHANNEL": "测试版", + "ALPHA_ADDON_CHANNEL": "阿尔法" + }, + "COLUMNS_CONTEXT_MENU": { + "TITLE": "显示列" + }, + "UPDATE_ALL_CONTEXT_MENU": { + "UPDATE_RETAIL_CLASSIC_BUTTON": "更新零售/经典", + "UPDATE_ALL_CLIENTS_BUTTON": "更新所有客户端" + } + }, + "OPTIONS": { + "APPLICATION": { + "MINIMIZE_ON_CLOSE_LABEL": "关闭时最小化", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "关闭WowUp窗口时,最小化到系统托盘。", + "TELEMETRY_DESCRIPTION": "通过匿名发送数据和/或安装错误来帮助改进WowUp。", + "TELEMETRY_LABEL": "遥测", + "TITLE": "程序" + }, + "DEBUG": { + "DEBUG_DATA_BUTTON": "转储调试数据", + "DEBUG_DATA_DESCRIPTION": "记录调试数据以帮助诊断潜在的问题。这可以在您最新的日志文件中找到。", + "DEBUG_DATA_LABEL": "调试数据", + "LOG_FILES_BUTTON": "显示日志文件", + "LOG_FILES_DESCRIPTION": "打开包含您最后几个日志文件的文件夹。", + "LOG_FILES_LABEL": "日志文件", + "TITLE": "除错" + }, + "WOW": { + "AUTO_UPDATE_DESCRIPTION": "新安装的插件将默认设置为自动更新", + "AUTO_UPDATE_LABEL": "自动更新", + "TITLE": "World of Warcraft", + "DEFAULT_ADDON_CHANNEL_LABEL": "默认附加组件频道", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "附加组件频道", + "RESCAN_CLIENTS_BUTTON": "重新扫描", + "RESCAN_CLIENTS_LABEL": "重新扫描已安装的Warcraft产品世界" + } + } + }, + "DIALOGS": { + "ADDON_DETAILS": { + "VIEW_IN_BROWSER_BUTTON": "在浏览器中查看" + }, + "ALERT": { + "POSITIVE_BUTTON": "好的" + }, + "CONFIRM": { + "NEGATIVE_BUTTON": "否", + "POSITIVE_BUTTON": "是" + }, + "INSTALL_FROM_URL": { + "ADDON_URL_INPUT_LABEL": "Addon URL", + "ADDON_URL_INPUT_PLACEHOLDER": "例。 GitHub或WowInterface URL", + "CLOSE_BUTTON": "关闭", + "IMPORT_BUTTON": "导入", + "INSTALL_BUTTON": "安装", + "INSTALL_SUCCESS_LABEL": "已安装!", + "TITLE": "通过网址远程安装", + "DESCRIPTION": "如果您想直接从下面的 URL 粘贴中安装一个插件,就开始了。", + "SUPPORTED_SOURCES": "支持Wow界面和 GitHub*" + }, + "TELEMETRY": { + "DESCRIPTION": "通过发送匿名应用安装数据和/或错误来帮助我改进WowUp?", + "NEGATIVE_BUTTON": "不,谢谢!", + "POSITIVE_BUTTON": "当然!", + "TITLE": "WowUp遥测" + } + } +} From 9840e9fe89aaf0beebbfe75f7372ca1c99a67007 Mon Sep 17 00:00:00 2001 From: john liddell Date: Tue, 6 Oct 2020 22:47:52 -0500 Subject: [PATCH 02/12] Fix an issue not waitinfg for addon sync to finish. --- .../src/app/business-objects/my-addons-list-item.ts | 4 ++-- wowup-electron/src/app/pages/my-addons/my-addons.component.ts | 1 + wowup-electron/src/app/services/addons/addon.service.ts | 2 +- wowup-electron/src/app/services/warcraft/warcraft.service.ts | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/wowup-electron/src/app/business-objects/my-addons-list-item.ts b/wowup-electron/src/app/business-objects/my-addons-list-item.ts index 8c94dd70..439e64e7 100644 --- a/wowup-electron/src/app/business-objects/my-addons-list-item.ts +++ b/wowup-electron/src/app/business-objects/my-addons-list-item.ts @@ -48,7 +48,7 @@ export class MyAddonsListItem { return AddonDisplayState.Install; } - if (this.addon.installedVersion != this.addon.latestVersion) { + if (this.addon.installedVersion !== this.addon.latestVersion) { return AddonDisplayState.Update; } @@ -65,7 +65,6 @@ export class MyAddonsListItem { } public onClicked() { - console.log(this.addon.name); this.selected = !this.selected; } @@ -79,6 +78,7 @@ export class MyAddonsListItem { case AddonDisplayState.Install: case AddonDisplayState.Unknown: default: + console.log('Unhandled display state', this.displayState) return ''; } } diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index 95b8efba..e77dee4d 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -374,6 +374,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { }); }, error: (err) => { + console.error(err); this.isBusy = false; this.enableControls = true; } diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 157cf70e..317b1a24 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -306,7 +306,7 @@ export class AddonService { this.updateAddons(addons, newAddons); } - this.syncAddons(clientType, addons); + await this.syncAddons(clientType, addons); return addons; } diff --git a/wowup-electron/src/app/services/warcraft/warcraft.service.ts b/wowup-electron/src/app/services/warcraft/warcraft.service.ts index 67846d12..4f77bcdc 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft.service.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft.service.ts @@ -167,6 +167,10 @@ export class WarcraftService { public async listAddons(clientType: WowClientType) { const addonFolders: AddonFolder[] = []; + if (clientType === WowClientType.None) { + return addonFolders; + } + const addonFolderPath = this.getAddonFolderPath(clientType); // Folder may not exist if no addons have been installed From 55ac8c5def92b25d5ea8c5a9190abdbfd1de5539 Mon Sep 17 00:00:00 2001 From: john liddell Date: Tue, 6 Oct 2020 23:12:42 -0500 Subject: [PATCH 03/12] start the page context text on footer --- .../app/components/footer/footer.component.html | 7 +++++-- .../app/components/footer/footer.component.scss | 5 +++++ .../app/pages/my-addons/my-addons.component.ts | 1 + .../src/app/services/session/session.service.ts | 16 +++++++++++----- wowup-electron/src/styles.scss | 10 ++++++++++ 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/wowup-electron/src/app/components/footer/footer.component.html b/wowup-electron/src/app/components/footer/footer.component.html index 20a2aa3f..e8791796 100644 --- a/wowup-electron/src/app/components/footer/footer.component.html +++ b/wowup-electron/src/app/components/footer/footer.component.html @@ -2,6 +2,9 @@ -
{{sessionService.statusText$ | async}}
-
v{{wowUpService.applicationVersion}}
+

{{sessionService.statusText$ | async}}

+
+

{{sessionService.pageContextText$ | async}}

+

v{{wowUpService.applicationVersion}}

+
\ No newline at end of file diff --git a/wowup-electron/src/app/components/footer/footer.component.scss b/wowup-electron/src/app/components/footer/footer.component.scss index 2a892810..ee251114 100644 --- a/wowup-electron/src/app/components/footer/footer.component.scss +++ b/wowup-electron/src/app/components/footer/footer.component.scss @@ -10,6 +10,11 @@ footer { justify-content: space-between; align-items: center; + + p { + margin: 0; + } + a, img { height: 25px; -webkit-app-region: no-drag; diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index e77dee4d..e8cc2833 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -370,6 +370,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { this.isBusy = false; this.enableControls = true; this._ngZone.run(() => { + this._sessionService.contextText = `${addons.length} addons`; this._displayAddonsSrc.next(this.formatAddons(addons)); }); }, diff --git a/wowup-electron/src/app/services/session/session.service.ts b/wowup-electron/src/app/services/session/session.service.ts index fa9eb67f..18c7b6d5 100644 --- a/wowup-electron/src/app/services/session/session.service.ts +++ b/wowup-electron/src/app/services/session/session.service.ts @@ -16,26 +16,32 @@ export class SessionService { private readonly _selectedClientTypeSrc = new BehaviorSubject( WowClientType.None ); - private readonly _statusTextSrc = new BehaviorSubject(""); - private readonly _selectedHomeTab = new BehaviorSubject(0); + private readonly _pageContextTextSrc = new BehaviorSubject(""); // right side bar text, context to the screen + private readonly _statusTextSrc = new BehaviorSubject(""); // left side bar text, context to the app + private readonly _selectedHomeTabSrc = new BehaviorSubject(0); private _autoUpdateInterval?: number; public readonly selectedClientType$ = this._selectedClientTypeSrc.asObservable(); public readonly statusText$ = this._statusTextSrc.asObservable(); - public readonly selectedHomeTab$ = this._selectedHomeTab.asObservable(); + public readonly selectedHomeTab$ = this._selectedHomeTabSrc.asObservable(); + public readonly pageContextText$ = this._pageContextTextSrc.asObservable(); constructor( private _addonService: AddonService, - private _electronService: ElectronService, private _warcraftService: WarcraftService, private _wowUpService: WowUpService ) { this.loadInitialClientType().pipe(first()).subscribe(); } + public set contextText(text: string){ + this._pageContextTextSrc.next(text); + } + public set selectedHomeTab(tabIndex: number) { - this._selectedHomeTab.next(tabIndex); + this._selectedHomeTabSrc.next(tabIndex); + this.contextText = ''; } public set selectedClientType(clientType: WowClientType) { diff --git a/wowup-electron/src/styles.scss b/wowup-electron/src/styles.scss index 2da20d08..c5ed9364 100644 --- a/wowup-electron/src/styles.scss +++ b/wowup-electron/src/styles.scss @@ -56,6 +56,16 @@ img { margin: 0 !important; } +.mr-1 { + margin-right: .25em !important; +} +.mr-2 { + margin-right: .5em !important; +} +.mr-3 { + margin-right: 1em !important; +} + .no-reg-drag { -webkit-app-region: no-drag; } From 43b2eab0668fe640b845e134c2a955da453adf6b Mon Sep 17 00:00:00 2001 From: jliddev Date: Tue, 6 Oct 2020 23:16:02 -0500 Subject: [PATCH 04/12] put nsis back --- wowup-electron/electron-builder.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wowup-electron/electron-builder.json b/wowup-electron/electron-builder.json index 6946c27f..e84d1f17 100644 --- a/wowup-electron/electron-builder.json +++ b/wowup-electron/electron-builder.json @@ -41,7 +41,7 @@ ], "win": { "icon": "electron-build/icon.ico", - "target": ["portable"] + "target": ["nsis", "portable"] }, "mac": { "icon": "dist/assets/icons", From 8a98c2f26c24409672ae4ab64aebce88b9c50fb5 Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 7 Oct 2020 09:41:25 -0500 Subject: [PATCH 05/12] Fix issue with My Addons Provider column not sorting --- .../src/app/pages/about/about.component.ts | 4 +- .../pages/get-addons/get-addons.component.ts | 4 +- .../src/app/pages/home/home.component.html | 13 +- .../pages/my-addons/my-addons.component.html | 2 +- .../pages/my-addons/my-addons.component.ts | 338 +++++++++++------- .../app/pages/options/options.component.ts | 4 +- 6 files changed, 226 insertions(+), 139 deletions(-) diff --git a/wowup-electron/src/app/pages/about/about.component.ts b/wowup-electron/src/app/pages/about/about.component.ts index f7b40d79..4cc8c726 100644 --- a/wowup-electron/src/app/pages/about/about.component.ts +++ b/wowup-electron/src/app/pages/about/about.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { remote } from 'electron' import { ChangeLog } from '../../models/wowup/change-log'; @@ -12,6 +12,8 @@ import { ElectronService } from 'app/services'; styleUrls: ['./about.component.scss'] }) export class AboutComponent implements OnInit { + + @Input('tabIndex') tabIndex: number; public version = ''; public changeLogs: ChangeLog[] = ChangeLogJson.ChangeLogs; diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index 81d01e24..9e8ca51c 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { Component, Input, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { MatDialog } from "@angular/material/dialog"; import { AddonDetailComponent } from "app/components/addon-detail/addon-detail.component"; import { InstallFromUrlDialogComponent } from "app/components/install-from-url-dialog/install-from-url-dialog.component"; @@ -23,6 +23,8 @@ import * as _ from 'lodash'; styleUrls: ["./get-addons.component.scss"], }) export class GetAddonsComponent implements OnInit, OnDestroy { + + @Input('tabIndex') tabIndex: number; @ViewChild(MatSort) sort: MatSort; diff --git a/wowup-electron/src/app/pages/home/home.component.html b/wowup-electron/src/app/pages/home/home.component.html index 07222778..898307f4 100644 --- a/wowup-electron/src/app/pages/home/home.component.html +++ b/wowup-electron/src/app/pages/home/home.component.html @@ -1,19 +1,18 @@
- + - + - + - + - +
diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index 46a589e9..a17db31e 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -98,7 +98,7 @@ - + {{'PAGES.MY_ADDONS.TABLE.PROVIDER_COLUMN_HEADER' | translate}} diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index e8cc2833..0ef01752 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -1,63 +1,95 @@ -import { Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; -import { WowClientType } from '../../models/warcraft/wow-client-type'; -import { map } from 'rxjs/operators'; -import { from, BehaviorSubject, Subscription, Subject } from 'rxjs'; -import { Addon } from 'app/entities/addon'; -import { WarcraftService } from 'app/services/warcraft/warcraft.service'; -import { AddonService } from 'app/services/addons/addon.service'; -import { SessionService } from 'app/services/session/session.service'; -import { Overlay, OverlayRef } from '@angular/cdk/overlay'; -import { ColumnState } from 'app/models/wowup/column-state'; -import { MatCheckboxChange } from '@angular/material/checkbox'; -import { MyAddonsListItem } from 'app/business-objects/my-addons-list-item'; -import * as _ from 'lodash'; -import { ElectronService } from 'app/services'; -import { AddonDisplayState } from 'app/models/wowup/addon-display-state'; -import { AddonInstallState } from 'app/models/wowup/addon-install-state'; -import { MatMenuTrigger } from '@angular/material/menu'; -import { MatRadioChange } from '@angular/material/radio'; -import { MatDialog } from '@angular/material/dialog'; -import { ConfirmDialogComponent } from 'app/components/confirm-dialog/confirm-dialog.component'; -import { getEnumName } from 'app/utils/enum.utils'; -import { MatTableDataSource } from '@angular/material/table'; -import { MatSort } from '@angular/material/sort'; -import { stringIncludes } from 'app/utils/string.utils'; +import { + Component, + Input, + NgZone, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef, +} from "@angular/core"; +import { WowClientType } from "../../models/warcraft/wow-client-type"; +import { filter, map } from "rxjs/operators"; +import { from, BehaviorSubject, Subscription, Subject } from "rxjs"; +import { Addon } from "app/entities/addon"; +import { WarcraftService } from "app/services/warcraft/warcraft.service"; +import { AddonService } from "app/services/addons/addon.service"; +import { SessionService } from "app/services/session/session.service"; +import { Overlay, OverlayRef } from "@angular/cdk/overlay"; +import { ColumnState } from "app/models/wowup/column-state"; +import { MatCheckboxChange } from "@angular/material/checkbox"; +import { MyAddonsListItem } from "app/business-objects/my-addons-list-item"; +import * as _ from "lodash"; +import { ElectronService } from "app/services"; +import { AddonDisplayState } from "app/models/wowup/addon-display-state"; +import { AddonInstallState } from "app/models/wowup/addon-install-state"; +import { MatMenuTrigger } from "@angular/material/menu"; +import { MatRadioChange } from "@angular/material/radio"; +import { MatDialog } from "@angular/material/dialog"; +import { ConfirmDialogComponent } from "app/components/confirm-dialog/confirm-dialog.component"; +import { getEnumName } from "app/utils/enum.utils"; +import { MatTableDataSource } from "@angular/material/table"; +import { MatSort } from "@angular/material/sort"; +import { stringIncludes } from "app/utils/string.utils"; @Component({ - selector: 'app-my-addons', - templateUrl: './my-addons.component.html', - styleUrls: ['./my-addons.component.scss'] + selector: "app-my-addons", + templateUrl: "./my-addons.component.html", + styleUrls: ["./my-addons.component.scss"], }) export class MyAddonsComponent implements OnInit, OnDestroy { + @Input("tabIndex") tabIndex: number; - @ViewChild('addonContextMenuTrigger') contextMenu: MatMenuTrigger; - @ViewChild('columnContextMenuTrigger') columnContextMenu: MatMenuTrigger; - @ViewChild('updateAllContextMenuTrigger') updateAllContextMenu: MatMenuTrigger; + @ViewChild("addonContextMenuTrigger") contextMenu: MatMenuTrigger; + @ViewChild("columnContextMenuTrigger") columnContextMenu: MatMenuTrigger; + @ViewChild("updateAllContextMenuTrigger") + updateAllContextMenu: MatMenuTrigger; @ViewChild(MatSort) sort: MatSort; - private readonly _displayAddonsSrc = new BehaviorSubject([]); + private readonly _displayAddonsSrc = new BehaviorSubject( + [] + ); private readonly _destroyed$ = new Subject(); private subscriptions: Subscription[] = []; - public spinnerMessage = 'Loading...'; + public spinnerMessage = "Loading..."; - contextMenuPosition = { x: '0px', y: '0px' }; + contextMenuPosition = { x: "0px", y: "0px" }; public dataSource = new MatTableDataSource([]); - public filter = ''; + public filter = ""; columns: ColumnState[] = [ - { name: 'addon.name', display: 'Addon', visible: true }, - { name: 'displayState', display: 'Status', visible: true }, - { name: 'addon.latestVersion', display: 'Latest Version', visible: true, allowToggle: true }, - { name: 'addon.gameVersion', display: 'Game Version', visible: true, allowToggle: true }, - { name: 'addon.provider', display: 'Provider', visible: true, allowToggle: true }, - { name: 'addon.author', display: 'Author', visible: true, allowToggle: true }, - ] + { name: "addon.name", display: "Addon", visible: true }, + { name: "displayState", display: "Status", visible: true }, + { + name: "addon.latestVersion", + display: "Latest Version", + visible: true, + allowToggle: true, + }, + { + name: "addon.gameVersion", + display: "Game Version", + visible: true, + allowToggle: true, + }, + { + name: "addon.providerName", + display: "Provider", + visible: true, + allowToggle: true, + }, + { + name: "addon.author", + display: "Author", + visible: true, + allowToggle: true, + }, + ]; public get displayedColumns(): string[] { - return this.columns.filter(col => col.visible).map(col => col.name); + return this.columns.filter((col) => col.visible).map((col) => col.name); } public selectedClient = WowClientType.None; @@ -76,60 +108,88 @@ export class MyAddonsComponent implements OnInit, OnDestroy { private _ngZone: NgZone, private _dialog: MatDialog ) { + const addonInstalledSubscription = this.addonService.addonInstalled$.subscribe( + (evt) => { + let listItems: MyAddonsListItem[] = [].concat( + this._displayAddonsSrc.value + ); - const addonInstalledSubscription = this.addonService.addonInstalled$.subscribe((evt) => { - console.log('UPDATE') - let listItems: MyAddonsListItem[] = [].concat(this._displayAddonsSrc.value); - const listItemIdx = listItems.findIndex(li => li.addon.id === evt.addon.id); - const listItem = this.createAddonListItem(evt.addon); - listItem.isInstalling = evt.installState === AddonInstallState.Installing || evt.installState === AddonInstallState.Downloading; - listItem.statusText = this.getInstallStateText(evt.installState); - listItem.installProgress = evt.progress; + const listItemIdx = listItems.findIndex( + (li) => li.addon.id === evt.addon.id + ); - if (listItemIdx === -1) { - listItems.push(listItem); - } else { - listItems[listItemIdx] = listItem; + const listItem = this.createAddonListItem(evt.addon); + listItem.isInstalling = + evt.installState === AddonInstallState.Installing || + evt.installState === AddonInstallState.Downloading; + listItem.statusText = this.getInstallStateText(evt.installState); + listItem.installProgress = evt.progress; + + if (listItemIdx === -1) { + listItems.push(listItem); + } else { + listItems[listItemIdx] = listItem; + } + + listItems = this.sortListItems(listItems); + + this._ngZone.run(() => { + this._displayAddonsSrc.next(listItems); + }); } + ); - listItems = this.sortListItems(listItems); - - this._ngZone.run(() => { - this._displayAddonsSrc.next(listItems); - }); - }); - - const addonRemovedSubscription = this.addonService.addonRemoved$ - .subscribe((addonId) => { - const addons: MyAddonsListItem[] = [].concat(this._displayAddonsSrc.value); - const listItemIdx = addons.findIndex(li => li.addon.id === addonId); + const addonRemovedSubscription = this.addonService.addonRemoved$.subscribe( + (addonId) => { + const addons: MyAddonsListItem[] = [].concat( + this._displayAddonsSrc.value + ); + const listItemIdx = addons.findIndex((li) => li.addon.id === addonId); addons.splice(listItemIdx, 1); this._ngZone.run(() => { this._displayAddonsSrc.next(addons); }); - }) + } + ); - const displayAddonSubscription = this._displayAddonsSrc - .subscribe((items: MyAddonsListItem[]) => { + const displayAddonSubscription = this._displayAddonsSrc.subscribe( + (items: MyAddonsListItem[]) => { this.dataSource.data = items; this.dataSource.sortingDataAccessor = _.get; - this.dataSource.filterPredicate = (item: MyAddonsListItem, filter: string) => { - if (stringIncludes(item.addon.name, filter) || stringIncludes(item.addon.latestVersion, filter) || stringIncludes(item.addon.author, filter)) { + this.dataSource.filterPredicate = ( + item: MyAddonsListItem, + filter: string + ) => { + if ( + stringIncludes(item.addon.name, filter) || + stringIncludes(item.addon.latestVersion, filter) || + stringIncludes(item.addon.author, filter) + ) { return true; } return false; - } + }; this.dataSource.sort = this.sort; - }); + } + ); - this.subscriptions.concat(...[addonInstalledSubscription, addonRemovedSubscription, displayAddonSubscription]); + const selectedTabSubscription = this._sessionService.selectedHomeTab$ + .pipe(filter((tabIndex) => this.tabIndex === this.tabIndex)) + .subscribe(() => this.setPageContextText()); + + this.subscriptions.push( + addonInstalledSubscription, + addonRemovedSubscription, + displayAddonSubscription, + selectedTabSubscription + ); } ngOnInit(): void { const selectedClientSubscription = this._sessionService.selectedClientType$ .pipe( - map(clientType => { + map((clientType) => { this.selectedClient = clientType; this.loadAddons(this.selectedClient); }) @@ -140,7 +200,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.subscriptions.forEach(sub => sub.unsubscribe()); + this.subscriptions.forEach((sub) => sub.unsubscribe()); this._destroyed$.next(); this._destroyed$.complete(); } @@ -151,7 +211,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { onRowClicked(event: MouseEvent, row: MyAddonsListItem, index: number) { console.log(row.displayState); - console.log('index clicked: ' + index); + console.log("index clicked: " + index); if (event.ctrlKey) { row.selected = !row.selected; @@ -161,7 +221,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { let listItems: MyAddonsListItem[] = [].concat(this._displayAddonsSrc.value); if (event.shiftKey) { - const startIdx = listItems.findIndex(item => item.selected); + const startIdx = listItems.findIndex((item) => item.selected); listItems.forEach((item, i) => { if (i >= startIdx && i <= index) { item.selected = true; @@ -189,7 +249,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { } onClearFilter(): void { - this.filter = ''; + this.filter = ""; this.filterAddons(); } @@ -197,11 +257,15 @@ export class MyAddonsComponent implements OnInit, OnDestroy { this.enableControls = false; try { - const listItems = _.filter(this._displayAddonsSrc.value, - listItem => listItem.displayState === AddonDisplayState.Install || listItem.displayState === AddonDisplayState.Update); + const listItems = _.filter( + this._displayAddonsSrc.value, + (listItem) => + listItem.displayState === AddonDisplayState.Install || + listItem.displayState === AddonDisplayState.Update + ); for (let listItem of listItems) { - await this.addonService.installAddon(listItem.addon.id,) + await this.addonService.installAddon(listItem.addon.id); } } catch (err) { console.error(err); @@ -211,26 +275,37 @@ export class MyAddonsComponent implements OnInit, OnDestroy { } async onUpdateAllRetailClassic() { - await this.updateAllWithSpinner(WowClientType.Retail, WowClientType.Classic); + await this.updateAllWithSpinner( + WowClientType.Retail, + WowClientType.Classic + ); } async onUpdateAllClients() { - await this.updateAllWithSpinner(WowClientType.Retail, WowClientType.RetailPtr, WowClientType.Beta, WowClientType.ClassicPtr, WowClientType.Classic); + await this.updateAllWithSpinner( + WowClientType.Retail, + WowClientType.RetailPtr, + WowClientType.Beta, + WowClientType.ClassicPtr, + WowClientType.Classic + ); } onHeaderContext(event: MouseEvent) { event.preventDefault(); this.updateContextMenuPosition(event); - this.columnContextMenu.menuData = { 'columns': this.columns.filter(col => col.allowToggle) }; - this.columnContextMenu.menu.focusFirstItem('mouse'); + this.columnContextMenu.menuData = { + columns: this.columns.filter((col) => col.allowToggle), + }; + this.columnContextMenu.menu.focusFirstItem("mouse"); this.columnContextMenu.openMenu(); } onCellContext(event: MouseEvent, listItem: MyAddonsListItem) { event.preventDefault(); this.updateContextMenuPosition(event); - this.contextMenu.menuData = { 'listItem': listItem }; - this.contextMenu.menu.focusFirstItem('mouse'); + this.contextMenu.menuData = { listItem: listItem }; + this.contextMenu.menu.focusFirstItem("mouse"); this.contextMenu.openMenu(); } @@ -257,7 +332,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { public onColumnVisibleChange(event: MatCheckboxChange, column: ColumnState) { console.log(event, column); - const col = this.columns.find(col => col.name === column.name); + const col = this.columns.find((col) => col.name === column.name); col.visible = event.checked; } @@ -265,15 +340,15 @@ export class MyAddonsComponent implements OnInit, OnDestroy { const dialogRef = this._dialog.open(ConfirmDialogComponent, { data: { title: `Start re-scan?`, - message: `Doing a re-scan may reset the addon information and attempt to re-guess what you have installed. This operation can take a moment.` - } + message: `Doing a re-scan may reset the addon information and attempt to re-guess what you have installed. This operation can take a moment.`, + }, }); - dialogRef.afterClosed().subscribe(result => { + dialogRef.afterClosed().subscribe((result) => { if (!result) { return; } - this.loadAddons(this.selectedClient, true) + this.loadAddons(this.selectedClient, true); }); } @@ -285,12 +360,12 @@ export class MyAddonsComponent implements OnInit, OnDestroy { const dialogRef = this._dialog.open(ConfirmDialogComponent, { data: { title: `Uninstall Addon?`, - message: `Are you sure you want to remove ${addon.name}?\nThis will remove all related folders from your World of Warcraft folder.` - } + message: `Are you sure you want to remove ${addon.name}?\nThis will remove all related folders from your World of Warcraft folder.`, + }, }); - dialogRef.afterClosed().subscribe(result => { - console.log('The dialog was closed', result); + dialogRef.afterClosed().subscribe((result) => { + console.log("The dialog was closed", result); if (!result) { return; } @@ -299,9 +374,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { }); } - onInstall() { - - } + onInstall() {} onClickIgnoreAddon(evt: MatCheckboxChange, listItem: MyAddonsListItem) { listItem.addon.isIgnored = evt.checked; @@ -322,7 +395,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { private async updateAllWithSpinner(...clientTypes: WowClientType[]) { this.isBusy = true; - this.spinnerMessage = 'Gathering addons...'; + this.spinnerMessage = "Gathering addons..."; try { let updatedCt = 0; @@ -333,78 +406,87 @@ export class MyAddonsComponent implements OnInit, OnDestroy { // Only care about the ones that need to be updated/installed addons = addons - .map(addon => new MyAddonsListItem(addon)) - .filter(listItem => listItem.needsUpdate || listItem.needsInstall) - .map(listItem => listItem.addon); + .map((addon) => new MyAddonsListItem(addon)) + .filter((listItem) => listItem.needsUpdate || listItem.needsInstall) + .map((listItem) => listItem.addon); this.spinnerMessage = `Updating ${updatedCt}/${addons.length}`; for (let addon of addons) { updatedCt += 1; - this.spinnerMessage = `Updating ${updatedCt}/${addons.length}\n${getEnumName(WowClientType, addon.clientType)}: ${addon.name}`; + this.spinnerMessage = `Updating ${updatedCt}/${ + addons.length + }\n${getEnumName(WowClientType, addon.clientType)}: ${addon.name}`; await this.addonService.installAddon(addon.id); } this.loadAddons(this.selectedClient); } catch (err) { - console.error('Failed to update classic/retail', err); + console.error("Failed to update classic/retail", err); this.isBusy = false; } } private updateContextMenuPosition(event: MouseEvent) { - this.contextMenuPosition.x = event.clientX + 'px'; - this.contextMenuPosition.y = event.clientY + 'px'; + this.contextMenuPosition.x = event.clientX + "px"; + this.contextMenuPosition.y = event.clientY + "px"; } private loadAddons(clientType: WowClientType, rescan = false) { this.isBusy = true; this.enableControls = false; - console.log('Load-addons', clientType); + console.log("Load-addons", clientType); - from(this.addonService.getAddons(clientType, rescan)) - .subscribe({ - next: (addons) => { - this.isBusy = false; - this.enableControls = true; - this._ngZone.run(() => { - this._sessionService.contextText = `${addons.length} addons`; - this._displayAddonsSrc.next(this.formatAddons(addons)); - }); - }, - error: (err) => { - console.error(err); - this.isBusy = false; - this.enableControls = true; - } - }); + from(this.addonService.getAddons(clientType, rescan)).subscribe({ + next: (addons) => { + this.isBusy = false; + this.enableControls = true; + this._ngZone.run(() => { + this._sessionService.contextText = `${addons.length} addons`; + this._displayAddonsSrc.next(this.formatAddons(addons)); + }); + }, + error: (err) => { + console.error(err); + this.isBusy = false; + this.enableControls = true; + }, + }); } private formatAddons(addons: Addon[]): MyAddonsListItem[] { - const listItems = addons.map(addon => this.createAddonListItem(addon)); + const listItems = addons.map((addon) => this.createAddonListItem(addon)); return this.sortListItems(listItems); } private sortListItems(listItems: MyAddonsListItem[]) { - return _.orderBy(listItems, ['displayState', 'addon.name']); + return _.orderBy(listItems, ["displayState", "addon.name"]); } private createAddonListItem(addon: Addon) { const listItem = new MyAddonsListItem(addon); if (!listItem.addon.thumbnailUrl) { - listItem.addon.thumbnailUrl = 'assets/wowup_logo_512np.png'; + listItem.addon.thumbnailUrl = "assets/wowup_logo_512np.png"; } if (!listItem.addon.installedVersion) { - listItem.addon.installedVersion = 'None'; + listItem.addon.installedVersion = "None"; } return listItem; } + private setPageContextText() { + if (!this._displayAddonsSrc.value?.length) { + return; + } + + this._sessionService.contextText = `${this._displayAddonsSrc.value.length} addons`; + } + private getInstallStateText(installState: AddonInstallState) { switch (installState) { case AddonInstallState.Pending: diff --git a/wowup-electron/src/app/pages/options/options.component.ts b/wowup-electron/src/app/pages/options/options.component.ts index cdc62e11..92c24aca 100644 --- a/wowup-electron/src/app/pages/options/options.component.ts +++ b/wowup-electron/src/app/pages/options/options.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, NgZone, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, OnInit, NgZone, OnChanges, SimpleChanges, Input } from '@angular/core'; import { MatSlideToggleChange } from '@angular/material/slide-toggle'; import { WowClientType } from 'app/models/warcraft/wow-client-type'; import { ElectronService } from 'app/services'; @@ -21,6 +21,8 @@ import { MatSelectChange } from '@angular/material/select'; }) export class OptionsComponent implements OnInit, OnChanges { + @Input('tabIndex') tabIndex: number; + public retailLocation = ''; public classicLocation = ''; public retailPtrLocation = ''; From 09cbb7be4fbb7353d87b4472e5afe2cc290b46d3 Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 7 Oct 2020 10:11:19 -0500 Subject: [PATCH 06/12] Min width on game version column --- .../src/app/pages/my-addons/my-addons.component.html | 4 ++-- .../src/app/pages/my-addons/my-addons.component.scss | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index a17db31e..e95f7a3f 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -90,10 +90,10 @@ - + {{'PAGES.MY_ADDONS.TABLE.GAME_VERSION_COLUMN_HEADER' | translate}} - + {{element.addon.gameVersion}} diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.scss b/wowup-electron/src/app/pages/my-addons/my-addons.component.scss index 8685b878..2cd53ec8 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.scss +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.scss @@ -75,6 +75,10 @@ white-space: pre-wrap; } + .game-version-cell { + min-width: 110px; + } + .status-column { display: flex; width: 130px; From a7ded97c11f3ad024d83f64ff26dbfee163d8071 Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 7 Oct 2020 12:20:07 -0500 Subject: [PATCH 07/12] Speed up scanning some Add some status output --- wowup-electron/ipc-events.ts | 1 - wowup-electron/package.json | 2 + .../addon-providers/curse-addon-provider.ts | 41 +- .../curse/curse-folder-scanner.ts | 381 +++++++++--------- wowup-electron/src/app/app.component.ts | 49 ++- wowup-electron/src/app/app.module.ts | 69 ++-- .../components/footer/footer.component.html | 2 +- .../components/footer/footer.component.scss | 1 - .../src/app/pages/home/home.component.ts | 23 +- .../src/app/pages/home/home.module.ts | 2 +- .../services/addons/addon.provider.factory.ts | 68 ++++ .../src/app/services/addons/addon.service.ts | 380 +++++++++++------ .../app/services/session/session.service.ts | 32 +- wowup-electron/src/app/utils/file.utils.ts | 28 +- 14 files changed, 664 insertions(+), 415 deletions(-) create mode 100644 wowup-electron/src/app/services/addons/addon.provider.factory.ts diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index 16be6677..11fccfe5 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -52,7 +52,6 @@ ipcMain.on(CURSE_HASH_FILE_CHANNEL, async (evt, arg: CurseHashFileRequest) => { }); ipcMain.on(LIST_FILES_CHANNEL, async (evt, arg: ListFilesRequest) => { - console.log('list files', arg); const response: ListFilesResponse = { files: [] }; diff --git a/wowup-electron/package.json b/wowup-electron/package.json index 59de589d..c69a0bfb 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -60,6 +60,7 @@ "@ngx-translate/core": "13.0.0", "@ngx-translate/http-loader": "6.0.0", "@types/adm-zip": "0.4.33", + "@types/async": "3.2.3", "@types/globrex": "0.1.0", "@types/jasmine": "3.5.14", "@types/jasminewd2": "2.0.8", @@ -109,6 +110,7 @@ "@angular/material": "10.2.3", "@types/lodash": "4.14.161", "adm-zip": "0.4.16", + "async": "3.2.0", "compare-versions": "3.6.0", "conf": "7.1.2", "electron-dl": "3.0.2", diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 5866bb45..86fef9d1 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -7,6 +7,7 @@ import { map, mergeMap } from "rxjs/operators"; import { CurseFile } from "../models/curse/curse-file"; import * as _ from "lodash"; import * as fp from "lodash/fp"; +import * as path from "path"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { from, Observable, of } from "rxjs"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; @@ -23,6 +24,10 @@ import { CurseScanResult } from "../models/curse/curse-scan-result"; import { CurseFingerprintsResponse } from "app/models/curse/curse-fingerprint-response"; import { CurseMatch } from "app/models/curse/curse-match"; import { v4 as uuidv4 } from "uuid"; +import * as async from "async"; +import { SessionService } from "app/services/session/session.service"; +import { Inject } from "@angular/core"; +import { Session } from "inspector"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; @@ -33,8 +38,9 @@ export class CurseAddonProvider implements AddonProvider { private _httpClient: HttpClient, private _cachingService: CachingService, private _electronService: ElectronService, + private _sessionService: SessionService, private _fileService: FileService - ) { } + ) {} async scan( clientType: WowClientType, @@ -157,30 +163,33 @@ export class CurseAddonProvider implements AddonProvider { return this._httpClient.post(url, addonIds); } - private async getScanResults( + private getScanResults = async ( addonFolders: AddonFolder[] - ): Promise { - const scanResults: CurseScanResult[] = []; + ): Promise => { + // const scanResults: CurseScanResult[] = []; const t1 = Date.now(); // Scan addon folders in parallel for speed!? - for (let folder of addonFolders) { - const scanResult = await new CurseFolderScanner( - this._electronService, - this._fileService - ).scanFolder(folder); - scanResults.push(scanResult); - } + const scanResults = await async.mapLimit( + addonFolders, + 2, + async (folder, callback) => { + this._sessionService.statusText = `Scanning ${folder.name}`; + const scanResult = await new CurseFolderScanner( + this._electronService, + this._fileService + ).scanFolder(folder); + + callback(undefined, scanResult); + } + ); console.log("scan delta", Date.now() - t1); - - // const str = _.orderBy(scanResults, sr => sr.folderName.toLowerCase()) - // .map(sr => `${sr.fingerprint} ${sr.folderName}`).join('\n'); - // console.log(str); + this._sessionService.statusText = ""; return scanResults; - } + }; async getAll( clientType: WowClientType, diff --git a/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts b/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts index e1140f6f..8f151569 100644 --- a/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts +++ b/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts @@ -1,215 +1,234 @@ -import { FileService } from "app/services/files/file.service"; -import * as path from 'path'; -import * as fs from 'fs'; -import * as _ from 'lodash'; +import * as path from "path"; +import * as fs from "fs"; +import * as _ from "lodash"; import { AddonFolder } from "app/models/wowup/addon-folder"; import { ElectronService } from "app/services"; import { CurseHashFileResponse } from "common/models/curse-hash-file-response"; import { CurseHashFileRequest } from "common/models/curse-hash-file-request"; import { CURSE_HASH_FILE_CHANNEL } from "common/constants"; -import { v4 as uuidv4 } from 'uuid'; +import { v4 as uuidv4 } from "uuid"; import { CurseScanResult } from "../../models/curse/curse-scan-result"; -import { from } from "rxjs"; -import { mergeMap } from "rxjs/operators"; +import * as async from "async"; +import { FileService } from "app/services/files/file.service"; export class CurseFolderScanner { + constructor( + private _electronService: ElectronService, + private _fileService: FileService + ) {} - constructor( - private _electronService: ElectronService, - private _fileService: FileService - ) { } + private get tocFileCommentsRegex() { + return /\s*#.*$/gm; + } - private get tocFileCommentsRegex() { - return /\s*#.*$/mg; + private get tocFileIncludesRegex() { + return /^\s*((?:(?/gi; + } + + private get bindingsXmlCommentsRegex() { + return //gs; + } + + async scanFolder(addonFolder: AddonFolder): Promise { + const folderPath = addonFolder.path; + + const files = await this._fileService.listAllFiles(folderPath); + console.log("listAllFiles", folderPath, files.length); + + let matchingFiles = await this.getMatchingFiles(folderPath, files); + matchingFiles = _.sortBy(matchingFiles, (f) => f.toLowerCase()); + + // console.log('matching files', matchingFiles.length) + // const fst = matchingFiles.map(f => f.toLowerCase()).join('\n'); + + const individualFingerprints = await async.mapLimit( + matchingFiles, + 2, + async (path, callback) => { + const normalizedFileHash = await this.computeNormalizedFileHash(path); + callback(undefined, normalizedFileHash); + } + ); + + // const individualFingerprints: number[] = []; + // for (let path of matchingFiles) { + // const normalizedFileHash = await this.computeNormalizedFileHash(path); + // individualFingerprints.push(normalizedFileHash); + // } + + const hashConcat = _.orderBy(individualFingerprints).join(""); + const fingerprint = await this.computeStringHash(hashConcat); + console.log("fingerprint", fingerprint); + + return { + directory: folderPath, + fileCount: matchingFiles.length, + fingerprint, + folderName: path.basename(folderPath), + individualFingerprints, + addonFolder, + }; + } + + private async getMatchingFiles( + folderPath: string, + filePaths: string[] + ): Promise { + const parentDir = path.dirname(folderPath) + path.sep; + const matchingFileList: string[] = []; + const fileInfoList: string[] = []; + for (let filePath of filePaths) { + const input = filePath.toLowerCase().replace(parentDir.toLowerCase(), ""); + + if (this.tocFileRegex.test(input)) { + fileInfoList.push(filePath); + } else if (this.bindingsXmlRegex.test(input)) { + matchingFileList.push(filePath); + } } - private get tocFileIncludesRegex() { - return /^\s*((?:(?/ig; + const dirname = path.dirname(fileInfo); + for (let include of inclusions) { + const fileName = path.join(dirname, include.replace(/\\/g, path.sep)); + await this.processIncludeFile(matchingFileList, fileName); } + } - private get bindingsXmlCommentsRegex() { - return //gs; + private getFileInclusionMatches( + fileInfo: string, + fileContent: string + ): string[] | null { + const ext = path.extname(fileInfo); + switch (ext) { + case ".xml": + return this.matchAll(fileContent, this.bindingsXmlIncludesRegex); + case ".toc": + return this.matchAll(fileContent, this.tocFileIncludesRegex); + default: + return null; } + } - async scanFolder(addonFolder: AddonFolder): Promise { - const folderPath = addonFolder.path; - const files = await this._fileService.listAllFiles(folderPath); - console.log('listAllFiles', folderPath, files.length); + private removeComments(fileInfo: string, fileContent: string): string { + const ext = path.extname(fileInfo); + switch (ext) { + case ".xml": + return fileContent.replace(this.bindingsXmlCommentsRegex, ""); + case ".toc": + return fileContent.replace(this.tocFileCommentsRegex, ""); + default: + return fileContent; + } + } - let matchingFiles = await this.getMatchingFiles(folderPath, files); - matchingFiles = _.sortBy(matchingFiles, f => f.toLowerCase()); + private matchAll(str: string, regex: RegExp): string[] { + const matches: string[] = []; + let currentMatch: RegExpExecArray; + do { + currentMatch = regex.exec(str); + if (currentMatch) { + matches.push(currentMatch[1]); + } + } while (currentMatch); - // console.log('matching files', matchingFiles.length) - // const fst = matchingFiles.map(f => f.toLowerCase()).join('\n'); + return matches; + } - const individualFingerprints: number[] = []; - for (let path of matchingFiles) { - const normalizedFileHash = await this.computeNormalizedFileHash(path); - individualFingerprints.push(normalizedFileHash); + private computeNormalizedFileHash = (filePath: string) => { + return this.computeFileHash(filePath, true); + }; + + private computeFileHash = ( + filePath: string, + normalizeWhitespace: boolean + ) => { + return this.computeHash(filePath, 0, normalizeWhitespace); + }; + + private computeStringHash = (str: string): Promise => { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { + if (arg.error) { + return reject(arg.error); } - const hashConcat = _.orderBy(individualFingerprints).join(''); - const fingerprint = await this.computeStringHash(hashConcat); - console.log('fingerprint', fingerprint); + resolve(arg.fingerprint); + }; - return { - directory: folderPath, - fileCount: matchingFiles.length, - fingerprint, - folderName: path.basename(folderPath), - individualFingerprints, - addonFolder - }; - } + const request: CurseHashFileRequest = { + targetString: str, + targetStringEncoding: "ascii", + responseKey: uuidv4(), + normalizeWhitespace: false, + precomputedLength: 0, + }; - private async getMatchingFiles(folderPath: string, filePaths: string[]): Promise { - const parentDir = path.dirname(folderPath) + path.sep; - const matchingFileList: string[] = []; - const fileInfoList: string[] = []; - for (let filePath of filePaths) { - const input = filePath.toLowerCase().replace(parentDir.toLowerCase(), ''); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); + }); + }; - if (this.tocFileRegex.test(input)) { - fileInfoList.push(filePath); - } else if (this.bindingsXmlRegex.test(input)) { - matchingFileList.push(filePath); - } + private computeHash = ( + filePath: string, + precomputedLength: number = 0, + normalizeWhitespace: boolean = false + ): Promise => { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { + if (arg.error) { + return reject(arg.error); } - // console.log('fileInfoList', fileInfoList.length) - for (let fileInfo of fileInfoList) { - await this.processIncludeFile(matchingFileList, fileInfo); - } + resolve(arg.fingerprint); + }; - return matchingFileList; - } + const request: CurseHashFileRequest = { + responseKey: uuidv4(), + filePath, + normalizeWhitespace, + precomputedLength, + }; - private async processIncludeFile(matchingFileList: string[], fileInfo: string) { - if (!fs.existsSync(fileInfo) || matchingFileList.indexOf(fileInfo) !== -1) { - return; - } - - matchingFileList.push(fileInfo); - - let input = await this._fileService.readFile(fileInfo); - input = this.removeComments(fileInfo, input); - - const inclusions = this.getFileInclusionMatches(fileInfo, input); - if (!inclusions || !inclusions.length) { - return; - } - - const dirname = path.dirname(fileInfo); - for (let include of inclusions) { - const fileName = path.join(dirname, include.replace(/\\/g, path.sep)); - await this.processIncludeFile(matchingFileList, fileName); - } - } - - private getFileInclusionMatches(fileInfo: string, fileContent: string): string[] | null { - const ext = path.extname(fileInfo); - switch (ext) { - case '.xml': - return this.matchAll(fileContent, this.bindingsXmlIncludesRegex); - case '.toc': - return this.matchAll(fileContent, this.tocFileIncludesRegex); - default: - return null; - } - } - - private removeComments(fileInfo: string, fileContent: string): string { - const ext = path.extname(fileInfo); - switch (ext) { - case '.xml': - return fileContent.replace(this.bindingsXmlCommentsRegex, ''); - case '.toc': - return fileContent.replace(this.tocFileCommentsRegex, ''); - default: - return fileContent; - } - } - - private matchAll(str: string, regex: RegExp): string[] { - const matches: string[] = []; - let currentMatch: RegExpExecArray; - do { - currentMatch = regex.exec(str); - if (currentMatch) { - matches.push(currentMatch[1]); - } - } while (currentMatch); - - return matches; - } - - private computeNormalizedFileHash(filePath: string) { - return this.computeFileHash(filePath, true); - } - - private computeFileHash(filePath: string, normalizeWhitespace: boolean) { - return this.computeHash(filePath, 0, normalizeWhitespace); - } - - private computeStringHash(str: string): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { - if (arg.error) { - return reject(arg.error); - } - - resolve(arg.fingerprint); - }; - - const request: CurseHashFileRequest = { - targetString: str, - targetStringEncoding: 'ascii', - responseKey: uuidv4(), - normalizeWhitespace: false, - precomputedLength: 0 - }; - - this._electronService.ipcRenderer.once(request.responseKey, eventHandler); - this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); - }); - } - - private computeHash( - filePath: string, - precomputedLength: number = 0, - normalizeWhitespace: boolean = false - ): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { - if (arg.error) { - return reject(arg.error); - } - - resolve(arg.fingerprint); - }; - - const request: CurseHashFileRequest = { - responseKey: uuidv4(), - filePath, - normalizeWhitespace, - precomputedLength - }; - - this._electronService.ipcRenderer.once(request.responseKey, eventHandler); - this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); - }); - } - -} \ No newline at end of file + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); + }); + }; +} diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index 24a117cb..5a4860a8 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -1,28 +1,34 @@ -import { AfterViewInit, Component } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { TranslateService } from '@ngx-translate/core'; -import { AppConfig } from '../environments/environment'; -import { TelemetryDialogComponent } from './components/telemetry-dialog/telemetry-dialog.component'; -import { ElectronService } from './services'; -import { AnalyticsService } from './services/analytics/analytics.service'; -import { WarcraftService } from './services/warcraft/warcraft.service'; -import { WowUpService } from './services/wowup/wowup.service'; +import { AfterViewInit, Component } from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; +import { TranslateService } from "@ngx-translate/core"; +import { AppConfig } from "../environments/environment"; +import { TelemetryDialogComponent } from "./components/telemetry-dialog/telemetry-dialog.component"; +import { ElectronService } from "./services"; +import { AddonService } from "./services/addons/addon.service"; +import { AnalyticsService } from "./services/analytics/analytics.service"; +import { WarcraftService } from "./services/warcraft/warcraft.service"; +import { WowUpService } from "./services/wowup/wowup.service"; + +const AUTO_UPDATE_PERIOD_MS = 60 * 60 * 1000; // 1 hour @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + selector: "app-root", + templateUrl: "./app.component.html", + styleUrls: ["./app.component.scss"], }) export class AppComponent implements AfterViewInit { + private _autoUpdateInterval?: number; + constructor( private _analyticsService: AnalyticsService, private electronService: ElectronService, private translate: TranslateService, private warcraft: WarcraftService, private _wowUpService: WowUpService, - private _dialog: MatDialog + private _dialog: MatDialog, + private _addonService: AddonService ) { - this.translate.setDefaultLang('en'); + this.translate.setDefaultLang("en"); this.translate.use(this.electronService.locale); } @@ -33,15 +39,26 @@ export class AppComponent implements AfterViewInit { } else { // TODO track startup } + + this.onAutoUpdateInterval(); + this._autoUpdateInterval = window.setInterval( + this.onAutoUpdateInterval, + AUTO_UPDATE_PERIOD_MS + ); } openDialog(): void { const dialogRef = this._dialog.open(TelemetryDialogComponent, { - disableClose: true + disableClose: true, }); - dialogRef.afterClosed().subscribe(result => { + dialogRef.afterClosed().subscribe((result) => { this._wowUpService.telemetryEnabled = result; }); } + + private onAutoUpdateInterval = async () => { + console.log("Auto update"); + const updateCount = await this._addonService.processAutoUpdates(); + }; } diff --git a/wowup-electron/src/app/app.module.ts b/wowup-electron/src/app/app.module.ts index 12ad934f..357a2806 100644 --- a/wowup-electron/src/app/app.module.ts +++ b/wowup-electron/src/app/app.module.ts @@ -1,41 +1,41 @@ -import 'reflect-metadata'; -import '../polyfills'; +import "reflect-metadata"; +import "../polyfills"; -import { BrowserModule } from '@angular/platform-browser'; -import { ErrorHandler, InjectionToken, NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { HttpClientModule, HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http'; -import { SharedModule } from './shared/shared.module'; +import { BrowserModule } from "@angular/platform-browser"; +import { ErrorHandler, NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { + HttpClientModule, + HttpClient, + HTTP_INTERCEPTORS, +} from "@angular/common/http"; +import { SharedModule } from "./shared/shared.module"; -import { AppRoutingModule } from './app-routing.module'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { AppRoutingModule } from "./app-routing.module"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; // NG Translate -import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; -import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { TranslateModule, TranslateLoader } from "@ngx-translate/core"; +import { TranslateHttpLoader } from "@ngx-translate/http-loader"; -import { HomeModule } from './pages/home/home.module'; +import { HomeModule } from "./pages/home/home.module"; -import { AppComponent } from './app.component'; -import { TitlebarComponent } from './components/titlebar/titlebar.component'; -import { FooterComponent } from './components/footer/footer.component'; -import { DefaultHeadersInterceptor } from './interceptors/default-headers.interceptor'; -import { AnalyticsService } from './services/analytics/analytics.service'; -import { DirectiveModule } from './directive.module'; -import { MatModule } from './mat-module'; -import { MatProgressButtonsModule } from 'mat-progress-buttons'; +import { AppComponent } from "./app.component"; +import { TitlebarComponent } from "./components/titlebar/titlebar.component"; +import { FooterComponent } from "./components/footer/footer.component"; +import { DefaultHeadersInterceptor } from "./interceptors/default-headers.interceptor"; +import { AnalyticsService } from "./services/analytics/analytics.service"; +import { DirectiveModule } from "./directive.module"; +import { MatModule } from "./mat-module"; +import { MatProgressButtonsModule } from "mat-progress-buttons"; // AoT requires an exported function for factories export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader { - return new TranslateHttpLoader(http, './assets/i18n/', '.json'); + return new TranslateHttpLoader(http, "./assets/i18n/", ".json"); } @NgModule({ - declarations: [ - AppComponent, - TitlebarComponent, - FooterComponent, - ], + declarations: [AppComponent, TitlebarComponent, FooterComponent], imports: [ BrowserModule, FormsModule, @@ -50,16 +50,19 @@ export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader { loader: { provide: TranslateLoader, useFactory: httpLoaderFactory, - deps: [HttpClient] - } + deps: [HttpClient], + }, }), BrowserAnimationsModule, - ], providers: [ - { provide: HTTP_INTERCEPTORS, useClass: DefaultHeadersInterceptor, multi: true }, - { provide: ErrorHandler, useClass: AnalyticsService } + { + provide: HTTP_INTERCEPTORS, + useClass: DefaultHeadersInterceptor, + multi: true, + }, + { provide: ErrorHandler, useClass: AnalyticsService }, ], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/wowup-electron/src/app/components/footer/footer.component.html b/wowup-electron/src/app/components/footer/footer.component.html index e8791796..888a881c 100644 --- a/wowup-electron/src/app/components/footer/footer.component.html +++ b/wowup-electron/src/app/components/footer/footer.component.html @@ -2,7 +2,7 @@ -

{{sessionService.statusText$ | async}}

+

{{sessionService.statusText$ | async}}

{{sessionService.pageContextText$ | async}}

v{{wowUpService.applicationVersion}}

diff --git a/wowup-electron/src/app/components/footer/footer.component.scss b/wowup-electron/src/app/components/footer/footer.component.scss index ee251114..88723397 100644 --- a/wowup-electron/src/app/components/footer/footer.component.scss +++ b/wowup-electron/src/app/components/footer/footer.component.scss @@ -7,7 +7,6 @@ footer { height: 25px; padding: 0.25em 0.5em; display: flex; - justify-content: space-between; align-items: center; diff --git a/wowup-electron/src/app/pages/home/home.component.ts b/wowup-electron/src/app/pages/home/home.component.ts index ac32dd31..c04c1608 100644 --- a/wowup-electron/src/app/pages/home/home.component.ts +++ b/wowup-electron/src/app/pages/home/home.component.ts @@ -15,21 +15,18 @@ export class HomeComponent implements OnInit { private _sessionService: SessionService, private _warcraftService: WarcraftService ) { - this._warcraftService.installedClientTypes$ - .subscribe((clientTypes) => { - if(clientTypes === undefined){ - this.hasWowClient = false; - this.selectedIndex = 3; - } else { - this.hasWowClient = clientTypes.length > 0; - this.selectedIndex = this.hasWowClient ? 0 : 3; - } - }); + this._warcraftService.installedClientTypes$.subscribe((clientTypes) => { + if (clientTypes === undefined) { + this.hasWowClient = false; + this.selectedIndex = 3; + } else { + this.hasWowClient = clientTypes.length > 0; + this.selectedIndex = this.hasWowClient ? 0 : 3; + } + }); } - ngOnInit(): void { - this._sessionService.appLoaded(); - } + ngOnInit(): void {} onSelectedIndexChange(index: number) { this._sessionService.selectedHomeTab = index; diff --git a/wowup-electron/src/app/pages/home/home.module.ts b/wowup-electron/src/app/pages/home/home.module.ts index d63d93a0..a0dd5ede 100644 --- a/wowup-electron/src/app/pages/home/home.module.ts +++ b/wowup-electron/src/app/pages/home/home.module.ts @@ -43,7 +43,7 @@ import { AddonInstallButtonComponent } from "app/components/addon-install-button InstallFromUrlDialogComponent, AddonDetailComponent, AddonProviderBadgeComponent, - AddonInstallButtonComponent + AddonInstallButtonComponent, ], imports: [ CommonModule, diff --git a/wowup-electron/src/app/services/addons/addon.provider.factory.ts b/wowup-electron/src/app/services/addons/addon.provider.factory.ts new file mode 100644 index 00000000..5ccdeed3 --- /dev/null +++ b/wowup-electron/src/app/services/addons/addon.provider.factory.ts @@ -0,0 +1,68 @@ +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { AddonProvider } from "app/addon-providers/addon-provider"; +import { GitHubAddonProvider } from "app/addon-providers/github-addon-provider"; +import { TukUiAddonProvider } from "app/addon-providers/tukui-addon-provider"; +import { WowInterfaceAddonProvider } from "app/addon-providers/wow-interface-addon-provider"; + +import { CurseAddonProvider } from "../../addon-providers/curse-addon-provider"; +import { CachingService } from "../caching/caching-service"; +import { ElectronService } from "../electron/electron.service"; +import { FileService } from "../files/file.service"; +import { SessionService } from "../session/session.service"; + +@Injectable({ + providedIn: "root", +}) +export class AddonProviderFactory { + constructor( + private _cachingService: CachingService, + private _electronService: ElectronService, + private _httpClient: HttpClient, + private _sessionService: SessionService, + private _fileService: FileService + ) {} + + public getAddonProvider(providerType: T & AddonProvider) { + switch (providerType.name) { + case CurseAddonProvider.name: + return this.createCurseAddonProvider(); + case TukUiAddonProvider.name: + break; + default: + break; + } + } + + public createCurseAddonProvider(): CurseAddonProvider { + return new CurseAddonProvider( + this._httpClient, + this._cachingService, + this._electronService, + this._sessionService, + this._fileService + ); + } + + public createTukUiAddonProvider(): TukUiAddonProvider { + return new TukUiAddonProvider( + this._httpClient, + this._cachingService, + this._electronService, + this._fileService + ); + } + + public createWowInterfaceAddonProvider(): WowInterfaceAddonProvider { + return new WowInterfaceAddonProvider( + this._httpClient, + this._cachingService, + this._electronService, + this._fileService + ); + } + + public createGitHubAddonProvider(): GitHubAddonProvider { + return new GitHubAddonProvider(this._httpClient); + } +} diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 317b1a24..5511c732 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -1,14 +1,14 @@ -import { Injectable } from "@angular/core"; +import { Injectable, Injector } from "@angular/core"; import { AddonStorageService } from "../storage/addon-storage.service"; import { Addon } from "../../entities/addon"; import { WarcraftService } from "../warcraft/warcraft.service"; import { AddonProvider } from "../../addon-providers/addon-provider"; import { CurseAddonProvider } from "../../addon-providers/curse-addon-provider"; import { HttpClient } from "@angular/common/http"; -import * as _ from 'lodash'; -import { v4 as uuidv4 } from 'uuid'; -import * as path from 'path'; -import * as fs from 'fs'; +import * as _ from "lodash"; +import { v4 as uuidv4 } from "uuid"; +import * as path from "path"; +import * as fs from "fs"; import { WowUpApiService } from "../wowup-api/wowup-api.service"; import { WowClientType } from "app/models/warcraft/wow-client-type"; import { PotentialAddon } from "app/models/wowup/potential-addon"; @@ -29,12 +29,12 @@ import { TukUiAddonProvider } from "app/addon-providers/tukui-addon-provider"; import { AddonUpdateEvent } from "app/models/wowup/addon-update-event"; import { WowInterfaceAddonProvider } from "app/addon-providers/wow-interface-addon-provider"; import { GitHubAddonProvider } from "app/addon-providers/github-addon-provider"; +import { AddonProviderFactory } from "./addon.provider.factory"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class AddonService { - private readonly _addonProviders: AddonProvider[]; private readonly _addonInstalledSrc = new Subject(); private readonly _addonRemovedSrc = new Subject(); @@ -44,21 +44,18 @@ export class AddonService { constructor( private _addonStorage: AddonStorageService, - private _cachingService: CachingService, private _warcraftService: WarcraftService, private _wowUpService: WowUpService, - private _wowupApiService: WowUpApiService, private _downloadService: DownloadSevice, - private _electronService: ElectronService, private _fileService: FileService, private _tocService: TocService, - httpClient: HttpClient + private _addonProviderFactory: AddonProviderFactory ) { this._addonProviders = [ - new CurseAddonProvider(httpClient, this._cachingService, this._electronService, this._fileService), - new TukUiAddonProvider(httpClient, this._cachingService, this._electronService, this._fileService), - new WowInterfaceAddonProvider(httpClient, this._cachingService, this._electronService, this._fileService), - new GitHubAddonProvider(httpClient), + this._addonProviderFactory.createCurseAddonProvider(), + this._addonProviderFactory.createTukUiAddonProvider(), + this._addonProviderFactory.createWowInterfaceAddonProvider(), + this._addonProviderFactory.createGitHubAddonProvider(), ]; } @@ -66,41 +63,62 @@ export class AddonService { this._addonStorage.set(addon.id, addon); } - public async search(query: string, clientType: WowClientType): Promise { - var searchTasks = this._addonProviders.map(p => p.searchByQuery(query, clientType)); + public async search( + query: string, + clientType: WowClientType + ): Promise { + var searchTasks = this._addonProviders.map((p) => + p.searchByQuery(query, clientType) + ); var searchResults = await Promise.all(searchTasks); // await _analyticsService.TrackUserAction("Addons", "Search", $"{clientType}|{query}"); const flatResults = searchResults.flat(1); - return _.orderBy(flatResults, 'downloadCount').reverse(); + return _.orderBy(flatResults, "downloadCount").reverse(); } public async installPotentialAddon( potentialAddon: PotentialAddon, clientType: WowClientType, - onUpdate: (installState: AddonInstallState, progress: number) => void = undefined + onUpdate: ( + installState: AddonInstallState, + progress: number + ) => void = undefined ) { - var existingAddon = this._addonStorage.getByExternalId(potentialAddon.externalId, clientType); + var existingAddon = this._addonStorage.getByExternalId( + potentialAddon.externalId, + clientType + ); if (existingAddon) { - throw new Error('Addon already installed'); + throw new Error("Addon already installed"); } - const addon = await this.getAddon(potentialAddon.externalId, potentialAddon.providerName, clientType).toPromise(); + const addon = await this.getAddon( + potentialAddon.externalId, + potentialAddon.providerName, + clientType + ).toPromise(); this._addonStorage.set(addon.id, addon); await this.installAddon(addon.id, onUpdate); } public async processAutoUpdates(): Promise { const autoUpdateAddons = this.getAutoUpdateEnabledAddons(); - const clientTypeGroups = _.groupBy(autoUpdateAddons, addon => addon.clientType); + const clientTypeGroups = _.groupBy( + autoUpdateAddons, + (addon) => addon.clientType + ); let updateCt = 0; for (let clientTypeStr in clientTypeGroups) { const clientType: WowClientType = parseInt(clientTypeStr, 10); // console.log('clientType', clientType, clientTypeGroups[clientType]); - const synced = await this.syncAddons(clientType, clientTypeGroups[clientType]); + const synced = await this.syncAddons( + clientType, + clientTypeGroups[clientType] + ); if (!synced) { continue; } @@ -113,30 +131,33 @@ export class AddonService { try { await this.installAddon(addon.id); updateCt += 1; - } - catch (err) - { + } catch (err) { // _analyticsService.Track(ex, "Failed to install addon"); } } } - + return updateCt; } public canUpdateAddon(addon: Addon) { - return addon.installedVersion && addon.installedVersion !== addon.latestVersion; + return ( + addon.installedVersion && addon.installedVersion !== addon.latestVersion + ); } public getAutoUpdateEnabledAddons() { - return this._addonStorage.queryAll(addon => { + return this._addonStorage.queryAll((addon) => { return addon.isIgnored !== true && addon.autoUpdateEnabled; }); } public async installAddon( addonId: string, - onUpdate: (installState: AddonInstallState, progress: number) => void = undefined + onUpdate: ( + installState: AddonInstallState, + progress: number + ) => void = undefined ) { const addon = this.getAddonById(addonId); if (addon == null || !addon.downloadUrl) { @@ -144,35 +165,56 @@ export class AddonService { } onUpdate?.call(this, AddonInstallState.Downloading, 25); - this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Downloading, progress: 25 }); + this._addonInstalledSrc.next({ + addon, + installState: AddonInstallState.Downloading, + progress: 25, + }); - let downloadedFilePath = ''; - let unzippedDirectory = ''; - let downloadedThumbnail = ''; + let downloadedFilePath = ""; + let unzippedDirectory = ""; + let downloadedThumbnail = ""; try { - downloadedFilePath = await this._downloadService.downloadZipFile(addon.downloadUrl, this._wowUpService.applicationDownloadsFolderPath); + downloadedFilePath = await this._downloadService.downloadZipFile( + addon.downloadUrl, + this._wowUpService.applicationDownloadsFolderPath + ); onUpdate?.call(this, AddonInstallState.Installing, 75); - this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Installing, progress: 75 }); + this._addonInstalledSrc.next({ + addon, + installState: AddonInstallState.Installing, + progress: 75, + }); - const unzipPath = path.join(this._wowUpService.applicationDownloadsFolderPath, uuidv4()); - unzippedDirectory = await this._downloadService.unzipFile(downloadedFilePath, unzipPath); + const unzipPath = path.join( + this._wowUpService.applicationDownloadsFolderPath, + uuidv4() + ); + unzippedDirectory = await this._downloadService.unzipFile( + downloadedFilePath, + unzipPath + ); await this.installUnzippedDirectory(unzippedDirectory, addon.clientType); - const unzippedDirectoryNames = await this._fileService.listDirectories(unzippedDirectory); + const unzippedDirectoryNames = await this._fileService.listDirectories( + unzippedDirectory + ); addon.installedVersion = addon.latestVersion; addon.installedAt = new Date(); - addon.installedFolders = unzippedDirectoryNames.join(','); + addon.installedFolders = unzippedDirectoryNames.join(","); if (!!addon.gameVersion) { - addon.gameVersion = await this.getLatestGameVersion(unzippedDirectory, unzippedDirectoryNames); + addon.gameVersion = await this.getLatestGameVersion( + unzippedDirectory, + unzippedDirectoryNames + ); } this._addonStorage.set(addon.id, addon); // await _analyticsService.TrackUserAction("Addons", "InstallById", $"{addon.ClientType}|{addon.Name}"); - } catch (err) { console.error(err); @@ -188,10 +230,17 @@ export class AddonService { } onUpdate?.call(this, AddonInstallState.Complete, 100); - this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Complete, progress: 100 }); + this._addonInstalledSrc.next({ + addon, + installState: AddonInstallState.Complete, + progress: 100, + }); } - private async getLatestGameVersion(baseDir: string, installedFolders: string[]) { + private async getLatestGameVersion( + baseDir: string, + installedFolders: string[] + ) { const versions = []; for (let dir of installedFolders) { @@ -211,31 +260,44 @@ export class AddonService { versions.push(toc.interface); } - return _.orderBy(versions)[0] || ''; + return _.orderBy(versions)[0] || ""; } - private async installUnzippedDirectory(unzippedDirectory: string, clientType: WowClientType) { - const addonFolderPath = this._warcraftService.getAddonFolderPath(clientType); - const unzippedFolders = await this._fileService.listDirectories(unzippedDirectory); + private async installUnzippedDirectory( + unzippedDirectory: string, + clientType: WowClientType + ) { + const addonFolderPath = this._warcraftService.getAddonFolderPath( + clientType + ); + const unzippedFolders = await this._fileService.listDirectories( + unzippedDirectory + ); for (let unzippedFolder of unzippedFolders) { const unzippedFilePath = path.join(unzippedDirectory, unzippedFolder); const unzipLocation = path.join(addonFolderPath, unzippedFolder); - const unzipBackupLocation = path.join(addonFolderPath, `${unzippedFolder}-bak`); + const unzipBackupLocation = path.join( + addonFolderPath, + `${unzippedFolder}-bak` + ); try { // If the user already has the addon installed, create a temporary backup if (fs.existsSync(unzipLocation)) { - console.log('BACKING UP', unzipLocation); - await this._fileService.renameDirectory(unzipLocation, unzipBackupLocation); + console.log("BACKING UP", unzipLocation); + await this._fileService.renameDirectory( + unzipLocation, + unzipBackupLocation + ); } // Copy contents from unzipped new directory to existing addon folder location - console.log('COPY', unzipLocation); + console.log("COPY", unzipLocation); await this._fileService.copyDirectory(unzippedFilePath, unzipLocation); // If the copy succeeds, delete the backup if (fs.existsSync(unzipBackupLocation)) { - console.log('DELETE BKUP', unzipLocation); + console.log("DELETE BKUP", unzipLocation); await this._fileService.deleteDirectory(unzipBackupLocation); } } catch (err) { @@ -251,7 +313,10 @@ export class AddonService { // Move the backup folder into the original location console.log(`Attempting to roll back ${unzipBackupLocation}`); - await this._fileService.copyDirectory(unzipBackupLocation, unzipLocation); + await this._fileService.copyDirectory( + unzipBackupLocation, + unzipLocation + ); } throw err; @@ -269,27 +334,39 @@ export class AddonService { return await provider.searchByUrl(url, clientType); } - public getAddon(externalId: string, providerName: string, clientType: WowClientType) { - const targetAddonChannel = this._wowUpService.getDefaultAddonChannel(clientType); + public getAddon( + externalId: string, + providerName: string, + clientType: WowClientType + ) { + const targetAddonChannel = this._wowUpService.getDefaultAddonChannel( + clientType + ); const provider = this.getProvider(providerName); - return provider.getById(externalId, clientType) - .pipe( - map(searchResult => { - console.log('SEARCH RES', searchResult); - let latestFile = this.getLatestFile(searchResult, targetAddonChannel); - if (!latestFile) { - latestFile = searchResult.files[0]; - } + return provider.getById(externalId, clientType).pipe( + map((searchResult) => { + console.log("SEARCH RES", searchResult); + let latestFile = this.getLatestFile(searchResult, targetAddonChannel); + if (!latestFile) { + latestFile = searchResult.files[0]; + } - return this.createAddon(latestFile.folders[0], searchResult, latestFile, clientType); - }) - ) + return this.createAddon( + latestFile.folders[0], + searchResult, + latestFile, + clientType + ); + }) + ); } public async removeAddon(addon: Addon) { - const installedDirectories = addon.installedFolders.split(','); + const installedDirectories = addon.installedFolders.split(","); - const addonFolderPath = this._warcraftService.getAddonFolderPath(addon.clientType); + const addonFolderPath = this._warcraftService.getAddonFolderPath( + addon.clientType + ); for (let directory of installedDirectories) { const addonDirectory = path.join(addonFolderPath, directory); await this._fileService.deleteDirectory(addonDirectory); @@ -299,7 +376,10 @@ export class AddonService { this._addonRemovedSrc.next(addon.id); } - public async getAddons(clientType: WowClientType, rescan = false): Promise { + public async getAddons( + clientType: WowClientType, + rescan = false + ): Promise { let addons = this._addonStorage.getAllForClientType(clientType); if (rescan || !addons.length) { const newAddons = await this.scanAddons(clientType); @@ -312,18 +392,28 @@ export class AddonService { } private updateAddons(existingAddons: Addon[], newAddons: Addon[]): Addon[] { - const removedAddons = existingAddons - .filter(existingAddon => !newAddons.some(newAddon => this.addonsMatch(existingAddon, newAddon))); + const removedAddons = existingAddons.filter( + (existingAddon) => + !newAddons.some((newAddon) => this.addonsMatch(existingAddon, newAddon)) + ); - const addedAddons = newAddons - .filter(newAddon => !existingAddons.some(existingAddon => this.addonsMatch(existingAddon, newAddon))); + const addedAddons = newAddons.filter( + (newAddon) => + !existingAddons.some((existingAddon) => + this.addonsMatch(existingAddon, newAddon) + ) + ); - _.remove(existingAddons, addon => removedAddons.some(removedAddon => removedAddon.id === addon.id)); + _.remove(existingAddons, (addon) => + removedAddons.some((removedAddon) => removedAddon.id === addon.id) + ); existingAddons.push(...addedAddons); for (let existingAddon of existingAddons) { - var matchingAddon = newAddons.find(newAddon => this.addonsMatch(newAddon, existingAddon)); + var matchingAddon = newAddons.find((newAddon) => + this.addonsMatch(newAddon, existingAddon) + ); if (!matchingAddon) { continue; } @@ -346,9 +436,11 @@ export class AddonService { } private addonsMatch(addon1: Addon, addon2: Addon): boolean { - return addon1.externalId == addon2.externalId && + return ( + addon1.externalId == addon2.externalId && addon1.providerName == addon2.providerName && - addon1.clientType == addon2.clientType; + addon1.clientType == addon2.clientType + ); } private async syncAddons(clientType: WowClientType, addons: Addon[]) { @@ -358,25 +450,40 @@ export class AddonService { } return true; - } - catch (err) { + } catch (err) { console.error(err); return false; } } - private async syncProviderAddons(clientType: WowClientType, addons: Addon[], addonProvider: AddonProvider) { - const providerAddonIds = this.getExternalIdsForProvider(addonProvider, addons); + private async syncProviderAddons( + clientType: WowClientType, + addons: Addon[], + addonProvider: AddonProvider + ) { + const providerAddonIds = this.getExternalIdsForProvider( + addonProvider, + addons + ); if (!providerAddonIds.length) { return; } - const searchResults = await addonProvider.getAll(clientType, providerAddonIds); + const searchResults = await addonProvider.getAll( + clientType, + providerAddonIds + ); for (let result of searchResults) { - const addon = addons.find(addon => addon.externalId === result?.externalId); + const addon = addons.find( + (addon) => addon.externalId === result?.externalId + ); const latestFile = this.getLatestFile(result, addon?.channelType); - if (!result || !latestFile || latestFile.version === addon.latestVersion) { + if ( + !result || + !latestFile || + latestFile.version === addon.latestVersion + ) { continue; } @@ -396,37 +503,57 @@ export class AddonService { } } - private getExternalIdsForProvider(addonProvider: AddonProvider, addons: Addon[]): string[] { - return addons.filter(addon => addon.providerName === addonProvider.name) - .map(addon => addon.externalId); + private getExternalIdsForProvider( + addonProvider: AddonProvider, + addons: Addon[] + ): string[] { + return addons + .filter((addon) => addon.providerName === addonProvider.name) + .map((addon) => addon.externalId); } private async scanAddons(clientType: WowClientType): Promise { const addonFolders = await this._warcraftService.listAddons(clientType); for (let provider of this._addonProviders) { try { - const validFolders = addonFolders.filter(af => !af.matchingAddon && af.toc) - await provider.scan(clientType, this._wowUpService.getDefaultAddonChannel(clientType), validFolders); + const validFolders = addonFolders.filter( + (af) => !af.matchingAddon && af.toc + ); + await provider.scan( + clientType, + this._wowUpService.getDefaultAddonChannel(clientType), + validFolders + ); } catch (err) { console.log(err); } } - const matchedAddonFolders = addonFolders.filter(addonFolder => !!addonFolder.matchingAddon); - const matchedGroups = _.groupBy(matchedAddonFolders, addonFolder => `${addonFolder.matchingAddon.providerName}${addonFolder.matchingAddon.externalId}`); + const matchedAddonFolders = addonFolders.filter( + (addonFolder) => !!addonFolder.matchingAddon + ); + const matchedGroups = _.groupBy( + matchedAddonFolders, + (addonFolder) => + `${addonFolder.matchingAddon.providerName}${addonFolder.matchingAddon.externalId}` + ); console.log(Object.keys(matchedGroups)); - console.log(matchedGroups['Curse2382']) - return Object.values(matchedGroups).map(value => value[0].matchingAddon); + console.log(matchedGroups["Curse2382"]); + + return Object.values(matchedGroups).map((value) => value[0].matchingAddon); } - public getFeaturedAddons(clientType: WowClientType): Observable { - return forkJoin(this._addonProviders.map(p => p.getFeaturedAddons(clientType))) - .pipe( - map(results => { - return _.orderBy(results.flat(1), ['downloadCount']).reverse(); - }) - ); + public getFeaturedAddons( + clientType: WowClientType + ): Observable { + return forkJoin( + this._addonProviders.map((p) => p.getFeaturedAddons(clientType)) + ).pipe( + map((results) => { + return _.orderBy(results.flat(1), ["downloadCount"]).reverse(); + }) + ); } public isInstalled(externalId: string, clientType: WowClientType) { @@ -434,17 +561,19 @@ export class AddonService { } private getProvider(providerName: string) { - return this._addonProviders.find(provider => provider.name === providerName); + return this._addonProviders.find( + (provider) => provider.name === providerName + ); } private getAllStoredAddons(clientType: WowClientType) { const addons: Addon[] = []; - this._addonStorage.query(store => { + this._addonStorage.query((store) => { for (const result of store) { addons.push(result[1] as Addon); } - }) + }); return addons; } @@ -452,7 +581,7 @@ export class AddonService { private async getLocalAddons(clientType: WowClientType): Promise { const addonFolders = await this._warcraftService.listAddons(clientType); const addons: Addon[] = []; - console.log('addonFolders', addonFolders); + console.log("addonFolders", addonFolders); for (const folder of addonFolders) { try { @@ -461,7 +590,6 @@ export class AddonService { if (folder.toc.curseProjectId) { addon = await this.getCurseAddonById(folder, clientType); } else { - } if (!addon) { @@ -478,22 +606,42 @@ export class AddonService { } private getAddonProvider(addonUri: URL): AddonProvider { - return this._addonProviders.find(provider => provider.isValidAddonUri(addonUri)); + return this._addonProviders.find((provider) => + provider.isValidAddonUri(addonUri) + ); } private async getCurseAddonById( addonFolder: AddonFolder, clientType: WowClientType ) { - const curseProvider = this._addonProviders.find(p => p instanceof CurseAddonProvider); - const searchResult = await curseProvider.getById(addonFolder.toc.curseProjectId, clientType).toPromise(); - const latestFile = this.getLatestFile(searchResult, AddonChannelType.Stable); - return this.createAddon(addonFolder.name, searchResult, latestFile, clientType); + const curseProvider = this._addonProviders.find( + (p) => p instanceof CurseAddonProvider + ); + const searchResult = await curseProvider + .getById(addonFolder.toc.curseProjectId, clientType) + .toPromise(); + const latestFile = this.getLatestFile( + searchResult, + AddonChannelType.Stable + ); + return this.createAddon( + addonFolder.name, + searchResult, + latestFile, + clientType + ); } - private getLatestFile(searchResult: AddonSearchResult, channelType: AddonChannelType): AddonSearchResultFile { - let files = _.filter(searchResult.files, (f: AddonSearchResultFile) => f.channelType <= channelType); - files = _.orderBy(files, ['releaseDate']).reverse(); + private getLatestFile( + searchResult: AddonSearchResult, + channelType: AddonChannelType + ): AddonSearchResultFile { + let files = _.filter( + searchResult.files, + (f: AddonSearchResultFile) => f.channelType <= channelType + ); + files = _.orderBy(files, ["releaseDate"]).reverse(); return _.first(files); } @@ -525,4 +673,4 @@ export class AddonService { autoUpdateEnabled: this._wowUpService.getDefaultAutoUpdate(clientType), }; } -} \ No newline at end of file +} diff --git a/wowup-electron/src/app/services/session/session.service.ts b/wowup-electron/src/app/services/session/session.service.ts index 18c7b6d5..ae7d07b6 100644 --- a/wowup-electron/src/app/services/session/session.service.ts +++ b/wowup-electron/src/app/services/session/session.service.ts @@ -1,14 +1,10 @@ -import { Injectable } from "@angular/core"; +import { Injectable, InjectionToken } from "@angular/core"; import { WowClientType } from "app/models/warcraft/wow-client-type"; import { BehaviorSubject } from "rxjs"; import { filter, first, map } from "rxjs/operators"; -import { AddonService } from "../addons/addon.service"; -import { ElectronService } from "../electron/electron.service"; import { WarcraftService } from "../warcraft/warcraft.service"; import { WowUpService } from "../wowup/wowup.service"; -const AUTO_UPDATE_PERIOD_MS = 60 * 60 * 1000; // 1 hour - @Injectable({ providedIn: "root", }) @@ -20,28 +16,29 @@ export class SessionService { private readonly _statusTextSrc = new BehaviorSubject(""); // left side bar text, context to the app private readonly _selectedHomeTabSrc = new BehaviorSubject(0); - private _autoUpdateInterval?: number; - public readonly selectedClientType$ = this._selectedClientTypeSrc.asObservable(); public readonly statusText$ = this._statusTextSrc.asObservable(); public readonly selectedHomeTab$ = this._selectedHomeTabSrc.asObservable(); public readonly pageContextText$ = this._pageContextTextSrc.asObservable(); constructor( - private _addonService: AddonService, private _warcraftService: WarcraftService, private _wowUpService: WowUpService ) { this.loadInitialClientType().pipe(first()).subscribe(); } - public set contextText(text: string){ + public set contextText(text: string) { this._pageContextTextSrc.next(text); } + public set statusText(text: string) { + this._statusTextSrc.next(text); + } + public set selectedHomeTab(tabIndex: number) { this._selectedHomeTabSrc.next(tabIndex); - this.contextText = ''; + this.contextText = ""; } public set selectedClientType(clientType: WowClientType) { @@ -53,25 +50,10 @@ export class SessionService { return this._selectedClientTypeSrc.value; } - public appLoaded() { - if (!this._autoUpdateInterval) { - this.onAutoUpdateInterval(); - this._autoUpdateInterval = window.setInterval( - this.onAutoUpdateInterval, - AUTO_UPDATE_PERIOD_MS - ); - } - } - public startUpdaterCheck() { this.checkUpdaterApp(); } - private onAutoUpdateInterval = async () => { - console.log("Auto update"); - const updateCount = await this._addonService.processAutoUpdates(); - }; - private loadInitialClientType() { return this._warcraftService.installedClientTypes$.pipe( filter((clientTypes) => clientTypes !== undefined), diff --git a/wowup-electron/src/app/utils/file.utils.ts b/wowup-electron/src/app/utils/file.utils.ts index 1b31cce0..4794a15d 100644 --- a/wowup-electron/src/app/utils/file.utils.ts +++ b/wowup-electron/src/app/utils/file.utils.ts @@ -1,23 +1,29 @@ -import * as fs from 'fs'; -import * as util from 'util'; -import { remote } from 'electron' +import * as fs from "fs"; +import * as util from "util"; +import { remote } from "electron"; +import { ListFilesResponse } from "common/models/list-files-response"; +import { ListFilesRequest } from "common/models/list-files-request"; +import { v4 as uuidv4 } from "uuid"; +import { LIST_FILES_CHANNEL, READ_FILE_CHANNEL } from "common/constants"; +import { ReadFileResponse } from "common/models/read-file-response"; +import { ReadFileRequest } from "common/models/read-file-request"; -const fsAccess = util.promisify(fs.access) -const fsReadFile = util.promisify(fs.readFile) -const userDataPath = remote.app.getPath('userData'); +const fsAccess = util.promisify(fs.access); +const fsReadFile = util.promisify(fs.readFile); +const userDataPath = remote.app.getPath("userData"); export class FileUtils { static async exists(path: string) { try { - await fsAccess(path, fs.constants.F_OK) - return true + await fsAccess(path, fs.constants.F_OK); + return true; } catch (e) { - return false + return false; } } static readFile(path: string) { - return fsReadFile(path) + return fsReadFile(path); } static readFileSync(path: string) { @@ -27,4 +33,4 @@ export class FileUtils { static getUserDataPath() { return userDataPath; } -} \ No newline at end of file +} From 233a1a798ed858a1d30c777734f15762607d45b6 Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 7 Oct 2020 14:28:08 -0500 Subject: [PATCH 08/12] Re-work the curse scanner, much faster. --- wowup-electron/file.utils.js | 50 +++ wowup-electron/file.utils.ts | 36 ++ wowup-electron/ipc-events.ts | 91 +++-- wowup-electron/main.ts | 335 ++++++++++-------- .../addon-providers/curse-addon-provider.ts | 89 +++-- .../app/models/curse/app-curse-scan-result.ts | 6 + .../curse/curse-fingerprint-response.ts | 2 +- .../curse/curse-get-featured-response.ts | 4 +- .../app/models/curse/curse-search-result.ts | 16 +- .../services/addons/addon.provider.factory.ts | 4 +- wowup-electron/src/common/constants.ts | 27 +- .../curse/curse-attachment.ts | 0 .../models => common}/curse/curse-author.ts | 0 .../curse/curse-category-section.ts | 0 .../models => common}/curse/curse-category.ts | 0 .../curse/curse-dependency.ts | 0 .../models => common}/curse/curse-file.ts | 0 .../curse/curse-folder-scanner.ts | 185 +++++----- .../curse/curse-game-version-latest-file.ts | 0 .../curse/curse-get-scan-results-request.ts | 5 + .../curse/curse-get-scan-results-response.ts | 6 + .../models => common}/curse/curse-match.ts | 0 .../models => common}/curse/curse-module.ts | 0 .../curse/curse-release-type.ts | 0 .../curse/curse-scan-result.ts | 2 - .../curse/curse-sortable-game-version.ts | 0 .../src/common/models/read-file-response.ts | 2 +- 27 files changed, 511 insertions(+), 349 deletions(-) create mode 100644 wowup-electron/file.utils.js create mode 100644 wowup-electron/file.utils.ts create mode 100644 wowup-electron/src/app/models/curse/app-curse-scan-result.ts rename wowup-electron/src/{app/models => common}/curse/curse-attachment.ts (100%) rename wowup-electron/src/{app/models => common}/curse/curse-author.ts (100%) rename wowup-electron/src/{app/models => common}/curse/curse-category-section.ts (100%) rename wowup-electron/src/{app/models => common}/curse/curse-category.ts (100%) rename wowup-electron/src/{app/models => common}/curse/curse-dependency.ts (100%) rename wowup-electron/src/{app/models => common}/curse/curse-file.ts (100%) rename wowup-electron/src/{app/addon-providers => common}/curse/curse-folder-scanner.ts (55%) rename wowup-electron/src/{app/models => common}/curse/curse-game-version-latest-file.ts (100%) create mode 100644 wowup-electron/src/common/curse/curse-get-scan-results-request.ts create mode 100644 wowup-electron/src/common/curse/curse-get-scan-results-response.ts rename wowup-electron/src/{app/models => common}/curse/curse-match.ts (100%) rename wowup-electron/src/{app/models => common}/curse/curse-module.ts (100%) rename wowup-electron/src/{app/models => common}/curse/curse-release-type.ts (100%) rename wowup-electron/src/{app/models => common}/curse/curse-scan-result.ts (79%) rename wowup-electron/src/{app/models => common}/curse/curse-sortable-game-version.ts (100%) diff --git a/wowup-electron/file.utils.js b/wowup-electron/file.utils.js new file mode 100644 index 00000000..4cd52a95 --- /dev/null +++ b/wowup-electron/file.utils.js @@ -0,0 +1,50 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.readFile = exports.readDirRecursive = void 0; +const fs = require("fs"); +const path = require("path"); +function readDirRecursive(sourcePath) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + const dirFiles = []; + fs.readdir(sourcePath, { withFileTypes: true }, (err, files) => __awaiter(this, void 0, void 0, function* () { + if (err) { + return reject(err); + } + for (let file of files) { + const filePath = path.join(sourcePath, file.name); + if (file.isDirectory()) { + const nestedFiles = yield readDirRecursive(filePath); + dirFiles.push(...nestedFiles); + } + else { + dirFiles.push(filePath); + } + } + resolve(dirFiles); + })); + }); + }); +} +exports.readDirRecursive = readDirRecursive; +function readFile(sourcePath) { + return new Promise((resolve, reject) => { + fs.readFile(sourcePath, { encoding: "utf-8" }, (err, data) => { + if (err) { + return reject(err); + } + return resolve(data); + }); + }); +} +exports.readFile = readFile; +//# sourceMappingURL=file.utils.js.map \ No newline at end of file diff --git a/wowup-electron/file.utils.ts b/wowup-electron/file.utils.ts new file mode 100644 index 00000000..7e9c6017 --- /dev/null +++ b/wowup-electron/file.utils.ts @@ -0,0 +1,36 @@ +import * as fs from "fs"; +import * as path from "path"; + +export async function readDirRecursive(sourcePath: string): Promise { + return new Promise((resolve, reject) => { + const dirFiles: string[] = []; + fs.readdir(sourcePath, { withFileTypes: true }, async (err, files) => { + if (err) { + return reject(err); + } + + for (let file of files) { + const filePath = path.join(sourcePath, file.name); + if (file.isDirectory()) { + const nestedFiles = await readDirRecursive(filePath); + dirFiles.push(...nestedFiles); + } else { + dirFiles.push(filePath); + } + } + + resolve(dirFiles); + }); + }); +} + +export function readFile(sourcePath: string): Promise { + return new Promise((resolve, reject) => { + fs.readFile(sourcePath, { encoding: "utf-8" }, (err, data) => { + if (err) { + return reject(err); + } + return resolve(data); + }); + }); +} diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index 11fccfe5..0556cc12 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -1,32 +1,48 @@ import { ipcMain, shell } from "electron"; -import * as fs from 'fs'; -import * as path from 'path'; -import { CURSE_HASH_FILE_CHANNEL, LIST_DIRECTORIES_CHANNEL, LIST_FILES_CHANNEL, SHOW_DIRECTORY, PATH_EXISTS_CHANNEL } from './src/common/constants'; -import { CurseHashFileRequest } from './src/common/models/curse-hash-file-request'; -import { CurseHashFileResponse } from './src/common/models/curse-hash-file-response'; +import * as fs from "fs"; +import * as async from "async"; + +import { readDirRecursive } from "./file.utils"; +import { + CURSE_HASH_FILE_CHANNEL, + LIST_DIRECTORIES_CHANNEL, + LIST_FILES_CHANNEL, + SHOW_DIRECTORY, + PATH_EXISTS_CHANNEL, + CURSE_GET_SCAN_RESULTS, +} from "./src/common/constants"; +import { CurseGetScanResultsRequest } from "./src/common/models/curse-get-scan-results-request"; +import { CurseGetScanResultsResponse } from "./src/common/curse/curse-get-scan-results-response"; +import { CurseHashFileRequest } from "./src/common/models/curse-hash-file-request"; +import { CurseHashFileResponse } from "./src/common/models/curse-hash-file-response"; import { ListFilesRequest } from "./src/common/models/list-files-request"; import { ListFilesResponse } from "./src/common/models/list-files-response"; import { ShowDirectoryRequest } from "./src/common/models/show-directory-request"; import { ValueRequest } from "./src/common/models/value-request"; import { ValueResponse } from "./src/common/models/value-response"; +import { CurseScanResult } from "./src/common/curse/curse-scan-result"; +import { CurseFolderScanner } from "./src/common/curse/curse-folder-scanner"; -const nativeAddon = require('./build/Release/addon.node'); +const nativeAddon = require("./build/Release/addon.node"); ipcMain.on(SHOW_DIRECTORY, async (evt, arg: ShowDirectoryRequest) => { const result = await shell.openPath(arg.sourceDir); evt.reply(arg.responseKey, true); -}) +}); ipcMain.on(CURSE_HASH_FILE_CHANNEL, async (evt, arg: CurseHashFileRequest) => { // console.log(CURSE_HASH_FILE_CHANNEL, arg); const response: CurseHashFileResponse = { - fingerprint: 0 + fingerprint: 0, }; try { if (arg.targetString !== undefined) { - const strBuffer = Buffer.from(arg.targetString, arg.targetStringEncoding || 'ascii'); + const strBuffer = Buffer.from( + arg.targetString, + arg.targetStringEncoding || "ascii" + ); const hash = nativeAddon.computeHash(strBuffer, strBuffer.length); response.fingerprint = hash; evt.reply(arg.responseKey, response); @@ -53,7 +69,7 @@ ipcMain.on(CURSE_HASH_FILE_CHANNEL, async (evt, arg: CurseHashFileRequest) => { ipcMain.on(LIST_FILES_CHANNEL, async (evt, arg: ListFilesRequest) => { const response: ListFilesResponse = { - files: [] + files: [], }; try { @@ -72,19 +88,21 @@ ipcMain.on(LIST_DIRECTORIES_CHANNEL, (evt, arg: ValueRequest) => { if (err) { response.error = err; } else { - response.value = files.filter(file => file.isDirectory()).map(file => file.name); + response.value = files + .filter((file) => file.isDirectory()) + .map((file) => file.name); } evt.reply(arg.responseKey, response); }); -}) +}); ipcMain.on(PATH_EXISTS_CHANNEL, (evt, arg: ValueRequest) => { const response: ValueResponse = { value: false }; - fs.open(arg.value, 'r', (err, fid) => { + fs.open(arg.value, "r", (err, fid) => { if (err) { - if (err.code === 'ENOENT') { + if (err.code === "ENOENT") { response.value = false; } else { response.error = err; @@ -95,27 +113,32 @@ ipcMain.on(PATH_EXISTS_CHANNEL, (evt, arg: ValueRequest) => { evt.reply(arg.responseKey, response); }); -}) +}); -async function readDirRecursive(sourcePath: string): Promise { - return new Promise((resolve, reject) => { - const dirFiles: string[] = []; - fs.readdir(sourcePath, { withFileTypes: true }, async (err, files) => { - if (err) { - return reject(err); - } +ipcMain.on( + CURSE_GET_SCAN_RESULTS, + async (evt, arg: CurseGetScanResultsRequest) => { + const response: CurseGetScanResultsResponse = { + scanResults: [], + }; - for (let file of files) { - const filePath = path.join(sourcePath, file.name); - if (file.isDirectory()) { - const nestedFiles = await readDirRecursive(filePath); - dirFiles.push(...nestedFiles); - } else { - dirFiles.push(filePath) + try { + // Scan addon folders in parallel for speed!? + const scanResults = await async.mapLimit( + arg.filePaths, + 2, + async (folder, callback) => { + const scanResult = await new CurseFolderScanner().scanFolder(folder); + + callback(undefined, scanResult); } - } + ); - resolve(dirFiles); - }); - }); -} \ No newline at end of file + response.scanResults = scanResults; + } catch (err) { + response.error = err; + } + + evt.reply(arg.responseKey, response); + } +); diff --git a/wowup-electron/main.ts b/wowup-electron/main.ts index 1b713ba1..4fbbda95 100644 --- a/wowup-electron/main.ts +++ b/wowup-electron/main.ts @@ -1,155 +1,178 @@ -import { app, BrowserWindow, screen, BrowserWindowConstructorOptions, Tray, Menu, nativeImage, ipcMain, MenuItem, MenuItemConstructorOptions } from 'electron'; -import * as path from 'path'; -import * as url from 'url'; -import * as fs from 'fs'; -import { release, arch } from 'os'; -import * as electronDl from 'electron-dl'; -import * as admZip from 'adm-zip'; -import { DownloadRequest } from './src/common/models/download-request'; -import { DownloadStatus } from './src/common/models/download-status'; -import { DownloadStatusType } from './src/common/models/download-status-type'; -import { UnzipStatus } from './src/common/models/unzip-status'; -import { DOWNLOAD_FILE_CHANNEL, UNZIP_FILE_CHANNEL, COPY_FILE_CHANNEL, COPY_DIRECTORY_CHANNEL, DELETE_DIRECTORY_CHANNEL, RENAME_DIRECTORY_CHANNEL, READ_FILE_CHANNEL } from './src/common/constants'; -import { UnzipStatusType } from './src/common/models/unzip-status-type'; -import { UnzipRequest } from './src/common/models/unzip-request'; -import { CopyFileRequest } from './src/common/models/copy-file-request'; -import { CopyDirectoryRequest } from './src/common/models/copy-directory-request'; -import { DeleteDirectoryRequest } from './src/common/models/delete-directory-request'; -import { ReadFileRequest } from './src/common/models/read-file-request'; -import { ReadFileResponse } from './src/common/models/read-file-response'; -import './ipc-events'; -import { ncp } from 'ncp'; -import * as rimraf from 'rimraf'; -import * as log from 'electron-log'; -import { autoUpdater } from "electron-updater" -import * as Store from 'electron-store' +import { + app, + BrowserWindow, + screen, + BrowserWindowConstructorOptions, + Tray, + Menu, + nativeImage, + ipcMain, + MenuItem, + MenuItemConstructorOptions, +} from "electron"; +import * as path from "path"; +import * as url from "url"; +import * as fs from "fs"; +import { release, arch } from "os"; +import * as electronDl from "electron-dl"; +import * as admZip from "adm-zip"; +import { DownloadRequest } from "./src/common/models/download-request"; +import { DownloadStatus } from "./src/common/models/download-status"; +import { DownloadStatusType } from "./src/common/models/download-status-type"; +import { UnzipStatus } from "./src/common/models/unzip-status"; +import { + DOWNLOAD_FILE_CHANNEL, + UNZIP_FILE_CHANNEL, + COPY_FILE_CHANNEL, + COPY_DIRECTORY_CHANNEL, + DELETE_DIRECTORY_CHANNEL, + RENAME_DIRECTORY_CHANNEL, + READ_FILE_CHANNEL, +} from "./src/common/constants"; +import { UnzipStatusType } from "./src/common/models/unzip-status-type"; +import { UnzipRequest } from "./src/common/models/unzip-request"; +import { CopyFileRequest } from "./src/common/models/copy-file-request"; +import { CopyDirectoryRequest } from "./src/common/models/copy-directory-request"; +import { DeleteDirectoryRequest } from "./src/common/models/delete-directory-request"; +import { ReadFileRequest } from "./src/common/models/read-file-request"; +import { ReadFileResponse } from "./src/common/models/read-file-response"; +import "./ipc-events"; +import { ncp } from "ncp"; +import * as rimraf from "rimraf"; +import * as log from "electron-log"; +import { autoUpdater } from "electron-updater"; +import * as Store from "electron-store"; +import { readFile } from "./file.utils"; -const isMac = process.platform === 'darwin'; -const isWin = process.platform === 'win32'; -const preferenceStore = new Store({ name: 'preferences' }); +const isMac = process.platform === "darwin"; +const isWin = process.platform === "win32"; +const preferenceStore = new Store({ name: "preferences" }); let appIsQuitting = false; autoUpdater.logger = log; -autoUpdater.on('update-available', () => { - log.info('AVAILABLE') - win.webContents.send('update_available'); +autoUpdater.on("update-available", () => { + log.info("AVAILABLE"); + win.webContents.send("update_available"); }); -autoUpdater.on('update-downloaded', () => { - log.info('DOWNLOADED') - win.webContents.send('update_downloaded'); +autoUpdater.on("update-downloaded", () => { + log.info("DOWNLOADED"); + win.webContents.send("update_downloaded"); }); -const appMenuTemplate: Array = isMac ? [ - { - label: app.name, - submenu: [ - { role: 'quit' } +const appMenuTemplate: Array = isMac + ? [ + { + label: app.name, + submenu: [{ role: "quit" }], + }, + { + label: "Edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + { role: "selectAll" }, + ], + }, + { + label: "View", + submenu: [ + { role: "reload" }, + { role: "forceReload" }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, ] - }, - { - label: "Edit", - submenu: [ - { role: 'undo' }, - { role: 'redo' }, - { type: "separator" }, - { role: 'cut' }, - { role: 'copy' }, - { role: 'paste' }, - { role: 'selectAll' } - ] - }, - { - label: 'View', - submenu: [ - { role: 'reload' }, - { role: 'forceReload' }, - { role: 'toggleDevTools' }, - { type: 'separator' }, - { role: 'resetZoom' }, - { role: 'zoomIn' }, - { role: 'zoomOut' }, - { type: 'separator' }, - { role: 'togglefullscreen' } - ] - } -] : []; + : []; const appMenu = Menu.buildFromTemplate(appMenuTemplate); Menu.setApplicationMenu(appMenu); -const LOG_PATH = path.join(app.getPath('userData'), 'logs'); +const LOG_PATH = path.join(app.getPath("userData"), "logs"); app.setAppLogsPath(LOG_PATH); -log.transports.file.resolvePath = (variables: log.PathVariables, message?: log.LogMessage) => { - console.log('RES', path.join(LOG_PATH, variables.fileName)) +log.transports.file.resolvePath = ( + variables: log.PathVariables, + message?: log.LogMessage +) => { + console.log("RES", path.join(LOG_PATH, variables.fileName)); return path.join(LOG_PATH, variables.fileName); -} -log.info('Main starting'); +}; +log.info("Main starting"); -app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors'); +app.commandLine.appendSwitch("disable-features", "OutOfBlinkCors"); electronDl(); const USER_AGENT = `WowUp-Client/${app.getVersion()} (${release()}; ${arch()}; +https://wowup.io)`; -log.info('USER_AGENT', USER_AGENT); +log.info("USER_AGENT", USER_AGENT); let win: BrowserWindow = null; let tray: Tray = null; const args = process.argv.slice(1), - serve = args.some(val => val === '--serve'); + serve = args.some((val) => val === "--serve"); function createTray() { - console.log('TRAY') - const trayIconPath = path.join(__dirname, 'assets', 'wowup_logo_512np.png'); + console.log("TRAY"); + const trayIconPath = path.join(__dirname, "assets", "wowup_logo_512np.png"); const icon = nativeImage.createFromPath(trayIconPath).resize({ width: 16 }); - tray = new Tray(icon) + tray = new Tray(icon); const contextMenu = Menu.buildFromTemplate([ - { label: app.name, type: 'normal', icon: icon, enabled: false }, + { label: app.name, type: "normal", icon: icon, enabled: false }, { - label: 'Show', click: () => { + label: "Show", + click: () => { win.show(); if (isMac) { app.dock.show(); } - } + }, }, - { role: 'quit' }, + { role: "quit" }, ]); if (isWin) { - tray.on('click', function (event) { - console.log('SHOW') + tray.on("click", function (event) { + console.log("SHOW"); win.show(); }); } - tray.setToolTip('WowUp') - tray.setContextMenu(contextMenu) + tray.setToolTip("WowUp"); + tray.setContextMenu(contextMenu); } function createWindow(): BrowserWindow { - const electronScreen = screen; const size = electronScreen.getPrimaryDisplay().workAreaSize; const windowOptions: BrowserWindowConstructorOptions = { width: 900, height: 600, - backgroundColor: '#444444', + backgroundColor: "#444444", // frame: false, - title: 'WowUp', - titleBarStyle: 'hidden', + title: "WowUp", + titleBarStyle: "hidden", webPreferences: { - preload: path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, "preload.js"), nodeIntegration: true, - allowRunningInsecureContent: (serve) ? true : false, + allowRunningInsecureContent: serve ? true : false, webSecurity: false, - enableRemoteModule: true + enableRemoteModule: true, }, minWidth: 900, - minHeight: 550 + minHeight: 550, }; if (isWin) { @@ -161,15 +184,14 @@ function createWindow(): BrowserWindow { win.webContents.userAgent = USER_AGENT; - win.once('ready-to-show', () => { - autoUpdater.checkForUpdatesAndNotify() - .then((result) => { - console.log('UPDATE', result) - }) + win.once("ready-to-show", () => { + autoUpdater.checkForUpdatesAndNotify().then((result) => { + console.log("UPDATE", result); + }); }); if (isMac) { - win.on('close', (e) => { + win.on("close", (e) => { if (appIsQuitting) { return; } @@ -177,28 +199,29 @@ function createWindow(): BrowserWindow { e.preventDefault(); win.hide(); - if (preferenceStore.get('collapse_to_tray') === true) { + if (preferenceStore.get("collapse_to_tray") === true) { app.dock.hide(); } }); } - win.once('closed', () => { + win.once("closed", () => { win = null; - }) + }); if (serve) { - require('electron-reload')(__dirname, { - electron: require(`${__dirname}/node_modules/electron`) + require("electron-reload")(__dirname, { + electron: require(`${__dirname}/node_modules/electron`), }); - win.loadURL('http://localhost:4200'); - + win.loadURL("http://localhost:4200"); } else { - win.loadURL(url.format({ - pathname: path.join(__dirname, 'dist/index.html'), - protocol: 'file:', - slashes: true - })); + win.loadURL( + url.format({ + pathname: path.join(__dirname, "dist/index.html"), + protocol: "file:", + slashes: true, + }) + ); } // Emitted when the window is closed. @@ -209,7 +232,6 @@ function createWindow(): BrowserWindow { // win = null; // }); - // win.on('minimize', function (event) { // event.preventDefault(); // win.hide(); @@ -229,27 +251,27 @@ try { // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. // Added 400 ms to fix the black background issue while using transparent window. More detais at https://github.com/electron/electron/issues/15947 - app.on('ready', () => { + app.on("ready", () => { setTimeout(() => { createWindow(); createTray(); - }, 400) + }, 400); }); - app.on('before-quit', (e) => { + app.on("before-quit", (e) => { appIsQuitting = true; - }) + }); // Quit when all windows are closed. - app.on('window-all-closed', () => { + app.on("window-all-closed", () => { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { + if (process.platform !== "darwin") { app.quit(); } }); - app.on('activate', () => { + app.on("activate", () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (isMac) { @@ -261,7 +283,6 @@ try { createWindow(); } }); - } catch (e) { // Catch Error // throw e; @@ -269,27 +290,26 @@ try { ipcMain.on(DOWNLOAD_FILE_CHANNEL, async (evt, arg: DownloadRequest) => { try { - const download = await electronDl.download( - win, - arg.url, - { - directory: arg.outputFolder, - onProgress: (progress) => { - win.webContents.send(arg.url, { - type: DownloadStatusType.Progress, - progress: parseFloat((progress.percent * 100.0).toFixed(2)) - } as DownloadStatus); - } - } - ); + const download = await electronDl.download(win, arg.url, { + directory: arg.outputFolder, + onProgress: (progress) => { + win.webContents.send(arg.url, { + type: DownloadStatusType.Progress, + progress: parseFloat((progress.percent * 100.0).toFixed(2)), + } as DownloadStatus); + }, + }); win.webContents.send(arg.url, { type: DownloadStatusType.Complete, - savePath: download.getSavePath() + savePath: download.getSavePath(), } as DownloadStatus); } catch (err) { console.error(err); - win.webContents.send(arg.url, { type: DownloadStatusType.Error, error: err } as DownloadStatus) + win.webContents.send(arg.url, { + type: DownloadStatusType.Error, + error: err, + } as DownloadStatus); } }); @@ -301,7 +321,7 @@ ipcMain.on(UNZIP_FILE_CHANNEL, async (evt, arg: UnzipRequest) => { zip.extractAllToAsync(outputFolder, true, (err) => { const status: UnzipStatus = { type: UnzipStatusType.Complete, - outputFolder + outputFolder, }; if (err) { @@ -309,47 +329,48 @@ ipcMain.on(UNZIP_FILE_CHANNEL, async (evt, arg: UnzipRequest) => { status.error = err; } - win.webContents.send(zipFilePath, status) + win.webContents.send(zipFilePath, status); }); }); ipcMain.on(COPY_FILE_CHANNEL, async (evt, arg: CopyFileRequest) => { - console.log('Copy File', arg); + console.log("Copy File", arg); fs.copyFile(arg.sourceFilePath, arg.destinationFilePath, (err) => { win.webContents.send(arg.destinationFilePath, { error: err }); }); }); ipcMain.on(COPY_DIRECTORY_CHANNEL, async (evt, arg: CopyDirectoryRequest) => { - console.log('Copy Dir', arg); + console.log("Copy Dir", arg); ncp(arg.sourcePath, arg.destinationPath, (err) => { win.webContents.send(arg.destinationPath, err); }); }); -ipcMain.on(DELETE_DIRECTORY_CHANNEL, async (evt, arg: DeleteDirectoryRequest) => { - console.log('Delete Dir', arg); - rimraf(arg.sourcePath, (err) => { - win.webContents.send(arg.sourcePath, err); - }); -}); +ipcMain.on( + DELETE_DIRECTORY_CHANNEL, + async (evt, arg: DeleteDirectoryRequest) => { + console.log("Delete Dir", arg); + rimraf(arg.sourcePath, (err) => { + win.webContents.send(arg.sourcePath, err); + }); + } +); ipcMain.on(RENAME_DIRECTORY_CHANNEL, async (evt, arg: CopyDirectoryRequest) => { - console.log('Rename Dir', arg); + console.log("Rename Dir", arg); fs.rename(arg.sourcePath, arg.destinationPath, (err) => { win.webContents.send(arg.destinationPath, err); - }) + }); }); ipcMain.on(READ_FILE_CHANNEL, async (evt, arg: ReadFileRequest) => { // console.log('Read File', arg); - fs.readFile(arg.sourcePath, { encoding: 'utf-8' }, (err, data) => { - const response: ReadFileResponse = { - data: data, - error: err - } - win.webContents.send(arg.sourcePath, response); - }); + const response: ReadFileResponse = { data: "" }; + try { + response.data = await readFile(arg.sourcePath); + } catch (err) { + response.error = err; + } + win.webContents.send(arg.sourcePath, response); }); - - diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 86fef9d1..894b4439 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -2,32 +2,28 @@ import { AddonProvider } from "./addon-provider"; import { WowClientType } from "../models/warcraft/wow-client-type"; import { Addon } from "../entities/addon"; import { HttpClient } from "@angular/common/http"; -import { CurseSearchResult } from "../models/curse/curse-search-result"; -import { map, mergeMap } from "rxjs/operators"; -import { CurseFile } from "../models/curse/curse-file"; +import { map } from "rxjs/operators"; import * as _ from "lodash"; import * as fp from "lodash/fp"; -import * as path from "path"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; -import { from, Observable, of } from "rxjs"; +import { Observable, of } from "rxjs"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; -import { CurseReleaseType } from "../models/curse/curse-release-type"; import { AddonChannelType } from "../models/wowup/addon-channel-type"; import { PotentialAddon } from "../models/wowup/potential-addon"; -import { CurseGetFeaturedResponse } from "../models/curse/curse-get-featured-response"; import { CachingService } from "app/services/caching/caching-service"; import { AddonFolder } from "app/models/wowup/addon-folder"; -import { FileService } from "app/services/files/file.service"; -import { CurseFolderScanner } from "./curse/curse-folder-scanner"; import { ElectronService } from "app/services"; -import { CurseScanResult } from "../models/curse/curse-scan-result"; -import { CurseFingerprintsResponse } from "app/models/curse/curse-fingerprint-response"; -import { CurseMatch } from "app/models/curse/curse-match"; +import { AppCurseScanResult } from "../models/curse/app-curse-scan-result"; import { v4 as uuidv4 } from "uuid"; -import * as async from "async"; -import { SessionService } from "app/services/session/session.service"; -import { Inject } from "@angular/core"; -import { Session } from "inspector"; +import { CURSE_GET_SCAN_RESULTS } from "common/constants"; +import { CurseGetScanResultsRequest } from "common/curse/curse-get-scan-results-request"; +import { CurseGetScanResultsResponse } from "common/curse/curse-get-scan-results-response"; +import { CurseMatch } from "common/curse/curse-match"; +import { CurseFingerprintsResponse } from "../models/curse/curse-fingerprint-response"; +import { CurseSearchResult } from "../models/curse/curse-search-result"; +import { CurseFile } from "common/curse/curse-file"; +import { CurseReleaseType } from "common/curse/curse-release-type"; +import { CurseGetFeaturedResponse } from "app/models/curse/curse-get-featured-response"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; @@ -37,9 +33,7 @@ export class CurseAddonProvider implements AddonProvider { constructor( private _httpClient: HttpClient, private _cachingService: CachingService, - private _electronService: ElectronService, - private _sessionService: SessionService, - private _fileService: FileService + private _electronService: ElectronService ) {} async scan( @@ -60,13 +54,11 @@ export class CurseAddonProvider implements AddonProvider { console.log("mapAddonFolders"); const addonIds = fp.flow( - fp.filter((sr: CurseScanResult) => !!sr.exactMatch), - fp.map((sr: CurseScanResult) => sr.exactMatch.id), + fp.filter((sr: AppCurseScanResult) => !!sr.exactMatch), + fp.map((sr: AppCurseScanResult) => sr.exactMatch.id), fp.uniq )(scanResults); - // console.log(_.sortBy(addonIds).join('\n')); - var addonResults = await this.getAllIds(addonIds).toPromise(); for (let addonFolder of addonFolders) { @@ -98,7 +90,7 @@ export class CurseAddonProvider implements AddonProvider { } private async mapAddonFolders( - scanResults: CurseScanResult[], + scanResults: AppCurseScanResult[], clientType: WowClientType ) { const fingerprintResponse = await this.getAddonsByFingerprints( @@ -130,7 +122,7 @@ export class CurseAddonProvider implements AddonProvider { } private hasMatchingFingerprint( - scanResult: CurseScanResult, + scanResult: AppCurseScanResult, exactMatch: CurseMatch ) { return exactMatch.file.modules.some( @@ -165,30 +157,37 @@ export class CurseAddonProvider implements AddonProvider { private getScanResults = async ( addonFolders: AddonFolder[] - ): Promise => { - // const scanResults: CurseScanResult[] = []; - + ): Promise => { const t1 = Date.now(); - // Scan addon folders in parallel for speed!? - const scanResults = await async.mapLimit( - addonFolders, - 2, - async (folder, callback) => { - this._sessionService.statusText = `Scanning ${folder.name}`; - const scanResult = await new CurseFolderScanner( - this._electronService, - this._fileService - ).scanFolder(folder); + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: CurseGetScanResultsResponse) => { + if (arg.error) { + return reject(arg.error); + } - callback(undefined, scanResult); - } - ); + const appScanResults: AppCurseScanResult[] = arg.scanResults.map( + (scanResult) => { + const addonFolder = addonFolders.find( + (af) => af.path === scanResult.directory + ); - console.log("scan delta", Date.now() - t1); - this._sessionService.statusText = ""; + return Object.assign({}, scanResult, { addonFolder }); + } + ); - return scanResults; + console.log("scan delta", Date.now() - t1); + resolve(appScanResults); + }; + + const request: CurseGetScanResultsRequest = { + filePaths: addonFolders.map((addonFolder) => addonFolder.path), + responseKey: uuidv4(), + }; + + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(CURSE_GET_SCAN_RESULTS, request); + }); }; async getAll( @@ -472,7 +471,7 @@ export class CurseAddonProvider implements AddonProvider { private getAddon( clientType: WowClientType, addonChannelType: AddonChannelType, - scanResult: CurseScanResult + scanResult: AppCurseScanResult ): Addon { const currentVersion = scanResult.exactMatch.file; const authors = scanResult.searchResult.authors diff --git a/wowup-electron/src/app/models/curse/app-curse-scan-result.ts b/wowup-electron/src/app/models/curse/app-curse-scan-result.ts new file mode 100644 index 00000000..5859e0bb --- /dev/null +++ b/wowup-electron/src/app/models/curse/app-curse-scan-result.ts @@ -0,0 +1,6 @@ +import { AddonFolder } from "app/models/wowup/addon-folder"; +import { CurseScanResult } from "common/curse/curse-scan-result"; + +export interface AppCurseScanResult extends CurseScanResult { + addonFolder?: AddonFolder; +} diff --git a/wowup-electron/src/app/models/curse/curse-fingerprint-response.ts b/wowup-electron/src/app/models/curse/curse-fingerprint-response.ts index 77e75b02..124bf8a2 100644 --- a/wowup-electron/src/app/models/curse/curse-fingerprint-response.ts +++ b/wowup-electron/src/app/models/curse/curse-fingerprint-response.ts @@ -1,4 +1,4 @@ -import { CurseMatch } from "./curse-match"; +import { CurseMatch } from "../../../common/curse/curse-match"; export interface CurseFingerprintsResponse { isCacheBuild: boolean; diff --git a/wowup-electron/src/app/models/curse/curse-get-featured-response.ts b/wowup-electron/src/app/models/curse/curse-get-featured-response.ts index 9f6391d8..20b56c7e 100644 --- a/wowup-electron/src/app/models/curse/curse-get-featured-response.ts +++ b/wowup-electron/src/app/models/curse/curse-get-featured-response.ts @@ -1,7 +1,7 @@ -import { CurseSearchResult } from './curse-search-result'; +import { CurseSearchResult } from "./curse-search-result"; export interface CurseGetFeaturedResponse { Featured: CurseSearchResult[]; Popular: CurseSearchResult[]; RecentlyUpdated: CurseSearchResult[]; -} \ No newline at end of file +} diff --git a/wowup-electron/src/app/models/curse/curse-search-result.ts b/wowup-electron/src/app/models/curse/curse-search-result.ts index dbdf69ba..c634677f 100644 --- a/wowup-electron/src/app/models/curse/curse-search-result.ts +++ b/wowup-electron/src/app/models/curse/curse-search-result.ts @@ -1,16 +1,16 @@ -import { CurseAuthor } from "./curse-author"; -import { CurseAttachment } from "./curse-attachment"; -import { CurseFile } from "./curse-file"; -import { CurseCategory } from "./curse-category"; -import { CurseCategorySection } from "./curse-category-section"; -import { CurseGameVersionLatestFile } from "./curse-game-version-latest-file"; +import { CurseAuthor } from "../../../common/curse/curse-author"; +import { CurseAttachment } from "../../../common/curse/curse-attachment"; +import { CurseFile } from "../../../common/curse/curse-file"; +import { CurseCategory } from "../../../common/curse/curse-category"; +import { CurseCategorySection } from "../../../common/curse/curse-category-section"; +import { CurseGameVersionLatestFile } from "../../../common/curse/curse-game-version-latest-file"; export interface CurseSearchResult { id: number; name: string; authors: CurseAuthor[]; attachments: CurseAttachment[]; - websiteUrl: string + websiteUrl: string; gameId: number; defaultFileId: number; downloadCount: number; @@ -19,7 +19,7 @@ export interface CurseSearchResult { status: number; primaryCategoryId: number; categorySection: CurseCategorySection; - slug: string + slug: string; gameVersionLatestFiles: CurseGameVersionLatestFile[]; isFeatured: boolean; popularityScore: number; diff --git a/wowup-electron/src/app/services/addons/addon.provider.factory.ts b/wowup-electron/src/app/services/addons/addon.provider.factory.ts index 5ccdeed3..15386054 100644 --- a/wowup-electron/src/app/services/addons/addon.provider.factory.ts +++ b/wowup-electron/src/app/services/addons/addon.provider.factory.ts @@ -38,9 +38,7 @@ export class AddonProviderFactory { return new CurseAddonProvider( this._httpClient, this._cachingService, - this._electronService, - this._sessionService, - this._fileService + this._electronService ); } diff --git a/wowup-electron/src/common/constants.ts b/wowup-electron/src/common/constants.ts index ddee1a4d..39b014ff 100644 --- a/wowup-electron/src/common/constants.ts +++ b/wowup-electron/src/common/constants.ts @@ -1,13 +1,14 @@ -export const DOWNLOAD_FILE_CHANNEL = 'download-file'; -export const COPY_DIRECTORY_CHANNEL = 'copy-directory'; -export const DELETE_DIRECTORY_CHANNEL = 'delete-directory'; -export const RENAME_DIRECTORY_CHANNEL = 'rename-directory'; -export const STAT_DIRECTORY_CHANNEL = 'stat-directory'; -export const LIST_DIRECTORIES_CHANNEL = 'list-directories'; -export const PATH_EXISTS_CHANNEL = 'path-exists'; -export const LIST_FILES_CHANNEL = 'list-files'; -export const READ_FILE_CHANNEL = 'read-file'; -export const UNZIP_FILE_CHANNEL = 'unzip-file'; -export const COPY_FILE_CHANNEL = 'copy-file'; -export const CURSE_HASH_FILE_CHANNEL = 'curse-hash-file'; -export const SHOW_DIRECTORY = 'show-directory'; \ No newline at end of file +export const DOWNLOAD_FILE_CHANNEL = "download-file"; +export const COPY_DIRECTORY_CHANNEL = "copy-directory"; +export const DELETE_DIRECTORY_CHANNEL = "delete-directory"; +export const RENAME_DIRECTORY_CHANNEL = "rename-directory"; +export const STAT_DIRECTORY_CHANNEL = "stat-directory"; +export const LIST_DIRECTORIES_CHANNEL = "list-directories"; +export const PATH_EXISTS_CHANNEL = "path-exists"; +export const LIST_FILES_CHANNEL = "list-files"; +export const READ_FILE_CHANNEL = "read-file"; +export const UNZIP_FILE_CHANNEL = "unzip-file"; +export const COPY_FILE_CHANNEL = "copy-file"; +export const CURSE_HASH_FILE_CHANNEL = "curse-hash-file"; +export const SHOW_DIRECTORY = "show-directory"; +export const CURSE_GET_SCAN_RESULTS = "curse-get-scan-results"; diff --git a/wowup-electron/src/app/models/curse/curse-attachment.ts b/wowup-electron/src/common/curse/curse-attachment.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-attachment.ts rename to wowup-electron/src/common/curse/curse-attachment.ts diff --git a/wowup-electron/src/app/models/curse/curse-author.ts b/wowup-electron/src/common/curse/curse-author.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-author.ts rename to wowup-electron/src/common/curse/curse-author.ts diff --git a/wowup-electron/src/app/models/curse/curse-category-section.ts b/wowup-electron/src/common/curse/curse-category-section.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-category-section.ts rename to wowup-electron/src/common/curse/curse-category-section.ts diff --git a/wowup-electron/src/app/models/curse/curse-category.ts b/wowup-electron/src/common/curse/curse-category.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-category.ts rename to wowup-electron/src/common/curse/curse-category.ts diff --git a/wowup-electron/src/app/models/curse/curse-dependency.ts b/wowup-electron/src/common/curse/curse-dependency.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-dependency.ts rename to wowup-electron/src/common/curse/curse-dependency.ts diff --git a/wowup-electron/src/app/models/curse/curse-file.ts b/wowup-electron/src/common/curse/curse-file.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-file.ts rename to wowup-electron/src/common/curse/curse-file.ts diff --git a/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts b/wowup-electron/src/common/curse/curse-folder-scanner.ts similarity index 55% rename from wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts rename to wowup-electron/src/common/curse/curse-folder-scanner.ts index 8f151569..6b456930 100644 --- a/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts +++ b/wowup-electron/src/common/curse/curse-folder-scanner.ts @@ -1,22 +1,13 @@ import * as path from "path"; import * as fs from "fs"; import * as _ from "lodash"; -import { AddonFolder } from "app/models/wowup/addon-folder"; -import { ElectronService } from "app/services"; -import { CurseHashFileResponse } from "common/models/curse-hash-file-response"; -import { CurseHashFileRequest } from "common/models/curse-hash-file-request"; -import { CURSE_HASH_FILE_CHANNEL } from "common/constants"; -import { v4 as uuidv4 } from "uuid"; -import { CurseScanResult } from "../../models/curse/curse-scan-result"; import * as async from "async"; -import { FileService } from "app/services/files/file.service"; +import { CurseScanResult } from "./curse-scan-result"; +import { readDirRecursive, readFile } from "../../../file.utils"; + +const nativeAddon = require("../../../build/Release/addon.node"); export class CurseFolderScanner { - constructor( - private _electronService: ElectronService, - private _fileService: FileService - ) {} - private get tocFileCommentsRegex() { return /\s*#.*$/gm; } @@ -41,35 +32,24 @@ export class CurseFolderScanner { return //gs; } - async scanFolder(addonFolder: AddonFolder): Promise { - const folderPath = addonFolder.path; - - const files = await this._fileService.listAllFiles(folderPath); + async scanFolder(folderPath: string): Promise { + const files = await readDirRecursive(folderPath); console.log("listAllFiles", folderPath, files.length); let matchingFiles = await this.getMatchingFiles(folderPath, files); matchingFiles = _.sortBy(matchingFiles, (f) => f.toLowerCase()); - // console.log('matching files', matchingFiles.length) - // const fst = matchingFiles.map(f => f.toLowerCase()).join('\n'); - const individualFingerprints = await async.mapLimit( matchingFiles, 2, async (path, callback) => { - const normalizedFileHash = await this.computeNormalizedFileHash(path); + const normalizedFileHash = await this.getFileHash(path); callback(undefined, normalizedFileHash); } ); - // const individualFingerprints: number[] = []; - // for (let path of matchingFiles) { - // const normalizedFileHash = await this.computeNormalizedFileHash(path); - // individualFingerprints.push(normalizedFileHash); - // } - const hashConcat = _.orderBy(individualFingerprints).join(""); - const fingerprint = await this.computeStringHash(hashConcat); + const fingerprint = this.getStringHash(hashConcat); console.log("fingerprint", fingerprint); return { @@ -78,7 +58,6 @@ export class CurseFolderScanner { fingerprint, folderName: path.basename(folderPath), individualFingerprints, - addonFolder, }; } @@ -117,7 +96,7 @@ export class CurseFolderScanner { matchingFileList.push(fileInfo); - let input = await this._fileService.readFile(fileInfo); + let input = await readFile(fileInfo); input = this.removeComments(fileInfo, input); const inclusions = this.getFileInclusionMatches(fileInfo, input); @@ -172,63 +151,103 @@ export class CurseFolderScanner { return matches; } - private computeNormalizedFileHash = (filePath: string) => { - return this.computeFileHash(filePath, true); - }; + // private computeNormalizedFileHash = (filePath: string) => { + // return this.computeFileHash(filePath, true); + // }; - private computeFileHash = ( - filePath: string, - normalizeWhitespace: boolean - ) => { - return this.computeHash(filePath, 0, normalizeWhitespace); - }; + // private computeFileHash = ( + // filePath: string, + // normalizeWhitespace: boolean + // ) => { + // return this.getFileHash(filePath); + // }; - private computeStringHash = (str: string): Promise => { + // private computeStringHash = (str: string): Promise => { + // return new Promise((resolve, reject) => { + // const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { + // if (arg.error) { + // return reject(arg.error); + // } + + // resolve(arg.fingerprint); + // }; + + // const request: CurseHashFileRequest = { + // targetString: str, + // targetStringEncoding: "ascii", + // responseKey: uuidv4(), + // normalizeWhitespace: false, + // precomputedLength: 0, + // }; + + // this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + // this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); + // }); + // }; + + // private computeHash = ( + // filePath: string, + // precomputedLength: number = 0, + // normalizeWhitespace: boolean = false + // ): Promise => { + // return new Promise((resolve, reject) => { + // const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { + // if (arg.error) { + // return reject(arg.error); + // } + + // resolve(arg.fingerprint); + // }; + + // const request: CurseHashFileRequest = { + // responseKey: uuidv4(), + // filePath, + // normalizeWhitespace, + // precomputedLength, + // }; + + // this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + // this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); + // }); + // }; + + private getStringHash( + targetString: string, + targetStringEncoding?: BufferEncoding + ): number { + try { + const strBuffer = Buffer.from( + targetString, + targetStringEncoding || "ascii" + ); + + const hash = nativeAddon.computeHash(strBuffer, strBuffer.length); + + return hash; + } catch (err) { + console.error(err); + console.log(targetString, targetStringEncoding); + throw err; + } + } + + private getFileHash(filePath: string): Promise { return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { - if (arg.error) { - return reject(arg.error); - } + try { + fs.readFile(filePath, (err, buffer) => { + if (err) { + return reject(err); + } - resolve(arg.fingerprint); - }; + const hash = nativeAddon.computeHash(buffer, buffer.length); - const request: CurseHashFileRequest = { - targetString: str, - targetStringEncoding: "ascii", - responseKey: uuidv4(), - normalizeWhitespace: false, - precomputedLength: 0, - }; - - this._electronService.ipcRenderer.once(request.responseKey, eventHandler); - this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); + return resolve(hash); + }); + } catch (err) { + console.error(err); + console.log(filePath); + return reject(err); + } }); - }; - - private computeHash = ( - filePath: string, - precomputedLength: number = 0, - normalizeWhitespace: boolean = false - ): Promise => { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { - if (arg.error) { - return reject(arg.error); - } - - resolve(arg.fingerprint); - }; - - const request: CurseHashFileRequest = { - responseKey: uuidv4(), - filePath, - normalizeWhitespace, - precomputedLength, - }; - - this._electronService.ipcRenderer.once(request.responseKey, eventHandler); - this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); - }); - }; + } } diff --git a/wowup-electron/src/app/models/curse/curse-game-version-latest-file.ts b/wowup-electron/src/common/curse/curse-game-version-latest-file.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-game-version-latest-file.ts rename to wowup-electron/src/common/curse/curse-game-version-latest-file.ts diff --git a/wowup-electron/src/common/curse/curse-get-scan-results-request.ts b/wowup-electron/src/common/curse/curse-get-scan-results-request.ts new file mode 100644 index 00000000..20f4db17 --- /dev/null +++ b/wowup-electron/src/common/curse/curse-get-scan-results-request.ts @@ -0,0 +1,5 @@ +import { IpcRequest } from "../models/ipc-request"; + +export interface CurseGetScanResultsRequest extends IpcRequest { + filePaths: string[]; +} diff --git a/wowup-electron/src/common/curse/curse-get-scan-results-response.ts b/wowup-electron/src/common/curse/curse-get-scan-results-response.ts new file mode 100644 index 00000000..cb102857 --- /dev/null +++ b/wowup-electron/src/common/curse/curse-get-scan-results-response.ts @@ -0,0 +1,6 @@ +import { CurseScanResult } from "./curse-scan-result"; + +export interface CurseGetScanResultsResponse { + error?: Error; + scanResults: CurseScanResult[]; +} diff --git a/wowup-electron/src/app/models/curse/curse-match.ts b/wowup-electron/src/common/curse/curse-match.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-match.ts rename to wowup-electron/src/common/curse/curse-match.ts diff --git a/wowup-electron/src/app/models/curse/curse-module.ts b/wowup-electron/src/common/curse/curse-module.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-module.ts rename to wowup-electron/src/common/curse/curse-module.ts diff --git a/wowup-electron/src/app/models/curse/curse-release-type.ts b/wowup-electron/src/common/curse/curse-release-type.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-release-type.ts rename to wowup-electron/src/common/curse/curse-release-type.ts diff --git a/wowup-electron/src/app/models/curse/curse-scan-result.ts b/wowup-electron/src/common/curse/curse-scan-result.ts similarity index 79% rename from wowup-electron/src/app/models/curse/curse-scan-result.ts rename to wowup-electron/src/common/curse/curse-scan-result.ts index 91ad7db7..0a7e286e 100644 --- a/wowup-electron/src/app/models/curse/curse-scan-result.ts +++ b/wowup-electron/src/common/curse/curse-scan-result.ts @@ -1,4 +1,3 @@ -import { AddonFolder } from "app/models/wowup/addon-folder"; import { CurseMatch } from "./curse-match"; import { CurseSearchResult } from "./curse-search-result"; @@ -9,7 +8,6 @@ export interface CurseScanResult { folderName: string; individualFingerprints: number[]; directory: string; - addonFolder?: AddonFolder; exactMatch?: CurseMatch; searchResult?: CurseSearchResult; } \ No newline at end of file diff --git a/wowup-electron/src/app/models/curse/curse-sortable-game-version.ts b/wowup-electron/src/common/curse/curse-sortable-game-version.ts similarity index 100% rename from wowup-electron/src/app/models/curse/curse-sortable-game-version.ts rename to wowup-electron/src/common/curse/curse-sortable-game-version.ts diff --git a/wowup-electron/src/common/models/read-file-response.ts b/wowup-electron/src/common/models/read-file-response.ts index da507ffe..ce5fc921 100644 --- a/wowup-electron/src/common/models/read-file-response.ts +++ b/wowup-electron/src/common/models/read-file-response.ts @@ -1,4 +1,4 @@ export interface ReadFileResponse { - error: Error; + error?: Error; data: string; } \ No newline at end of file From 59082f22be291ca9d6a48aed0c5da29371d86be3 Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 7 Oct 2020 15:49:20 -0500 Subject: [PATCH 09/12] Smarter channel matching for curse scans --- .../addon-providers/curse-addon-provider.ts | 50 +++++++++++++++---- .../src/app/services/addons/addon.service.ts | 5 +- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 894b4439..77f323c3 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -53,11 +53,11 @@ export class CurseAddonProvider implements AddonProvider { console.log("mapAddonFolders"); - const addonIds = fp.flow( - fp.filter((sr: AppCurseScanResult) => !!sr.exactMatch), - fp.map((sr: AppCurseScanResult) => sr.exactMatch.id), - fp.uniq - )(scanResults); + const matchedScanResults = scanResults.filter((sr) => !!sr.exactMatch); + const matchedScanResultIds = matchedScanResults.map( + (sr) => sr.exactMatch.id + ); + const addonIds = _.uniq(matchedScanResultIds); var addonResults = await this.getAllIds(addonIds).toPromise(); @@ -66,6 +66,7 @@ export class CurseAddonProvider implements AddonProvider { (sr) => sr.addonFolder.name === addonFolder.name ); if (!scanResult.exactMatch) { + console.log("No search result match", scanResult.directory); continue; } @@ -73,16 +74,21 @@ export class CurseAddonProvider implements AddonProvider { (addonResult) => addonResult.id === scanResult.exactMatch.id ); if (!scanResult.searchResult) { + console.log("No search result match", scanResult.directory); continue; } try { - addonFolder.matchingAddon = this.getAddon( + const newAddon = this.getAddon( clientType, addonChannelType, scanResult ); + + addonFolder.matchingAddon = newAddon; } catch (err) { + console.error(scanResult); + console.error(err); // TODO // _analyticsService.Track(ex, $"Failed to create addon for result {scanResult.FolderScanner.Fingerprint}"); } @@ -93,6 +99,10 @@ export class CurseAddonProvider implements AddonProvider { scanResults: AppCurseScanResult[], clientType: WowClientType ) { + if (clientType === WowClientType.None) { + return; + } + const fingerprintResponse = await this.getAddonsByFingerprints( scanResults.map((result) => result.fingerprint) ).toPromise(); @@ -113,7 +123,7 @@ export class CurseAddonProvider implements AddonProvider { if (!scanResult.exactMatch) { scanResult.exactMatch = fingerprintResponse.partialMatches.find( (partialMatch) => - partialMatch.file.modules.some( + partialMatch.file?.modules?.some( (module) => module.fingerprint === scanResult.fingerprint ) ); @@ -468,6 +478,18 @@ export class CurseAddonProvider implements AddonProvider { } } + private getWowUpChannel(releaseType: CurseReleaseType): AddonChannelType { + switch (releaseType) { + case CurseReleaseType.Alpha: + return AddonChannelType.Alpha; + case CurseReleaseType.Beta: + return AddonChannelType.Beta; + case CurseReleaseType.Release: + default: + return AddonChannelType.Stable; + } + } + private getAddon( clientType: WowClientType, addonChannelType: AddonChannelType, @@ -484,17 +506,25 @@ export class CurseAddonProvider implements AddonProvider { scanResult.searchResult, clientType ); - const latestVersion = latestFiles.find( + + let channelType = addonChannelType; + let latestVersion = latestFiles.find( (lf) => this.getChannelType(lf.releaseType) <= addonChannelType ); + // If there were no releases that met the channel type restrictions + if (!latestVersion) { + latestVersion = _.first(latestFiles); + channelType = this.getWowUpChannel(latestVersion.releaseType); + } + return { id: uuidv4(), author: authors, name: scanResult.searchResult.name, - channelType: addonChannelType, + channelType, autoUpdateEnabled: false, - clientType: clientType, + clientType, downloadUrl: latestVersion.downloadUrl, externalUrl: scanResult.searchResult.websiteUrl, externalId: scanResult.searchResult.id.toString(), diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 5511c732..5289e52d 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -513,6 +513,10 @@ export class AddonService { } private async scanAddons(clientType: WowClientType): Promise { + if (clientType === WowClientType.None) { + return []; + } + const addonFolders = await this._warcraftService.listAddons(clientType); for (let provider of this._addonProviders) { try { @@ -539,7 +543,6 @@ export class AddonService { ); console.log(Object.keys(matchedGroups)); - console.log(matchedGroups["Curse2382"]); return Object.values(matchedGroups).map((value) => value[0].matchingAddon); } From e99653ccadb7635570ece74723c397727761973d Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 7 Oct 2020 16:04:28 -0500 Subject: [PATCH 10/12] Add show folder context button --- .../app/pages/my-addons/my-addons.component.html | 3 +++ .../app/pages/my-addons/my-addons.component.ts | 15 ++++++++++++--- .../src/app/services/addons/addon.service.ts | 7 +++++++ wowup-electron/src/assets/i18n/de.json | 3 ++- wowup-electron/src/assets/i18n/en.json | 1 + wowup-electron/src/assets/i18n/es.json | 3 ++- wowup-electron/src/assets/i18n/fr.json | 3 ++- wowup-electron/src/assets/i18n/it.json | 1 + wowup-electron/src/assets/i18n/pt.json | 3 ++- wowup-electron/src/assets/i18n/ru.json | 3 ++- wowup-electron/src/assets/i18n/zh.json | 3 ++- 11 files changed, 36 insertions(+), 9 deletions(-) diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index e95f7a3f..cd96ca3e 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -151,6 +151,9 @@ + diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index 0ef01752..9bc63e94 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -101,12 +101,12 @@ export class MyAddonsComponent implements OnInit, OnDestroy { constructor( private addonService: AddonService, private _sessionService: SessionService, + private _ngZone: NgZone, + private _dialog: MatDialog, public electronService: ElectronService, public overlay: Overlay, public viewContainerRef: ViewContainerRef, - public warcraftService: WarcraftService, - private _ngZone: NgZone, - private _dialog: MatDialog + public warcraftService: WarcraftService ) { const addonInstalledSubscription = this.addonService.addonInstalled$.subscribe( (evt) => { @@ -323,6 +323,15 @@ export class MyAddonsComponent implements OnInit, OnDestroy { } } + onShowfolder(addon: Addon) { + try { + const addonPath = this.addonService.getFullInstallPath(addon); + this.electronService.shell.openExternal(addonPath); + } catch (err) { + console.error(err); + } + } + onUpdateAddon(listItem: MyAddonsListItem) { listItem.isInstalling = true; diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 5289e52d..06d8451e 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -361,6 +361,13 @@ export class AddonService { ); } + public getFullInstallPath(addon: Addon) { + const addonFolderPath = this._warcraftService.getAddonFolderPath( + addon.clientType + ); + return path.join(addonFolderPath, addon.folderName); + } + public async removeAddon(addon: Addon) { const installedDirectories = addon.installedFolders.split(","); diff --git a/wowup-electron/src/assets/i18n/de.json b/wowup-electron/src/assets/i18n/de.json index c6da1648..fb48d421 100644 --- a/wowup-electron/src/assets/i18n/de.json +++ b/wowup-electron/src/assets/i18n/de.json @@ -48,6 +48,7 @@ "IGNORE_ADDON_BUTTON": "Ignorieren", "AUTO_UPDATE_ADDON_BUTTON": "Automatisches Aktualisieren", "CHANNEL_SUBMENT_TITLE": "Kanal", + "SHOW_FOLDER": "SHOW_FOLDER", "REINSTALL_ADDON_BUTTON": "Neu installieren", "REMOVE_ADDON_BUTTON": "Entfernen", "STABLE_ADDON_CHANNEL": "Stall", @@ -119,4 +120,4 @@ "TITLE": "WowUp Telemetrie" } } -} +} diff --git a/wowup-electron/src/assets/i18n/en.json b/wowup-electron/src/assets/i18n/en.json index ba5f431a..685d8b46 100644 --- a/wowup-electron/src/assets/i18n/en.json +++ b/wowup-electron/src/assets/i18n/en.json @@ -49,6 +49,7 @@ "IGNORE_ADDON_BUTTON": "Ignore", "AUTO_UPDATE_ADDON_BUTTON": "Auto Update", "CHANNEL_SUBMENT_TITLE": "Channel", + "SHOW_FOLDER": "Show Folder", "REINSTALL_ADDON_BUTTON": "Re-Install", "REMOVE_ADDON_BUTTON": "Remove", "STABLE_ADDON_CHANNEL": "Stable", diff --git a/wowup-electron/src/assets/i18n/es.json b/wowup-electron/src/assets/i18n/es.json index d4ca50d3..47f03cfc 100644 --- a/wowup-electron/src/assets/i18n/es.json +++ b/wowup-electron/src/assets/i18n/es.json @@ -48,6 +48,7 @@ "IGNORE_ADDON_BUTTON": "Ignorar", "AUTO_UPDATE_ADDON_BUTTON": "Actualización automática", "CHANNEL_SUBMENT_TITLE": "Canal", + "SHOW_FOLDER": "SHOW_FOLDER", "REINSTALL_ADDON_BUTTON": "Reinstalar", "REMOVE_ADDON_BUTTON": "Eliminar", "STABLE_ADDON_CHANNEL": "Estable", @@ -119,4 +120,4 @@ "TITLE": "Telemetría WowUp" } } -} +} diff --git a/wowup-electron/src/assets/i18n/fr.json b/wowup-electron/src/assets/i18n/fr.json index f5989414..6c4b0a94 100644 --- a/wowup-electron/src/assets/i18n/fr.json +++ b/wowup-electron/src/assets/i18n/fr.json @@ -48,6 +48,7 @@ "IGNORE_ADDON_BUTTON": "Ignorer", "AUTO_UPDATE_ADDON_BUTTON": "Mise à jour automatique", "CHANNEL_SUBMENT_TITLE": "Chaîne", + "SHOW_FOLDER": "SHOW_FOLDER", "REINSTALL_ADDON_BUTTON": "Réinstaller", "REMOVE_ADDON_BUTTON": "Retirer", "STABLE_ADDON_CHANNEL": "Écurie", @@ -119,4 +120,4 @@ "TITLE": "Télémétrie WowUp" } } -} +} diff --git a/wowup-electron/src/assets/i18n/it.json b/wowup-electron/src/assets/i18n/it.json index 6bfd1340..c525f452 100644 --- a/wowup-electron/src/assets/i18n/it.json +++ b/wowup-electron/src/assets/i18n/it.json @@ -48,6 +48,7 @@ "IGNORE_ADDON_BUTTON": "Ignora", "AUTO_UPDATE_ADDON_BUTTON": "Aggiornamento Automatico", "CHANNEL_SUBMENT_TITLE": "Canale", + "SHOW_FOLDER": "SHOW_FOLDER", "REINSTALL_ADDON_BUTTON": "Reinstalla", "REMOVE_ADDON_BUTTON": "Rimuovi", "STABLE_ADDON_CHANNEL": "Stabile", diff --git a/wowup-electron/src/assets/i18n/pt.json b/wowup-electron/src/assets/i18n/pt.json index bb374a65..ef0a484c 100644 --- a/wowup-electron/src/assets/i18n/pt.json +++ b/wowup-electron/src/assets/i18n/pt.json @@ -48,7 +48,8 @@ "IGNORE_ADDON_BUTTON": "Ignorar", "AUTO_UPDATE_ADDON_BUTTON": "Atualização Automática", "CHANNEL_SUBMENT_TITLE": "Canal", - "REINSTALL_ADDON_BUTTON": "Reinstalar", + "SHOW_FOLDER": "SHOW_FOLDER", + "REINSTALL_ADDON_BUTTON": "Reinstalar", "REMOVE_ADDON_BUTTON": "Remover", "STABLE_ADDON_CHANNEL": "Estável", "BETA_ADDON_CHANNEL": "Beta", diff --git a/wowup-electron/src/assets/i18n/ru.json b/wowup-electron/src/assets/i18n/ru.json index dbf05986..11d68759 100644 --- a/wowup-electron/src/assets/i18n/ru.json +++ b/wowup-electron/src/assets/i18n/ru.json @@ -48,6 +48,7 @@ "IGNORE_ADDON_BUTTON": "Пропустить", "AUTO_UPDATE_ADDON_BUTTON": "Автообновление", "CHANNEL_SUBMENT_TITLE": "Канал", + "SHOW_FOLDER": "SHOW_FOLDER", "REINSTALL_ADDON_BUTTON": "Переустановить", "REMOVE_ADDON_BUTTON": "Удалить", "STABLE_ADDON_CHANNEL": "Конюшня", @@ -119,4 +120,4 @@ "TITLE": "WowUp Телеметрия" } } -} +} diff --git a/wowup-electron/src/assets/i18n/zh.json b/wowup-electron/src/assets/i18n/zh.json index 6a0ecd70..55d11d02 100644 --- a/wowup-electron/src/assets/i18n/zh.json +++ b/wowup-electron/src/assets/i18n/zh.json @@ -48,6 +48,7 @@ "IGNORE_ADDON_BUTTON": "忽略", "AUTO_UPDATE_ADDON_BUTTON": "自动更新", "CHANNEL_SUBMENT_TITLE": "频道", + "SHOW_FOLDER": "SHOW_FOLDER", "REINSTALL_ADDON_BUTTON": "重新安装", "REMOVE_ADDON_BUTTON": "删除", "STABLE_ADDON_CHANNEL": "稳定", @@ -119,4 +120,4 @@ "TITLE": "WowUp遥测" } } -} +} From a0df98a923780e1fc02f9d8010977ec2670512dd Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 7 Oct 2020 16:31:45 -0500 Subject: [PATCH 11/12] More page context text --- .../app/components/footer/footer.component.ts | 28 ++++---- .../pages/get-addons/get-addons.component.ts | 71 ++++++++++++------- .../pages/my-addons/my-addons.component.ts | 23 +++--- .../app/services/session/session.service.ts | 8 ++- 4 files changed, 82 insertions(+), 48 deletions(-) diff --git a/wowup-electron/src/app/components/footer/footer.component.ts b/wowup-electron/src/app/components/footer/footer.component.ts index 1cadc3ca..927e84bc 100644 --- a/wowup-electron/src/app/components/footer/footer.component.ts +++ b/wowup-electron/src/app/components/footer/footer.component.ts @@ -1,27 +1,27 @@ -import { Component, NgZone, OnInit } from '@angular/core'; -import { SessionService } from 'app/services/session/session.service'; -import { WowUpService } from 'app/services/wowup/wowup.service'; +import { Component, NgZone, OnInit } from "@angular/core"; +import { SessionService } from "app/services/session/session.service"; +import { WowUpService } from "app/services/wowup/wowup.service"; @Component({ - selector: 'app-footer', - templateUrl: './footer.component.html', - styleUrls: ['./footer.component.scss'] + selector: "app-footer", + templateUrl: "./footer.component.html", + styleUrls: ["./footer.component.scss"], }) export class FooterComponent implements OnInit { - constructor( private _zone: NgZone, public wowUpService: WowUpService, public sessionService: SessionService - ) { } + ) {} ngOnInit(): void { - // Force the angular zone to pump for every progress update since its outside the zone - this.sessionService.statusText$ - .subscribe(text => { - this._zone.run(() => { }); - }) - } + this.sessionService.statusText$.subscribe((text) => { + this._zone.run(() => {}); + }); + this.sessionService.pageContextText$.subscribe((text) => { + this._zone.run(() => {}); + }); + } } diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index 9e8ca51c..2f4def4e 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -4,7 +4,6 @@ import { AddonDetailComponent } from "app/components/addon-detail/addon-detail.c import { InstallFromUrlDialogComponent } from "app/components/install-from-url-dialog/install-from-url-dialog.component"; import { WowClientType } from "app/models/warcraft/wow-client-type"; import { AddonDetailModel } from "app/models/wowup/addon-detail.model"; -import { AddonUpdateEvent } from "app/models/wowup/addon-update-event"; import { ColumnState } from "app/models/wowup/column-state"; import { PotentialAddon } from "app/models/wowup/potential-addon"; import { ElectronService } from "app/services"; @@ -13,9 +12,9 @@ import { SessionService } from "app/services/session/session.service"; import { WarcraftService } from "app/services/warcraft/warcraft.service"; import { BehaviorSubject, Subject, Subscription } from "rxjs"; import { map } from "rxjs/operators"; -import { MatTableDataSource } from '@angular/material/table'; -import { MatSort } from '@angular/material/sort'; -import * as _ from 'lodash'; +import { MatTableDataSource } from "@angular/material/table"; +import { MatSort } from "@angular/material/sort"; +import * as _ from "lodash"; @Component({ selector: "app-get-addons", @@ -23,14 +22,16 @@ import * as _ from 'lodash'; styleUrls: ["./get-addons.component.scss"], }) export class GetAddonsComponent implements OnInit, OnDestroy { - - @Input('tabIndex') tabIndex: number; + @Input("tabIndex") tabIndex: number; @ViewChild(MatSort) sort: MatSort; - - private readonly _displayAddonsSrc = new BehaviorSubject([]); + + private readonly _displayAddonsSrc = new BehaviorSubject( + [] + ); private readonly _destroyed$ = new Subject(); private subscriptions: Subscription[] = []; + private isSelectedTab: boolean = false; public dataSource = new MatTableDataSource([]); @@ -55,33 +56,45 @@ export class GetAddonsComponent implements OnInit, OnDestroy { private _dialog: MatDialog, public electronService: ElectronService, public warcraftService: WarcraftService - ) { } + ) { + _sessionService.selectedHomeTab$.subscribe((tabIndex) => { + this.isSelectedTab = tabIndex === this.tabIndex; + if (this.isSelectedTab) { + this.setPageContextText(); + } + }); + } ngOnInit(): void { - const selectedClientSubscription = this._sessionService.selectedClientType$.pipe( - map((clientType) => { - this.selectedClient = clientType; - this.loadPopularAddons(this.selectedClient); - }) - ).subscribe(); + const selectedClientSubscription = this._sessionService.selectedClientType$ + .pipe( + map((clientType) => { + this.selectedClient = clientType; + this.loadPopularAddons(this.selectedClient); + }) + ) + .subscribe(); - const addonRemovedSubscription = this._addonService.addonRemoved$.pipe( - map((event: string) => { - this.onRefresh(); - }) - ).subscribe(); + const addonRemovedSubscription = this._addonService.addonRemoved$ + .pipe( + map((event: string) => { + this.onRefresh(); + }) + ) + .subscribe(); - const displayAddonSubscription = this._displayAddonsSrc - .subscribe((items: PotentialAddon[]) => { + const displayAddonSubscription = this._displayAddonsSrc.subscribe( + (items: PotentialAddon[]) => { this.dataSource.data = items; this.dataSource.sortingDataAccessor = _.get; this.dataSource.sort = this.sort; - }); + } + ); this.subscriptions = [ selectedClientSubscription, addonRemovedSubscription, - displayAddonSubscription + displayAddonSubscription, ]; } @@ -112,6 +125,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { async onSearch() { if (!this.query) { this.loadPopularAddons(this.selectedClient); + this.setPageContextText(); return; } @@ -126,6 +140,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { this.formatAddons(searchResults); this._displayAddonsSrc.next(searchResults); this.isBusy = false; + this.setPageContextText(); } openDetailDialog(addon: PotentialAddon) { @@ -173,4 +188,12 @@ export class GetAddonsComponent implements OnInit, OnDestroy { } }); } + + private setPageContextText() { + const contextStr = this._displayAddonsSrc.value?.length + ? `${this._displayAddonsSrc.value.length} results` + : ""; + + this._sessionService.setContextText(this.tabIndex, contextStr); + } } diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index 9bc63e94..df89531a 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -51,6 +51,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { private readonly _destroyed$ = new Subject(); private subscriptions: Subscription[] = []; + private isSelectedTab: boolean = false; public spinnerMessage = "Loading..."; @@ -108,6 +109,14 @@ export class MyAddonsComponent implements OnInit, OnDestroy { public viewContainerRef: ViewContainerRef, public warcraftService: WarcraftService ) { + _sessionService.selectedHomeTab$.subscribe((tabIndex) => { + this.isSelectedTab = tabIndex === this.tabIndex; + console.log("TAB CHANGE", tabIndex, this.tabIndex); + if (this.isSelectedTab) { + this.setPageContextText(); + } + }); + const addonInstalledSubscription = this.addonService.addonInstalled$.subscribe( (evt) => { let listItems: MyAddonsListItem[] = [].concat( @@ -174,15 +183,10 @@ export class MyAddonsComponent implements OnInit, OnDestroy { } ); - const selectedTabSubscription = this._sessionService.selectedHomeTab$ - .pipe(filter((tabIndex) => this.tabIndex === this.tabIndex)) - .subscribe(() => this.setPageContextText()); - this.subscriptions.push( addonInstalledSubscription, addonRemovedSubscription, - displayAddonSubscription, - selectedTabSubscription + displayAddonSubscription ); } @@ -453,8 +457,8 @@ export class MyAddonsComponent implements OnInit, OnDestroy { this.isBusy = false; this.enableControls = true; this._ngZone.run(() => { - this._sessionService.contextText = `${addons.length} addons`; this._displayAddonsSrc.next(this.formatAddons(addons)); + this.setPageContextText(); }); }, error: (err) => { @@ -493,7 +497,10 @@ export class MyAddonsComponent implements OnInit, OnDestroy { return; } - this._sessionService.contextText = `${this._displayAddonsSrc.value.length} addons`; + this._sessionService.setContextText( + this.tabIndex, + `${this._displayAddonsSrc.value.length} addons` + ); } private getInstallStateText(installState: AddonInstallState) { diff --git a/wowup-electron/src/app/services/session/session.service.ts b/wowup-electron/src/app/services/session/session.service.ts index ae7d07b6..42586745 100644 --- a/wowup-electron/src/app/services/session/session.service.ts +++ b/wowup-electron/src/app/services/session/session.service.ts @@ -28,7 +28,11 @@ export class SessionService { this.loadInitialClientType().pipe(first()).subscribe(); } - public set contextText(text: string) { + public setContextText(tabIndex: number, text: string) { + if (tabIndex !== this._selectedHomeTabSrc.value) { + return; + } + this._pageContextTextSrc.next(text); } @@ -37,8 +41,8 @@ export class SessionService { } public set selectedHomeTab(tabIndex: number) { + this._pageContextTextSrc.next(""); this._selectedHomeTabSrc.next(tabIndex); - this.contextText = ""; } public set selectedClientType(clientType: WowClientType) { From 2277ba1a725942cac5c249799e35c3d4aa581bfe Mon Sep 17 00:00:00 2001 From: john liddell Date: Wed, 7 Oct 2020 20:18:10 -0500 Subject: [PATCH 12/12] fix issues? --- wowup-electron/ipc-events.ts | 2 +- .../src/app/addon-providers/curse-addon-provider.ts | 3 +-- .../app/models/curse/curse-get-featured-response.ts | 2 +- .../models => common}/curse/curse-search-result.ts | 12 ++++++------ 4 files changed, 9 insertions(+), 10 deletions(-) rename wowup-electron/src/{app/models => common}/curse/curse-search-result.ts (62%) diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index 0556cc12..a724440a 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -11,7 +11,7 @@ import { PATH_EXISTS_CHANNEL, CURSE_GET_SCAN_RESULTS, } from "./src/common/constants"; -import { CurseGetScanResultsRequest } from "./src/common/models/curse-get-scan-results-request"; +import { CurseGetScanResultsRequest } from "./src/common/curse/curse-get-scan-results-request"; import { CurseGetScanResultsResponse } from "./src/common/curse/curse-get-scan-results-response"; import { CurseHashFileRequest } from "./src/common/models/curse-hash-file-request"; import { CurseHashFileResponse } from "./src/common/models/curse-hash-file-response"; diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 77f323c3..b5047aec 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -4,7 +4,6 @@ import { Addon } from "../entities/addon"; import { HttpClient } from "@angular/common/http"; import { map } from "rxjs/operators"; import * as _ from "lodash"; -import * as fp from "lodash/fp"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { Observable, of } from "rxjs"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; @@ -20,7 +19,7 @@ import { CurseGetScanResultsRequest } from "common/curse/curse-get-scan-results- import { CurseGetScanResultsResponse } from "common/curse/curse-get-scan-results-response"; import { CurseMatch } from "common/curse/curse-match"; import { CurseFingerprintsResponse } from "../models/curse/curse-fingerprint-response"; -import { CurseSearchResult } from "../models/curse/curse-search-result"; +import { CurseSearchResult } from "../../common/curse/curse-search-result"; import { CurseFile } from "common/curse/curse-file"; import { CurseReleaseType } from "common/curse/curse-release-type"; import { CurseGetFeaturedResponse } from "app/models/curse/curse-get-featured-response"; diff --git a/wowup-electron/src/app/models/curse/curse-get-featured-response.ts b/wowup-electron/src/app/models/curse/curse-get-featured-response.ts index 20b56c7e..0898290a 100644 --- a/wowup-electron/src/app/models/curse/curse-get-featured-response.ts +++ b/wowup-electron/src/app/models/curse/curse-get-featured-response.ts @@ -1,4 +1,4 @@ -import { CurseSearchResult } from "./curse-search-result"; +import { CurseSearchResult } from "../../../common/curse/curse-search-result"; export interface CurseGetFeaturedResponse { Featured: CurseSearchResult[]; diff --git a/wowup-electron/src/app/models/curse/curse-search-result.ts b/wowup-electron/src/common/curse/curse-search-result.ts similarity index 62% rename from wowup-electron/src/app/models/curse/curse-search-result.ts rename to wowup-electron/src/common/curse/curse-search-result.ts index c634677f..6aa3848b 100644 --- a/wowup-electron/src/app/models/curse/curse-search-result.ts +++ b/wowup-electron/src/common/curse/curse-search-result.ts @@ -1,9 +1,9 @@ -import { CurseAuthor } from "../../../common/curse/curse-author"; -import { CurseAttachment } from "../../../common/curse/curse-attachment"; -import { CurseFile } from "../../../common/curse/curse-file"; -import { CurseCategory } from "../../../common/curse/curse-category"; -import { CurseCategorySection } from "../../../common/curse/curse-category-section"; -import { CurseGameVersionLatestFile } from "../../../common/curse/curse-game-version-latest-file"; +import { CurseAuthor } from "./curse-author"; +import { CurseAttachment } from "./curse-attachment"; +import { CurseFile } from "./curse-file"; +import { CurseCategory } from "./curse-category"; +import { CurseCategorySection } from "./curse-category-section"; +import { CurseGameVersionLatestFile } from "./curse-game-version-latest-file"; export interface CurseSearchResult { id: number;