mirror of
https://github.com/CCOSTAN/Home-AssistantConfig.git
synced 2025-08-16 02:25:10 +00:00
Updated HACS and also fixed Garadget #727
This commit is contained in:
0
config/custom_components/hacs/.translations/da.json
Executable file → Normal file
0
config/custom_components/hacs/.translations/da.json
Executable file → Normal file
14
config/custom_components/hacs/.translations/de.json
Executable file → Normal file
14
config/custom_components/hacs/.translations/de.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Integration",
|
"integration": "Integration",
|
||||||
"integrations": "Integrationen",
|
"integrations": "Integrationen",
|
||||||
"manage": "verwalten",
|
"manage": "verwalten",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Apps",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
"python_script": "Python Skript",
|
"python_script": "Python Skript",
|
||||||
@@ -33,11 +35,12 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "AppDaemon App-Discovery & Tracking aktivieren",
|
"appdaemon": "AppDaemon App-Entdeckung & Nachverfolgung aktivieren",
|
||||||
"python_script": "Python Script-Discovery & Tracking aktivieren",
|
"netdaemon": "NetDaemon App-Entdeckung & Nachverfolgung aktivieren",
|
||||||
|
"python_script": "Python Script-Entdeckung & Nachverfolgung aktivieren",
|
||||||
"sidepanel_icon": "Sidepanel Symbol",
|
"sidepanel_icon": "Sidepanel Symbol",
|
||||||
"sidepanel_title": "Sidepanel Titel",
|
"sidepanel_title": "Sidepanel Titel",
|
||||||
"theme": "Theme-Discovery & Tracking aktivieren",
|
"theme": "Theme-Entdeckung & Nachverfolgung aktivieren",
|
||||||
"token": "Persönlicher GitHub Zugriffstoken"
|
"token": "Persönlicher GitHub Zugriffstoken"
|
||||||
},
|
},
|
||||||
"description": "Wenn du Hilfe mit den Einstellungen brauchst, kannst du hier nachsehen: https:\/\/hacs.xyz\/docs\/configuration\/start",
|
"description": "Wenn du Hilfe mit den Einstellungen brauchst, kannst du hier nachsehen: https:\/\/hacs.xyz\/docs\/configuration\/start",
|
||||||
@@ -56,6 +59,7 @@
|
|||||||
"exist": "{item} existiert bereits",
|
"exist": "{item} existiert bereits",
|
||||||
"generic": "Bist du dir sicher?",
|
"generic": "Bist du dir sicher?",
|
||||||
"home_assistant_is_restarting": "Bitte warte, Home Assistant wird jetzt neu gestartet.",
|
"home_assistant_is_restarting": "Bitte warte, Home Assistant wird jetzt neu gestartet.",
|
||||||
|
"home_assistant_version_not_correct": "Du benutzt die Home Assistant-Version '{haversion}', für dieses Repository muss jedoch die Mindestversion '{minversion}' installiert sein.",
|
||||||
"no": "Nein",
|
"no": "Nein",
|
||||||
"no_upgrades": "Keine Upgrades ausstehend",
|
"no_upgrades": "Keine Upgrades ausstehend",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
@@ -70,10 +74,11 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "AppDaemon App-Discovery & Tracking aktivieren",
|
"appdaemon": "AppDaemon App-Entdeckung & Nachverfolgung aktivieren",
|
||||||
"country": "Nach Ländercode filtern.",
|
"country": "Nach Ländercode filtern.",
|
||||||
"debug": "Debug aktivieren.",
|
"debug": "Debug aktivieren.",
|
||||||
"experimental": "Experimentelle Funktionen aktivieren",
|
"experimental": "Experimentelle Funktionen aktivieren",
|
||||||
|
"netdaemon": "NetDaemon App-Entdeckung & Nachverfolgung aktivieren",
|
||||||
"not_in_use": "Nicht in Verwendung mit YAML",
|
"not_in_use": "Nicht in Verwendung mit YAML",
|
||||||
"release_limit": "Anzahl anzuzeigender Releases.",
|
"release_limit": "Anzahl anzuzeigender Releases.",
|
||||||
"sidepanel_icon": "Sidepanel Symbol",
|
"sidepanel_icon": "Sidepanel Symbol",
|
||||||
@@ -115,6 +120,7 @@
|
|||||||
"note_installed": "Wird installiert nach",
|
"note_installed": "Wird installiert nach",
|
||||||
"note_integration": "du musst es dann noch in die Datei 'configuration.yaml' hinzufügen",
|
"note_integration": "du musst es dann noch in die Datei 'configuration.yaml' hinzufügen",
|
||||||
"note_plugin": "du musst es dann noch in deine Lovelace-Einstellungen ('ui-lovelace.yaml' oder im Raw-Konfigurationseditor) hinzufügen",
|
"note_plugin": "du musst es dann noch in deine Lovelace-Einstellungen ('ui-lovelace.yaml' oder im Raw-Konfigurationseditor) hinzufügen",
|
||||||
|
"note_plugin_post_107": "Du musst es noch zu deiner Lovelace-Konfiguration hinzufügen ('configuration.yaml' oder der Ressourceneditor '\/config\/lovelace\/resources')",
|
||||||
"open_issue": "Problem melden",
|
"open_issue": "Problem melden",
|
||||||
"open_plugin": "Plugin öffnen",
|
"open_plugin": "Plugin öffnen",
|
||||||
"reinstall": "Neu installieren",
|
"reinstall": "Neu installieren",
|
||||||
|
7
config/custom_components/hacs/.translations/el.json
Executable file → Normal file
7
config/custom_components/hacs/.translations/el.json
Executable file → Normal file
@@ -5,12 +5,15 @@
|
|||||||
"appdaemon_apps": "AppDaemon Apps",
|
"appdaemon_apps": "AppDaemon Apps",
|
||||||
"background_task": "Τρέχει μια διεργασία στο παρασκήνιο, η σελίδα θα ανανεωθεί μόλις αυτό ολοκληρωθεί.",
|
"background_task": "Τρέχει μια διεργασία στο παρασκήνιο, η σελίδα θα ανανεωθεί μόλις αυτό ολοκληρωθεί.",
|
||||||
"check_log_file": "Ελέγξτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες.",
|
"check_log_file": "Ελέγξτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες.",
|
||||||
|
"continue": "Να συνεχίσει",
|
||||||
"disabled": "Απενεργοποιημένο",
|
"disabled": "Απενεργοποιημένο",
|
||||||
"documentation": "Τεκμηρίωση",
|
"documentation": "Τεκμηρίωση",
|
||||||
"hacs_is_disabled": "Το HACS είναι απενεργοποιημένο",
|
"hacs_is_disabled": "Το HACS είναι απενεργοποιημένο",
|
||||||
"installed": "εγκατεστημένο",
|
"installed": "εγκατεστημένο",
|
||||||
"integration": "Ενσωμάτωση",
|
"integration": "Ενσωμάτωση",
|
||||||
"integrations": "Ενσωματωμένα",
|
"integrations": "Ενσωματωμένα",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Apps",
|
||||||
"plugin": "Πρόσθετο",
|
"plugin": "Πρόσθετο",
|
||||||
"plugins": "Πρόσθετα",
|
"plugins": "Πρόσθετα",
|
||||||
"python_script": "Πρόγραμμα Python",
|
"python_script": "Πρόγραμμα Python",
|
||||||
@@ -32,6 +35,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Ενεργοποίηση εύρεσης & παρακολούθησης για το AppDaemon",
|
"appdaemon": "Ενεργοποίηση εύρεσης & παρακολούθησης για το AppDaemon",
|
||||||
|
"netdaemon": "Ενεργοποίηση εύρεσης & παρακολούθησης για το NetDaemon",
|
||||||
"python_script": "Ενεργοποίηση εύρεσης & παρακολούθησης για τα python_scripts",
|
"python_script": "Ενεργοποίηση εύρεσης & παρακολούθησης για τα python_scripts",
|
||||||
"sidepanel_icon": "Εικονίδιο πλαϊνού πάνελ",
|
"sidepanel_icon": "Εικονίδιο πλαϊνού πάνελ",
|
||||||
"sidepanel_title": "Τίτλος πλαϊνού πάνελ",
|
"sidepanel_title": "Τίτλος πλαϊνού πάνελ",
|
||||||
@@ -50,6 +54,7 @@
|
|||||||
"cancel": "Ακύρωση",
|
"cancel": "Ακύρωση",
|
||||||
"continue": "Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
|
"continue": "Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
|
||||||
"delete": "Είστε σίγουροι ότι θέλετε να διαγράψετε το '{item}';",
|
"delete": "Είστε σίγουροι ότι θέλετε να διαγράψετε το '{item}';",
|
||||||
|
"delete_installed": "Το '{item}' είναι εγκατεστημένο, πρέπει να το απεγκαταστήσετε πριν να το διαγράψετε.",
|
||||||
"exist": "{item} υπάρχει ήδη",
|
"exist": "{item} υπάρχει ήδη",
|
||||||
"generic": "Είστε βέβαιοι;",
|
"generic": "Είστε βέβαιοι;",
|
||||||
"home_assistant_is_restarting": "Περιμένετε, το Home Assistant επανεκκινείται τώρα.",
|
"home_assistant_is_restarting": "Περιμένετε, το Home Assistant επανεκκινείται τώρα.",
|
||||||
@@ -70,6 +75,7 @@
|
|||||||
"country": "Κριτήριο με βάση τον κωδικό χώρας.",
|
"country": "Κριτήριο με βάση τον κωδικό χώρας.",
|
||||||
"debug": "Ενεργοποίηση εντοπισμού σφαλμάτων.",
|
"debug": "Ενεργοποίηση εντοπισμού σφαλμάτων.",
|
||||||
"experimental": "Ενεργοποίση πειραματικών λειτουργιών",
|
"experimental": "Ενεργοποίση πειραματικών λειτουργιών",
|
||||||
|
"netdaemon": "Ενεργοποίηση εύρεσης & παρακολούθησης για το NetDaemon",
|
||||||
"release_limit": "Αριθμός εκδόσεων που να παραθέτονται.",
|
"release_limit": "Αριθμός εκδόσεων που να παραθέτονται.",
|
||||||
"sidepanel_icon": "Εικονίδιο πλαϊνού πάνελ",
|
"sidepanel_icon": "Εικονίδιο πλαϊνού πάνελ",
|
||||||
"sidepanel_title": "Τίτλος πλαϊνού πάνελ"
|
"sidepanel_title": "Τίτλος πλαϊνού πάνελ"
|
||||||
@@ -110,6 +116,7 @@
|
|||||||
"open_plugin": "Ανοιχτό πρόσθετο",
|
"open_plugin": "Ανοιχτό πρόσθετο",
|
||||||
"reinstall": "Επανεγκατάσταση",
|
"reinstall": "Επανεγκατάσταση",
|
||||||
"repository": "Αποθετήριο",
|
"repository": "Αποθετήριο",
|
||||||
|
"restart_home_assistant": "Επανεκκίνηση του Home Assistant",
|
||||||
"show_beta": "Εμφάνιση του beta",
|
"show_beta": "Εμφάνιση του beta",
|
||||||
"uninstall": "Απεγκατάσταση",
|
"uninstall": "Απεγκατάσταση",
|
||||||
"update_information": "Ενημέρωση πληροφοριών",
|
"update_information": "Ενημέρωση πληροφοριών",
|
||||||
|
5
config/custom_components/hacs/.translations/en.json
Executable file → Normal file
5
config/custom_components/hacs/.translations/en.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Integration",
|
"integration": "Integration",
|
||||||
"integrations": "Integrations",
|
"integrations": "Integrations",
|
||||||
"manage": "manage",
|
"manage": "manage",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Apps",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
"python_script": "Python Script",
|
"python_script": "Python Script",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Enable AppDaemon apps discovery & tracking",
|
"appdaemon": "Enable AppDaemon apps discovery & tracking",
|
||||||
|
"netdaemon": "Enable NetDaemon apps discovery & tracking",
|
||||||
"python_script": "Enable python_scripts discovery & tracking",
|
"python_script": "Enable python_scripts discovery & tracking",
|
||||||
"sidepanel_icon": "Side panel icon",
|
"sidepanel_icon": "Side panel icon",
|
||||||
"sidepanel_title": "Side panel title",
|
"sidepanel_title": "Side panel title",
|
||||||
@@ -75,6 +78,7 @@
|
|||||||
"country": "Filter with country code.",
|
"country": "Filter with country code.",
|
||||||
"debug": "Enable debug.",
|
"debug": "Enable debug.",
|
||||||
"experimental": "Enable experimental features",
|
"experimental": "Enable experimental features",
|
||||||
|
"netdaemon": "Enable NetDaemon apps discovery & tracking",
|
||||||
"not_in_use": "Not in use with YAML",
|
"not_in_use": "Not in use with YAML",
|
||||||
"release_limit": "Number of releases to show.",
|
"release_limit": "Number of releases to show.",
|
||||||
"sidepanel_icon": "Side panel icon",
|
"sidepanel_icon": "Side panel icon",
|
||||||
@@ -116,6 +120,7 @@
|
|||||||
"note_installed": "When installed, this will be located in",
|
"note_installed": "When installed, this will be located in",
|
||||||
"note_integration": "you still need to add it to your 'configuration.yaml' file",
|
"note_integration": "you still need to add it to your 'configuration.yaml' file",
|
||||||
"note_plugin": "you still need to add it to your lovelace configuration ('ui-lovelace.yaml' or the raw UI config editor)",
|
"note_plugin": "you still need to add it to your lovelace configuration ('ui-lovelace.yaml' or the raw UI config editor)",
|
||||||
|
"note_plugin_post_107": "you still need to add it to your lovelace configuration ('configuration.yaml' or the resource editor '\/config\/lovelace\/resources')",
|
||||||
"open_issue": "Open issue",
|
"open_issue": "Open issue",
|
||||||
"open_plugin": "Open plugin",
|
"open_plugin": "Open plugin",
|
||||||
"reinstall": "Reinstall",
|
"reinstall": "Reinstall",
|
||||||
|
6
config/custom_components/hacs/.translations/es.json
Executable file → Normal file
6
config/custom_components/hacs/.translations/es.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Integración",
|
"integration": "Integración",
|
||||||
"integrations": "Integraciones",
|
"integrations": "Integraciones",
|
||||||
"manage": "Administrar",
|
"manage": "Administrar",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Apps",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
"python_script": "Python Script",
|
"python_script": "Python Script",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de AppDaemon",
|
"appdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de AppDaemon",
|
||||||
|
"netdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de NetDaemon",
|
||||||
"python_script": "Habilitar el descubrimiento y seguimiento en python_scripts",
|
"python_script": "Habilitar el descubrimiento y seguimiento en python_scripts",
|
||||||
"sidepanel_icon": "Ícono del panel lateral",
|
"sidepanel_icon": "Ícono del panel lateral",
|
||||||
"sidepanel_title": "Título del panel lateral",
|
"sidepanel_title": "Título del panel lateral",
|
||||||
@@ -56,6 +59,7 @@
|
|||||||
"exist": "{item} ya existe",
|
"exist": "{item} ya existe",
|
||||||
"generic": "¿Estás seguro?",
|
"generic": "¿Estás seguro?",
|
||||||
"home_assistant_is_restarting": "Espera, Home Assistant se está reiniciando.",
|
"home_assistant_is_restarting": "Espera, Home Assistant se está reiniciando.",
|
||||||
|
"home_assistant_version_not_correct": "Está ejecutando la versión '{haversion}' de Home Assistant, pero este repositorio requiere la instalación de la versión '{minversion}' mínima.",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"no_upgrades": "No hay actualizaciones pendientes",
|
"no_upgrades": "No hay actualizaciones pendientes",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
@@ -74,6 +78,7 @@
|
|||||||
"country": "Filtrar por el código de país.",
|
"country": "Filtrar por el código de país.",
|
||||||
"debug": "Habilitar depuración.",
|
"debug": "Habilitar depuración.",
|
||||||
"experimental": "Habilitar funciones experimentales",
|
"experimental": "Habilitar funciones experimentales",
|
||||||
|
"netdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de NetDaemon",
|
||||||
"not_in_use": "No usarse con YAML",
|
"not_in_use": "No usarse con YAML",
|
||||||
"release_limit": "Número de versiones a mostrar.",
|
"release_limit": "Número de versiones a mostrar.",
|
||||||
"sidepanel_icon": "Icono del panel lateral",
|
"sidepanel_icon": "Icono del panel lateral",
|
||||||
@@ -84,6 +89,7 @@
|
|||||||
},
|
},
|
||||||
"repository_banner": {
|
"repository_banner": {
|
||||||
"config_flow": "Esta integración soporta config_flow, lo que significa que ahora puede ir a la sección de integración de su UI para configurarlo.",
|
"config_flow": "Esta integración soporta config_flow, lo que significa que ahora puede ir a la sección de integración de su UI para configurarlo.",
|
||||||
|
"config_flow_title": "Configuración de UI soportada",
|
||||||
"integration_not_loaded": "Esta integración no se carga en Home Assistant.",
|
"integration_not_loaded": "Esta integración no se carga en Home Assistant.",
|
||||||
"no_restart_required": "No es necesario reiniciar",
|
"no_restart_required": "No es necesario reiniciar",
|
||||||
"not_loaded": "No está cargado",
|
"not_loaded": "No está cargado",
|
||||||
|
4
config/custom_components/hacs/.translations/fr.json
Executable file → Normal file
4
config/custom_components/hacs/.translations/fr.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Intégration",
|
"integration": "Intégration",
|
||||||
"integrations": "Intégrations",
|
"integrations": "Intégrations",
|
||||||
"manage": "gérer",
|
"manage": "gérer",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "Applications NetDaemon",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
"python_script": "Script Python",
|
"python_script": "Script Python",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Activer la découverte et le suivi des applications AppDaemon",
|
"appdaemon": "Activer la découverte et le suivi des applications AppDaemon",
|
||||||
|
"netdaemon": "Activer la découverte et le suivi des applications NetDaemon",
|
||||||
"python_script": "Activer la découverte et le suivi des scripts python",
|
"python_script": "Activer la découverte et le suivi des scripts python",
|
||||||
"sidepanel_icon": "Icône de la barre latérale",
|
"sidepanel_icon": "Icône de la barre latérale",
|
||||||
"sidepanel_title": "Titre de la barre latérale",
|
"sidepanel_title": "Titre de la barre latérale",
|
||||||
@@ -75,6 +78,7 @@
|
|||||||
"country": "Filtrer par code pays.",
|
"country": "Filtrer par code pays.",
|
||||||
"debug": "Activez le débogage.",
|
"debug": "Activez le débogage.",
|
||||||
"experimental": "Activer les fonctionnalités expérimentales",
|
"experimental": "Activer les fonctionnalités expérimentales",
|
||||||
|
"netdaemon": "Activer la découverte et le suivi des applications NetDaemon",
|
||||||
"not_in_use": "Non utilisé avec YAML",
|
"not_in_use": "Non utilisé avec YAML",
|
||||||
"release_limit": "Nombre de recensés à afficher.",
|
"release_limit": "Nombre de recensés à afficher.",
|
||||||
"sidepanel_icon": "Icône de la barre latérale",
|
"sidepanel_icon": "Icône de la barre latérale",
|
||||||
|
0
config/custom_components/hacs/.translations/hu.json
Executable file → Normal file
0
config/custom_components/hacs/.translations/hu.json
Executable file → Normal file
7
config/custom_components/hacs/.translations/it.json
Executable file → Normal file
7
config/custom_components/hacs/.translations/it.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Integrazione",
|
"integration": "Integrazione",
|
||||||
"integrations": "Integrazioni",
|
"integrations": "Integrazioni",
|
||||||
"manage": "gestione",
|
"manage": "gestione",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "Applicazioni NetDaemon",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugin",
|
"plugins": "Plugin",
|
||||||
"python_script": "Script python",
|
"python_script": "Script python",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni AppDaemon",
|
"appdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni AppDaemon",
|
||||||
|
"netdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni NetDaemon",
|
||||||
"python_script": "Abilita il rilevamento e il monitoraggio dei python_scripts",
|
"python_script": "Abilita il rilevamento e il monitoraggio dei python_scripts",
|
||||||
"sidepanel_icon": "Icona nel pannello laterale",
|
"sidepanel_icon": "Icona nel pannello laterale",
|
||||||
"sidepanel_title": "Titolo nel pannello laterale",
|
"sidepanel_title": "Titolo nel pannello laterale",
|
||||||
@@ -56,6 +59,7 @@
|
|||||||
"exist": "{item} esiste già",
|
"exist": "{item} esiste già",
|
||||||
"generic": "Sei sicuro?",
|
"generic": "Sei sicuro?",
|
||||||
"home_assistant_is_restarting": "Aspetta, Home Assistant si sta riavviando.",
|
"home_assistant_is_restarting": "Aspetta, Home Assistant si sta riavviando.",
|
||||||
|
"home_assistant_version_not_correct": "Stai eseguendo la versione Home Assistant '{haversion}', ma questo repository richiede l'installazione della versione minima '{minversion}'.",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"no_upgrades": "Nessun aggiornamento in sospeso",
|
"no_upgrades": "Nessun aggiornamento in sospeso",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
@@ -74,6 +78,8 @@
|
|||||||
"country": "Filtra con prefisso internazionale.",
|
"country": "Filtra con prefisso internazionale.",
|
||||||
"debug": "Abilita debug.",
|
"debug": "Abilita debug.",
|
||||||
"experimental": "Abilita funzionalità sperimentali",
|
"experimental": "Abilita funzionalità sperimentali",
|
||||||
|
"netdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni NetDaemon",
|
||||||
|
"not_in_use": "Non in uso con YAML",
|
||||||
"release_limit": "Numero di versioni da mostrare.",
|
"release_limit": "Numero di versioni da mostrare.",
|
||||||
"sidepanel_icon": "Icona nel pannello laterale",
|
"sidepanel_icon": "Icona nel pannello laterale",
|
||||||
"sidepanel_title": "Titolo nel pannello laterale"
|
"sidepanel_title": "Titolo nel pannello laterale"
|
||||||
@@ -83,6 +89,7 @@
|
|||||||
},
|
},
|
||||||
"repository_banner": {
|
"repository_banner": {
|
||||||
"config_flow": "Questa integrazione supporta config_flow, questo significa che è ora possibile passare alla sezione \"IntegrazionI\" dell'interfaccia utente per la configurazione.",
|
"config_flow": "Questa integrazione supporta config_flow, questo significa che è ora possibile passare alla sezione \"IntegrazionI\" dell'interfaccia utente per la configurazione.",
|
||||||
|
"config_flow_title": "Configurazione dell'interfaccia utente supportata",
|
||||||
"integration_not_loaded": "Questa integrazione non è caricata in Home Assistant.",
|
"integration_not_loaded": "Questa integrazione non è caricata in Home Assistant.",
|
||||||
"no_restart_required": "Non è necessario riavviare",
|
"no_restart_required": "Non è necessario riavviare",
|
||||||
"not_loaded": "Non caricato",
|
"not_loaded": "Non caricato",
|
||||||
|
4
config/custom_components/hacs/.translations/nb.json
Executable file → Normal file
4
config/custom_components/hacs/.translations/nb.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Integrasjon",
|
"integration": "Integrasjon",
|
||||||
"integrations": "Integrasjoner",
|
"integrations": "Integrasjoner",
|
||||||
"manage": "manage",
|
"manage": "manage",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Apper",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
"python_script": "Python-skript",
|
"python_script": "Python-skript",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Aktiver oppdagelse og sporing av AppDaemon-apper",
|
"appdaemon": "Aktiver oppdagelse og sporing av AppDaemon-apper",
|
||||||
|
"netdaemon": "Aktiver oppdagelse og sporing av NetDaemon-apper",
|
||||||
"python_script": "Aktiver oppdagelse og sporing av python_scripts",
|
"python_script": "Aktiver oppdagelse og sporing av python_scripts",
|
||||||
"sidepanel_icon": "Sidepanel ikon",
|
"sidepanel_icon": "Sidepanel ikon",
|
||||||
"sidepanel_title": "Sidepanel tittel",
|
"sidepanel_title": "Sidepanel tittel",
|
||||||
@@ -75,6 +78,7 @@
|
|||||||
"country": "Filtrer med landskode.",
|
"country": "Filtrer med landskode.",
|
||||||
"debug": "Aktiver debug",
|
"debug": "Aktiver debug",
|
||||||
"experimental": "Aktiver eksperimentelle funksjoner",
|
"experimental": "Aktiver eksperimentelle funksjoner",
|
||||||
|
"netdaemon": "Aktiver oppdagelse og sporing av NetDaemon-apper",
|
||||||
"not_in_use": "Ikke i bruk med YAML",
|
"not_in_use": "Ikke i bruk med YAML",
|
||||||
"release_limit": "Antall utgivelser som skal vises.",
|
"release_limit": "Antall utgivelser som skal vises.",
|
||||||
"sidepanel_icon": "Sidepanel ikon",
|
"sidepanel_icon": "Sidepanel ikon",
|
||||||
|
5
config/custom_components/hacs/.translations/nl.json
Executable file → Normal file
5
config/custom_components/hacs/.translations/nl.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Integratie",
|
"integration": "Integratie",
|
||||||
"integrations": "Integraties",
|
"integrations": "Integraties",
|
||||||
"manage": "beheer",
|
"manage": "beheer",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Apps",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
"python_script": "Python Script",
|
"python_script": "Python Script",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Zet AppDaemon apps ontdekken & traceren aan",
|
"appdaemon": "Zet AppDaemon apps ontdekken & traceren aan",
|
||||||
|
"netdaemon": "Zet NetDaemon apps ontdekken & traceren aan",
|
||||||
"python_script": "Zet python_scripts ontdekken & traceren aan",
|
"python_script": "Zet python_scripts ontdekken & traceren aan",
|
||||||
"sidepanel_icon": "Zijpaneel icoon",
|
"sidepanel_icon": "Zijpaneel icoon",
|
||||||
"sidepanel_title": "Zijpaneel titel",
|
"sidepanel_title": "Zijpaneel titel",
|
||||||
@@ -74,6 +77,7 @@
|
|||||||
"country": "Filter met land code.",
|
"country": "Filter met land code.",
|
||||||
"debug": "Schakel debug in.",
|
"debug": "Schakel debug in.",
|
||||||
"experimental": "Zet experimentele functies aan",
|
"experimental": "Zet experimentele functies aan",
|
||||||
|
"netdaemon": "Zet NetDaemon apps ontdekken & traceren aan",
|
||||||
"not_in_use": "Niet in gebruik met YAML",
|
"not_in_use": "Niet in gebruik met YAML",
|
||||||
"release_limit": "Aantal releases om te laten zien.",
|
"release_limit": "Aantal releases om te laten zien.",
|
||||||
"sidepanel_icon": "Zijpaneel icoon",
|
"sidepanel_icon": "Zijpaneel icoon",
|
||||||
@@ -84,6 +88,7 @@
|
|||||||
},
|
},
|
||||||
"repository_banner": {
|
"repository_banner": {
|
||||||
"config_flow": "Deze integratie ondersteunt config_flow, wat betekent dat u via uw \"Instellingen\" naar \"Integraties\" kunt gaan om het te configureren.",
|
"config_flow": "Deze integratie ondersteunt config_flow, wat betekent dat u via uw \"Instellingen\" naar \"Integraties\" kunt gaan om het te configureren.",
|
||||||
|
"config_flow_title": "UI-configuratie ondersteund",
|
||||||
"integration_not_loaded": "Deze integratie wordt niet geladen in Home Assistant.",
|
"integration_not_loaded": "Deze integratie wordt niet geladen in Home Assistant.",
|
||||||
"no_restart_required": "Geen herstart vereist",
|
"no_restart_required": "Geen herstart vereist",
|
||||||
"not_loaded": "Niet geladen",
|
"not_loaded": "Niet geladen",
|
||||||
|
86
config/custom_components/hacs/.translations/nn.json
Executable file → Normal file
86
config/custom_components/hacs/.translations/nn.json
Executable file → Normal file
@@ -4,14 +4,17 @@
|
|||||||
"appdaemon": "AppDaemon",
|
"appdaemon": "AppDaemon",
|
||||||
"appdaemon_apps": "AppDeamon-appar",
|
"appdaemon_apps": "AppDeamon-appar",
|
||||||
"background_task": "Bakgrunnsoppgåve køyrer. Denne sida kjem til å laste seg omatt når ho er ferdig.",
|
"background_task": "Bakgrunnsoppgåve køyrer. Denne sida kjem til å laste seg omatt når ho er ferdig.",
|
||||||
"continue": "Fortsette",
|
"check_log_file": "Sjå i loggfila di for meir detaljar.",
|
||||||
|
"continue": "Hald fram",
|
||||||
"disabled": "Deaktivert",
|
"disabled": "Deaktivert",
|
||||||
"documentation": "dokumentasjon",
|
"documentation": "Dokumentasjon",
|
||||||
"hacs_is_disabled": "HACS er deaktivert",
|
"hacs_is_disabled": "HACS er deaktivert",
|
||||||
"installed": "Installert",
|
"installed": "Installert",
|
||||||
"integration": "Integrasjon",
|
"integration": "Integrasjon",
|
||||||
"integrations": "Integrasjonar",
|
"integrations": "Integrasjonar",
|
||||||
"manage": "manage",
|
"manage": "Handtere",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDeamon-appar",
|
||||||
"plugin": "Tillegg",
|
"plugin": "Tillegg",
|
||||||
"plugins": "Tillegg",
|
"plugins": "Tillegg",
|
||||||
"python_script": "Pythonskript",
|
"python_script": "Pythonskript",
|
||||||
@@ -33,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Aktiver AppDeamon-appar-oppdaging og sporing",
|
"appdaemon": "Aktiver AppDeamon-appar-oppdaging og sporing",
|
||||||
|
"netdaemon": "Aktiver NetDeamon-appar-oppdaging og sporing",
|
||||||
"python_script": "Aktiver pythonscript-oppdaging og sporing",
|
"python_script": "Aktiver pythonscript-oppdaging og sporing",
|
||||||
"sidepanel_icon": "Sidepanelikon",
|
"sidepanel_icon": "Sidepanelikon",
|
||||||
"sidepanel_title": "Sidepaneltittel",
|
"sidepanel_title": "Sidepaneltittel",
|
||||||
@@ -47,18 +51,23 @@
|
|||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"add_to_lovelace": "Er du sikker på at du vil legge til dette i Lovelace-ressursane dine?",
|
"add_to_lovelace": "Er du sikker på at du vil legge til dette i Lovelace-ressursane dine?",
|
||||||
|
"bg_task": "Handlinga er deaktivert medan bakgrunnsoppgåveene køyrer.",
|
||||||
"cancel": "Avbryt",
|
"cancel": "Avbryt",
|
||||||
"continue": "Er du sikker på at du vil halde fram?",
|
"continue": "Er du sikker på at du vil halde fram?",
|
||||||
"delete": "Er du sikker på at du vil slette '{item}'?",
|
"delete": "Er du sikker på at du vil slette '{item}'?",
|
||||||
"exist": "{item} eksisterer allerede",
|
"delete_installed": "'{item}' er installert. Du må avinstallere det før du kan slette det.",
|
||||||
|
"exist": "{item} eksisterer allereie",
|
||||||
"generic": "Er du sikker?",
|
"generic": "Er du sikker?",
|
||||||
"home_assistant_is_restarting": "Vent, Home Assistant starter nå på nytt.",
|
"home_assistant_is_restarting": "Vent... Home Assistant starter på nytt no.",
|
||||||
|
"home_assistant_version_not_correct": "Du køyrer Home Assistant-versjonen '{haversion}', men dette kodedepoet krev minimum versjon '{minversion}' for å bli installert.",
|
||||||
"no": "Nei",
|
"no": "Nei",
|
||||||
|
"no_upgrades": "Ingen ventande oppgradringer",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"overwrite": "Ved å gjere dette kjem du til å overskrive.",
|
"overwrite": "Ved å gjere dette kjem du til å overskrive.",
|
||||||
|
"reload_data": "Dette laster inn dataa til depota HACS veit om, og dette vil ta litt tid å fullføre.",
|
||||||
"restart_home_assistant": "Er du sikker på at du vil starte Home Assistant på nytt?",
|
"restart_home_assistant": "Er du sikker på at du vil starte Home Assistant på nytt?",
|
||||||
"uninstall": "Er du sikker på at du vil avinstallere '{item}'?",
|
"uninstall": "Er du sikker på at du vil avinstallere '{item}'?",
|
||||||
"upgrade_all": "Dette vil oppgradere alle disse repositorene, sørg for at du har lest utgivelses notatene for dem alle før du fortsetter.",
|
"upgrade_all": "Dette kjem til å oppgradere alle depota. Ver sikker på at du har lest alle versjonsmerknadene før du held fram.",
|
||||||
"yes": "Ja"
|
"yes": "Ja"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
@@ -69,6 +78,8 @@
|
|||||||
"country": "Filterer med landskode",
|
"country": "Filterer med landskode",
|
||||||
"debug": "Aktiver debug.",
|
"debug": "Aktiver debug.",
|
||||||
"experimental": "Aktiver ekspreimentelle funksjonar",
|
"experimental": "Aktiver ekspreimentelle funksjonar",
|
||||||
|
"netdaemon": "Aktiver NetDeamon-appar-oppdaging og sporing",
|
||||||
|
"not_in_use": "Kan ikkje brukast saman med YAML",
|
||||||
"release_limit": "Talet på utgivingar",
|
"release_limit": "Talet på utgivingar",
|
||||||
"sidepanel_icon": "Sidepanelikon",
|
"sidepanel_icon": "Sidepanelikon",
|
||||||
"sidepanel_title": "Sidepaneltittel"
|
"sidepanel_title": "Sidepaneltittel"
|
||||||
@@ -77,8 +88,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repository_banner": {
|
"repository_banner": {
|
||||||
|
"config_flow": "Denne integrasjonen støttar config_flow, som betyr at du no kan gå til integrasjonssida i brukargrensesnittet for å konfigurere den.",
|
||||||
|
"config_flow_title": "UI-konfigurasjon støtta",
|
||||||
"integration_not_loaded": "Integrasjonen er ikkje lasta inn i Home Assistant.",
|
"integration_not_loaded": "Integrasjonen er ikkje lasta inn i Home Assistant.",
|
||||||
"no_restart_required": "Ingen omstart kreves",
|
"no_restart_required": "Ingen omstart kravd",
|
||||||
"not_loaded": "Ikkje lasta",
|
"not_loaded": "Ikkje lasta",
|
||||||
"plugin_not_loaded": "Tillegget er ikkje lagt til i Lovelace-ressursane dine.",
|
"plugin_not_loaded": "Tillegget er ikkje lagt til i Lovelace-ressursane dine.",
|
||||||
"restart": "Du må starte Home Assistant på nytt",
|
"restart": "Du må starte Home Assistant på nytt",
|
||||||
@@ -92,9 +105,9 @@
|
|||||||
"changelog": "Endre logg",
|
"changelog": "Endre logg",
|
||||||
"downloads": "Nedlastinger",
|
"downloads": "Nedlastinger",
|
||||||
"flag_this": "Marker dette",
|
"flag_this": "Marker dette",
|
||||||
"frontend_version": "Frontend versjon",
|
"frontend_version": "Frontend-versjon",
|
||||||
"github_stars": "GitHub-stjerner",
|
"github_stars": "GitHub-stjerner",
|
||||||
"goto_integrations": "Gå til integrasjoner",
|
"goto_integrations": "Gå til integrasjonar",
|
||||||
"hide": "Gøym",
|
"hide": "Gøym",
|
||||||
"hide_beta": "Gøym beta",
|
"hide_beta": "Gøym beta",
|
||||||
"install": "Installer",
|
"install": "Installer",
|
||||||
@@ -121,7 +134,7 @@
|
|||||||
"add_custom_repository": "LEGG TIL EIN ANNAN REPOSITORY",
|
"add_custom_repository": "LEGG TIL EIN ANNAN REPOSITORY",
|
||||||
"adding_new_repo": "Legger til ny repository '{repo}'",
|
"adding_new_repo": "Legger til ny repository '{repo}'",
|
||||||
"adding_new_repo_category": "Med kategori '{category}'.",
|
"adding_new_repo_category": "Med kategori '{category}'.",
|
||||||
"bg_task_custom": "Custom repositories er skjult mens bakgrunnsoppgaver kjører.",
|
"bg_task_custom": "Custom repositories er skjult medan bakgrunnsoppgaver køyrer.",
|
||||||
"category": "Kategori",
|
"category": "Kategori",
|
||||||
"compact_mode": "Kompaktmodus",
|
"compact_mode": "Kompaktmodus",
|
||||||
"custom_repositories": "VANLEG REPOSITORY",
|
"custom_repositories": "VANLEG REPOSITORY",
|
||||||
@@ -130,10 +143,11 @@
|
|||||||
"grid": "rutenett",
|
"grid": "rutenett",
|
||||||
"hacs_repo": "HACS repo",
|
"hacs_repo": "HACS repo",
|
||||||
"hidden_repositories": "gøymde repositories",
|
"hidden_repositories": "gøymde repositories",
|
||||||
"open_repository": "Åpne repository",
|
"missing_category": "Du må velje éin kategori",
|
||||||
|
"open_repository": "Opne repository",
|
||||||
"reload_data": "Last om dataa",
|
"reload_data": "Last om dataa",
|
||||||
"reload_window": "Last inn vinduet på nytt",
|
"reload_window": "Last inn vindauget på nytt",
|
||||||
"repository_configuration": "Repository konfigurasjon",
|
"repository_configuration": "Repository-konfigurasjon",
|
||||||
"save": "Lagre",
|
"save": "Lagre",
|
||||||
"table": "Tavle",
|
"table": "Tavle",
|
||||||
"table_view": "Tabellvisning",
|
"table_view": "Tabellvisning",
|
||||||
@@ -141,43 +155,43 @@
|
|||||||
"upgrade_all": "Oppdater alle"
|
"upgrade_all": "Oppdater alle"
|
||||||
},
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"ascending": "stigende",
|
"ascending": "stigande",
|
||||||
"clear_new": "Fjern alle nye repositories",
|
"clear_new": "Fjern alle nye repositories",
|
||||||
"descending": "synkende",
|
"descending": "synkande",
|
||||||
"last_updated": "Sist oppdatert",
|
"last_updated": "Sist oppdatert",
|
||||||
"name": "Namn",
|
"name": "Namn",
|
||||||
"new_repositories": "Ny repository",
|
"new_repositories": "Ny repository",
|
||||||
"pending_upgrades": "Venter på oppgraderinger",
|
"pending_upgrades": "Ventande oppgraderinger",
|
||||||
"placeholder_search": "Ver vennleg og skriv inn ei søkefrase",
|
"placeholder_search": "Ver vennleg og skriv inn ei søkefrase",
|
||||||
"sort": "Sorter",
|
"sort": "Sorter",
|
||||||
"stars": "Stjerner",
|
"stars": "Stjerner",
|
||||||
"status": "Status"
|
"status": "Status"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"ago": "siden",
|
"ago": "sidan",
|
||||||
"day": "dag",
|
"day": "dag",
|
||||||
"days": "dager",
|
"days": "dagar",
|
||||||
"hour": "time",
|
"hour": "time",
|
||||||
"hours": "timer",
|
"hours": "timar",
|
||||||
"minute": "minutt",
|
"minute": "minutt",
|
||||||
"minutes": "minutter",
|
"minutes": "minutt",
|
||||||
"month": "måned",
|
"month": "månad",
|
||||||
"months": "måneder",
|
"months": "månadar",
|
||||||
"one": "En",
|
"one": "Éin",
|
||||||
"one_day_ago": "for en dag siden",
|
"one_day_ago": "for éin dag sidan",
|
||||||
"one_hour_ago": "en time siden",
|
"one_hour_ago": "éin time sidan",
|
||||||
"one_minute_ago": "ett minutt siden",
|
"one_minute_ago": "eitt minutt sidan",
|
||||||
"one_month_ago": "en måned siden",
|
"one_month_ago": "ein månad sidan",
|
||||||
"one_second_ago": "ett sekund siden",
|
"one_second_ago": "eitt sekund sidan",
|
||||||
"one_year_ago": "ett år siden",
|
"one_year_ago": "eitt år sidan",
|
||||||
"second": "sekund",
|
"second": "sekund",
|
||||||
"seconds": "sekunder",
|
"seconds": "sekund",
|
||||||
"x_days_ago": "{x} dager siden",
|
"x_days_ago": "{x} dagar siden",
|
||||||
"x_hours_ago": "{x} timer siden",
|
"x_hours_ago": "{x} timer sidan",
|
||||||
"x_minutes_ago": "{x} minutter siden",
|
"x_minutes_ago": "{x} minutt sidan",
|
||||||
"x_months_ago": "{x} måneder siden",
|
"x_months_ago": "{x} månadar sidan",
|
||||||
"x_seconds_ago": "{x} sekunder siden",
|
"x_seconds_ago": "{x} sekund sidan",
|
||||||
"x_years_ago": "{x} år siden",
|
"x_years_ago": "{x} år sidan",
|
||||||
"year": "år",
|
"year": "år",
|
||||||
"years": "år"
|
"years": "år"
|
||||||
}
|
}
|
||||||
|
4
config/custom_components/hacs/.translations/pl.json
Executable file → Normal file
4
config/custom_components/hacs/.translations/pl.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Integracja",
|
"integration": "Integracja",
|
||||||
"integrations": "Integracje",
|
"integrations": "Integracje",
|
||||||
"manage": "zarządzaj",
|
"manage": "zarządzaj",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "Aplikacje NetDaemon",
|
||||||
"plugin": "Wtyczka",
|
"plugin": "Wtyczka",
|
||||||
"plugins": "Wtyczki",
|
"plugins": "Wtyczki",
|
||||||
"python_script": "Skrypt Python",
|
"python_script": "Skrypt Python",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Włącz wykrywanie i śledzenie aplikacji AppDaemon",
|
"appdaemon": "Włącz wykrywanie i śledzenie aplikacji AppDaemon",
|
||||||
|
"netdaemon": "Włącz wykrywanie i śledzenie aplikacji NetDaemon",
|
||||||
"python_script": "Włącz wykrywanie i śledzenie skryptów Python",
|
"python_script": "Włącz wykrywanie i śledzenie skryptów Python",
|
||||||
"sidepanel_icon": "Ikona w panelu bocznym",
|
"sidepanel_icon": "Ikona w panelu bocznym",
|
||||||
"sidepanel_title": "Tytuł w panelu bocznym",
|
"sidepanel_title": "Tytuł w panelu bocznym",
|
||||||
@@ -75,6 +78,7 @@
|
|||||||
"country": "Filtruj według kodu kraju",
|
"country": "Filtruj według kodu kraju",
|
||||||
"debug": "Włącz debugowanie.",
|
"debug": "Włącz debugowanie.",
|
||||||
"experimental": "Włącz funkcje eksperymentalne",
|
"experimental": "Włącz funkcje eksperymentalne",
|
||||||
|
"netdaemon": "Włącz wykrywanie i śledzenie aplikacji NetDaemon",
|
||||||
"not_in_use": "Nieużywany z YAML",
|
"not_in_use": "Nieużywany z YAML",
|
||||||
"release_limit": "Liczba wydań do wyświetlenia",
|
"release_limit": "Liczba wydań do wyświetlenia",
|
||||||
"sidepanel_icon": "Ikona w panelu bocznym",
|
"sidepanel_icon": "Ikona w panelu bocznym",
|
||||||
|
15
config/custom_components/hacs/.translations/pt-BR.json
Executable file → Normal file
15
config/custom_components/hacs/.translations/pt-BR.json
Executable file → Normal file
@@ -12,6 +12,8 @@
|
|||||||
"integration": "Integração",
|
"integration": "Integração",
|
||||||
"integrations": "Integrações",
|
"integrations": "Integrações",
|
||||||
"manage": "gerenciar",
|
"manage": "gerenciar",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Apps",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
"python_script": "Python Script",
|
"python_script": "Python Script",
|
||||||
@@ -33,6 +35,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Habilitar AppDaemon apps descoberta & rastreamento",
|
"appdaemon": "Habilitar AppDaemon apps descoberta & rastreamento",
|
||||||
|
"netdaemon": "Habilitar NetDaemon apps descoberta & rastreamento",
|
||||||
"python_script": "Habilitar python_scripts descoberta & rastreamento",
|
"python_script": "Habilitar python_scripts descoberta & rastreamento",
|
||||||
"sidepanel_icon": "Icone do painel lateral",
|
"sidepanel_icon": "Icone do painel lateral",
|
||||||
"sidepanel_title": "Titulo do painel lateral",
|
"sidepanel_title": "Titulo do painel lateral",
|
||||||
@@ -47,10 +50,13 @@
|
|||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
|
"continue": "Tem certeza que quer continuar?",
|
||||||
"delete": "Tem certeza de que deseja excluir '{item}'?",
|
"delete": "Tem certeza de que deseja excluir '{item}'?",
|
||||||
"exist": "{item} já existe",
|
"exist": "{item} já existe",
|
||||||
|
"home_assistant_is_restarting": "Espere, o Home Assistant está agora a reiniciar.",
|
||||||
"no": "Não",
|
"no": "Não",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
|
"restart_home_assistant": "Tem certeza de que deseja reiniciar o Home Assistant?",
|
||||||
"uninstall": "Tem certeza de que deseja desinstalar '{item}'?",
|
"uninstall": "Tem certeza de que deseja desinstalar '{item}'?",
|
||||||
"yes": "Sim"
|
"yes": "Sim"
|
||||||
},
|
},
|
||||||
@@ -61,6 +67,8 @@
|
|||||||
"appdaemon": "Habilitar AppDaemon apps descoberta & rastreamento",
|
"appdaemon": "Habilitar AppDaemon apps descoberta & rastreamento",
|
||||||
"country": "Filtro pelo código do país.",
|
"country": "Filtro pelo código do país.",
|
||||||
"experimental": "Ativar recursos experimentais",
|
"experimental": "Ativar recursos experimentais",
|
||||||
|
"netdaemon": "Habilitar NetDaemon apps descoberta & rastreamento",
|
||||||
|
"not_in_use": "Não está em uso com o YAML",
|
||||||
"release_limit": "Número de lançamentos a serem exibidos.",
|
"release_limit": "Número de lançamentos a serem exibidos.",
|
||||||
"sidepanel_icon": "Icone do painel lateral",
|
"sidepanel_icon": "Icone do painel lateral",
|
||||||
"sidepanel_title": "Titulo do painel lateral"
|
"sidepanel_title": "Titulo do painel lateral"
|
||||||
@@ -69,6 +77,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repository_banner": {
|
"repository_banner": {
|
||||||
|
"integration_not_loaded": "Esta integração não é carregada no Home Assistant.",
|
||||||
"no_restart_required": "Não é necessário reiniciar",
|
"no_restart_required": "Não é necessário reiniciar",
|
||||||
"not_loaded": "Não carregado",
|
"not_loaded": "Não carregado",
|
||||||
"restart": "Você precisa reiniciar o Home Assistant.",
|
"restart": "Você precisa reiniciar o Home Assistant.",
|
||||||
@@ -80,6 +89,7 @@
|
|||||||
"available": "Disponível",
|
"available": "Disponível",
|
||||||
"back_to": "Voltar para",
|
"back_to": "Voltar para",
|
||||||
"changelog": "Changelog",
|
"changelog": "Changelog",
|
||||||
|
"downloads": "Downloads",
|
||||||
"flag_this": "Sinalizar isso",
|
"flag_this": "Sinalizar isso",
|
||||||
"frontend_version": "Versão Frontend",
|
"frontend_version": "Versão Frontend",
|
||||||
"github_stars": "Estrelas de GitHub",
|
"github_stars": "Estrelas de GitHub",
|
||||||
@@ -118,12 +128,14 @@
|
|||||||
"grid": "Grade",
|
"grid": "Grade",
|
||||||
"hacs_repo": "HACS repo",
|
"hacs_repo": "HACS repo",
|
||||||
"hidden_repositories": "repositórios ocultos",
|
"hidden_repositories": "repositórios ocultos",
|
||||||
|
"missing_category": "Você precisa selecionar uma categoria",
|
||||||
"open_repository": "Repositório aberto",
|
"open_repository": "Repositório aberto",
|
||||||
"reload_data": "Recarregar dados",
|
"reload_data": "Recarregar dados",
|
||||||
"reload_window": "Recarregar janela",
|
"reload_window": "Recarregar janela",
|
||||||
"repository_configuration": "Configuração do Repositório",
|
"repository_configuration": "Configuração do Repositório",
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"table": "Tabela",
|
"table": "Tabela",
|
||||||
|
"table_view": "Vista de mesa",
|
||||||
"unhide": "reexibir",
|
"unhide": "reexibir",
|
||||||
"upgrade_all": "Atualizar tudo"
|
"upgrade_all": "Atualizar tudo"
|
||||||
},
|
},
|
||||||
@@ -136,7 +148,8 @@
|
|||||||
"new_repositories": "Novos Repositórios",
|
"new_repositories": "Novos Repositórios",
|
||||||
"pending_upgrades": "Atualizações pendentes",
|
"pending_upgrades": "Atualizações pendentes",
|
||||||
"placeholder_search": "Por favor insira um termo de pesquisa...",
|
"placeholder_search": "Por favor insira um termo de pesquisa...",
|
||||||
"stars": "Estrelas"
|
"stars": "Estrelas",
|
||||||
|
"status": "Status"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"ago": "atrás",
|
"ago": "atrás",
|
||||||
|
17
config/custom_components/hacs/.translations/ro.json
Executable file → Normal file
17
config/custom_components/hacs/.translations/ro.json
Executable file → Normal file
@@ -11,6 +11,8 @@
|
|||||||
"installed": "instalat",
|
"installed": "instalat",
|
||||||
"integration": "Integrare",
|
"integration": "Integrare",
|
||||||
"integrations": "Integrări",
|
"integrations": "Integrări",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "Aplicații NetDaemon",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugin-uri",
|
"plugins": "Plugin-uri",
|
||||||
"python_script": "Script Python",
|
"python_script": "Script Python",
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Activați descoperirea și urmărirea aplicațiilor AppDaemon",
|
"appdaemon": "Activați descoperirea și urmărirea aplicațiilor AppDaemon",
|
||||||
|
"netdaemon": "Activați descoperirea și urmărirea aplicațiilor NetDaemon",
|
||||||
"python_script": "Activați descoperirea și urmărirea python_scripts",
|
"python_script": "Activați descoperirea și urmărirea python_scripts",
|
||||||
"sidepanel_icon": "Pictogramă Panou lateral",
|
"sidepanel_icon": "Pictogramă Panou lateral",
|
||||||
"sidepanel_title": "Titlu panou lateral",
|
"sidepanel_title": "Titlu panou lateral",
|
||||||
@@ -45,6 +48,7 @@
|
|||||||
"title": "HACS (Home Assistant Community Store)"
|
"title": "HACS (Home Assistant Community Store)"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
|
"bg_task": "Acțiunea este dezactivată în timp ce activitățile de fundal se execută.",
|
||||||
"cancel": "Anulare",
|
"cancel": "Anulare",
|
||||||
"delete": "Sigur doriți să ștergeți '{item}'?",
|
"delete": "Sigur doriți să ștergeți '{item}'?",
|
||||||
"exist": "{item} există deja",
|
"exist": "{item} există deja",
|
||||||
@@ -60,6 +64,7 @@
|
|||||||
"appdaemon": "Activați descoperirea și urmărirea aplicațiilor AppDaemon",
|
"appdaemon": "Activați descoperirea și urmărirea aplicațiilor AppDaemon",
|
||||||
"country": "Filtrează cu codul țării.",
|
"country": "Filtrează cu codul țării.",
|
||||||
"experimental": "Activați funcțiile experimentale",
|
"experimental": "Activați funcțiile experimentale",
|
||||||
|
"netdaemon": "Activați descoperirea și urmărirea aplicațiilor NetDaemon",
|
||||||
"not_in_use": "Nu este utilizat cu YAML",
|
"not_in_use": "Nu este utilizat cu YAML",
|
||||||
"release_limit": "Număr de versiuni afișate.",
|
"release_limit": "Număr de versiuni afișate.",
|
||||||
"sidepanel_icon": "Pictogramă Panou lateral",
|
"sidepanel_icon": "Pictogramă Panou lateral",
|
||||||
@@ -69,8 +74,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repository_banner": {
|
"repository_banner": {
|
||||||
|
"integration_not_loaded": "Această integrare nu este încărcată în Home Assistant.",
|
||||||
"no_restart_required": "Nu este necesară repornirea",
|
"no_restart_required": "Nu este necesară repornirea",
|
||||||
"restart": "Trebuie să reporniți Home Assistant."
|
"restart": "Trebuie să reporniți Home Assistant.",
|
||||||
|
"restart_pending": "Reporniți în așteptare"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"add_to_lovelace": "Adăugați la Lovelace",
|
"add_to_lovelace": "Adăugați la Lovelace",
|
||||||
@@ -81,6 +88,7 @@
|
|||||||
"downloads": "Descărcări",
|
"downloads": "Descărcări",
|
||||||
"flag_this": "Semnalizează",
|
"flag_this": "Semnalizează",
|
||||||
"frontend_version": "Versiune frontend",
|
"frontend_version": "Versiune frontend",
|
||||||
|
"github_stars": "Stele GitHub",
|
||||||
"hide": "Ascunde",
|
"hide": "Ascunde",
|
||||||
"hide_beta": "Ascundere beta",
|
"hide_beta": "Ascundere beta",
|
||||||
"install": "Instalează",
|
"install": "Instalează",
|
||||||
@@ -97,6 +105,7 @@
|
|||||||
"open_plugin": "Deschide plugin",
|
"open_plugin": "Deschide plugin",
|
||||||
"reinstall": "Reinstalare",
|
"reinstall": "Reinstalare",
|
||||||
"repository": "Depozit",
|
"repository": "Depozit",
|
||||||
|
"restart_home_assistant": "Reporniți Home Assistant",
|
||||||
"show_beta": "Afișare beta",
|
"show_beta": "Afișare beta",
|
||||||
"uninstall": "Dezinstalare",
|
"uninstall": "Dezinstalare",
|
||||||
"update_information": "Actualizare informație",
|
"update_information": "Actualizare informație",
|
||||||
@@ -114,8 +123,11 @@
|
|||||||
"grid": "Grilă",
|
"grid": "Grilă",
|
||||||
"hacs_repo": "HACS repo",
|
"hacs_repo": "HACS repo",
|
||||||
"hidden_repositories": "depozite ascunse",
|
"hidden_repositories": "depozite ascunse",
|
||||||
|
"missing_category": "Trebuie să selectați o categorie",
|
||||||
|
"open_repository": "Deschideți depozitul",
|
||||||
"reload_data": "Reîncărcați datele",
|
"reload_data": "Reîncărcați datele",
|
||||||
"reload_window": "Reîncărcați fereastra",
|
"reload_window": "Reîncărcați fereastra",
|
||||||
|
"repository_configuration": "Configurația depozitului",
|
||||||
"save": "Salveaza",
|
"save": "Salveaza",
|
||||||
"table": "Tabel",
|
"table": "Tabel",
|
||||||
"table_view": "Vizualizare tabel",
|
"table_view": "Vizualizare tabel",
|
||||||
@@ -130,7 +142,8 @@
|
|||||||
"new_repositories": "Noi depozite",
|
"new_repositories": "Noi depozite",
|
||||||
"placeholder_search": "Vă rugăm să introduceți un termen de căutare ...",
|
"placeholder_search": "Vă rugăm să introduceți un termen de căutare ...",
|
||||||
"sort": "fel",
|
"sort": "fel",
|
||||||
"stars": "Stele"
|
"stars": "Stele",
|
||||||
|
"status": "Starea"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"ago": "în urmă",
|
"ago": "în urmă",
|
||||||
|
8
config/custom_components/hacs/.translations/ru.json
Executable file → Normal file
8
config/custom_components/hacs/.translations/ru.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Интеграция",
|
"integration": "Интеграция",
|
||||||
"integrations": "Интеграции",
|
"integrations": "Интеграции",
|
||||||
"manage": "управлять",
|
"manage": "управлять",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "Приложения NetDaemon",
|
||||||
"plugin": "Плагин",
|
"plugin": "Плагин",
|
||||||
"plugins": "Плагины",
|
"plugins": "Плагины",
|
||||||
"python_script": "Скрипт Python",
|
"python_script": "Скрипт Python",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Включить поиск и отслеживание приложений AppDaemon",
|
"appdaemon": "Включить поиск и отслеживание приложений AppDaemon",
|
||||||
|
"netdaemon": "Включить поиск и отслеживание приложений NetDaemon",
|
||||||
"python_script": "Включить поиск и отслеживание скриптов Python",
|
"python_script": "Включить поиск и отслеживание скриптов Python",
|
||||||
"sidepanel_icon": "Иконка в боковом меню",
|
"sidepanel_icon": "Иконка в боковом меню",
|
||||||
"sidepanel_title": "Название в боковом меню",
|
"sidepanel_title": "Название в боковом меню",
|
||||||
@@ -69,9 +72,10 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Включить поиск и отслеживание приложений AppDaemon",
|
"appdaemon": "Включить поиск и отслеживание приложений AppDaemon",
|
||||||
"country": "Фильтр с кодом страны.",
|
"country": "Фильтр по стране.",
|
||||||
"debug": "Включить отладку.",
|
"debug": "Включить отладку.",
|
||||||
"experimental": "Включить экспериментальные функции",
|
"experimental": "Вкл. экспериментальные функции",
|
||||||
|
"netdaemon": "Включить поиск и отслеживание приложений NetDaemon",
|
||||||
"not_in_use": "Не используется с YAML",
|
"not_in_use": "Не используется с YAML",
|
||||||
"release_limit": "Число доступных версий.",
|
"release_limit": "Число доступных версий.",
|
||||||
"sidepanel_icon": "Иконка в боковом меню",
|
"sidepanel_icon": "Иконка в боковом меню",
|
||||||
|
6
config/custom_components/hacs/.translations/sl.json
Executable file → Normal file
6
config/custom_components/hacs/.translations/sl.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "Integracija",
|
"integration": "Integracija",
|
||||||
"integrations": "Integracije",
|
"integrations": "Integracije",
|
||||||
"manage": "upravljanje",
|
"manage": "upravljanje",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Aplikacije",
|
||||||
"plugin": "vtičnik",
|
"plugin": "vtičnik",
|
||||||
"plugins": "Vtičniki",
|
"plugins": "Vtičniki",
|
||||||
"python_script": "Python skripta",
|
"python_script": "Python skripta",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Omogoči odkrivanje in sledenje aplikacij AppDaemon",
|
"appdaemon": "Omogoči odkrivanje in sledenje aplikacij AppDaemon",
|
||||||
|
"netdaemon": "Omogoči odkrivanje in sledenje aplikacij NetDaemon",
|
||||||
"python_script": "Omogoči odkrivanje in sledenje python_scripts",
|
"python_script": "Omogoči odkrivanje in sledenje python_scripts",
|
||||||
"sidepanel_icon": "Ikona stranske plošče",
|
"sidepanel_icon": "Ikona stranske plošče",
|
||||||
"sidepanel_title": "Naslov stranske plošče",
|
"sidepanel_title": "Naslov stranske plošče",
|
||||||
@@ -56,6 +59,7 @@
|
|||||||
"exist": "{item} že obstaja",
|
"exist": "{item} že obstaja",
|
||||||
"generic": "Ali si prepričan?",
|
"generic": "Ali si prepričan?",
|
||||||
"home_assistant_is_restarting": "Počakaj, Home Assistant se zdaj znova zaganja.",
|
"home_assistant_is_restarting": "Počakaj, Home Assistant se zdaj znova zaganja.",
|
||||||
|
"home_assistant_version_not_correct": "Uporabljate Home Assistant verzije '{haversion}', vendar to skladišče zahteva nameščeno najmanj različico '{minversion}'.",
|
||||||
"no": "Ne",
|
"no": "Ne",
|
||||||
"no_upgrades": "Ni nadgradenj",
|
"no_upgrades": "Ni nadgradenj",
|
||||||
"ok": "V redu",
|
"ok": "V redu",
|
||||||
@@ -74,6 +78,7 @@
|
|||||||
"country": "Filtrirajte s kodo države.",
|
"country": "Filtrirajte s kodo države.",
|
||||||
"debug": "Omogoči odpravljanje napak.",
|
"debug": "Omogoči odpravljanje napak.",
|
||||||
"experimental": "Omogočite poskusne funkcije",
|
"experimental": "Omogočite poskusne funkcije",
|
||||||
|
"netdaemon": "Omogoči odkrivanje in sledenje aplikacij NetDaemon",
|
||||||
"not_in_use": "Ni v uporabi z YAML",
|
"not_in_use": "Ni v uporabi z YAML",
|
||||||
"release_limit": "Število izdaj, ki jih želite prikazati.",
|
"release_limit": "Število izdaj, ki jih želite prikazati.",
|
||||||
"sidepanel_icon": "Ikona stranske plošče",
|
"sidepanel_icon": "Ikona stranske plošče",
|
||||||
@@ -84,6 +89,7 @@
|
|||||||
},
|
},
|
||||||
"repository_banner": {
|
"repository_banner": {
|
||||||
"config_flow": "Ta integracija podpira config_flow, kar pomeni, da lahko zdaj greste na odsek integracije vašega uporabniškega vmesnika, da ga konfigurirate.",
|
"config_flow": "Ta integracija podpira config_flow, kar pomeni, da lahko zdaj greste na odsek integracije vašega uporabniškega vmesnika, da ga konfigurirate.",
|
||||||
|
"config_flow_title": "Konfiguracija uporabniškega vmesnika je podprta",
|
||||||
"integration_not_loaded": "Ta integracija ni naložena v programu Home Assistant.",
|
"integration_not_loaded": "Ta integracija ni naložena v programu Home Assistant.",
|
||||||
"no_restart_required": "Ponovni zagon ni potreben",
|
"no_restart_required": "Ponovni zagon ni potreben",
|
||||||
"not_loaded": "Ni naloženo",
|
"not_loaded": "Ni naloženo",
|
||||||
|
7
config/custom_components/hacs/.translations/sv.json
Executable file → Normal file
7
config/custom_components/hacs/.translations/sv.json
Executable file → Normal file
@@ -11,6 +11,8 @@
|
|||||||
"installed": "installerad",
|
"installed": "installerad",
|
||||||
"integration": "Integration",
|
"integration": "Integration",
|
||||||
"integrations": "Integrationer",
|
"integrations": "Integrationer",
|
||||||
|
"netdaemon": "NetDaemon",
|
||||||
|
"netdaemon_apps": "NetDaemon Applikationer",
|
||||||
"plugin": "Plugin",
|
"plugin": "Plugin",
|
||||||
"plugins": "Plugins",
|
"plugins": "Plugins",
|
||||||
"python_script": "Python skript",
|
"python_script": "Python skript",
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "Upptäck och följ Appdaemon applikationer",
|
"appdaemon": "Upptäck och följ Appdaemon applikationer",
|
||||||
|
"netdaemon": "Upptäck och följ NetDaemon applikationer",
|
||||||
"python_script": "Upptäck och följ python_scripts",
|
"python_script": "Upptäck och följ python_scripts",
|
||||||
"sidepanel_icon": "Ikon för sidpanel",
|
"sidepanel_icon": "Ikon för sidpanel",
|
||||||
"sidepanel_title": "Rubrik för sidpanel",
|
"sidepanel_title": "Rubrik för sidpanel",
|
||||||
@@ -70,6 +73,7 @@
|
|||||||
"country": "Filtrera på landskod.",
|
"country": "Filtrera på landskod.",
|
||||||
"debug": "Aktivera felsökning",
|
"debug": "Aktivera felsökning",
|
||||||
"experimental": "Använd experimentella funktioner",
|
"experimental": "Använd experimentella funktioner",
|
||||||
|
"netdaemon": "Upptäck och följ NetDaemon applikationer",
|
||||||
"release_limit": "Antalet releaser som visas.",
|
"release_limit": "Antalet releaser som visas.",
|
||||||
"sidepanel_icon": "Ikon för sidpanel",
|
"sidepanel_icon": "Ikon för sidpanel",
|
||||||
"sidepanel_title": "Rubrik för sidpanel"
|
"sidepanel_title": "Rubrik för sidpanel"
|
||||||
@@ -112,6 +116,7 @@
|
|||||||
"open_plugin": "Öppna plugin",
|
"open_plugin": "Öppna plugin",
|
||||||
"reinstall": "Ominstallera",
|
"reinstall": "Ominstallera",
|
||||||
"repository": "Repository",
|
"repository": "Repository",
|
||||||
|
"restart_home_assistant": "Starta om Home Assistant",
|
||||||
"show_beta": "Visa betaversioner",
|
"show_beta": "Visa betaversioner",
|
||||||
"uninstall": "Avinstallera",
|
"uninstall": "Avinstallera",
|
||||||
"update_information": "Uppdatera information",
|
"update_information": "Uppdatera information",
|
||||||
@@ -131,6 +136,7 @@
|
|||||||
"hacs_repo": "HACS repo",
|
"hacs_repo": "HACS repo",
|
||||||
"hidden_repositories": "dolda förråd",
|
"hidden_repositories": "dolda förråd",
|
||||||
"missing_category": "Du behöver välja en kategori",
|
"missing_category": "Du behöver välja en kategori",
|
||||||
|
"open_repository": "Öppna Repository",
|
||||||
"reload_data": "Ladda om data",
|
"reload_data": "Ladda om data",
|
||||||
"reload_window": "Ladda om fönstret",
|
"reload_window": "Ladda om fönstret",
|
||||||
"save": "Spara",
|
"save": "Spara",
|
||||||
@@ -167,6 +173,7 @@
|
|||||||
"one_minute_ago": "en minut sedan",
|
"one_minute_ago": "en minut sedan",
|
||||||
"one_month_ago": "en månad sedan",
|
"one_month_ago": "en månad sedan",
|
||||||
"one_second_ago": "för en sekund sedan",
|
"one_second_ago": "för en sekund sedan",
|
||||||
|
"one_year_ago": "ett år sedan",
|
||||||
"second": "andra",
|
"second": "andra",
|
||||||
"seconds": "sekunder",
|
"seconds": "sekunder",
|
||||||
"x_days_ago": "{x} dagar sedan",
|
"x_days_ago": "{x} dagar sedan",
|
||||||
|
14
config/custom_components/hacs/.translations/zh-Hans.json
Executable file → Normal file
14
config/custom_components/hacs/.translations/zh-Hans.json
Executable file → Normal file
@@ -13,6 +13,8 @@
|
|||||||
"integration": "自定义组件",
|
"integration": "自定义组件",
|
||||||
"integrations": "自定义组件",
|
"integrations": "自定义组件",
|
||||||
"manage": "管理",
|
"manage": "管理",
|
||||||
|
"netdaemon": "NetDaemon应用",
|
||||||
|
"netdaemon_apps": "NetDaemon应用",
|
||||||
"plugin": "Lovelace插件",
|
"plugin": "Lovelace插件",
|
||||||
"plugins": "Lovelace插件",
|
"plugins": "Lovelace插件",
|
||||||
"python_script": "Python脚本",
|
"python_script": "Python脚本",
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"appdaemon": "启用AppDaemon应用发现和跟踪",
|
"appdaemon": "启用AppDaemon应用发现和跟踪",
|
||||||
|
"netdaemon": "启用NetDaemon应用发现和跟踪",
|
||||||
"python_script": "启用python_scripts发现和跟踪",
|
"python_script": "启用python_scripts发现和跟踪",
|
||||||
"sidepanel_icon": "侧面板图标",
|
"sidepanel_icon": "侧面板图标",
|
||||||
"sidepanel_title": "侧面板标题",
|
"sidepanel_title": "侧面板标题",
|
||||||
@@ -52,12 +55,12 @@
|
|||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"continue": "你确定你要继续吗?",
|
"continue": "你确定你要继续吗?",
|
||||||
"delete": "是否确实要删除\"{item}\"?",
|
"delete": "是否确实要删除\"{item}\"?",
|
||||||
"delete_installed": "已安装“ {item}”,需要先将其卸载,然后才能将其删除。",
|
"delete_installed": "已安装 '{item}',需要先将其卸载,然后才能将其删除。",
|
||||||
"exist": "{item}已经存在",
|
"exist": "{item}已经存在",
|
||||||
"generic": "你确定吗?",
|
"generic": "你确定吗?",
|
||||||
"home_assistant_is_restarting": "请等待,Home Assistant现在正在重新启动。",
|
"home_assistant_is_restarting": "请等待,Home Assistant现在正在重新启动。",
|
||||||
"home_assistant_version_not_correct": "您正在运行Home Assistant的版本为'{haversion}',但是这个需要安装最低版本是'{minversion}'。",
|
"home_assistant_version_not_correct": "您正在运行Home Assistant的版本为'{haversion}',但是这个需要安装最低版本是'{minversion}'。",
|
||||||
"no": "不",
|
"no": "取消",
|
||||||
"no_upgrades": "暂无升级",
|
"no_upgrades": "暂无升级",
|
||||||
"ok": "确定",
|
"ok": "确定",
|
||||||
"overwrite": "这样做会覆盖它。",
|
"overwrite": "这样做会覆盖它。",
|
||||||
@@ -65,7 +68,7 @@
|
|||||||
"restart_home_assistant": "您确定要重新启动Home Assistant吗?",
|
"restart_home_assistant": "您确定要重新启动Home Assistant吗?",
|
||||||
"uninstall": "您确定要卸载“ {item} ”吗?",
|
"uninstall": "您确定要卸载“ {item} ”吗?",
|
||||||
"upgrade_all": "这将升级所有这些仓库,请确保在继续之前已阅读所有仓库的发行说明。",
|
"upgrade_all": "这将升级所有这些仓库,请确保在继续之前已阅读所有仓库的发行说明。",
|
||||||
"yes": "是"
|
"yes": "确定"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"step": {
|
"step": {
|
||||||
@@ -75,6 +78,7 @@
|
|||||||
"country": "用国家代码过滤。",
|
"country": "用国家代码过滤。",
|
||||||
"debug": "启用调试。",
|
"debug": "启用调试。",
|
||||||
"experimental": "启用实验功能",
|
"experimental": "启用实验功能",
|
||||||
|
"netdaemon": "启用NetDaemon应用发现和跟踪",
|
||||||
"not_in_use": "不适用于 YAML",
|
"not_in_use": "不适用于 YAML",
|
||||||
"release_limit": "要显示的发行数量。",
|
"release_limit": "要显示的发行数量。",
|
||||||
"sidepanel_icon": "侧面板图标",
|
"sidepanel_icon": "侧面板图标",
|
||||||
@@ -128,8 +132,8 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"add_custom_repository": "添加自定义仓库",
|
"add_custom_repository": "添加自定义仓库",
|
||||||
"adding_new_repo": "添加新的仓库“ {repo}”",
|
"adding_new_repo": "添加新的仓库 '{repo}'",
|
||||||
"adding_new_repo_category": "类别为“ {category}”。",
|
"adding_new_repo_category": "类别为 '{category}'。",
|
||||||
"bg_task_custom": "自定义仓库在后台任务运行时被隐藏。",
|
"bg_task_custom": "自定义仓库在后台任务运行时被隐藏。",
|
||||||
"category": "类别",
|
"category": "类别",
|
||||||
"compact_mode": "紧凑模式",
|
"compact_mode": "紧凑模式",
|
||||||
|
105
config/custom_components/hacs/__init__.py
Executable file → Normal file
105
config/custom_components/hacs/__init__.py
Executable file → Normal file
@@ -4,6 +4,7 @@ Custom element manager for community created elements.
|
|||||||
For more details about this integration, please refer to the documentation at
|
For more details about this integration, please refer to the documentation at
|
||||||
https://hacs.xyz/
|
https://hacs.xyz/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from aiogithubapi import AIOGitHub
|
from aiogithubapi import AIOGitHub
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
@@ -14,13 +15,23 @@ from homeassistant.exceptions import ConfigEntryNotReady, ServiceNotFound
|
|||||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
|
||||||
from .configuration_schema import hacs_base_config_schema, hacs_config_option_schema
|
from custom_components.hacs.configuration_schema import (
|
||||||
from .const import DOMAIN, ELEMENT_TYPES, STARTUP, VERSION
|
hacs_base_config_schema,
|
||||||
from .constrains import check_constans
|
hacs_config_option_schema,
|
||||||
from .hacsbase import Hacs
|
)
|
||||||
from .hacsbase.configuration import Configuration
|
from custom_components.hacs.const import DOMAIN, ELEMENT_TYPES, STARTUP, VERSION
|
||||||
from .hacsbase.data import HacsData
|
from custom_components.hacs.constrains import check_constans, check_requirements
|
||||||
from .setup import add_sensor, load_hacs_repository, setup_frontend
|
from custom_components.hacs.hacsbase.configuration import Configuration
|
||||||
|
from custom_components.hacs.hacsbase.data import HacsData
|
||||||
|
from custom_components.hacs.setup import (
|
||||||
|
add_sensor,
|
||||||
|
load_hacs_repository,
|
||||||
|
setup_frontend,
|
||||||
|
)
|
||||||
|
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
|
|
||||||
|
from custom_components.hacs.helpers.network import internet_connectivity_check
|
||||||
|
|
||||||
SCHEMA = hacs_base_config_schema()
|
SCHEMA = hacs_base_config_schema()
|
||||||
SCHEMA[vol.Optional("options")] = hacs_config_option_schema()
|
SCHEMA[vol.Optional("options")] = hacs_config_option_schema()
|
||||||
@@ -29,16 +40,18 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: SCHEMA}, extra=vol.ALLOW_EXTRA)
|
|||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up this integration using yaml."""
|
"""Set up this integration using yaml."""
|
||||||
|
hacs = get_hacs()
|
||||||
if DOMAIN not in config:
|
if DOMAIN not in config:
|
||||||
return True
|
return True
|
||||||
hass.data[DOMAIN] = config
|
hass.data[DOMAIN] = config
|
||||||
Hacs.hass = hass
|
hacs.hass = hass
|
||||||
Hacs.configuration = Configuration.from_dict(
|
hacs.session = async_create_clientsession(hass)
|
||||||
|
hacs.configuration = Configuration.from_dict(
|
||||||
config[DOMAIN], config[DOMAIN].get("options")
|
config[DOMAIN], config[DOMAIN].get("options")
|
||||||
)
|
)
|
||||||
Hacs.configuration.config = config
|
hacs.configuration.config = config
|
||||||
Hacs.configuration.config_type = "yaml"
|
hacs.configuration.config_type = "yaml"
|
||||||
await startup_wrapper_for_yaml(Hacs)
|
await startup_wrapper_for_yaml()
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.flow.async_init(
|
hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={}
|
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={}
|
||||||
@@ -49,6 +62,7 @@ async def async_setup(hass, config):
|
|||||||
|
|
||||||
async def async_setup_entry(hass, config_entry):
|
async def async_setup_entry(hass, config_entry):
|
||||||
"""Set up this integration using UI."""
|
"""Set up this integration using UI."""
|
||||||
|
hacs = get_hacs()
|
||||||
conf = hass.data.get(DOMAIN)
|
conf = hass.data.get(DOMAIN)
|
||||||
if config_entry.source == config_entries.SOURCE_IMPORT:
|
if config_entry.source == config_entries.SOURCE_IMPORT:
|
||||||
if conf is None:
|
if conf is None:
|
||||||
@@ -56,25 +70,26 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
hass.config_entries.async_remove(config_entry.entry_id)
|
hass.config_entries.async_remove(config_entry.entry_id)
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
Hacs.hass = hass
|
hacs.hass = hass
|
||||||
|
hacs.session = async_create_clientsession(hass)
|
||||||
Hacs.configuration = Configuration.from_dict(
|
hacs.configuration = Configuration.from_dict(
|
||||||
config_entry.data, config_entry.options
|
config_entry.data, config_entry.options
|
||||||
)
|
)
|
||||||
Hacs.configuration.config_type = "flow"
|
hacs.configuration.config_type = "flow"
|
||||||
Hacs.configuration.config_entry = config_entry
|
hacs.configuration.config_entry = config_entry
|
||||||
config_entry.add_update_listener(reload_hacs)
|
config_entry.add_update_listener(reload_hacs)
|
||||||
startup_result = await hacs_startup(Hacs)
|
startup_result = await hacs_startup()
|
||||||
if not startup_result:
|
if not startup_result:
|
||||||
Hacs.system.disabled = True
|
hacs.system.disabled = True
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
Hacs.system.disabled = False
|
hacs.system.disabled = False
|
||||||
return startup_result
|
return startup_result
|
||||||
|
|
||||||
|
|
||||||
async def startup_wrapper_for_yaml(hacs):
|
async def startup_wrapper_for_yaml():
|
||||||
"""Startup wrapper for yaml config."""
|
"""Startup wrapper for yaml config."""
|
||||||
startup_result = await hacs_startup(hacs)
|
hacs = get_hacs()
|
||||||
|
startup_result = await hacs_startup()
|
||||||
if not startup_result:
|
if not startup_result:
|
||||||
hacs.system.disabled = True
|
hacs.system.disabled = True
|
||||||
hacs.hass.components.frontend.async_remove_panel(
|
hacs.hass.components.frontend.async_remove_panel(
|
||||||
@@ -83,13 +98,16 @@ async def startup_wrapper_for_yaml(hacs):
|
|||||||
.replace("-", "_")
|
.replace("-", "_")
|
||||||
)
|
)
|
||||||
hacs.logger.info("Could not setup HACS, trying again in 15 min")
|
hacs.logger.info("Could not setup HACS, trying again in 15 min")
|
||||||
async_call_later(hacs.hass, 900, startup_wrapper_for_yaml(hacs))
|
async_call_later(hacs.hass, 900, startup_wrapper_for_yaml())
|
||||||
return
|
return
|
||||||
hacs.system.disabled = False
|
hacs.system.disabled = False
|
||||||
|
|
||||||
|
|
||||||
async def hacs_startup(hacs):
|
async def hacs_startup():
|
||||||
"""HACS startup tasks."""
|
"""HACS startup tasks."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
if not check_requirements():
|
||||||
|
return False
|
||||||
if hacs.configuration.debug:
|
if hacs.configuration.debug:
|
||||||
try:
|
try:
|
||||||
await hacs.hass.services.async_call(
|
await hacs.hass.services.async_call(
|
||||||
@@ -115,20 +133,21 @@ async def hacs_startup(hacs):
|
|||||||
hacs.data = HacsData()
|
hacs.data = HacsData()
|
||||||
|
|
||||||
# Check HACS Constrains
|
# Check HACS Constrains
|
||||||
if not await hacs.hass.async_add_executor_job(check_constans, hacs):
|
if not await hacs.hass.async_add_executor_job(check_constans):
|
||||||
if hacs.configuration.config_type == "flow":
|
if hacs.configuration.config_type == "flow":
|
||||||
if hacs.configuration.config_entry is not None:
|
if hacs.configuration.config_entry is not None:
|
||||||
await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
|
await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Set up frontend
|
# Set up frontend
|
||||||
await setup_frontend(hacs)
|
await setup_frontend()
|
||||||
|
|
||||||
# Set up sensor
|
if not await hacs.hass.async_add_executor_job(internet_connectivity_check):
|
||||||
await hacs.hass.async_add_executor_job(add_sensor, hacs)
|
hacs.logger.critical("No network connectivity")
|
||||||
|
return False
|
||||||
|
|
||||||
# Load HACS
|
# Load HACS
|
||||||
if not await load_hacs_repository(hacs):
|
if not await load_hacs_repository():
|
||||||
if hacs.configuration.config_type == "flow":
|
if hacs.configuration.config_type == "flow":
|
||||||
if hacs.configuration.config_entry is not None:
|
if hacs.configuration.config_entry is not None:
|
||||||
await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
|
await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
|
||||||
@@ -136,7 +155,7 @@ async def hacs_startup(hacs):
|
|||||||
|
|
||||||
# Restore from storefiles
|
# Restore from storefiles
|
||||||
if not await hacs.data.restore():
|
if not await hacs.data.restore():
|
||||||
hacs_repo = hacs().get_by_name("hacs/integration")
|
hacs_repo = hacs.get_by_name("hacs/integration")
|
||||||
hacs_repo.pending_restart = True
|
hacs_repo.pending_restart = True
|
||||||
if hacs.configuration.config_type == "flow":
|
if hacs.configuration.config_type == "flow":
|
||||||
if hacs.configuration.config_entry is not None:
|
if hacs.configuration.config_entry is not None:
|
||||||
@@ -147,6 +166,8 @@ async def hacs_startup(hacs):
|
|||||||
hacs.common.categories = ELEMENT_TYPES
|
hacs.common.categories = ELEMENT_TYPES
|
||||||
if hacs.configuration.appdaemon:
|
if hacs.configuration.appdaemon:
|
||||||
hacs.common.categories.append("appdaemon")
|
hacs.common.categories.append("appdaemon")
|
||||||
|
if hacs.configuration.netdaemon:
|
||||||
|
hacs.common.categories.append("netdaemon")
|
||||||
if hacs.configuration.python_script:
|
if hacs.configuration.python_script:
|
||||||
hacs.configuration.python_script = False
|
hacs.configuration.python_script = False
|
||||||
if hacs.configuration.config_type == "yaml":
|
if hacs.configuration.config_type == "yaml":
|
||||||
@@ -162,37 +183,39 @@ async def hacs_startup(hacs):
|
|||||||
|
|
||||||
# Setup startup tasks
|
# Setup startup tasks
|
||||||
if hacs.configuration.config_type == "yaml":
|
if hacs.configuration.config_type == "yaml":
|
||||||
hacs.hass.bus.async_listen_once(
|
hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, hacs.startup_tasks())
|
||||||
EVENT_HOMEASSISTANT_START, hacs().startup_tasks()
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
async_call_later(hacs.hass, 5, hacs().startup_tasks())
|
async_call_later(hacs.hass, 5, hacs.startup_tasks())
|
||||||
|
|
||||||
# Show the configuration
|
# Show the configuration
|
||||||
hacs.configuration.print()
|
hacs.configuration.print()
|
||||||
|
|
||||||
|
# Set up sensor
|
||||||
|
await hacs.hass.async_add_executor_job(add_sensor)
|
||||||
|
|
||||||
# Mischief managed!
|
# Mischief managed!
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_remove_entry(hass, config_entry):
|
async def async_remove_entry(hass, config_entry):
|
||||||
"""Handle removal of an entry."""
|
"""Handle removal of an entry."""
|
||||||
Hacs().logger.info("Disabling HACS")
|
hacs = get_hacs()
|
||||||
Hacs().logger.info("Removing recuring tasks")
|
hacs.logger.info("Disabling HACS")
|
||||||
for task in Hacs().recuring_tasks:
|
hacs.logger.info("Removing recuring tasks")
|
||||||
|
for task in hacs.recuring_tasks:
|
||||||
task()
|
task()
|
||||||
Hacs().logger.info("Removing sensor")
|
hacs.logger.info("Removing sensor")
|
||||||
try:
|
try:
|
||||||
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
Hacs().logger.info("Removing sidepanel")
|
hacs.logger.info("Removing sidepanel")
|
||||||
try:
|
try:
|
||||||
hass.components.frontend.async_remove_panel("hacs")
|
hass.components.frontend.async_remove_panel("hacs")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
Hacs().system.disabled = True
|
hacs.system.disabled = True
|
||||||
Hacs().logger.info("HACS is now disabled")
|
hacs.logger.info("HACS is now disabled")
|
||||||
|
|
||||||
|
|
||||||
async def reload_hacs(hass, config_entry):
|
async def reload_hacs(hass, config_entry):
|
||||||
|
13
config/custom_components/hacs/config_flow.py
Executable file → Normal file
13
config/custom_components/hacs/config_flow.py
Executable file → Normal file
@@ -2,15 +2,16 @@
|
|||||||
# pylint: disable=dangerous-default-value
|
# pylint: disable=dangerous-default-value
|
||||||
import logging
|
import logging
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from aiogithubapi import AIOGitHub, AIOGitHubException, AIOGitHubAuthentication
|
from aiogithubapi import AIOGitHubException, AIOGitHubAuthentication
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from . import Hacs
|
|
||||||
from .configuration_schema import hacs_base_config_schema, hacs_config_option_schema
|
from .configuration_schema import hacs_base_config_schema, hacs_config_option_schema
|
||||||
|
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
|
from custom_components.hacs.helpers.information import get_repository
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Show the configuration form to edit location data."""
|
"""Show the configuration form to edit location data."""
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
data_schema=vol.Schema(hacs_base_config_schema(user_input)),
|
data_schema=vol.Schema(hacs_base_config_schema(user_input, True)),
|
||||||
errors=self._errors,
|
errors=self._errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,8 +70,7 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Return true if token is valid."""
|
"""Return true if token is valid."""
|
||||||
try:
|
try:
|
||||||
session = aiohttp_client.async_get_clientsession(self.hass)
|
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
client = AIOGitHub(token, session)
|
await get_repository(session, token, "hacs/org")
|
||||||
await client.get_repo("hacs/org")
|
|
||||||
return True
|
return True
|
||||||
except (AIOGitHubException, AIOGitHubAuthentication) as exception:
|
except (AIOGitHubException, AIOGitHubAuthentication) as exception:
|
||||||
_LOGGER.error(exception)
|
_LOGGER.error(exception)
|
||||||
@@ -90,10 +90,11 @@ class HacsOptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
|
hacs = get_hacs()
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
return self.async_create_entry(title="", data=user_input)
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
if Hacs.configuration.config_type == "yaml":
|
if hacs.configuration.config_type == "yaml":
|
||||||
schema = {vol.Optional("not_in_use", default=""): str}
|
schema = {vol.Optional("not_in_use", default=""): str}
|
||||||
else:
|
else:
|
||||||
schema = hacs_config_option_schema(self.config_entry.options)
|
schema = hacs_config_option_schema(self.config_entry.options)
|
||||||
|
13
config/custom_components/hacs/configuration_schema.py
Executable file → Normal file
13
config/custom_components/hacs/configuration_schema.py
Executable file → Normal file
@@ -8,6 +8,7 @@ TOKEN = "token"
|
|||||||
SIDEPANEL_TITLE = "sidepanel_title"
|
SIDEPANEL_TITLE = "sidepanel_title"
|
||||||
SIDEPANEL_ICON = "sidepanel_icon"
|
SIDEPANEL_ICON = "sidepanel_icon"
|
||||||
APPDAEMON = "appdaemon"
|
APPDAEMON = "appdaemon"
|
||||||
|
NETDAEMON = "netdaemon"
|
||||||
PYTHON_SCRIPT = "python_script"
|
PYTHON_SCRIPT = "python_script"
|
||||||
THEME = "theme"
|
THEME = "theme"
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ RELEASE_LIMIT = "release_limit"
|
|||||||
EXPERIMENTAL = "experimental"
|
EXPERIMENTAL = "experimental"
|
||||||
|
|
||||||
|
|
||||||
def hacs_base_config_schema(config: dict = {}) -> dict:
|
def hacs_base_config_schema(config: dict = {}, config_flow: bool = False) -> dict:
|
||||||
"""Return a shcema configuration dict for HACS."""
|
"""Return a shcema configuration dict for HACS."""
|
||||||
if not config:
|
if not config:
|
||||||
config = {
|
config = {
|
||||||
@@ -26,14 +27,24 @@ def hacs_base_config_schema(config: dict = {}) -> dict:
|
|||||||
SIDEPANEL_ICON: "mdi:alpha-c-box",
|
SIDEPANEL_ICON: "mdi:alpha-c-box",
|
||||||
SIDEPANEL_TITLE: "HACS",
|
SIDEPANEL_TITLE: "HACS",
|
||||||
APPDAEMON: False,
|
APPDAEMON: False,
|
||||||
|
NETDAEMON: False,
|
||||||
PYTHON_SCRIPT: False,
|
PYTHON_SCRIPT: False,
|
||||||
THEME: False,
|
THEME: False,
|
||||||
}
|
}
|
||||||
|
if config_flow:
|
||||||
|
return {
|
||||||
|
vol.Required(TOKEN, default=config.get(TOKEN)): str,
|
||||||
|
vol.Optional(SIDEPANEL_TITLE, default=config.get(SIDEPANEL_TITLE)): str,
|
||||||
|
vol.Optional(SIDEPANEL_ICON, default=config.get(SIDEPANEL_ICON)): str,
|
||||||
|
vol.Optional(APPDAEMON, default=config.get(APPDAEMON)): bool,
|
||||||
|
vol.Optional(NETDAEMON, default=config.get(NETDAEMON)): bool,
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
vol.Required(TOKEN, default=config.get(TOKEN)): str,
|
vol.Required(TOKEN, default=config.get(TOKEN)): str,
|
||||||
vol.Optional(SIDEPANEL_TITLE, default=config.get(SIDEPANEL_TITLE)): str,
|
vol.Optional(SIDEPANEL_TITLE, default=config.get(SIDEPANEL_TITLE)): str,
|
||||||
vol.Optional(SIDEPANEL_ICON, default=config.get(SIDEPANEL_ICON)): str,
|
vol.Optional(SIDEPANEL_ICON, default=config.get(SIDEPANEL_ICON)): str,
|
||||||
vol.Optional(APPDAEMON, default=config.get(APPDAEMON)): bool,
|
vol.Optional(APPDAEMON, default=config.get(APPDAEMON)): bool,
|
||||||
|
vol.Optional(NETDAEMON, default=config.get(NETDAEMON)): bool,
|
||||||
vol.Optional(PYTHON_SCRIPT, default=config.get(PYTHON_SCRIPT)): bool,
|
vol.Optional(PYTHON_SCRIPT, default=config.get(PYTHON_SCRIPT)): bool,
|
||||||
vol.Optional(THEME, default=config.get(THEME)): bool,
|
vol.Optional(THEME, default=config.get(THEME)): bool,
|
||||||
}
|
}
|
||||||
|
2
config/custom_components/hacs/const.py
Executable file → Normal file
2
config/custom_components/hacs/const.py
Executable file → Normal file
@@ -1,7 +1,7 @@
|
|||||||
"""Constants for HACS"""
|
"""Constants for HACS"""
|
||||||
NAME_LONG = "HACS (Home Assistant Community Store)"
|
NAME_LONG = "HACS (Home Assistant Community Store)"
|
||||||
NAME_SHORT = "HACS"
|
NAME_SHORT = "HACS"
|
||||||
VERSION = "0.21.5"
|
VERSION = "0.23.2"
|
||||||
DOMAIN = "hacs"
|
DOMAIN = "hacs"
|
||||||
PROJECT_URL = "https://github.com/hacs/integration/"
|
PROJECT_URL = "https://github.com/hacs/integration/"
|
||||||
CUSTOM_UPDATER_LOCATIONS = [
|
CUSTOM_UPDATER_LOCATIONS = [
|
||||||
|
62
config/custom_components/hacs/constrains.py
Executable file → Normal file
62
config/custom_components/hacs/constrains.py
Executable file → Normal file
@@ -5,22 +5,25 @@ import os
|
|||||||
from .const import CUSTOM_UPDATER_LOCATIONS, CUSTOM_UPDATER_WARNING
|
from .const import CUSTOM_UPDATER_LOCATIONS, CUSTOM_UPDATER_WARNING
|
||||||
from .helpers.misc import version_left_higher_then_right
|
from .helpers.misc import version_left_higher_then_right
|
||||||
|
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
|
|
||||||
MINIMUM_HA_VERSION = "0.98.0"
|
MINIMUM_HA_VERSION = "0.98.0"
|
||||||
|
|
||||||
|
|
||||||
def check_constans(hacs):
|
def check_constans():
|
||||||
"""Check HACS constrains."""
|
"""Check HACS constrains."""
|
||||||
if not constrain_translations(hacs):
|
if not constrain_translations():
|
||||||
return False
|
return False
|
||||||
if not constrain_custom_updater(hacs):
|
if not constrain_custom_updater():
|
||||||
return False
|
return False
|
||||||
if not constrain_version(hacs):
|
if not constrain_version():
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def constrain_custom_updater(hacs):
|
def constrain_custom_updater():
|
||||||
"""Check if custom_updater exist."""
|
"""Check if custom_updater exist."""
|
||||||
|
hacs = get_hacs()
|
||||||
for location in CUSTOM_UPDATER_LOCATIONS:
|
for location in CUSTOM_UPDATER_LOCATIONS:
|
||||||
if os.path.exists(location.format(hacs.system.config_path)):
|
if os.path.exists(location.format(hacs.system.config_path)):
|
||||||
msg = CUSTOM_UPDATER_WARNING.format(
|
msg = CUSTOM_UPDATER_WARNING.format(
|
||||||
@@ -31,8 +34,9 @@ def constrain_custom_updater(hacs):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def constrain_version(hacs):
|
def constrain_version():
|
||||||
"""Check if the version is valid."""
|
"""Check if the version is valid."""
|
||||||
|
hacs = get_hacs()
|
||||||
if not version_left_higher_then_right(hacs.system.ha_version, MINIMUM_HA_VERSION):
|
if not version_left_higher_then_right(hacs.system.ha_version, MINIMUM_HA_VERSION):
|
||||||
hacs.logger.critical(
|
hacs.logger.critical(
|
||||||
f"You need HA version {MINIMUM_HA_VERSION} or newer to use this integration."
|
f"You need HA version {MINIMUM_HA_VERSION} or newer to use this integration."
|
||||||
@@ -41,11 +45,55 @@ def constrain_version(hacs):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def constrain_translations(hacs):
|
def constrain_translations():
|
||||||
"""Check if traslations exist."""
|
"""Check if traslations exist."""
|
||||||
|
hacs = get_hacs()
|
||||||
if not os.path.exists(
|
if not os.path.exists(
|
||||||
f"{hacs.system.config_path}/custom_components/hacs/.translations"
|
f"{hacs.system.config_path}/custom_components/hacs/.translations"
|
||||||
):
|
):
|
||||||
hacs.logger.critical("You are missing the translations directory.")
|
hacs.logger.critical("You are missing the translations directory.")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_requirements():
|
||||||
|
"""Check the requirements"""
|
||||||
|
missing = []
|
||||||
|
try:
|
||||||
|
from aiogithubapi import AIOGitHubException # pylint: disable=unused-import
|
||||||
|
except ImportError:
|
||||||
|
missing.append("aiogithubapi")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hacs_frontend import locate_gz # pylint: disable=unused-import
|
||||||
|
except ImportError:
|
||||||
|
missing.append("hacs_frontend")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import semantic_version # pylint: disable=unused-import
|
||||||
|
except ImportError:
|
||||||
|
missing.append("semantic_version")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from integrationhelper import Logger # pylint: disable=unused-import
|
||||||
|
except ImportError:
|
||||||
|
missing.append("integrationhelper")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import backoff # pylint: disable=unused-import
|
||||||
|
except ImportError:
|
||||||
|
missing.append("backoff")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import aiofiles # pylint: disable=unused-import
|
||||||
|
except ImportError:
|
||||||
|
missing.append("aiofiles")
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
hacs = get_hacs()
|
||||||
|
for requirement in missing:
|
||||||
|
hacs.logger.critical(
|
||||||
|
f"Required python requirement '{requirement}' is not installed"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
29
config/custom_components/hacs/globals.py
Normal file
29
config/custom_components/hacs/globals.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# pylint: disable=invalid-name, missing-docstring
|
||||||
|
hacs = []
|
||||||
|
removed_repositories = []
|
||||||
|
|
||||||
|
|
||||||
|
def get_hacs():
|
||||||
|
if not hacs:
|
||||||
|
from custom_components.hacs.hacsbase import Hacs
|
||||||
|
|
||||||
|
hacs.append(Hacs())
|
||||||
|
|
||||||
|
return hacs[0]
|
||||||
|
|
||||||
|
|
||||||
|
def is_removed(repository):
|
||||||
|
return repository in [x.repository for x in removed_repositories]
|
||||||
|
|
||||||
|
|
||||||
|
def get_removed(repository):
|
||||||
|
if not is_removed(repository):
|
||||||
|
from custom_components.hacs.repositories.removed import RemovedRepository
|
||||||
|
|
||||||
|
removed_repo = RemovedRepository()
|
||||||
|
removed_repo.repository = repository
|
||||||
|
removed_repositories.append(removed_repo)
|
||||||
|
filter_repos = [
|
||||||
|
x for x in removed_repositories if x.repository.lower() == repository.lower()
|
||||||
|
]
|
||||||
|
return filter_repos[0]
|
127
config/custom_components/hacs/hacsbase/__init__.py
Executable file → Normal file
127
config/custom_components/hacs/hacsbase/__init__.py
Executable file → Normal file
@@ -9,13 +9,20 @@ from homeassistant.helpers.event import async_call_later, async_track_time_inter
|
|||||||
from aiogithubapi import AIOGitHubException, AIOGitHubRatelimit
|
from aiogithubapi import AIOGitHubException, AIOGitHubRatelimit
|
||||||
from integrationhelper import Logger
|
from integrationhelper import Logger
|
||||||
|
|
||||||
from .task_factory import HacsTaskFactory
|
from custom_components.hacs.hacsbase.task_factory import HacsTaskFactory
|
||||||
from .exceptions import HacsException
|
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||||
|
|
||||||
from ..const import ELEMENT_TYPES
|
from custom_components.hacs.const import ELEMENT_TYPES
|
||||||
from ..setup import setup_extra_stores
|
from custom_components.hacs.setup import setup_extra_stores
|
||||||
from ..store import async_load_from_store, async_save_to_store
|
from custom_components.hacs.store import async_load_from_store, async_save_to_store
|
||||||
from ..helpers.get_defaults import get_default_repos_lists, get_default_repos_orgs
|
from custom_components.hacs.helpers.get_defaults import (
|
||||||
|
get_default_repos_lists,
|
||||||
|
get_default_repos_orgs,
|
||||||
|
)
|
||||||
|
|
||||||
|
from custom_components.hacs.helpers.register_repository import register_repository
|
||||||
|
from custom_components.hacs.globals import removed_repositories, get_removed, is_removed
|
||||||
|
from custom_components.hacs.repositories.removed import RemovedRepository
|
||||||
|
|
||||||
|
|
||||||
class HacsStatus:
|
class HacsStatus:
|
||||||
@@ -40,7 +47,6 @@ class HacsCommon:
|
|||||||
"""Common for HACS."""
|
"""Common for HACS."""
|
||||||
|
|
||||||
categories = []
|
categories = []
|
||||||
blacklist = []
|
|
||||||
default = []
|
default = []
|
||||||
installed = []
|
installed = []
|
||||||
skip = []
|
skip = []
|
||||||
@@ -90,6 +96,7 @@ class Hacs:
|
|||||||
github = None
|
github = None
|
||||||
hass = None
|
hass = None
|
||||||
version = None
|
version = None
|
||||||
|
session = None
|
||||||
factory = HacsTaskFactory()
|
factory = HacsTaskFactory()
|
||||||
system = System()
|
system = System()
|
||||||
recuring_tasks = []
|
recuring_tasks = []
|
||||||
@@ -114,7 +121,7 @@ class Hacs:
|
|||||||
"""Get repository by full_name."""
|
"""Get repository by full_name."""
|
||||||
try:
|
try:
|
||||||
for repository in self.repositories:
|
for repository in self.repositories:
|
||||||
if repository.information.full_name == repository_full_name:
|
if repository.data.full_name.lower() == repository_full_name.lower():
|
||||||
return repository
|
return repository
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
pass
|
pass
|
||||||
@@ -122,10 +129,9 @@ class Hacs:
|
|||||||
|
|
||||||
def is_known(self, repository_full_name):
|
def is_known(self, repository_full_name):
|
||||||
"""Return a bool if the repository is known."""
|
"""Return a bool if the repository is known."""
|
||||||
for repository in self.repositories:
|
return repository_full_name.lower() in [
|
||||||
if repository.information.full_name == repository_full_name:
|
x.data.full_name.lower() for x in self.repositories
|
||||||
return True
|
]
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sorted_by_name(self):
|
def sorted_by_name(self):
|
||||||
@@ -135,59 +141,16 @@ class Hacs:
|
|||||||
@property
|
@property
|
||||||
def sorted_by_repository_name(self):
|
def sorted_by_repository_name(self):
|
||||||
"""Return a sorted(by repository_name) list of repository objects."""
|
"""Return a sorted(by repository_name) list of repository objects."""
|
||||||
return sorted(self.repositories, key=lambda x: x.information.full_name)
|
return sorted(self.repositories, key=lambda x: x.data.full_name)
|
||||||
|
|
||||||
async def register_repository(self, full_name, category, check=True):
|
async def register_repository(self, full_name, category, check=True):
|
||||||
"""Register a repository."""
|
"""Register a repository."""
|
||||||
from ..repositories.repository import RERPOSITORY_CLASSES
|
await register_repository(full_name, category, check=True)
|
||||||
|
|
||||||
if full_name in self.common.skip:
|
|
||||||
if full_name != "hacs/integration":
|
|
||||||
self.logger.debug(f"Skipping {full_name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if category not in RERPOSITORY_CLASSES:
|
|
||||||
msg = f"{category} is not a valid repository category."
|
|
||||||
self.logger.error(msg)
|
|
||||||
raise HacsException(msg)
|
|
||||||
|
|
||||||
repository = RERPOSITORY_CLASSES[category](full_name)
|
|
||||||
if check:
|
|
||||||
try:
|
|
||||||
await repository.registration()
|
|
||||||
if self.system.status.new:
|
|
||||||
repository.status.new = False
|
|
||||||
if repository.validate.errors:
|
|
||||||
self.common.skip.append(repository.information.full_name)
|
|
||||||
if not self.system.status.startup:
|
|
||||||
self.logger.error(f"Validation for {full_name} failed.")
|
|
||||||
return repository.validate.errors
|
|
||||||
repository.logger.info("Registration complete")
|
|
||||||
except AIOGitHubException as exception:
|
|
||||||
self.logger.debug(self.github.ratelimits.remaining)
|
|
||||||
self.logger.debug(self.github.ratelimits.reset_utc)
|
|
||||||
self.common.skip.append(repository.information.full_name)
|
|
||||||
# if not self.system.status.startup:
|
|
||||||
if self.system.status.startup:
|
|
||||||
self.logger.error(
|
|
||||||
f"Validation for {full_name} failed with {exception}."
|
|
||||||
)
|
|
||||||
return exception
|
|
||||||
self.hass.bus.async_fire(
|
|
||||||
"hacs/repository",
|
|
||||||
{
|
|
||||||
"id": 1337,
|
|
||||||
"action": "registration",
|
|
||||||
"repository": repository.information.full_name,
|
|
||||||
"repository_id": repository.information.uid,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.repositories.append(repository)
|
|
||||||
|
|
||||||
async def startup_tasks(self):
|
async def startup_tasks(self):
|
||||||
"""Tasks tha are started after startup."""
|
"""Tasks tha are started after startup."""
|
||||||
self.system.status.background_task = True
|
self.system.status.background_task = True
|
||||||
await self.hass.async_add_executor_job(setup_extra_stores, self)
|
await self.hass.async_add_executor_job(setup_extra_stores)
|
||||||
self.hass.bus.async_fire("hacs/status", {})
|
self.hass.bus.async_fire("hacs/status", {})
|
||||||
self.logger.debug(self.github.ratelimits.remaining)
|
self.logger.debug(self.github.ratelimits.remaining)
|
||||||
self.logger.debug(self.github.ratelimits.reset_utc)
|
self.logger.debug(self.github.ratelimits.reset_utc)
|
||||||
@@ -195,7 +158,7 @@ class Hacs:
|
|||||||
await self.handle_critical_repositories_startup()
|
await self.handle_critical_repositories_startup()
|
||||||
await self.handle_critical_repositories()
|
await self.handle_critical_repositories()
|
||||||
await self.load_known_repositories()
|
await self.load_known_repositories()
|
||||||
await self.clear_out_blacklisted_repositories()
|
await self.clear_out_removed_repositories()
|
||||||
|
|
||||||
self.recuring_tasks.append(
|
self.recuring_tasks.append(
|
||||||
async_track_time_interval(
|
async_track_time_interval(
|
||||||
@@ -257,7 +220,8 @@ class Hacs:
|
|||||||
stored_critical = []
|
stored_critical = []
|
||||||
|
|
||||||
for repository in critical:
|
for repository in critical:
|
||||||
self.common.blacklist.append(repository["repository"])
|
removed_repo = get_removed(repository["repository"])
|
||||||
|
removed_repo.removal_type = "critical"
|
||||||
repo = self.get_by_name(repository["repository"])
|
repo = self.get_by_name(repository["repository"])
|
||||||
|
|
||||||
stored = {
|
stored = {
|
||||||
@@ -266,7 +230,6 @@ class Hacs:
|
|||||||
"link": repository["link"],
|
"link": repository["link"],
|
||||||
"acknowledged": True,
|
"acknowledged": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
if repository["repository"] not in instored:
|
if repository["repository"] not in instored:
|
||||||
if repo is not None and repo.installed:
|
if repo is not None and repo.installed:
|
||||||
self.logger.critical(
|
self.logger.critical(
|
||||||
@@ -278,6 +241,7 @@ class Hacs:
|
|||||||
repo.remove()
|
repo.remove()
|
||||||
await repo.uninstall()
|
await repo.uninstall()
|
||||||
stored_critical.append(stored)
|
stored_critical.append(stored)
|
||||||
|
removed_repo.update_data(stored)
|
||||||
|
|
||||||
# Save to FS
|
# Save to FS
|
||||||
await async_save_to_store(self.hass, "critical", stored_critical)
|
await async_save_to_store(self.hass, "critical", stored_critical)
|
||||||
@@ -299,7 +263,7 @@ class Hacs:
|
|||||||
for repository in self.repositories:
|
for repository in self.repositories:
|
||||||
if (
|
if (
|
||||||
repository.status.installed
|
repository.status.installed
|
||||||
and repository.category in self.common.categories
|
and repository.data.category in self.common.categories
|
||||||
):
|
):
|
||||||
self.factory.tasks.append(self.factory.safe_update(repository))
|
self.factory.tasks.append(self.factory.safe_update(repository))
|
||||||
|
|
||||||
@@ -313,33 +277,35 @@ class Hacs:
|
|||||||
async def recuring_tasks_all(self, notarealarg=None):
|
async def recuring_tasks_all(self, notarealarg=None):
|
||||||
"""Recuring tasks for all repositories."""
|
"""Recuring tasks for all repositories."""
|
||||||
self.logger.debug("Starting recuring background task for all repositories")
|
self.logger.debug("Starting recuring background task for all repositories")
|
||||||
|
await self.hass.async_add_executor_job(setup_extra_stores)
|
||||||
self.system.status.background_task = True
|
self.system.status.background_task = True
|
||||||
self.hass.bus.async_fire("hacs/status", {})
|
self.hass.bus.async_fire("hacs/status", {})
|
||||||
self.logger.debug(self.github.ratelimits.remaining)
|
self.logger.debug(self.github.ratelimits.remaining)
|
||||||
self.logger.debug(self.github.ratelimits.reset_utc)
|
self.logger.debug(self.github.ratelimits.reset_utc)
|
||||||
for repository in self.repositories:
|
for repository in self.repositories:
|
||||||
if repository.category in self.common.categories:
|
if repository.data.category in self.common.categories:
|
||||||
self.factory.tasks.append(self.factory.safe_common_update(repository))
|
self.factory.tasks.append(self.factory.safe_common_update(repository))
|
||||||
|
|
||||||
await self.factory.execute()
|
await self.factory.execute()
|
||||||
await self.load_known_repositories()
|
await self.load_known_repositories()
|
||||||
await self.clear_out_blacklisted_repositories()
|
await self.clear_out_removed_repositories()
|
||||||
self.system.status.background_task = False
|
self.system.status.background_task = False
|
||||||
await self.data.async_write()
|
await self.data.async_write()
|
||||||
self.hass.bus.async_fire("hacs/status", {})
|
self.hass.bus.async_fire("hacs/status", {})
|
||||||
self.hass.bus.async_fire("hacs/repository", {"action": "reload"})
|
self.hass.bus.async_fire("hacs/repository", {"action": "reload"})
|
||||||
self.logger.debug("Recuring background task for all repositories done")
|
self.logger.debug("Recuring background task for all repositories done")
|
||||||
|
|
||||||
async def clear_out_blacklisted_repositories(self):
|
async def clear_out_removed_repositories(self):
|
||||||
"""Clear out blaclisted repositories."""
|
"""Clear out blaclisted repositories."""
|
||||||
need_to_save = False
|
need_to_save = False
|
||||||
for repository in self.common.blacklist:
|
for removed in removed_repositories:
|
||||||
if self.is_known(repository):
|
if self.is_known(removed.repository):
|
||||||
repository = self.get_by_name(repository)
|
repository = self.get_by_name(removed.repository)
|
||||||
if repository.status.installed:
|
if repository.status.installed and removed.removal_type != "critical":
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"You have {repository.information.full_name} installed with HACS "
|
f"You have {repository.data.full_name} installed with HACS "
|
||||||
+ "this repository has been blacklisted, please consider removing it."
|
+ f"this repository has been removed, please consider removing it. "
|
||||||
|
+ f"Removal reason ({removed.removal_type})"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
need_to_save = True
|
need_to_save = True
|
||||||
@@ -353,7 +319,7 @@ class Hacs:
|
|||||||
repositories = {}
|
repositories = {}
|
||||||
for category in self.common.categories:
|
for category in self.common.categories:
|
||||||
repositories[category] = await get_default_repos_lists(
|
repositories[category] = await get_default_repos_lists(
|
||||||
self.github, category
|
self.session, self.configuration.token, category
|
||||||
)
|
)
|
||||||
org = await get_default_repos_orgs(self.github, category)
|
org = await get_default_repos_orgs(self.github, category)
|
||||||
for repo in org:
|
for repo in org:
|
||||||
@@ -370,17 +336,20 @@ class Hacs:
|
|||||||
self.logger.info("Loading known repositories")
|
self.logger.info("Loading known repositories")
|
||||||
repositories = await self.get_repositories()
|
repositories = await self.get_repositories()
|
||||||
|
|
||||||
for item in await get_default_repos_lists(self.github, "blacklist"):
|
for item in await get_default_repos_lists(
|
||||||
if item not in self.common.blacklist:
|
self.session, self.configuration.token, "removed"
|
||||||
self.common.blacklist.append(item)
|
):
|
||||||
|
removed = get_removed(item["repository"])
|
||||||
|
removed.reason = item.get("reason")
|
||||||
|
removed.link = item.get("link")
|
||||||
|
removed.removal_type = item.get("removal_type")
|
||||||
|
|
||||||
for category in repositories:
|
for category in repositories:
|
||||||
for repo in repositories[category]:
|
for repo in repositories[category]:
|
||||||
if repo in self.common.blacklist:
|
if is_removed(repo):
|
||||||
continue
|
continue
|
||||||
if self.is_known(repo):
|
if self.is_known(repo):
|
||||||
continue
|
continue
|
||||||
self.factory.tasks.append(
|
self.factory.tasks.append(self.factory.safe_register(repo, category))
|
||||||
self.factory.safe_register(self, repo, category)
|
|
||||||
)
|
|
||||||
await self.factory.execute()
|
await self.factory.execute()
|
||||||
|
self.logger.info("Loading known repositories finished")
|
||||||
|
45
config/custom_components/hacs/hacsbase/backup.py
Executable file → Normal file
45
config/custom_components/hacs/hacsbase/backup.py
Executable file → Normal file
@@ -70,3 +70,48 @@ class Backup:
|
|||||||
while os.path.exists(self.backup_path):
|
while os.path.exists(self.backup_path):
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
self.logger.debug(f"Backup dir {self.backup_path} cleared")
|
self.logger.debug(f"Backup dir {self.backup_path} cleared")
|
||||||
|
|
||||||
|
|
||||||
|
class BackupNetDaemon:
|
||||||
|
"""BackupNetDaemon."""
|
||||||
|
|
||||||
|
def __init__(self, repository):
|
||||||
|
"""Initialize."""
|
||||||
|
self.repository = repository
|
||||||
|
self.logger = Logger("hacs.backup")
|
||||||
|
self.backup_path = (
|
||||||
|
tempfile.gettempdir() + "/hacs_persistent_netdaemon/" + repository.data.name
|
||||||
|
)
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
"""Create a backup in /tmp"""
|
||||||
|
if os.path.exists(self.backup_path):
|
||||||
|
shutil.rmtree(self.backup_path)
|
||||||
|
while os.path.exists(self.backup_path):
|
||||||
|
sleep(0.1)
|
||||||
|
os.makedirs(self.backup_path, exist_ok=True)
|
||||||
|
|
||||||
|
for filename in os.listdir(self.repository.content.path.local):
|
||||||
|
if filename.endswith(".yaml"):
|
||||||
|
source_file_name = f"{self.repository.content.path.local}/{filename}"
|
||||||
|
target_file_name = f"{self.backup_path}/{filename}"
|
||||||
|
shutil.copyfile(source_file_name, target_file_name)
|
||||||
|
|
||||||
|
def restore(self):
|
||||||
|
"""Create a backup in /tmp"""
|
||||||
|
if os.path.exists(self.backup_path):
|
||||||
|
for filename in os.listdir(self.backup_path):
|
||||||
|
if filename.endswith(".yaml"):
|
||||||
|
source_file_name = f"{self.backup_path}/{filename}"
|
||||||
|
target_file_name = (
|
||||||
|
f"{self.repository.content.path.local}/{filename}"
|
||||||
|
)
|
||||||
|
shutil.copyfile(source_file_name, target_file_name)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Create a backup in /tmp"""
|
||||||
|
if os.path.exists(self.backup_path):
|
||||||
|
shutil.rmtree(self.backup_path)
|
||||||
|
while os.path.exists(self.backup_path):
|
||||||
|
sleep(0.1)
|
||||||
|
self.logger.debug(f"Backup dir {self.backup_path} cleared")
|
||||||
|
2
config/custom_components/hacs/hacsbase/configuration.py
Executable file → Normal file
2
config/custom_components/hacs/hacsbase/configuration.py
Executable file → Normal file
@@ -11,6 +11,8 @@ class Configuration:
|
|||||||
# Main configuration:
|
# Main configuration:
|
||||||
appdaemon_path: str = "appdaemon/apps/"
|
appdaemon_path: str = "appdaemon/apps/"
|
||||||
appdaemon: bool = False
|
appdaemon: bool = False
|
||||||
|
netdaemon_path: str = "netdaemon/apps/"
|
||||||
|
netdaemon: bool = False
|
||||||
config: dict = {}
|
config: dict = {}
|
||||||
config_entry: dict = {}
|
config_entry: dict = {}
|
||||||
config_type: str = None
|
config_type: str = None
|
||||||
|
0
config/custom_components/hacs/hacsbase/const.py
Executable file → Normal file
0
config/custom_components/hacs/hacsbase/const.py
Executable file → Normal file
82
config/custom_components/hacs/hacsbase/data.py
Executable file → Normal file
82
config/custom_components/hacs/hacsbase/data.py
Executable file → Normal file
@@ -1,50 +1,57 @@
|
|||||||
"""Data handler for HACS."""
|
"""Data handler for HACS."""
|
||||||
from integrationhelper import Logger
|
from integrationhelper import Logger
|
||||||
from . import Hacs
|
|
||||||
from ..const import VERSION
|
from ..const import VERSION
|
||||||
from ..repositories.repository import HacsRepository
|
from ..repositories.repository import HacsRepository
|
||||||
from ..repositories.manifest import HacsManifest
|
from ..repositories.manifest import HacsManifest
|
||||||
from ..store import async_save_to_store, async_load_from_store
|
from ..store import async_save_to_store, async_load_from_store
|
||||||
|
|
||||||
|
from custom_components.hacs.globals import get_hacs, removed_repositories, get_removed
|
||||||
|
from custom_components.hacs.helpers.register_repository import register_repository
|
||||||
|
|
||||||
class HacsData(Hacs):
|
|
||||||
|
class HacsData:
|
||||||
"""HacsData class."""
|
"""HacsData class."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.logger = Logger("hacs.data")
|
self.logger = Logger("hacs.data")
|
||||||
|
self.hacs = get_hacs()
|
||||||
|
|
||||||
async def async_write(self):
|
async def async_write(self):
|
||||||
"""Write content to the store files."""
|
"""Write content to the store files."""
|
||||||
if self.system.status.background_task or self.system.disabled:
|
if self.hacs.system.status.background_task or self.hacs.system.disabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.debug("Saving data")
|
self.logger.debug("Saving data")
|
||||||
|
|
||||||
# Hacs
|
# Hacs
|
||||||
await async_save_to_store(
|
await async_save_to_store(
|
||||||
self.hass,
|
self.hacs.hass,
|
||||||
"hacs",
|
"hacs",
|
||||||
{
|
{
|
||||||
"view": self.configuration.frontend_mode,
|
"view": self.hacs.configuration.frontend_mode,
|
||||||
"compact": self.configuration.frontend_compact,
|
"compact": self.hacs.configuration.frontend_compact,
|
||||||
"onboarding_done": self.configuration.onboarding_done,
|
"onboarding_done": self.hacs.configuration.onboarding_done,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await async_save_to_store(
|
||||||
|
self.hacs.hass, "removed", [x.__dict__ for x in removed_repositories]
|
||||||
|
)
|
||||||
|
|
||||||
# Repositories
|
# Repositories
|
||||||
content = {}
|
content = {}
|
||||||
for repository in self.repositories:
|
for repository in self.hacs.repositories:
|
||||||
if repository.repository_manifest is not None:
|
if repository.repository_manifest is not None:
|
||||||
repository_manifest = repository.repository_manifest.manifest
|
repository_manifest = repository.repository_manifest.manifest
|
||||||
else:
|
else:
|
||||||
repository_manifest = None
|
repository_manifest = None
|
||||||
content[repository.information.uid] = {
|
content[repository.information.uid] = {
|
||||||
"authors": repository.information.authors,
|
"authors": repository.data.authors,
|
||||||
"category": repository.information.category,
|
"category": repository.data.category,
|
||||||
"description": repository.information.description,
|
"description": repository.data.description,
|
||||||
"downloads": repository.releases.last_release_object_downloads,
|
"downloads": repository.releases.downloads,
|
||||||
"full_name": repository.information.full_name,
|
"full_name": repository.data.full_name,
|
||||||
"first_install": repository.status.first_install,
|
"first_install": repository.status.first_install,
|
||||||
"hide": repository.status.hide,
|
"hide": repository.status.hide,
|
||||||
"installed_commit": repository.versions.installed_commit,
|
"installed_commit": repository.versions.installed_commit,
|
||||||
@@ -52,54 +59,56 @@ class HacsData(Hacs):
|
|||||||
"last_commit": repository.versions.available_commit,
|
"last_commit": repository.versions.available_commit,
|
||||||
"last_release_tag": repository.versions.available,
|
"last_release_tag": repository.versions.available,
|
||||||
"last_updated": repository.information.last_updated,
|
"last_updated": repository.information.last_updated,
|
||||||
"name": repository.information.name,
|
"name": repository.data.name,
|
||||||
"new": repository.status.new,
|
"new": repository.status.new,
|
||||||
"repository_manifest": repository_manifest,
|
"repository_manifest": repository_manifest,
|
||||||
"selected_tag": repository.status.selected_tag,
|
"selected_tag": repository.status.selected_tag,
|
||||||
"show_beta": repository.status.show_beta,
|
"show_beta": repository.status.show_beta,
|
||||||
"stars": repository.information.stars,
|
"stars": repository.data.stargazers_count,
|
||||||
"topics": repository.information.topics,
|
"topics": repository.data.topics,
|
||||||
"version_installed": repository.versions.installed,
|
"version_installed": repository.versions.installed,
|
||||||
}
|
}
|
||||||
|
|
||||||
await async_save_to_store(self.hass, "repositories", content)
|
await async_save_to_store(self.hacs.hass, "repositories", content)
|
||||||
self.hass.bus.async_fire("hacs/repository", {})
|
self.hacs.hass.bus.async_fire("hacs/repository", {})
|
||||||
self.hass.bus.fire("hacs/config", {})
|
self.hacs.hass.bus.fire("hacs/config", {})
|
||||||
|
|
||||||
async def restore(self):
|
async def restore(self):
|
||||||
"""Restore saved data."""
|
"""Restore saved data."""
|
||||||
hacs = await async_load_from_store(self.hass, "hacs")
|
hacs = await async_load_from_store(self.hacs.hass, "hacs")
|
||||||
repositories = await async_load_from_store(self.hass, "repositories")
|
repositories = await async_load_from_store(self.hacs.hass, "repositories")
|
||||||
|
removed = await async_load_from_store(self.hacs.hass, "removed")
|
||||||
try:
|
try:
|
||||||
if not hacs and not repositories:
|
if not hacs and not repositories:
|
||||||
# Assume new install
|
# Assume new install
|
||||||
self.system.status.new = True
|
self.hacs.system.status.new = True
|
||||||
return True
|
return True
|
||||||
self.logger.info("Restore started")
|
self.logger.info("Restore started")
|
||||||
|
|
||||||
# Hacs
|
# Hacs
|
||||||
self.configuration.frontend_mode = hacs.get("view", "Grid")
|
self.hacs.configuration.frontend_mode = hacs.get("view", "Grid")
|
||||||
self.configuration.frontend_compact = hacs.get("compact", False)
|
self.hacs.configuration.frontend_compact = hacs.get("compact", False)
|
||||||
self.configuration.onboarding_done = hacs.get("onboarding_done", False)
|
self.hacs.configuration.onboarding_done = hacs.get("onboarding_done", False)
|
||||||
|
|
||||||
|
for entry in removed:
|
||||||
|
removed_repo = get_removed(entry["repository"])
|
||||||
|
removed_repo.update_data(entry)
|
||||||
|
|
||||||
# Repositories
|
# Repositories
|
||||||
for entry in repositories:
|
for entry in repositories:
|
||||||
repo = repositories[entry]
|
repo = repositories[entry]
|
||||||
if repo["full_name"] == "hacs/integration":
|
if not self.hacs.is_known(repo["full_name"]):
|
||||||
# Skip the old repo location
|
await register_repository(
|
||||||
continue
|
|
||||||
if not self.is_known(repo["full_name"]):
|
|
||||||
await self.register_repository(
|
|
||||||
repo["full_name"], repo["category"], False
|
repo["full_name"], repo["category"], False
|
||||||
)
|
)
|
||||||
repository = self.get_by_name(repo["full_name"])
|
repository = self.hacs.get_by_name(repo["full_name"])
|
||||||
if repository is None:
|
if repository is None:
|
||||||
self.logger.error(f"Did not find {repo['full_name']}")
|
self.logger.error(f"Did not find {repo['full_name']}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Restore repository attributes
|
# Restore repository attributes
|
||||||
repository.information.uid = entry
|
repository.information.uid = entry
|
||||||
await self.hass.async_add_executor_job(
|
await self.hacs.hass.async_add_executor_job(
|
||||||
restore_repository_data, repository, repo
|
restore_repository_data, repository, repo
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,13 +123,12 @@ def restore_repository_data(
|
|||||||
repository: type(HacsRepository), repository_data: dict
|
repository: type(HacsRepository), repository_data: dict
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Restore Repository Data"""
|
"""Restore Repository Data"""
|
||||||
repository.information.authors = repository_data.get("authors", [])
|
repository.data.authors = repository_data.get("authors", [])
|
||||||
repository.information.description = repository_data.get("description")
|
repository.data.description = repository_data.get("description")
|
||||||
repository.information.name = repository_data.get("name")
|
|
||||||
repository.releases.last_release_object_downloads = repository_data.get("downloads")
|
repository.releases.last_release_object_downloads = repository_data.get("downloads")
|
||||||
repository.information.last_updated = repository_data.get("last_updated")
|
repository.information.last_updated = repository_data.get("last_updated")
|
||||||
repository.information.topics = repository_data.get("topics", [])
|
repository.data.topics = repository_data.get("topics", [])
|
||||||
repository.information.stars = repository_data.get("stars", 0)
|
repository.data.stargazers_count = repository_data.get("stars", 0)
|
||||||
repository.releases.last_release = repository_data.get("last_release_tag")
|
repository.releases.last_release = repository_data.get("last_release_tag")
|
||||||
repository.status.hide = repository_data.get("hide", False)
|
repository.status.hide = repository_data.get("hide", False)
|
||||||
repository.status.installed = repository_data.get("installed", False)
|
repository.status.installed = repository_data.get("installed", False)
|
||||||
|
4
config/custom_components/hacs/hacsbase/exceptions.py
Executable file → Normal file
4
config/custom_components/hacs/hacsbase/exceptions.py
Executable file → Normal file
@@ -3,3 +3,7 @@
|
|||||||
|
|
||||||
class HacsException(Exception):
|
class HacsException(Exception):
|
||||||
"""Super basic."""
|
"""Super basic."""
|
||||||
|
|
||||||
|
|
||||||
|
class HacsExpectedException(HacsException):
|
||||||
|
"""For stuff that are expected."""
|
||||||
|
20
config/custom_components/hacs/hacsbase/task_factory.py
Executable file → Normal file
20
config/custom_components/hacs/hacsbase/task_factory.py
Executable file → Normal file
@@ -5,6 +5,10 @@ from datetime import timedelta
|
|||||||
import asyncio
|
import asyncio
|
||||||
from aiogithubapi import AIOGitHubException
|
from aiogithubapi import AIOGitHubException
|
||||||
|
|
||||||
|
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||||
|
from custom_components.hacs.helpers.register_repository import register_repository
|
||||||
|
|
||||||
|
|
||||||
max_concurrent_tasks = asyncio.Semaphore(15)
|
max_concurrent_tasks = asyncio.Semaphore(15)
|
||||||
sleeper = 5
|
sleeper = 5
|
||||||
|
|
||||||
@@ -44,8 +48,8 @@ class HacsTaskFactory:
|
|||||||
async with max_concurrent_tasks:
|
async with max_concurrent_tasks:
|
||||||
try:
|
try:
|
||||||
await repository.common_update()
|
await repository.common_update()
|
||||||
except AIOGitHubException as exception:
|
except (AIOGitHubException, HacsException) as exception:
|
||||||
logger.error(exception)
|
logger.error("%s - %s", repository.data.full_name, exception)
|
||||||
|
|
||||||
# Due to GitHub ratelimits we need to sleep a bit
|
# Due to GitHub ratelimits we need to sleep a bit
|
||||||
await asyncio.sleep(sleeper)
|
await asyncio.sleep(sleeper)
|
||||||
@@ -54,18 +58,18 @@ class HacsTaskFactory:
|
|||||||
async with max_concurrent_tasks:
|
async with max_concurrent_tasks:
|
||||||
try:
|
try:
|
||||||
await repository.update_repository()
|
await repository.update_repository()
|
||||||
except AIOGitHubException as exception:
|
except (AIOGitHubException, HacsException) as exception:
|
||||||
logger.error(exception)
|
logger.error("%s - %s", repository.data.full_name, exception)
|
||||||
|
|
||||||
# Due to GitHub ratelimits we need to sleep a bit
|
# Due to GitHub ratelimits we need to sleep a bit
|
||||||
await asyncio.sleep(sleeper)
|
await asyncio.sleep(sleeper)
|
||||||
|
|
||||||
async def safe_register(self, hacs, repo, category):
|
async def safe_register(self, repo, category):
|
||||||
async with max_concurrent_tasks:
|
async with max_concurrent_tasks:
|
||||||
try:
|
try:
|
||||||
await hacs.register_repository(repo, category)
|
await register_repository(repo, category)
|
||||||
except AIOGitHubException as exception:
|
except (AIOGitHubException, HacsException) as exception:
|
||||||
logger.error(exception)
|
logger.error("%s - %s", repo, exception)
|
||||||
|
|
||||||
# Due to GitHub ratelimits we need to sleep a bit
|
# Due to GitHub ratelimits we need to sleep a bit
|
||||||
await asyncio.sleep(sleeper)
|
await asyncio.sleep(sleeper)
|
||||||
|
0
config/custom_components/hacs/handler/__init__.py
Executable file → Normal file
0
config/custom_components/hacs/handler/__init__.py
Executable file → Normal file
10
config/custom_components/hacs/handler/download.py
Executable file → Normal file
10
config/custom_components/hacs/handler/download.py
Executable file → Normal file
@@ -7,15 +7,17 @@ import aiofiles
|
|||||||
import async_timeout
|
import async_timeout
|
||||||
from integrationhelper import Logger
|
from integrationhelper import Logger
|
||||||
import backoff
|
import backoff
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
||||||
from ..hacsbase.exceptions import HacsException
|
from ..hacsbase.exceptions import HacsException
|
||||||
|
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
|
|
||||||
|
|
||||||
@backoff.on_exception(backoff.expo, Exception, max_tries=5)
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5)
|
||||||
async def async_download_file(hass, url):
|
async def async_download_file(url):
|
||||||
"""
|
"""
|
||||||
Download files, and return the content.
|
Download files, and return the content.
|
||||||
"""
|
"""
|
||||||
|
hacs = get_hacs()
|
||||||
logger = Logger("hacs.download.downloader")
|
logger = Logger("hacs.download.downloader")
|
||||||
if url is None:
|
if url is None:
|
||||||
return
|
return
|
||||||
@@ -28,8 +30,8 @@ async def async_download_file(hass, url):
|
|||||||
|
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
with async_timeout.timeout(60, loop=hass.loop):
|
with async_timeout.timeout(60, loop=hacs.hass.loop):
|
||||||
request = await async_get_clientsession(hass).get(url)
|
request = await hacs.session.get(url)
|
||||||
|
|
||||||
# Make sure that we got a valid result
|
# Make sure that we got a valid result
|
||||||
if request.status == 200:
|
if request.status == 200:
|
||||||
|
0
config/custom_components/hacs/handler/template.py
Executable file → Normal file
0
config/custom_components/hacs/handler/template.py
Executable file → Normal file
124
config/custom_components/hacs/helpers/download.py
Executable file → Normal file
124
config/custom_components/hacs/helpers/download.py
Executable file → Normal file
@@ -16,9 +16,13 @@ class FileInformation:
|
|||||||
|
|
||||||
def should_try_releases(repository):
|
def should_try_releases(repository):
|
||||||
"""Return a boolean indicating whether to download releases or not."""
|
"""Return a boolean indicating whether to download releases or not."""
|
||||||
if repository.ref == repository.information.default_branch:
|
if repository.data.zip_release:
|
||||||
|
if repository.data.filename.endswith(".zip"):
|
||||||
|
if repository.ref != repository.data.default_branch:
|
||||||
|
return True
|
||||||
|
if repository.ref == repository.data.default_branch:
|
||||||
return False
|
return False
|
||||||
if repository.information.category not in ["plugin", "theme"]:
|
if repository.data.category not in ["plugin", "theme"]:
|
||||||
return False
|
return False
|
||||||
if not repository.releases.releases:
|
if not repository.releases.releases:
|
||||||
return False
|
return False
|
||||||
@@ -31,7 +35,7 @@ def gather_files_to_download(repository):
|
|||||||
tree = repository.tree
|
tree = repository.tree
|
||||||
ref = f"{repository.ref}".replace("tags/", "")
|
ref = f"{repository.ref}".replace("tags/", "")
|
||||||
releaseobjects = repository.releases.objects
|
releaseobjects = repository.releases.objects
|
||||||
category = repository.information.category
|
category = repository.data.category
|
||||||
remotelocation = repository.content.path.remote
|
remotelocation = repository.content.path.remote
|
||||||
|
|
||||||
if should_try_releases(repository):
|
if should_try_releases(repository):
|
||||||
@@ -44,7 +48,7 @@ def gather_files_to_download(repository):
|
|||||||
|
|
||||||
if repository.content.single:
|
if repository.content.single:
|
||||||
for treefile in tree:
|
for treefile in tree:
|
||||||
if treefile.filename == repository.information.file_name:
|
if treefile.filename == repository.data.file_name:
|
||||||
files.append(
|
files.append(
|
||||||
FileInformation(
|
FileInformation(
|
||||||
treefile.download_url, treefile.full_path, treefile.filename
|
treefile.download_url, treefile.full_path, treefile.filename
|
||||||
@@ -55,28 +59,29 @@ def gather_files_to_download(repository):
|
|||||||
if category == "plugin":
|
if category == "plugin":
|
||||||
for treefile in tree:
|
for treefile in tree:
|
||||||
if treefile.path in ["", "dist"]:
|
if treefile.path in ["", "dist"]:
|
||||||
if not remotelocation:
|
|
||||||
if treefile.filename != repository.information.file_name:
|
|
||||||
continue
|
|
||||||
if remotelocation == "dist" and not treefile.filename.startswith(
|
if remotelocation == "dist" and not treefile.filename.startswith(
|
||||||
"dist"
|
"dist"
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
if treefile.is_directory:
|
if not remotelocation:
|
||||||
continue
|
if not treefile.filename.endswith(".js"):
|
||||||
files.append(
|
continue
|
||||||
FileInformation(
|
if treefile.path != "":
|
||||||
treefile.download_url, treefile.full_path, treefile.filename
|
continue
|
||||||
|
if not treefile.is_directory:
|
||||||
|
files.append(
|
||||||
|
FileInformation(
|
||||||
|
treefile.download_url, treefile.full_path, treefile.filename
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
if files:
|
if files:
|
||||||
return files
|
return files
|
||||||
|
|
||||||
if repository.repository_manifest.content_in_root:
|
if repository.data.content_in_root:
|
||||||
if repository.repository_manifest.filename is None:
|
if not repository.data.filename:
|
||||||
if category == "theme":
|
if category == "theme":
|
||||||
tree = filter_content_return_one_of_type(
|
tree = filter_content_return_one_of_type(
|
||||||
repository.tree, "themes", "yaml", "full_path"
|
repository.tree, "", "yaml", "full_path"
|
||||||
)
|
)
|
||||||
|
|
||||||
for path in tree:
|
for path in tree:
|
||||||
@@ -104,21 +109,17 @@ async def download_zip(repository, validate):
|
|||||||
return validate
|
return validate
|
||||||
|
|
||||||
for content in contents:
|
for content in contents:
|
||||||
filecontent = await async_download_file(
|
filecontent = await async_download_file(content.download_url)
|
||||||
repository.hass, content.download_url
|
|
||||||
)
|
|
||||||
|
|
||||||
if filecontent is None:
|
if filecontent is None:
|
||||||
validate.errors.append(f"[{content.name}] was not downloaded.")
|
validate.errors.append(f"[{content.name}] was not downloaded.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result = await async_save_file(
|
result = await async_save_file(
|
||||||
f"{tempfile.gettempdir()}/{repository.repository_manifest.filename}",
|
f"{tempfile.gettempdir()}/{repository.data.filename}", filecontent
|
||||||
filecontent,
|
|
||||||
)
|
)
|
||||||
with zipfile.ZipFile(
|
with zipfile.ZipFile(
|
||||||
f"{tempfile.gettempdir()}/{repository.repository_manifest.filename}",
|
f"{tempfile.gettempdir()}/{repository.data.filename}", "r"
|
||||||
"r",
|
|
||||||
) as zip_file:
|
) as zip_file:
|
||||||
zip_file.extractall(repository.content.path.local)
|
zip_file.extractall(repository.content.path.local)
|
||||||
|
|
||||||
@@ -132,54 +133,49 @@ async def download_zip(repository, validate):
|
|||||||
return validate
|
return validate
|
||||||
|
|
||||||
|
|
||||||
async def download_content(repository, validate, local_directory):
|
async def download_content(repository):
|
||||||
"""Download the content of a directory."""
|
"""Download the content of a directory."""
|
||||||
contents = gather_files_to_download(repository)
|
contents = gather_files_to_download(repository)
|
||||||
try:
|
repository.logger.debug(repository.data.filename)
|
||||||
if not contents:
|
if not contents:
|
||||||
raise HacsException("No content to download")
|
raise HacsException("No content to download")
|
||||||
|
|
||||||
for content in contents:
|
for content in contents:
|
||||||
if repository.repository_manifest.content_in_root:
|
if repository.data.content_in_root and repository.data.filename:
|
||||||
if repository.repository_manifest.filename is not None:
|
if content.name != repository.data.filename:
|
||||||
if content.name != repository.repository_manifest.filename:
|
|
||||||
continue
|
|
||||||
repository.logger.debug(f"Downloading {content.name}")
|
|
||||||
|
|
||||||
filecontent = await async_download_file(
|
|
||||||
repository.hass, content.download_url
|
|
||||||
)
|
|
||||||
|
|
||||||
if filecontent is None:
|
|
||||||
validate.errors.append(f"[{content.name}] was not downloaded.")
|
|
||||||
continue
|
continue
|
||||||
|
repository.logger.debug(f"Downloading {content.name}")
|
||||||
|
|
||||||
# Save the content of the file.
|
filecontent = await async_download_file(content.download_url)
|
||||||
if repository.content.single or content.path is None:
|
|
||||||
local_directory = repository.content.path.local
|
|
||||||
|
|
||||||
else:
|
if filecontent is None:
|
||||||
_content_path = content.path
|
repository.validate.errors.append(f"[{content.name}] was not downloaded.")
|
||||||
if not repository.repository_manifest.content_in_root:
|
continue
|
||||||
_content_path = _content_path.replace(
|
|
||||||
f"{repository.content.path.remote}", ""
|
|
||||||
)
|
|
||||||
|
|
||||||
local_directory = f"{repository.content.path.local}/{_content_path}"
|
# Save the content of the file.
|
||||||
local_directory = local_directory.split("/")
|
if repository.content.single or content.path is None:
|
||||||
del local_directory[-1]
|
local_directory = repository.content.path.local
|
||||||
local_directory = "/".join(local_directory)
|
|
||||||
|
|
||||||
# Check local directory
|
else:
|
||||||
pathlib.Path(local_directory).mkdir(parents=True, exist_ok=True)
|
_content_path = content.path
|
||||||
|
if not repository.data.content_in_root:
|
||||||
|
_content_path = _content_path.replace(
|
||||||
|
f"{repository.content.path.remote}", ""
|
||||||
|
)
|
||||||
|
|
||||||
local_file_path = f"{local_directory}/{content.name}"
|
local_directory = f"{repository.content.path.local}/{_content_path}"
|
||||||
result = await async_save_file(local_file_path, filecontent)
|
local_directory = local_directory.split("/")
|
||||||
if result:
|
del local_directory[-1]
|
||||||
repository.logger.info(f"download of {content.name} complete")
|
local_directory = "/".join(local_directory)
|
||||||
continue
|
|
||||||
validate.errors.append(f"[{content.name}] was not downloaded.")
|
# Check local directory
|
||||||
|
pathlib.Path(local_directory).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
local_file_path = (f"{local_directory}/{content.name}").replace("//", "/")
|
||||||
|
|
||||||
|
result = await async_save_file(local_file_path, filecontent)
|
||||||
|
if result:
|
||||||
|
repository.logger.info(f"download of {content.name} complete")
|
||||||
|
continue
|
||||||
|
repository.validate.errors.append(f"[{content.name}] was not downloaded.")
|
||||||
|
|
||||||
except Exception as exception: # pylint: disable=broad-except
|
|
||||||
validate.errors.append(f"Download was not complete [{exception}]")
|
|
||||||
return validate
|
|
||||||
|
11
config/custom_components/hacs/helpers/filters.py
Executable file → Normal file
11
config/custom_components/hacs/helpers/filters.py
Executable file → Normal file
@@ -42,3 +42,14 @@ def find_first_of_filetype(content, filterfiltype, attr="name"):
|
|||||||
filename = getattr(_filename, attr)
|
filename = getattr(_filename, attr)
|
||||||
break
|
break
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def get_first_directory_in_directory(content, dirname):
|
||||||
|
"""Return the first directory in dirname or None."""
|
||||||
|
directory = None
|
||||||
|
for path in content:
|
||||||
|
if path.full_path.startswith(dirname) and path.full_path != dirname:
|
||||||
|
if path.is_directory:
|
||||||
|
directory = path.filename
|
||||||
|
break
|
||||||
|
return directory
|
||||||
|
5
config/custom_components/hacs/helpers/get_defaults.py
Executable file → Normal file
5
config/custom_components/hacs/helpers/get_defaults.py
Executable file → Normal file
@@ -2,6 +2,7 @@
|
|||||||
import json
|
import json
|
||||||
from aiogithubapi import AIOGitHub, AIOGitHubException
|
from aiogithubapi import AIOGitHub, AIOGitHubException
|
||||||
from integrationhelper import Logger
|
from integrationhelper import Logger
|
||||||
|
from custom_components.hacs.helpers.information import get_repository
|
||||||
|
|
||||||
|
|
||||||
async def get_default_repos_orgs(github: type(AIOGitHub), category: str) -> dict:
|
async def get_default_repos_orgs(github: type(AIOGitHub), category: str) -> dict:
|
||||||
@@ -27,13 +28,13 @@ async def get_default_repos_orgs(github: type(AIOGitHub), category: str) -> dict
|
|||||||
return repositories
|
return repositories
|
||||||
|
|
||||||
|
|
||||||
async def get_default_repos_lists(github: type(AIOGitHub), default: str) -> dict:
|
async def get_default_repos_lists(session, token, default: str) -> dict:
|
||||||
"""Gets repositories from default list."""
|
"""Gets repositories from default list."""
|
||||||
repositories = []
|
repositories = []
|
||||||
logger = Logger("hacs")
|
logger = Logger("hacs")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
repo = await github.get_repo("hacs/default")
|
repo = await get_repository(session, token, "hacs/default")
|
||||||
content = await repo.get_contents(default)
|
content = await repo.get_contents(default)
|
||||||
repositories = json.loads(content.content)
|
repositories = json.loads(content.content)
|
||||||
|
|
||||||
|
184
config/custom_components/hacs/helpers/information.py
Normal file
184
config/custom_components/hacs/helpers/information.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
"""Return repository information if any."""
|
||||||
|
import json
|
||||||
|
from aiogithubapi import AIOGitHubException, AIOGitHub
|
||||||
|
from custom_components.hacs.handler.template import render_template
|
||||||
|
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||||
|
|
||||||
|
|
||||||
|
def info_file(repository):
|
||||||
|
"""get info filename."""
|
||||||
|
if repository.data.render_readme:
|
||||||
|
for filename in ["readme", "readme.md", "README", "README.md", "README.MD"]:
|
||||||
|
if filename in repository.treefiles:
|
||||||
|
return filename
|
||||||
|
return ""
|
||||||
|
for filename in ["info", "info.md", "INFO", "INFO.md", "INFO.MD"]:
|
||||||
|
if filename in repository.treefiles:
|
||||||
|
return filename
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
async def get_info_md_content(repository):
|
||||||
|
"""Get the content of info.md"""
|
||||||
|
filename = info_file(repository)
|
||||||
|
if not filename:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
info = await repository.repository_object.get_contents(filename, repository.ref)
|
||||||
|
if info is None:
|
||||||
|
return ""
|
||||||
|
info = info.content.replace("<svg", "<disabled").replace("</svg", "</disabled")
|
||||||
|
return render_template(info, repository)
|
||||||
|
except (AIOGitHubException, Exception): # pylint: disable=broad-except
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
async def get_repository(session, token, repository_full_name):
|
||||||
|
"""Return a repository object or None."""
|
||||||
|
try:
|
||||||
|
github = AIOGitHub(token, session)
|
||||||
|
repository = await github.get_repo(repository_full_name)
|
||||||
|
return repository
|
||||||
|
except AIOGitHubException as exception:
|
||||||
|
raise HacsException(exception)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_tree(repository, ref):
|
||||||
|
"""Return the repository tree."""
|
||||||
|
try:
|
||||||
|
tree = await repository.get_tree(ref)
|
||||||
|
return tree
|
||||||
|
except AIOGitHubException as exception:
|
||||||
|
raise HacsException(exception)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_releases(repository, prerelease=False, returnlimit=5):
|
||||||
|
"""Return the repository releases."""
|
||||||
|
try:
|
||||||
|
releases = await repository.get_releases(prerelease, returnlimit)
|
||||||
|
return releases
|
||||||
|
except AIOGitHubException as exception:
|
||||||
|
raise HacsException(exception)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_integration_manifest(repository):
|
||||||
|
"""Return the integration manifest."""
|
||||||
|
if repository.data.content_in_root:
|
||||||
|
manifest_path = "manifest.json"
|
||||||
|
else:
|
||||||
|
manifest_path = f"{repository.content.path.remote}/manifest.json"
|
||||||
|
if not manifest_path in [x.full_path for x in repository.tree]:
|
||||||
|
raise HacsException(f"No file found '{manifest_path}'")
|
||||||
|
try:
|
||||||
|
manifest = await repository.repository_object.get_contents(
|
||||||
|
manifest_path, repository.ref
|
||||||
|
)
|
||||||
|
manifest = json.loads(manifest.content)
|
||||||
|
except Exception as exception: # pylint: disable=broad-except
|
||||||
|
raise HacsException(f"Could not read manifest.json [{exception}]")
|
||||||
|
|
||||||
|
try:
|
||||||
|
repository.integration_manifest = manifest
|
||||||
|
repository.data.authors = manifest["codeowners"]
|
||||||
|
repository.data.domain = manifest["domain"]
|
||||||
|
repository.data.manifest_name = manifest["name"]
|
||||||
|
repository.data.homeassistant = manifest.get("homeassistant")
|
||||||
|
|
||||||
|
# Set local path
|
||||||
|
repository.content.path.local = repository.localpath
|
||||||
|
|
||||||
|
except KeyError as exception:
|
||||||
|
raise HacsException(f"Missing expected key {exception} in 'manifest.json'")
|
||||||
|
|
||||||
|
|
||||||
|
def find_file_name(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
if repository.data.category == "plugin":
|
||||||
|
get_file_name_plugin(repository)
|
||||||
|
elif repository.data.category == "integration":
|
||||||
|
get_file_name_integration(repository)
|
||||||
|
elif repository.data.category == "theme":
|
||||||
|
get_file_name_theme(repository)
|
||||||
|
elif repository.data.category == "appdaemon":
|
||||||
|
get_file_name_appdaemon(repository)
|
||||||
|
elif repository.data.category == "python_script":
|
||||||
|
get_file_name_python_script(repository)
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_plugin(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
tree = repository.tree
|
||||||
|
releases = repository.releases.objects
|
||||||
|
|
||||||
|
if repository.data.content_in_root:
|
||||||
|
possible_locations = [""]
|
||||||
|
else:
|
||||||
|
possible_locations = ["release", "dist", ""]
|
||||||
|
|
||||||
|
# Handler for plug requirement 3
|
||||||
|
if repository.data.filename:
|
||||||
|
valid_filenames = [repository.data.filename]
|
||||||
|
else:
|
||||||
|
valid_filenames = [
|
||||||
|
f"{repository.data.name.replace('lovelace-', '')}.js",
|
||||||
|
f"{repository.data.name}.js",
|
||||||
|
f"{repository.data.name}.umd.js",
|
||||||
|
f"{repository.data.name}-bundle.js",
|
||||||
|
]
|
||||||
|
|
||||||
|
for location in possible_locations:
|
||||||
|
if location == "release":
|
||||||
|
if not releases:
|
||||||
|
continue
|
||||||
|
release = releases[0]
|
||||||
|
if not release.assets:
|
||||||
|
continue
|
||||||
|
asset = release.assets[0]
|
||||||
|
for filename in valid_filenames:
|
||||||
|
if filename == asset.name:
|
||||||
|
repository.data.file_name = filename
|
||||||
|
repository.content.path.remote = "release"
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
for filename in valid_filenames:
|
||||||
|
if f"{location+'/' if location else ''}{filename}" in [
|
||||||
|
x.full_path for x in tree
|
||||||
|
]:
|
||||||
|
repository.data.file_name = filename.split("/")[-1]
|
||||||
|
repository.content.path.remote = location
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_integration(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
tree = repository.tree
|
||||||
|
releases = repository.releases.objects
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_theme(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
tree = repository.tree
|
||||||
|
|
||||||
|
for treefile in tree:
|
||||||
|
if treefile.full_path.startswith(
|
||||||
|
repository.content.path.remote
|
||||||
|
) and treefile.full_path.endswith(".yaml"):
|
||||||
|
repository.data.file_name = treefile.filename
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_appdaemon(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
tree = repository.tree
|
||||||
|
releases = repository.releases.objects
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_python_script(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
tree = repository.tree
|
||||||
|
|
||||||
|
for treefile in tree:
|
||||||
|
if treefile.full_path.startswith(
|
||||||
|
repository.content.path.remote
|
||||||
|
) and treefile.full_path.endswith(".py"):
|
||||||
|
repository.data.file_name = treefile.filename
|
92
config/custom_components/hacs/helpers/install.py
Executable file → Normal file
92
config/custom_components/hacs/helpers/install.py
Executable file → Normal file
@@ -1,14 +1,17 @@
|
|||||||
"""Install helper for repositories."""
|
"""Install helper for repositories."""
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
from custom_components.hacs.hacsbase.exceptions import HacsException
|
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||||
from custom_components.hacs.hacsbase.backup import Backup
|
from custom_components.hacs.hacsbase.backup import Backup, BackupNetDaemon
|
||||||
|
from custom_components.hacs.helpers.download import download_content
|
||||||
|
|
||||||
|
|
||||||
async def install_repository(repository):
|
async def install_repository(repository):
|
||||||
"""Common installation steps of the repository."""
|
"""Common installation steps of the repository."""
|
||||||
persistent_directory = None
|
persistent_directory = None
|
||||||
await repository.update_repository()
|
await repository.update_repository()
|
||||||
|
repository.validate.errors = []
|
||||||
|
|
||||||
if not repository.can_install:
|
if not repository.can_install:
|
||||||
raise HacsException(
|
raise HacsException(
|
||||||
@@ -16,41 +19,36 @@ async def install_repository(repository):
|
|||||||
)
|
)
|
||||||
|
|
||||||
version = version_to_install(repository)
|
version = version_to_install(repository)
|
||||||
if version == repository.information.default_branch:
|
if version == repository.data.default_branch:
|
||||||
repository.ref = version
|
repository.ref = version
|
||||||
else:
|
else:
|
||||||
repository.ref = f"tags/{version}"
|
repository.ref = f"tags/{version}"
|
||||||
|
|
||||||
if repository.repository_manifest:
|
if repository.status.installed and repository.data.category == "netdaemon":
|
||||||
if repository.repository_manifest.persistent_directory:
|
persistent_directory = BackupNetDaemon(repository)
|
||||||
if os.path.exists(
|
persistent_directory.create()
|
||||||
f"{repository.content.path.local}/{repository.repository_manifest.persistent_directory}"
|
|
||||||
):
|
elif repository.data.persistent_directory:
|
||||||
persistent_directory = Backup(
|
if os.path.exists(
|
||||||
f"{repository.content.path.local}/{repository.repository_manifest.persistent_directory}",
|
f"{repository.content.path.local}/{repository.data.persistent_directory}"
|
||||||
tempfile.gettempdir() + "/hacs_persistent_directory/",
|
):
|
||||||
)
|
persistent_directory = Backup(
|
||||||
persistent_directory.create()
|
f"{repository.content.path.local}/{repository.data.persistent_directory}",
|
||||||
|
tempfile.gettempdir() + "/hacs_persistent_directory/",
|
||||||
|
)
|
||||||
|
persistent_directory.create()
|
||||||
|
|
||||||
if repository.status.installed and not repository.content.single:
|
if repository.status.installed and not repository.content.single:
|
||||||
backup = Backup(repository.content.path.local)
|
backup = Backup(repository.content.path.local)
|
||||||
backup.create()
|
backup.create()
|
||||||
|
|
||||||
if (
|
if repository.data.zip_release and version != repository.data.default_branch:
|
||||||
repository.repository_manifest.zip_release
|
await repository.download_zip(repository)
|
||||||
and version != repository.information.default_branch
|
|
||||||
):
|
|
||||||
validate = await repository.download_zip(repository.validate)
|
|
||||||
else:
|
else:
|
||||||
validate = await repository.download_content(
|
await download_content(repository)
|
||||||
repository.validate,
|
|
||||||
repository.content.path.remote,
|
|
||||||
repository.content.path.local,
|
|
||||||
repository.ref,
|
|
||||||
)
|
|
||||||
|
|
||||||
if validate.errors:
|
if repository.validate.errors:
|
||||||
for error in validate.errors:
|
for error in repository.validate.errors:
|
||||||
repository.logger.error(error)
|
repository.logger.error(error)
|
||||||
if repository.status.installed and not repository.content.single:
|
if repository.status.installed and not repository.content.single:
|
||||||
backup.restore()
|
backup.restore()
|
||||||
@@ -62,14 +60,14 @@ async def install_repository(repository):
|
|||||||
persistent_directory.restore()
|
persistent_directory.restore()
|
||||||
persistent_directory.cleanup()
|
persistent_directory.cleanup()
|
||||||
|
|
||||||
if validate.success:
|
if repository.validate.success:
|
||||||
if repository.information.full_name not in repository.common.installed:
|
if repository.data.full_name not in repository.hacs.common.installed:
|
||||||
if repository.information.full_name == "hacs/integration":
|
if repository.data.full_name == "hacs/integration":
|
||||||
repository.common.installed.append(repository.information.full_name)
|
repository.hacs.common.installed.append(repository.data.full_name)
|
||||||
repository.status.installed = True
|
repository.status.installed = True
|
||||||
repository.versions.installed_commit = repository.versions.available_commit
|
repository.versions.installed_commit = repository.versions.available_commit
|
||||||
|
|
||||||
if version == repository.information.default_branch:
|
if version == repository.data.default_branch:
|
||||||
repository.versions.installed = None
|
repository.versions.installed = None
|
||||||
else:
|
else:
|
||||||
repository.versions.installed = version
|
repository.versions.installed = version
|
||||||
@@ -80,28 +78,34 @@ async def install_repository(repository):
|
|||||||
|
|
||||||
async def reload_after_install(repository):
|
async def reload_after_install(repository):
|
||||||
"""Reload action after installation success."""
|
"""Reload action after installation success."""
|
||||||
if repository.information.category == "integration":
|
if repository.data.category == "integration":
|
||||||
if repository.config_flow:
|
if repository.config_flow:
|
||||||
if repository.information.full_name != "hacs/integration":
|
if repository.data.full_name != "hacs/integration":
|
||||||
await repository.reload_custom_components()
|
await repository.reload_custom_components()
|
||||||
repository.pending_restart = True
|
repository.pending_restart = True
|
||||||
|
|
||||||
elif repository.information.category == "theme":
|
elif repository.data.category == "theme":
|
||||||
try:
|
try:
|
||||||
await repository.hass.services.async_call("frontend", "reload_themes", {})
|
await repository.hacs.hass.services.async_call(
|
||||||
|
"frontend", "reload_themes", {}
|
||||||
|
)
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
pass
|
||||||
|
elif repository.data.category == "netdaemon":
|
||||||
|
try:
|
||||||
|
await repository.hacs.hass.services.async_call(
|
||||||
|
"hassio", "addon_restart", {"addon": "e466aeb3_netdaemon"}
|
||||||
|
)
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def installation_complete(repository):
|
def installation_complete(repository):
|
||||||
"""Action to run when the installation is complete."""
|
"""Action to run when the installation is complete."""
|
||||||
repository.hass.bus.async_fire(
|
hacs = get_hacs()
|
||||||
|
hacs.hass.bus.async_fire(
|
||||||
"hacs/repository",
|
"hacs/repository",
|
||||||
{
|
{"id": 1337, "action": "install", "repository": repository.data.full_name},
|
||||||
"id": 1337,
|
|
||||||
"action": "install",
|
|
||||||
"repository": repository.information.full_name,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -115,10 +119,10 @@ def version_to_install(repository):
|
|||||||
return repository.status.selected_tag
|
return repository.status.selected_tag
|
||||||
return repository.versions.available
|
return repository.versions.available
|
||||||
if repository.status.selected_tag is not None:
|
if repository.status.selected_tag is not None:
|
||||||
if repository.status.selected_tag == repository.information.default_branch:
|
if repository.status.selected_tag == repository.data.default_branch:
|
||||||
return repository.information.default_branch
|
return repository.data.default_branch
|
||||||
if repository.status.selected_tag in repository.releases.published_tags:
|
if repository.status.selected_tag in repository.releases.published_tags:
|
||||||
return repository.status.selected_tag
|
return repository.status.selected_tag
|
||||||
if repository.information.default_branch is None:
|
if repository.data.default_branch is None:
|
||||||
return "master"
|
return "master"
|
||||||
return repository.information.default_branch
|
return repository.data.default_branch
|
||||||
|
23
config/custom_components/hacs/helpers/misc.py
Executable file → Normal file
23
config/custom_components/hacs/helpers/misc.py
Executable file → Normal file
@@ -2,20 +2,23 @@
|
|||||||
import semantic_version
|
import semantic_version
|
||||||
|
|
||||||
|
|
||||||
def get_repository_name(
|
def get_repository_name(repository) -> str:
|
||||||
hacs_manifest, repository_name: str, category: str = None, manifest: dict = None
|
|
||||||
) -> str:
|
|
||||||
"""Return the name of the repository for use in the frontend."""
|
"""Return the name of the repository for use in the frontend."""
|
||||||
|
|
||||||
if hacs_manifest.name is not None:
|
if repository.repository_manifest.name is not None:
|
||||||
return hacs_manifest.name
|
return repository.repository_manifest.name
|
||||||
|
|
||||||
if category == "integration":
|
if repository.data.category == "integration":
|
||||||
if manifest:
|
if repository.integration_manifest:
|
||||||
if "name" in manifest:
|
if "name" in repository.integration_manifest:
|
||||||
return manifest["name"]
|
return repository.integration_manifest["name"]
|
||||||
|
|
||||||
return repository_name.replace("-", " ").replace("_", " ").title()
|
return (
|
||||||
|
repository.data.full_name.split("/")[-1]
|
||||||
|
.replace("-", " ")
|
||||||
|
.replace("_", " ")
|
||||||
|
.title()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def version_left_higher_then_right(new: str, old: str) -> bool:
|
def version_left_higher_then_right(new: str, old: str) -> bool:
|
||||||
|
8
config/custom_components/hacs/helpers/network.py
Normal file
8
config/custom_components/hacs/helpers/network.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""Verify network."""
|
||||||
|
from socket import gaierror
|
||||||
|
from integrationhelper import Logger
|
||||||
|
|
||||||
|
|
||||||
|
def internet_connectivity_check(host="api.github.com"):
|
||||||
|
"""Verify network connectivity."""
|
||||||
|
return True
|
49
config/custom_components/hacs/helpers/register_repository.py
Normal file
49
config/custom_components/hacs/helpers/register_repository.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""Register a repository."""
|
||||||
|
from aiogithubapi import AIOGitHubException
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
|
from custom_components.hacs.hacsbase.exceptions import (
|
||||||
|
HacsException,
|
||||||
|
HacsExpectedException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def register_repository(full_name, category, check=True):
|
||||||
|
"""Register a repository."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
from custom_components.hacs.repositories import (
|
||||||
|
RERPOSITORY_CLASSES,
|
||||||
|
) # To hanle import error
|
||||||
|
|
||||||
|
if full_name in hacs.common.skip:
|
||||||
|
if full_name != "hacs/integration":
|
||||||
|
raise HacsExpectedException(f"Skipping {full_name}")
|
||||||
|
|
||||||
|
if category not in RERPOSITORY_CLASSES:
|
||||||
|
raise HacsException(f"{category} is not a valid repository category.")
|
||||||
|
|
||||||
|
repository = RERPOSITORY_CLASSES[category](full_name)
|
||||||
|
if check:
|
||||||
|
try:
|
||||||
|
await repository.registration()
|
||||||
|
if hacs.system.status.new:
|
||||||
|
repository.status.new = False
|
||||||
|
if repository.validate.errors:
|
||||||
|
hacs.common.skip.append(repository.data.full_name)
|
||||||
|
if not hacs.system.status.startup:
|
||||||
|
hacs.logger.error(f"Validation for {full_name} failed.")
|
||||||
|
return repository.validate.errors
|
||||||
|
repository.logger.info("Registration complete")
|
||||||
|
except AIOGitHubException as exception:
|
||||||
|
hacs.common.skip.append(repository.data.full_name)
|
||||||
|
raise HacsException(f"Validation for {full_name} failed with {exception}.")
|
||||||
|
|
||||||
|
hacs.hass.bus.async_fire(
|
||||||
|
"hacs/repository",
|
||||||
|
{
|
||||||
|
"id": 1337,
|
||||||
|
"action": "registration",
|
||||||
|
"repository": repository.data.full_name,
|
||||||
|
"repository_id": repository.information.uid,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
hacs.repositories.append(repository)
|
90
config/custom_components/hacs/helpers/validate_repository.py
Normal file
90
config/custom_components/hacs/helpers/validate_repository.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
"""Helper to do common validation for repositories."""
|
||||||
|
from aiogithubapi import AIOGitHubException
|
||||||
|
from custom_components.hacs.globals import get_hacs, is_removed
|
||||||
|
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||||
|
from custom_components.hacs.helpers.install import version_to_install
|
||||||
|
from custom_components.hacs.helpers.information import (
|
||||||
|
get_repository,
|
||||||
|
get_tree,
|
||||||
|
get_releases,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def common_validate(repository):
|
||||||
|
"""Common validation steps of the repository."""
|
||||||
|
repository.validate.errors = []
|
||||||
|
|
||||||
|
# Make sure the repository exist.
|
||||||
|
repository.logger.debug("Checking repository.")
|
||||||
|
await common_update_data(repository)
|
||||||
|
|
||||||
|
# Step 6: Get the content of hacs.json
|
||||||
|
await repository.get_repository_manifest_content()
|
||||||
|
|
||||||
|
|
||||||
|
async def common_update_data(repository):
|
||||||
|
"""Common update data."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
try:
|
||||||
|
repository_object = await get_repository(
|
||||||
|
hacs.session, hacs.configuration.token, repository.data.full_name
|
||||||
|
)
|
||||||
|
repository.repository_object = repository_object
|
||||||
|
repository.data.update_data(repository_object.attributes)
|
||||||
|
except (AIOGitHubException, HacsException) as exception:
|
||||||
|
if not hacs.system.status.startup:
|
||||||
|
repository.logger.error(exception)
|
||||||
|
repository.validate.errors.append("Repository does not exist.")
|
||||||
|
raise HacsException(exception)
|
||||||
|
|
||||||
|
# Make sure the repository is not archived.
|
||||||
|
if repository.data.archived:
|
||||||
|
repository.validate.errors.append("Repository is archived.")
|
||||||
|
raise HacsException("Repository is archived.")
|
||||||
|
|
||||||
|
# Make sure the repository is not in the blacklist.
|
||||||
|
if is_removed(repository.data.full_name):
|
||||||
|
repository.validate.errors.append("Repository is in the blacklist.")
|
||||||
|
raise HacsException("Repository is in the blacklist.")
|
||||||
|
|
||||||
|
# Get releases.
|
||||||
|
try:
|
||||||
|
releases = await get_releases(
|
||||||
|
repository.repository_object,
|
||||||
|
repository.status.show_beta,
|
||||||
|
hacs.configuration.release_limit,
|
||||||
|
)
|
||||||
|
if releases:
|
||||||
|
repository.releases.releases = True
|
||||||
|
repository.releases.objects = releases
|
||||||
|
repository.releases.published_tags = [
|
||||||
|
x.tag_name for x in releases if not x.draft
|
||||||
|
]
|
||||||
|
repository.versions.available = next(iter(releases)).tag_name
|
||||||
|
for release in releases:
|
||||||
|
if release.tag_name == repository.ref:
|
||||||
|
assets = release.assets
|
||||||
|
if assets:
|
||||||
|
downloads = next(iter(assets)).attributes.get("download_count")
|
||||||
|
repository.releases.downloads = downloads
|
||||||
|
|
||||||
|
except (AIOGitHubException, HacsException):
|
||||||
|
repository.releases.releases = False
|
||||||
|
|
||||||
|
repository.ref = version_to_install(repository)
|
||||||
|
|
||||||
|
repository.logger.debug(
|
||||||
|
f"Running checks against {repository.ref.replace('tags/', '')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
repository.tree = await get_tree(repository.repository_object, repository.ref)
|
||||||
|
if not repository.tree:
|
||||||
|
raise HacsException("No files in tree")
|
||||||
|
repository.treefiles = []
|
||||||
|
for treefile in repository.tree:
|
||||||
|
repository.treefiles.append(treefile.full_path)
|
||||||
|
except (AIOGitHubException, HacsException) as exception:
|
||||||
|
if not hacs.system.status.startup:
|
||||||
|
repository.logger.error(exception)
|
||||||
|
raise HacsException(exception)
|
106
config/custom_components/hacs/http.py
Executable file → Normal file
106
config/custom_components/hacs/http.py
Executable file → Normal file
@@ -1,13 +1,14 @@
|
|||||||
"""HACS http endpoints."""
|
"""HACS http endpoints."""
|
||||||
import os
|
import os
|
||||||
|
from integrationhelper import Logger
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from hacs_frontend import locate_gz, locate_debug_gz
|
from hacs_frontend import locate_gz, locate_debug_gz
|
||||||
|
|
||||||
from .hacsbase import Hacs
|
from custom_components.hacs.globals import get_hacs
|
||||||
|
|
||||||
|
|
||||||
class HacsFrontend(HomeAssistantView, Hacs):
|
class HacsFrontend(HomeAssistantView):
|
||||||
"""Base View Class for HACS."""
|
"""Base View Class for HACS."""
|
||||||
|
|
||||||
requires_auth = False
|
requires_auth = False
|
||||||
@@ -16,50 +17,67 @@ class HacsFrontend(HomeAssistantView, Hacs):
|
|||||||
|
|
||||||
async def get(self, request, requested_file): # pylint: disable=unused-argument
|
async def get(self, request, requested_file): # pylint: disable=unused-argument
|
||||||
"""Handle HACS Web requests."""
|
"""Handle HACS Web requests."""
|
||||||
|
return await get_file_response(requested_file)
|
||||||
if requested_file.startswith("frontend-"):
|
|
||||||
if self.configuration.debug:
|
|
||||||
servefile = await self.hass.async_add_executor_job(locate_debug_gz)
|
|
||||||
self.logger.debug("Serving DEBUG frontend")
|
|
||||||
else:
|
|
||||||
servefile = await self.hass.async_add_executor_job(locate_gz)
|
|
||||||
|
|
||||||
if os.path.exists(servefile):
|
|
||||||
return web.FileResponse(servefile)
|
|
||||||
elif requested_file == "iconset.js":
|
|
||||||
return web.FileResponse(
|
|
||||||
f"{self.system.config_path}/custom_components/hacs/iconset.js"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if requested_file.startswith("themes"):
|
|
||||||
file = f"{self.system.config_path}/{requested_file}"
|
|
||||||
else:
|
|
||||||
file = f"{self.system.config_path}/www/community/{requested_file}"
|
|
||||||
|
|
||||||
# Serve .gz if it exist
|
|
||||||
if os.path.exists(file + ".gz"):
|
|
||||||
file += ".gz"
|
|
||||||
|
|
||||||
if os.path.exists(file):
|
|
||||||
self.logger.debug("Serving {} from {}".format(requested_file, file))
|
|
||||||
response = web.FileResponse(file)
|
|
||||||
response.headers["Cache-Control"] = "max-age=0, must-revalidate"
|
|
||||||
return response
|
|
||||||
else:
|
|
||||||
self.logger.error(f"Tried to serve up '{file}' but it does not exist")
|
|
||||||
|
|
||||||
except Exception as error: # pylint: disable=broad-except
|
|
||||||
self.logger.debug(
|
|
||||||
"there was an issue trying to serve {} - {}".format(
|
|
||||||
requested_file, error
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return web.Response(status=404)
|
|
||||||
|
|
||||||
|
|
||||||
class HacsPluginViewLegacy(HacsFrontend):
|
class HacsPluginViewLegacy(HacsFrontend):
|
||||||
"""Alias for legacy, remove with 2.0"""
|
"""Alias for legacy, remove with 1.0"""
|
||||||
|
|
||||||
|
name = "community_plugin"
|
||||||
url = r"/community_plugin/{requested_file:.+}"
|
url = r"/community_plugin/{requested_file:.+}"
|
||||||
|
|
||||||
|
async def get(self, request, requested_file): # pylint: disable=unused-argument
|
||||||
|
"""DEPRECATED."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
if hacs.system.ha_version.split(".")[1] >= "107":
|
||||||
|
logger = Logger("hacs.deprecated")
|
||||||
|
logger.warning(
|
||||||
|
"The '/community_plugin/*' is deprecated and will be removed in an upcomming version of HACS, it has been replaced by '/hacsfiles/*', if you use the UI to manage your lovelace configuration, you can update this by going to the settings tab in HACS, if you use YAML to manage your lovelace configuration, you manually need to replace the URL in your resources."
|
||||||
|
)
|
||||||
|
|
||||||
|
return await get_file_response(requested_file)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_file_response(requested_file):
|
||||||
|
"""Get file."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
|
||||||
|
if requested_file.startswith("frontend-"):
|
||||||
|
if hacs.configuration.debug:
|
||||||
|
servefile = await hacs.hass.async_add_executor_job(locate_debug_gz)
|
||||||
|
hacs.logger.debug("Serving DEBUG frontend")
|
||||||
|
else:
|
||||||
|
servefile = await hacs.hass.async_add_executor_job(locate_gz)
|
||||||
|
|
||||||
|
if os.path.exists(servefile):
|
||||||
|
return web.FileResponse(servefile)
|
||||||
|
elif requested_file == "iconset.js":
|
||||||
|
return web.FileResponse(
|
||||||
|
f"{hacs.system.config_path}/custom_components/hacs/iconset.js"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if requested_file.startswith("themes"):
|
||||||
|
file = f"{hacs.system.config_path}/{requested_file}"
|
||||||
|
else:
|
||||||
|
file = f"{hacs.system.config_path}/www/community/{requested_file}"
|
||||||
|
|
||||||
|
# Serve .gz if it exist
|
||||||
|
if os.path.exists(file + ".gz"):
|
||||||
|
file += ".gz"
|
||||||
|
|
||||||
|
if os.path.exists(file):
|
||||||
|
hacs.logger.debug("Serving {} from {}".format(requested_file, file))
|
||||||
|
response = web.FileResponse(file)
|
||||||
|
response.headers["Cache-Control"] = "no-store, max-age=0"
|
||||||
|
response.headers["Pragma"] = "no-store"
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
hacs.logger.error(f"Tried to serve up '{file}' but it does not exist")
|
||||||
|
|
||||||
|
except Exception as error: # pylint: disable=broad-except
|
||||||
|
hacs.logger.debug(
|
||||||
|
"there was an issue trying to serve {} - {}".format(requested_file, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
return web.Response(status=404)
|
||||||
|
0
config/custom_components/hacs/iconset.js
Executable file → Normal file
0
config/custom_components/hacs/iconset.js
Executable file → Normal file
5
config/custom_components/hacs/manifest.json
Executable file → Normal file
5
config/custom_components/hacs/manifest.json
Executable file → Normal file
@@ -9,14 +9,15 @@
|
|||||||
"persistent_notification",
|
"persistent_notification",
|
||||||
"lovelace"
|
"lovelace"
|
||||||
],
|
],
|
||||||
"documentation": "https://hacs.xyz",
|
"documentation": "https://hacs.xyz/docs/configuration/start",
|
||||||
"domain": "hacs",
|
"domain": "hacs",
|
||||||
|
"issues": "https://hacs.xyz/docs/issues",
|
||||||
"name": "HACS (Home Assistant Community Store)",
|
"name": "HACS (Home Assistant Community Store)",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aiofiles==0.4.0",
|
"aiofiles==0.4.0",
|
||||||
"aiogithubapi==0.5.0",
|
"aiogithubapi==0.5.0",
|
||||||
"backoff==1.10.0",
|
"backoff==1.10.0",
|
||||||
"hacs_frontend==20200212060803",
|
"hacs_frontend==20200309184730",
|
||||||
"integrationhelper==0.2.2",
|
"integrationhelper==0.2.2",
|
||||||
"semantic_version==2.8.4"
|
"semantic_version==2.8.4"
|
||||||
]
|
]
|
||||||
|
20
config/custom_components/hacs/repositories/__init__.py
Executable file → Normal file
20
config/custom_components/hacs/repositories/__init__.py
Executable file → Normal file
@@ -1,6 +1,16 @@
|
|||||||
"""Initialize repositories."""
|
"""Initialize repositories."""
|
||||||
from .theme import HacsTheme
|
from custom_components.hacs.repositories.theme import HacsTheme
|
||||||
from .integration import HacsIntegration
|
from custom_components.hacs.repositories.integration import HacsIntegration
|
||||||
from .python_script import HacsPythonScript
|
from custom_components.hacs.repositories.python_script import HacsPythonScript
|
||||||
from .appdaemon import HacsAppdaemon
|
from custom_components.hacs.repositories.appdaemon import HacsAppdaemon
|
||||||
from .plugin import HacsPlugin
|
from custom_components.hacs.repositories.netdaemon import HacsNetdaemon
|
||||||
|
from custom_components.hacs.repositories.plugin import HacsPlugin
|
||||||
|
|
||||||
|
RERPOSITORY_CLASSES = {
|
||||||
|
"theme": HacsTheme,
|
||||||
|
"integration": HacsIntegration,
|
||||||
|
"python_script": HacsPythonScript,
|
||||||
|
"appdaemon": HacsAppdaemon,
|
||||||
|
"netdaemon": HacsNetdaemon,
|
||||||
|
"plugin": HacsPlugin,
|
||||||
|
}
|
||||||
|
30
config/custom_components/hacs/repositories/appdaemon.py
Executable file → Normal file
30
config/custom_components/hacs/repositories/appdaemon.py
Executable file → Normal file
@@ -1,27 +1,27 @@
|
|||||||
"""Class for appdaemon apps in HACS."""
|
"""Class for appdaemon apps in HACS."""
|
||||||
from aiogithubapi import AIOGitHubException
|
from aiogithubapi import AIOGitHubException
|
||||||
from .repository import HacsRepository, register_repository_class
|
from integrationhelper import Logger
|
||||||
|
|
||||||
|
from .repository import HacsRepository
|
||||||
from ..hacsbase.exceptions import HacsException
|
from ..hacsbase.exceptions import HacsException
|
||||||
|
|
||||||
|
|
||||||
@register_repository_class
|
|
||||||
class HacsAppdaemon(HacsRepository):
|
class HacsAppdaemon(HacsRepository):
|
||||||
"""Appdaemon apps in HACS."""
|
"""Appdaemon apps in HACS."""
|
||||||
|
|
||||||
category = "appdaemon"
|
|
||||||
|
|
||||||
def __init__(self, full_name):
|
def __init__(self, full_name):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.information.full_name = full_name
|
self.data.full_name = full_name
|
||||||
self.information.category = self.category
|
self.data.category = "appdaemon"
|
||||||
self.content.path.local = self.localpath
|
self.content.path.local = self.localpath
|
||||||
self.content.path.remote = "apps"
|
self.content.path.remote = "apps"
|
||||||
|
self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def localpath(self):
|
def localpath(self):
|
||||||
"""Return localpath."""
|
"""Return localpath."""
|
||||||
return f"{self.system.config_path}/appdaemon/apps/{self.information.name}"
|
return f"{self.hacs.system.config_path}/appdaemon/apps/{self.data.name}"
|
||||||
|
|
||||||
async def validate_repository(self):
|
async def validate_repository(self):
|
||||||
"""Validate."""
|
"""Validate."""
|
||||||
@@ -39,19 +39,14 @@ class HacsAppdaemon(HacsRepository):
|
|||||||
self.validate.errors.append("Repostitory structure not compliant")
|
self.validate.errors.append("Repostitory structure not compliant")
|
||||||
|
|
||||||
self.content.path.remote = addir[0].path
|
self.content.path.remote = addir[0].path
|
||||||
self.information.name = addir[0].name
|
|
||||||
self.content.objects = await self.repository_object.get_contents(
|
self.content.objects = await self.repository_object.get_contents(
|
||||||
self.content.path.remote, self.ref
|
self.content.path.remote, self.ref
|
||||||
)
|
)
|
||||||
|
|
||||||
self.content.files = []
|
|
||||||
for filename in self.content.objects:
|
|
||||||
self.content.files.append(filename.name)
|
|
||||||
|
|
||||||
# Handle potential errors
|
# Handle potential errors
|
||||||
if self.validate.errors:
|
if self.validate.errors:
|
||||||
for error in self.validate.errors:
|
for error in self.validate.errors:
|
||||||
if not self.system.status.startup:
|
if not self.hacs.system.status.startup:
|
||||||
self.logger.error(error)
|
self.logger.error(error)
|
||||||
return self.validate.success
|
return self.validate.success
|
||||||
|
|
||||||
@@ -68,13 +63,13 @@ class HacsAppdaemon(HacsRepository):
|
|||||||
|
|
||||||
async def update_repository(self):
|
async def update_repository(self):
|
||||||
"""Update."""
|
"""Update."""
|
||||||
if self.github.ratelimits.remaining == 0:
|
if self.hacs.github.ratelimits.remaining == 0:
|
||||||
return
|
return
|
||||||
await self.common_update()
|
await self.common_update()
|
||||||
|
|
||||||
# Get appdaemon objects.
|
# Get appdaemon objects.
|
||||||
if self.repository_manifest:
|
if self.repository_manifest:
|
||||||
if self.repository_manifest.content_in_root:
|
if self.data.content_in_root:
|
||||||
self.content.path.remote = ""
|
self.content.path.remote = ""
|
||||||
|
|
||||||
if self.content.path.remote == "apps":
|
if self.content.path.remote == "apps":
|
||||||
@@ -82,14 +77,9 @@ class HacsAppdaemon(HacsRepository):
|
|||||||
self.content.path.remote, self.ref
|
self.content.path.remote, self.ref
|
||||||
)
|
)
|
||||||
self.content.path.remote = addir[0].path
|
self.content.path.remote = addir[0].path
|
||||||
self.information.name = addir[0].name
|
|
||||||
self.content.objects = await self.repository_object.get_contents(
|
self.content.objects = await self.repository_object.get_contents(
|
||||||
self.content.path.remote, self.ref
|
self.content.path.remote, self.ref
|
||||||
)
|
)
|
||||||
|
|
||||||
self.content.files = []
|
|
||||||
for filename in self.content.objects:
|
|
||||||
self.content.files.append(filename.name)
|
|
||||||
|
|
||||||
# Set local path
|
# Set local path
|
||||||
self.content.path.local = self.localpath
|
self.content.path.local = self.localpath
|
||||||
|
134
config/custom_components/hacs/repositories/integration.py
Executable file → Normal file
134
config/custom_components/hacs/repositories/integration.py
Executable file → Normal file
@@ -1,80 +1,56 @@
|
|||||||
"""Class for integrations in HACS."""
|
"""Class for integrations in HACS."""
|
||||||
import json
|
from integrationhelper import Logger
|
||||||
from aiogithubapi import AIOGitHubException
|
|
||||||
from homeassistant.loader import async_get_custom_components
|
from homeassistant.loader import async_get_custom_components
|
||||||
from .repository import HacsRepository, register_repository_class
|
|
||||||
from ..hacsbase.exceptions import HacsException
|
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||||
|
from custom_components.hacs.helpers.filters import get_first_directory_in_directory
|
||||||
|
from custom_components.hacs.helpers.information import get_integration_manifest
|
||||||
|
from custom_components.hacs.repositories.repository import HacsRepository
|
||||||
|
|
||||||
|
|
||||||
@register_repository_class
|
|
||||||
class HacsIntegration(HacsRepository):
|
class HacsIntegration(HacsRepository):
|
||||||
"""Integrations in HACS."""
|
"""Integrations in HACS."""
|
||||||
|
|
||||||
category = "integration"
|
|
||||||
|
|
||||||
def __init__(self, full_name):
|
def __init__(self, full_name):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.information.full_name = full_name
|
self.data.full_name = full_name
|
||||||
self.information.category = self.category
|
self.data.category = "integration"
|
||||||
self.domain = None
|
|
||||||
self.content.path.remote = "custom_components"
|
self.content.path.remote = "custom_components"
|
||||||
self.content.path.local = self.localpath
|
self.content.path.local = self.localpath
|
||||||
|
self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def localpath(self):
|
def localpath(self):
|
||||||
"""Return localpath."""
|
"""Return localpath."""
|
||||||
return f"{self.system.config_path}/custom_components/{self.domain}"
|
return f"{self.hacs.system.config_path}/custom_components/{self.data.domain}"
|
||||||
|
|
||||||
async def validate_repository(self):
|
async def validate_repository(self):
|
||||||
"""Validate."""
|
"""Validate."""
|
||||||
await self.common_validate()
|
await self.common_validate()
|
||||||
|
|
||||||
# Attach repository
|
|
||||||
if self.repository_object is None:
|
|
||||||
self.repository_object = await self.github.get_repo(
|
|
||||||
self.information.full_name
|
|
||||||
)
|
|
||||||
|
|
||||||
# Custom step 1: Validate content.
|
# Custom step 1: Validate content.
|
||||||
if self.repository_manifest:
|
if self.data.content_in_root:
|
||||||
if self.repository_manifest.content_in_root:
|
self.content.path.remote = ""
|
||||||
self.content.path.remote = ""
|
|
||||||
|
|
||||||
if self.content.path.remote == "custom_components":
|
if self.content.path.remote == "custom_components":
|
||||||
try:
|
name = get_first_directory_in_directory(self.tree, "custom_components")
|
||||||
ccdir = await self.repository_object.get_contents(
|
if name is None:
|
||||||
self.content.path.remote, self.ref
|
|
||||||
)
|
|
||||||
except AIOGitHubException:
|
|
||||||
raise HacsException(
|
raise HacsException(
|
||||||
f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant"
|
f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant"
|
||||||
)
|
)
|
||||||
|
self.content.path.remote = f"custom_components/{name}"
|
||||||
|
|
||||||
for item in ccdir or []:
|
try:
|
||||||
if item.type == "dir":
|
await get_integration_manifest(self)
|
||||||
self.content.path.remote = item.path
|
except HacsException as exception:
|
||||||
break
|
self.logger.error(exception)
|
||||||
|
|
||||||
if self.repository_manifest.zip_release:
|
|
||||||
self.content.objects = self.releases.last_release_object.assets
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.content.objects = await self.repository_object.get_contents(
|
|
||||||
self.content.path.remote, self.ref
|
|
||||||
)
|
|
||||||
|
|
||||||
self.content.files = []
|
|
||||||
for filename in self.content.objects or []:
|
|
||||||
self.content.files.append(filename.name)
|
|
||||||
|
|
||||||
if not await self.get_manifest():
|
|
||||||
self.validate.errors.append("Missing manifest file.")
|
|
||||||
|
|
||||||
# Handle potential errors
|
# Handle potential errors
|
||||||
if self.validate.errors:
|
if self.validate.errors:
|
||||||
for error in self.validate.errors:
|
for error in self.validate.errors:
|
||||||
if not self.system.status.startup:
|
if not self.hacs.system.status.startup:
|
||||||
self.logger.error(error)
|
self.logger.error(error)
|
||||||
return self.validate.success
|
return self.validate.success
|
||||||
|
|
||||||
@@ -86,46 +62,26 @@ class HacsIntegration(HacsRepository):
|
|||||||
# Run common registration steps.
|
# Run common registration steps.
|
||||||
await self.common_registration()
|
await self.common_registration()
|
||||||
|
|
||||||
# Get the content of the manifest file.
|
|
||||||
await self.get_manifest()
|
|
||||||
|
|
||||||
# Set local path
|
# Set local path
|
||||||
self.content.path.local = self.localpath
|
self.content.path.local = self.localpath
|
||||||
|
|
||||||
async def update_repository(self):
|
async def update_repository(self):
|
||||||
"""Update."""
|
"""Update."""
|
||||||
if self.github.ratelimits.remaining == 0:
|
if self.hacs.github.ratelimits.remaining == 0:
|
||||||
return
|
return
|
||||||
await self.common_update()
|
await self.common_update()
|
||||||
|
|
||||||
# Get integration objects.
|
if self.data.content_in_root:
|
||||||
|
self.content.path.remote = ""
|
||||||
if self.repository_manifest:
|
|
||||||
if self.repository_manifest.content_in_root:
|
|
||||||
self.content.path.remote = ""
|
|
||||||
|
|
||||||
if self.content.path.remote == "custom_components":
|
if self.content.path.remote == "custom_components":
|
||||||
ccdir = await self.repository_object.get_contents(
|
name = get_first_directory_in_directory(self.tree, "custom_components")
|
||||||
self.content.path.remote, self.ref
|
self.content.path.remote = f"custom_components/{name}"
|
||||||
)
|
|
||||||
if not isinstance(ccdir, list):
|
|
||||||
self.validate.errors.append("Repostitory structure not compliant")
|
|
||||||
|
|
||||||
self.content.path.remote = ccdir[0].path
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.content.objects = await self.repository_object.get_contents(
|
await get_integration_manifest(self)
|
||||||
self.content.path.remote, self.ref
|
except HacsException as exception:
|
||||||
)
|
self.logger.error(exception)
|
||||||
except AIOGitHubException:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.content.files = []
|
|
||||||
if isinstance(self.content.objects, list):
|
|
||||||
for filename in self.content.objects or []:
|
|
||||||
self.content.files.append(filename.name)
|
|
||||||
|
|
||||||
await self.get_manifest()
|
|
||||||
|
|
||||||
# Set local path
|
# Set local path
|
||||||
self.content.path.local = self.localpath
|
self.content.path.local = self.localpath
|
||||||
@@ -133,33 +89,5 @@ class HacsIntegration(HacsRepository):
|
|||||||
async def reload_custom_components(self):
|
async def reload_custom_components(self):
|
||||||
"""Reload custom_components (and config flows)in HA."""
|
"""Reload custom_components (and config flows)in HA."""
|
||||||
self.logger.info("Reloading custom_component cache")
|
self.logger.info("Reloading custom_component cache")
|
||||||
del self.hass.data["custom_components"]
|
del self.hacs.hass.data["custom_components"]
|
||||||
await async_get_custom_components(self.hass)
|
await async_get_custom_components(self.hacs.hass)
|
||||||
|
|
||||||
async def get_manifest(self):
|
|
||||||
"""Get info from the manifest file."""
|
|
||||||
manifest_path = f"{self.content.path.remote}/manifest.json"
|
|
||||||
try:
|
|
||||||
manifest = await self.repository_object.get_contents(
|
|
||||||
manifest_path, self.ref
|
|
||||||
)
|
|
||||||
manifest = json.loads(manifest.content)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
return False
|
|
||||||
|
|
||||||
if manifest:
|
|
||||||
try:
|
|
||||||
self.manifest = manifest
|
|
||||||
self.information.authors = manifest["codeowners"]
|
|
||||||
self.domain = manifest["domain"]
|
|
||||||
self.information.name = manifest["name"]
|
|
||||||
self.information.homeassistant_version = manifest.get("homeassistant")
|
|
||||||
|
|
||||||
# Set local path
|
|
||||||
self.content.path.local = self.localpath
|
|
||||||
return True
|
|
||||||
except KeyError as exception:
|
|
||||||
raise HacsException(
|
|
||||||
f"Missing expected key {exception} in 'manifest.json'"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
0
config/custom_components/hacs/repositories/manifest.py
Executable file → Normal file
0
config/custom_components/hacs/repositories/manifest.py
Executable file → Normal file
90
config/custom_components/hacs/repositories/netdaemon.py
Normal file
90
config/custom_components/hacs/repositories/netdaemon.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
"""Class for netdaemon apps in HACS."""
|
||||||
|
from integrationhelper import Logger
|
||||||
|
|
||||||
|
from .repository import HacsRepository
|
||||||
|
from ..hacsbase.exceptions import HacsException
|
||||||
|
|
||||||
|
from custom_components.hacs.helpers.filters import get_first_directory_in_directory
|
||||||
|
|
||||||
|
|
||||||
|
class HacsNetdaemon(HacsRepository):
|
||||||
|
"""Netdaemon apps in HACS."""
|
||||||
|
|
||||||
|
def __init__(self, full_name):
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__()
|
||||||
|
self.data.full_name = full_name
|
||||||
|
self.data.category = "netdaemon"
|
||||||
|
self.content.path.local = self.localpath
|
||||||
|
self.content.path.remote = "apps"
|
||||||
|
self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def localpath(self):
|
||||||
|
"""Return localpath."""
|
||||||
|
return f"{self.hacs.system.config_path}/netdaemon/apps/{self.data.name}"
|
||||||
|
|
||||||
|
async def validate_repository(self):
|
||||||
|
"""Validate."""
|
||||||
|
await self.common_validate()
|
||||||
|
|
||||||
|
# Custom step 1: Validate content.
|
||||||
|
if self.repository_manifest:
|
||||||
|
if self.data.content_in_root:
|
||||||
|
self.content.path.remote = ""
|
||||||
|
|
||||||
|
if self.content.path.remote == "apps":
|
||||||
|
self.data.domain = get_first_directory_in_directory(
|
||||||
|
self.tree, self.content.path.remote
|
||||||
|
)
|
||||||
|
self.content.path.remote = f"apps/{self.data.name}"
|
||||||
|
|
||||||
|
compliant = False
|
||||||
|
for treefile in self.treefiles:
|
||||||
|
if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith(
|
||||||
|
".cs"
|
||||||
|
):
|
||||||
|
compliant = True
|
||||||
|
break
|
||||||
|
if not compliant:
|
||||||
|
raise HacsException(
|
||||||
|
f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle potential errors
|
||||||
|
if self.validate.errors:
|
||||||
|
for error in self.validate.errors:
|
||||||
|
if not self.hacs.system.status.startup:
|
||||||
|
self.logger.error(error)
|
||||||
|
return self.validate.success
|
||||||
|
|
||||||
|
async def registration(self):
|
||||||
|
"""Registration."""
|
||||||
|
if not await self.validate_repository():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Run common registration steps.
|
||||||
|
await self.common_registration()
|
||||||
|
|
||||||
|
# Set local path
|
||||||
|
self.content.path.local = self.localpath
|
||||||
|
|
||||||
|
async def update_repository(self):
|
||||||
|
"""Update."""
|
||||||
|
if self.hacs.github.ratelimits.remaining == 0:
|
||||||
|
return
|
||||||
|
await self.common_update()
|
||||||
|
|
||||||
|
# Get appdaemon objects.
|
||||||
|
if self.repository_manifest:
|
||||||
|
if self.data.content_in_root:
|
||||||
|
self.content.path.remote = ""
|
||||||
|
|
||||||
|
if self.content.path.remote == "apps":
|
||||||
|
self.data.domain = get_first_directory_in_directory(
|
||||||
|
self.tree, self.content.path.remote
|
||||||
|
)
|
||||||
|
self.content.path.remote = f"apps/{self.data.name}"
|
||||||
|
|
||||||
|
# Set local path
|
||||||
|
self.content.path.local = self.localpath
|
95
config/custom_components/hacs/repositories/plugin.py
Executable file → Normal file
95
config/custom_components/hacs/repositories/plugin.py
Executable file → Normal file
@@ -1,26 +1,27 @@
|
|||||||
"""Class for plugins in HACS."""
|
"""Class for plugins in HACS."""
|
||||||
import json
|
import json
|
||||||
from aiogithubapi import AIOGitHubException
|
from integrationhelper import Logger
|
||||||
from .repository import HacsRepository, register_repository_class
|
|
||||||
|
from .repository import HacsRepository
|
||||||
from ..hacsbase.exceptions import HacsException
|
from ..hacsbase.exceptions import HacsException
|
||||||
|
|
||||||
|
from custom_components.hacs.helpers.information import find_file_name
|
||||||
|
|
||||||
|
|
||||||
@register_repository_class
|
|
||||||
class HacsPlugin(HacsRepository):
|
class HacsPlugin(HacsRepository):
|
||||||
"""Plugins in HACS."""
|
"""Plugins in HACS."""
|
||||||
|
|
||||||
category = "plugin"
|
|
||||||
|
|
||||||
def __init__(self, full_name):
|
def __init__(self, full_name):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.information.full_name = full_name
|
self.data.full_name = full_name
|
||||||
self.information.category = self.category
|
self.data.file_name = None
|
||||||
self.information.file_name = None
|
self.data.category = "plugin"
|
||||||
self.information.javascript_type = None
|
self.information.javascript_type = None
|
||||||
self.content.path.local = (
|
self.content.path.local = (
|
||||||
f"{self.system.config_path}/www/community/{full_name.split('/')[-1]}"
|
f"{self.hacs.system.config_path}/www/community/{full_name.split('/')[-1]}"
|
||||||
)
|
)
|
||||||
|
self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}")
|
||||||
|
|
||||||
async def validate_repository(self):
|
async def validate_repository(self):
|
||||||
"""Validate."""
|
"""Validate."""
|
||||||
@@ -28,7 +29,7 @@ class HacsPlugin(HacsRepository):
|
|||||||
await self.common_validate()
|
await self.common_validate()
|
||||||
|
|
||||||
# Custom step 1: Validate content.
|
# Custom step 1: Validate content.
|
||||||
await self.get_plugin_location()
|
find_file_name(self)
|
||||||
|
|
||||||
if self.content.path.remote is None:
|
if self.content.path.remote is None:
|
||||||
raise HacsException(
|
raise HacsException(
|
||||||
@@ -38,14 +39,10 @@ class HacsPlugin(HacsRepository):
|
|||||||
if self.content.path.remote == "release":
|
if self.content.path.remote == "release":
|
||||||
self.content.single = True
|
self.content.single = True
|
||||||
|
|
||||||
self.content.files = []
|
|
||||||
for filename in self.content.objects:
|
|
||||||
self.content.files.append(filename.name)
|
|
||||||
|
|
||||||
# Handle potential errors
|
# Handle potential errors
|
||||||
if self.validate.errors:
|
if self.validate.errors:
|
||||||
for error in self.validate.errors:
|
for error in self.validate.errors:
|
||||||
if not self.system.status.startup:
|
if not self.hacs.system.status.startup:
|
||||||
self.logger.error(error)
|
self.logger.error(error)
|
||||||
return self.validate.success
|
return self.validate.success
|
||||||
|
|
||||||
@@ -59,13 +56,13 @@ class HacsPlugin(HacsRepository):
|
|||||||
|
|
||||||
async def update_repository(self):
|
async def update_repository(self):
|
||||||
"""Update."""
|
"""Update."""
|
||||||
if self.github.ratelimits.remaining == 0:
|
if self.hacs.github.ratelimits.remaining == 0:
|
||||||
return
|
return
|
||||||
# Run common update steps.
|
# Run common update steps.
|
||||||
await self.common_update()
|
await self.common_update()
|
||||||
|
|
||||||
# Get plugin objects.
|
# Get plugin objects.
|
||||||
await self.get_plugin_location()
|
find_file_name(self)
|
||||||
|
|
||||||
# Get JS type
|
# Get JS type
|
||||||
await self.parse_readme_for_jstype()
|
await self.parse_readme_for_jstype()
|
||||||
@@ -76,68 +73,6 @@ class HacsPlugin(HacsRepository):
|
|||||||
if self.content.path.remote == "release":
|
if self.content.path.remote == "release":
|
||||||
self.content.single = True
|
self.content.single = True
|
||||||
|
|
||||||
self.content.files = []
|
|
||||||
for filename in self.content.objects:
|
|
||||||
self.content.files.append(filename.name)
|
|
||||||
|
|
||||||
async def get_plugin_location(self):
|
|
||||||
"""Get plugin location."""
|
|
||||||
if self.content.path.remote is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
possible_locations = ["dist", "release", ""]
|
|
||||||
|
|
||||||
if self.repository_manifest:
|
|
||||||
if self.repository_manifest.content_in_root:
|
|
||||||
possible_locations = [""]
|
|
||||||
|
|
||||||
for location in possible_locations:
|
|
||||||
if self.content.path.remote is not None:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
objects = []
|
|
||||||
files = []
|
|
||||||
if location != "release":
|
|
||||||
try:
|
|
||||||
objects = await self.repository_object.get_contents(
|
|
||||||
location, self.ref
|
|
||||||
)
|
|
||||||
except AIOGitHubException:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
await self.get_releases()
|
|
||||||
if self.releases.releases:
|
|
||||||
if self.releases.last_release_object.assets is not None:
|
|
||||||
objects = self.releases.last_release_object.assets
|
|
||||||
|
|
||||||
for item in objects:
|
|
||||||
if item.name.endswith(".js"):
|
|
||||||
files.append(item.name)
|
|
||||||
|
|
||||||
# Handler for plug requirement 3
|
|
||||||
valid_filenames = [
|
|
||||||
f"{self.information.name.replace('lovelace-', '')}.js",
|
|
||||||
f"{self.information.name}.js",
|
|
||||||
f"{self.information.name}.umd.js",
|
|
||||||
f"{self.information.name}-bundle.js",
|
|
||||||
]
|
|
||||||
|
|
||||||
if self.repository_manifest:
|
|
||||||
if self.repository_manifest.filename:
|
|
||||||
valid_filenames.append(self.repository_manifest.filename)
|
|
||||||
|
|
||||||
for name in valid_filenames:
|
|
||||||
if name in files:
|
|
||||||
# YES! We got it!
|
|
||||||
self.information.file_name = name
|
|
||||||
self.content.path.remote = location
|
|
||||||
self.content.objects = objects
|
|
||||||
self.content.files = files
|
|
||||||
break
|
|
||||||
|
|
||||||
except SystemError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def get_package_content(self):
|
async def get_package_content(self):
|
||||||
"""Get package content."""
|
"""Get package content."""
|
||||||
try:
|
try:
|
||||||
@@ -145,7 +80,7 @@ class HacsPlugin(HacsRepository):
|
|||||||
package = json.loads(package.content)
|
package = json.loads(package.content)
|
||||||
|
|
||||||
if package:
|
if package:
|
||||||
self.information.authors = package["author"]
|
self.data.authors = package["author"]
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
72
config/custom_components/hacs/repositories/python_script.py
Executable file → Normal file
72
config/custom_components/hacs/repositories/python_script.py
Executable file → Normal file
@@ -1,10 +1,11 @@
|
|||||||
"""Class for python_scripts in HACS."""
|
"""Class for python_scripts in HACS."""
|
||||||
from aiogithubapi import AIOGitHubException
|
from integrationhelper import Logger
|
||||||
from .repository import HacsRepository, register_repository_class
|
|
||||||
|
from .repository import HacsRepository
|
||||||
from ..hacsbase.exceptions import HacsException
|
from ..hacsbase.exceptions import HacsException
|
||||||
|
from ..helpers.information import find_file_name
|
||||||
|
|
||||||
|
|
||||||
@register_repository_class
|
|
||||||
class HacsPythonScript(HacsRepository):
|
class HacsPythonScript(HacsRepository):
|
||||||
"""python_scripts in HACS."""
|
"""python_scripts in HACS."""
|
||||||
|
|
||||||
@@ -13,11 +14,12 @@ class HacsPythonScript(HacsRepository):
|
|||||||
def __init__(self, full_name):
|
def __init__(self, full_name):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.information.full_name = full_name
|
self.data.full_name = full_name
|
||||||
self.information.category = self.category
|
self.data.category = "python_script"
|
||||||
self.content.path.remote = "python_scripts"
|
self.content.path.remote = "python_scripts"
|
||||||
self.content.path.local = f"{self.system.config_path}/python_scripts"
|
self.content.path.local = f"{self.hacs.system.config_path}/python_scripts"
|
||||||
self.content.single = True
|
self.content.single = True
|
||||||
|
self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}")
|
||||||
|
|
||||||
async def validate_repository(self):
|
async def validate_repository(self):
|
||||||
"""Validate."""
|
"""Validate."""
|
||||||
@@ -25,26 +27,25 @@ class HacsPythonScript(HacsRepository):
|
|||||||
await self.common_validate()
|
await self.common_validate()
|
||||||
|
|
||||||
# Custom step 1: Validate content.
|
# Custom step 1: Validate content.
|
||||||
try:
|
if self.data.content_in_root:
|
||||||
self.content.objects = await self.repository_object.get_contents(
|
self.content.path.remote = ""
|
||||||
self.content.path.remote, self.ref
|
|
||||||
)
|
compliant = False
|
||||||
except AIOGitHubException:
|
for treefile in self.treefiles:
|
||||||
|
if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith(
|
||||||
|
".py"
|
||||||
|
):
|
||||||
|
compliant = True
|
||||||
|
break
|
||||||
|
if not compliant:
|
||||||
raise HacsException(
|
raise HacsException(
|
||||||
f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant"
|
f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(self.content.objects, list):
|
|
||||||
self.validate.errors.append("Repostitory structure not compliant")
|
|
||||||
|
|
||||||
self.content.files = []
|
|
||||||
for filename in self.content.objects:
|
|
||||||
self.content.files.append(filename.name)
|
|
||||||
|
|
||||||
# Handle potential errors
|
# Handle potential errors
|
||||||
if self.validate.errors:
|
if self.validate.errors:
|
||||||
for error in self.validate.errors:
|
for error in self.validate.errors:
|
||||||
if not self.system.status.startup:
|
if not self.hacs.system.status.startup:
|
||||||
self.logger.error(error)
|
self.logger.error(error)
|
||||||
return self.validate.success
|
return self.validate.success
|
||||||
|
|
||||||
@@ -57,31 +58,30 @@ class HacsPythonScript(HacsRepository):
|
|||||||
await self.common_registration()
|
await self.common_registration()
|
||||||
|
|
||||||
# Set name
|
# Set name
|
||||||
self.information.name = self.content.objects[0].name.replace(".py", "")
|
find_file_name(self)
|
||||||
|
|
||||||
async def update_repository(self): # lgtm[py/similar-function]
|
async def update_repository(self): # lgtm[py/similar-function]
|
||||||
"""Update."""
|
"""Update."""
|
||||||
if self.github.ratelimits.remaining == 0:
|
if self.hacs.github.ratelimits.remaining == 0:
|
||||||
return
|
return
|
||||||
# Run common update steps.
|
# Run common update steps.
|
||||||
await self.common_update()
|
await self.common_update()
|
||||||
|
|
||||||
# Get python_script objects.
|
# Get python_script objects.
|
||||||
if self.repository_manifest:
|
if self.data.content_in_root:
|
||||||
if self.repository_manifest.content_in_root:
|
self.content.path.remote = ""
|
||||||
self.content.path.remote = ""
|
|
||||||
|
|
||||||
self.content.objects = await self.repository_object.get_contents(
|
compliant = False
|
||||||
self.content.path.remote, self.ref
|
for treefile in self.treefiles:
|
||||||
)
|
if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith(
|
||||||
|
".py"
|
||||||
self.content.files = []
|
):
|
||||||
for filename in self.content.objects:
|
compliant = True
|
||||||
self.content.files.append(filename.name)
|
break
|
||||||
|
if not compliant:
|
||||||
|
raise HacsException(
|
||||||
|
f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant"
|
||||||
|
)
|
||||||
|
|
||||||
# Update name
|
# Update name
|
||||||
self.information.name = self.content.objects[0].name.replace(".py", "")
|
find_file_name(self)
|
||||||
|
|
||||||
self.content.files = []
|
|
||||||
for filename in self.content.objects:
|
|
||||||
self.content.files.append(filename.name)
|
|
||||||
|
17
config/custom_components/hacs/repositories/removed.py
Normal file
17
config/custom_components/hacs/repositories/removed.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""Object for removed repositories."""
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class RemovedRepository:
|
||||||
|
repository: str = None
|
||||||
|
reason: str = None
|
||||||
|
link: str = None
|
||||||
|
removal_type: str = None # archived, not_compliant, critical, dev, broken
|
||||||
|
acknowledged: bool = False
|
||||||
|
|
||||||
|
def update_data(self, data: dict):
|
||||||
|
"""Update data of the repository."""
|
||||||
|
for key in data:
|
||||||
|
if key in self.__dict__:
|
||||||
|
setattr(self, key, data[key])
|
307
config/custom_components/hacs/repositories/repository.py
Executable file → Normal file
307
config/custom_components/hacs/repositories/repository.py
Executable file → Normal file
@@ -1,29 +1,27 @@
|
|||||||
"""Repository."""
|
"""Repository."""
|
||||||
# pylint: disable=broad-except, bad-continuation, no-member
|
# pylint: disable=broad-except, bad-continuation, no-member
|
||||||
import pathlib
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from integrationhelper import Validate, Logger
|
from integrationhelper import Validate
|
||||||
from aiogithubapi import AIOGitHubException
|
from aiogithubapi import AIOGitHubException
|
||||||
from .manifest import HacsManifest
|
from .manifest import HacsManifest
|
||||||
from ..helpers.misc import get_repository_name
|
from ..helpers.misc import get_repository_name
|
||||||
from ..hacsbase import Hacs
|
|
||||||
from ..hacsbase.exceptions import HacsException
|
|
||||||
from ..hacsbase.backup import Backup
|
|
||||||
from ..handler.download import async_download_file, async_save_file
|
from ..handler.download import async_download_file, async_save_file
|
||||||
from ..helpers.misc import version_left_higher_then_right
|
from ..helpers.misc import version_left_higher_then_right
|
||||||
from ..helpers.install import install_repository, version_to_install
|
from ..helpers.install import install_repository, version_to_install
|
||||||
|
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
RERPOSITORY_CLASSES = {}
|
from custom_components.hacs.helpers.information import (
|
||||||
|
get_info_md_content,
|
||||||
|
get_repository,
|
||||||
def register_repository_class(cls):
|
)
|
||||||
"""Register class."""
|
from custom_components.hacs.helpers.validate_repository import (
|
||||||
RERPOSITORY_CLASSES[cls.category] = cls
|
common_validate,
|
||||||
return cls
|
common_update_data,
|
||||||
|
)
|
||||||
|
from custom_components.hacs.repositories.repositorydata import RepositoryData
|
||||||
|
|
||||||
|
|
||||||
class RepositoryVersions:
|
class RepositoryVersions:
|
||||||
@@ -79,6 +77,7 @@ class RepositoryReleases:
|
|||||||
published_tags = []
|
published_tags = []
|
||||||
objects = []
|
objects = []
|
||||||
releases = False
|
releases = False
|
||||||
|
downloads = None
|
||||||
|
|
||||||
|
|
||||||
class RepositoryPath:
|
class RepositoryPath:
|
||||||
@@ -97,26 +96,25 @@ class RepositoryContent:
|
|||||||
single = False
|
single = False
|
||||||
|
|
||||||
|
|
||||||
class HacsRepository(Hacs):
|
class HacsRepository:
|
||||||
"""HacsRepository."""
|
"""HacsRepository."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Set up HacsRepository."""
|
"""Set up HacsRepository."""
|
||||||
|
self.hacs = get_hacs()
|
||||||
self.data = {}
|
self.data = RepositoryData()
|
||||||
self.content = RepositoryContent()
|
self.content = RepositoryContent()
|
||||||
self.content.path = RepositoryPath()
|
self.content.path = RepositoryPath()
|
||||||
self.information = RepositoryInformation()
|
self.information = RepositoryInformation()
|
||||||
self.repository_object = None
|
self.repository_object = None
|
||||||
self.status = RepositoryStatus()
|
self.status = RepositoryStatus()
|
||||||
self.state = None
|
self.state = None
|
||||||
self.manifest = {}
|
self.integration_manifest = {}
|
||||||
self.repository_manifest = HacsManifest.from_dict({})
|
self.repository_manifest = HacsManifest.from_dict({})
|
||||||
self.validate = Validate()
|
self.validate = Validate()
|
||||||
self.releases = RepositoryReleases()
|
self.releases = RepositoryReleases()
|
||||||
self.versions = RepositoryVersions()
|
self.versions = RepositoryVersions()
|
||||||
self.pending_restart = False
|
self.pending_restart = False
|
||||||
self.logger = None
|
|
||||||
self.tree = []
|
self.tree = []
|
||||||
self.treefiles = []
|
self.treefiles = []
|
||||||
self.ref = None
|
self.ref = None
|
||||||
@@ -126,7 +124,7 @@ class HacsRepository(Hacs):
|
|||||||
"""Return pending upgrade."""
|
"""Return pending upgrade."""
|
||||||
if self.status.installed:
|
if self.status.installed:
|
||||||
if self.status.selected_tag is not None:
|
if self.status.selected_tag is not None:
|
||||||
if self.status.selected_tag == self.information.default_branch:
|
if self.status.selected_tag == self.data.default_branch:
|
||||||
if self.versions.installed_commit != self.versions.available_commit:
|
if self.versions.installed_commit != self.versions.available_commit:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -137,23 +135,20 @@ class HacsRepository(Hacs):
|
|||||||
@property
|
@property
|
||||||
def config_flow(self):
|
def config_flow(self):
|
||||||
"""Return bool if integration has config_flow."""
|
"""Return bool if integration has config_flow."""
|
||||||
if self.manifest:
|
if self.integration_manifest:
|
||||||
if self.information.full_name == "hacs/integration":
|
if self.data.full_name == "hacs/integration":
|
||||||
return False
|
return False
|
||||||
return self.manifest.get("config_flow", False)
|
return self.integration_manifest.get("config_flow", False)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def custom(self):
|
def custom(self):
|
||||||
"""Return flag if the repository is custom."""
|
"""Return flag if the repository is custom."""
|
||||||
if self.information.full_name.split("/")[0] in [
|
if self.data.full_name.split("/")[0] in ["custom-components", "custom-cards"]:
|
||||||
"custom-components",
|
|
||||||
"custom-cards",
|
|
||||||
]:
|
|
||||||
return False
|
return False
|
||||||
if self.information.full_name in self.common.default:
|
if self.data.full_name.lower() in [x.lower() for x in self.hacs.common.default]:
|
||||||
return False
|
return False
|
||||||
if self.information.full_name == "hacs/integration":
|
if self.data.full_name == "hacs/integration":
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -164,24 +159,21 @@ class HacsRepository(Hacs):
|
|||||||
if self.information.homeassistant_version is not None:
|
if self.information.homeassistant_version is not None:
|
||||||
target = self.information.homeassistant_version
|
target = self.information.homeassistant_version
|
||||||
if self.repository_manifest is not None:
|
if self.repository_manifest is not None:
|
||||||
if self.repository_manifest.homeassistant is not None:
|
if self.data.homeassistant is not None:
|
||||||
target = self.repository_manifest.homeassistant
|
target = self.data.homeassistant
|
||||||
|
|
||||||
if target is not None:
|
if target is not None:
|
||||||
if self.releases.releases:
|
if self.releases.releases:
|
||||||
if not version_left_higher_then_right(self.system.ha_version, target):
|
if not version_left_higher_then_right(
|
||||||
|
self.hacs.system.ha_version, target
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
"""Return display name."""
|
"""Return display name."""
|
||||||
return get_repository_name(
|
return get_repository_name(self)
|
||||||
self.repository_manifest,
|
|
||||||
self.information.name,
|
|
||||||
self.information.category,
|
|
||||||
self.manifest,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_status(self):
|
def display_status(self):
|
||||||
@@ -257,127 +249,41 @@ class HacsRepository(Hacs):
|
|||||||
|
|
||||||
async def common_validate(self):
|
async def common_validate(self):
|
||||||
"""Common validation steps of the repository."""
|
"""Common validation steps of the repository."""
|
||||||
# Attach helpers
|
await common_validate(self)
|
||||||
self.validate.errors = []
|
|
||||||
self.logger = Logger(
|
|
||||||
f"hacs.repository.{self.information.category}.{self.information.full_name}"
|
|
||||||
)
|
|
||||||
if self.ref is None:
|
|
||||||
self.ref = version_to_install(self)
|
|
||||||
|
|
||||||
# Step 1: Make sure the repository exist.
|
|
||||||
self.logger.debug("Checking repository.")
|
|
||||||
try:
|
|
||||||
self.repository_object = await self.github.get_repo(
|
|
||||||
self.information.full_name
|
|
||||||
)
|
|
||||||
self.data = self.repository_object.attributes
|
|
||||||
except Exception as exception: # Gotta Catch 'Em All
|
|
||||||
if not self.system.status.startup:
|
|
||||||
self.logger.error(exception)
|
|
||||||
self.validate.errors.append("Repository does not exist.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.tree:
|
|
||||||
self.tree = await self.repository_object.get_tree(self.ref)
|
|
||||||
self.treefiles = []
|
|
||||||
for treefile in self.tree:
|
|
||||||
self.treefiles.append(treefile.full_path)
|
|
||||||
|
|
||||||
# Step 2: Make sure the repository is not archived.
|
|
||||||
if self.repository_object.archived:
|
|
||||||
self.validate.errors.append("Repository is archived.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Step 3: Make sure the repository is not in the blacklist.
|
|
||||||
if self.information.full_name in self.common.blacklist:
|
|
||||||
self.validate.errors.append("Repository is in the blacklist.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Step 4: default branch
|
|
||||||
self.information.default_branch = self.repository_object.default_branch
|
|
||||||
|
|
||||||
# Step 5: Get releases.
|
|
||||||
await self.get_releases()
|
|
||||||
|
|
||||||
# Step 6: Get the content of hacs.json
|
|
||||||
await self.get_repository_manifest_content()
|
|
||||||
|
|
||||||
# Set repository name
|
|
||||||
self.information.name = self.information.full_name.split("/")[1]
|
|
||||||
|
|
||||||
async def common_registration(self):
|
async def common_registration(self):
|
||||||
"""Common registration steps of the repository."""
|
"""Common registration steps of the repository."""
|
||||||
# Attach logger
|
|
||||||
if self.logger is None:
|
|
||||||
self.logger = Logger(
|
|
||||||
f"hacs.repository.{self.information.category}.{self.information.full_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Attach repository
|
# Attach repository
|
||||||
if self.repository_object is None:
|
if self.repository_object is None:
|
||||||
self.repository_object = await self.github.get_repo(
|
self.repository_object = await get_repository(
|
||||||
self.information.full_name
|
self.hacs.session, self.hacs.configuration.token, self.data.full_name
|
||||||
)
|
)
|
||||||
|
self.data.update_data(self.repository_object.attributes)
|
||||||
|
|
||||||
# Set id
|
# Set id
|
||||||
self.information.uid = str(self.repository_object.id)
|
self.information.uid = str(self.data.id)
|
||||||
|
|
||||||
# Set topics
|
# Set topics
|
||||||
self.information.topics = self.repository_object.topics
|
self.data.topics = self.data.topics
|
||||||
|
|
||||||
# Set stargazers_count
|
# Set stargazers_count
|
||||||
self.information.stars = self.repository_object.attributes.get(
|
self.data.stargazers_count = self.data.stargazers_count
|
||||||
"stargazers_count", 0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set description
|
# Set description
|
||||||
if self.repository_object.description:
|
self.data.description = self.data.description
|
||||||
self.information.description = self.repository_object.description
|
|
||||||
|
|
||||||
async def common_update(self):
|
async def common_update(self):
|
||||||
"""Common information update steps of the repository."""
|
"""Common information update steps of the repository."""
|
||||||
# Attach logger
|
|
||||||
if self.logger is None:
|
|
||||||
self.logger = Logger(
|
|
||||||
f"hacs.repository.{self.information.category}.{self.information.full_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.debug("Getting repository information")
|
self.logger.debug("Getting repository information")
|
||||||
|
|
||||||
# Set ref
|
|
||||||
if self.ref is None:
|
|
||||||
self.ref = version_to_install(self)
|
|
||||||
|
|
||||||
# Attach repository
|
# Attach repository
|
||||||
self.repository_object = await self.github.get_repo(self.information.full_name)
|
await common_update_data(self)
|
||||||
|
|
||||||
# Update tree
|
|
||||||
self.tree = await self.repository_object.get_tree(self.ref)
|
|
||||||
self.treefiles = []
|
|
||||||
for treefile in self.tree:
|
|
||||||
self.treefiles.append(treefile.full_path)
|
|
||||||
|
|
||||||
# Update description
|
|
||||||
if self.repository_object.description:
|
|
||||||
self.information.description = self.repository_object.description
|
|
||||||
|
|
||||||
# Set stargazers_count
|
|
||||||
self.information.stars = self.repository_object.attributes.get(
|
|
||||||
"stargazers_count", 0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update default branch
|
|
||||||
self.information.default_branch = self.repository_object.default_branch
|
|
||||||
|
|
||||||
# Update last updaeted
|
# Update last updaeted
|
||||||
self.information.last_updated = self.repository_object.attributes.get(
|
self.information.last_updated = self.repository_object.attributes.get(
|
||||||
"pushed_at", 0
|
"pushed_at", 0
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update topics
|
|
||||||
self.information.topics = self.repository_object.topics
|
|
||||||
|
|
||||||
# Update last available commit
|
# Update last available commit
|
||||||
await self.repository_object.set_last_commit()
|
await self.repository_object.set_last_commit()
|
||||||
self.versions.available_commit = self.repository_object.last_commit
|
self.versions.available_commit = self.repository_object.last_commit
|
||||||
@@ -386,10 +292,7 @@ class HacsRepository(Hacs):
|
|||||||
await self.get_repository_manifest_content()
|
await self.get_repository_manifest_content()
|
||||||
|
|
||||||
# Update "info.md"
|
# Update "info.md"
|
||||||
await self.get_info_md_content()
|
self.information.additional_info = await get_info_md_content(self)
|
||||||
|
|
||||||
# Update releases
|
|
||||||
await self.get_releases()
|
|
||||||
|
|
||||||
async def install(self):
|
async def install(self):
|
||||||
"""Common installation steps of the repository."""
|
"""Common installation steps of the repository."""
|
||||||
@@ -409,18 +312,17 @@ class HacsRepository(Hacs):
|
|||||||
return validate
|
return validate
|
||||||
|
|
||||||
for content in contents or []:
|
for content in contents or []:
|
||||||
filecontent = await async_download_file(self.hass, content.download_url)
|
filecontent = await async_download_file(content.download_url)
|
||||||
|
|
||||||
if filecontent is None:
|
if filecontent is None:
|
||||||
validate.errors.append(f"[{content.name}] was not downloaded.")
|
validate.errors.append(f"[{content.name}] was not downloaded.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result = await async_save_file(
|
result = await async_save_file(
|
||||||
f"{tempfile.gettempdir()}/{self.repository_manifest.filename}",
|
f"{tempfile.gettempdir()}/{self.data.filename}", filecontent
|
||||||
filecontent,
|
|
||||||
)
|
)
|
||||||
with zipfile.ZipFile(
|
with zipfile.ZipFile(
|
||||||
f"{tempfile.gettempdir()}/{self.repository_manifest.filename}", "r"
|
f"{tempfile.gettempdir()}/{self.data.filename}", "r"
|
||||||
) as zip_file:
|
) as zip_file:
|
||||||
zip_file.extractall(self.content.path.local)
|
zip_file.extractall(self.content.path.local)
|
||||||
|
|
||||||
@@ -437,11 +339,13 @@ class HacsRepository(Hacs):
|
|||||||
"""Download the content of a directory."""
|
"""Download the content of a directory."""
|
||||||
from custom_components.hacs.helpers.download import download_content
|
from custom_components.hacs.helpers.download import download_content
|
||||||
|
|
||||||
validate = await download_content(self, validate, local_directory)
|
validate = await download_content(self)
|
||||||
return validate
|
return validate
|
||||||
|
|
||||||
async def get_repository_manifest_content(self):
|
async def get_repository_manifest_content(self):
|
||||||
"""Get the content of the hacs.json file."""
|
"""Get the content of the hacs.json file."""
|
||||||
|
if not "hacs.json" in [x.filename for x in self.tree]:
|
||||||
|
return
|
||||||
if self.ref is None:
|
if self.ref is None:
|
||||||
self.ref = version_to_install(self)
|
self.ref = version_to_install(self)
|
||||||
try:
|
try:
|
||||||
@@ -449,125 +353,44 @@ class HacsRepository(Hacs):
|
|||||||
self.repository_manifest = HacsManifest.from_dict(
|
self.repository_manifest = HacsManifest.from_dict(
|
||||||
json.loads(manifest.content)
|
json.loads(manifest.content)
|
||||||
)
|
)
|
||||||
|
self.data.update_data(json.loads(manifest.content))
|
||||||
except (AIOGitHubException, Exception): # Gotta Catch 'Em All
|
except (AIOGitHubException, Exception): # Gotta Catch 'Em All
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_info_md_content(self):
|
|
||||||
"""Get the content of info.md"""
|
|
||||||
from ..handler.template import render_template
|
|
||||||
|
|
||||||
if self.ref is None:
|
|
||||||
self.ref = version_to_install(self)
|
|
||||||
|
|
||||||
info = None
|
|
||||||
info_files = ["info", "info.md"]
|
|
||||||
|
|
||||||
if self.repository_manifest is not None:
|
|
||||||
if self.repository_manifest.render_readme:
|
|
||||||
info_files = ["readme", "readme.md"]
|
|
||||||
try:
|
|
||||||
root = await self.repository_object.get_contents("", self.ref)
|
|
||||||
for file in root:
|
|
||||||
if file.name.lower() in info_files:
|
|
||||||
|
|
||||||
info = await self.repository_object.get_contents(
|
|
||||||
file.name, self.ref
|
|
||||||
)
|
|
||||||
break
|
|
||||||
if info is None:
|
|
||||||
self.information.additional_info = ""
|
|
||||||
else:
|
|
||||||
info = info.content.replace("<svg", "<disabled").replace(
|
|
||||||
"</svg", "</disabled"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.information.additional_info = render_template(info, self)
|
|
||||||
|
|
||||||
except (AIOGitHubException, Exception):
|
|
||||||
self.information.additional_info = ""
|
|
||||||
|
|
||||||
async def get_releases(self):
|
|
||||||
"""Get repository releases."""
|
|
||||||
if self.status.show_beta:
|
|
||||||
self.releases.objects = await self.repository_object.get_releases(
|
|
||||||
prerelease=True, returnlimit=self.configuration.release_limit
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.releases.objects = await self.repository_object.get_releases(
|
|
||||||
prerelease=False, returnlimit=self.configuration.release_limit
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.releases.objects:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.releases.releases = True
|
|
||||||
|
|
||||||
self.releases.published_tags = []
|
|
||||||
|
|
||||||
for release in self.releases.objects:
|
|
||||||
self.releases.published_tags.append(release.tag_name)
|
|
||||||
|
|
||||||
self.releases.last_release_object = self.releases.objects[0]
|
|
||||||
if self.status.selected_tag is not None:
|
|
||||||
if self.status.selected_tag != self.information.default_branch:
|
|
||||||
for release in self.releases.objects:
|
|
||||||
if release.tag_name == self.status.selected_tag:
|
|
||||||
self.releases.last_release_object = release
|
|
||||||
break
|
|
||||||
if self.releases.last_release_object.assets:
|
|
||||||
self.releases.last_release_object_downloads = self.releases.last_release_object.assets[
|
|
||||||
0
|
|
||||||
].attributes.get(
|
|
||||||
"download_count"
|
|
||||||
)
|
|
||||||
self.versions.available = self.releases.objects[0].tag_name
|
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""Run remove tasks."""
|
"""Run remove tasks."""
|
||||||
# Attach logger
|
|
||||||
if self.logger is None:
|
|
||||||
self.logger = Logger(
|
|
||||||
f"hacs.repository.{self.information.category}.{self.information.full_name}"
|
|
||||||
)
|
|
||||||
self.logger.info("Starting removal")
|
self.logger.info("Starting removal")
|
||||||
|
|
||||||
if self.information.uid in self.common.installed:
|
if self.information.uid in self.hacs.common.installed:
|
||||||
self.common.installed.remove(self.information.uid)
|
self.hacs.common.installed.remove(self.information.uid)
|
||||||
for repository in self.repositories:
|
for repository in self.hacs.repositories:
|
||||||
if repository.information.uid == self.information.uid:
|
if repository.information.uid == self.information.uid:
|
||||||
self.repositories.remove(repository)
|
self.hacs.repositories.remove(repository)
|
||||||
|
|
||||||
async def uninstall(self):
|
async def uninstall(self):
|
||||||
"""Run uninstall tasks."""
|
"""Run uninstall tasks."""
|
||||||
# Attach logger
|
|
||||||
if self.logger is None:
|
|
||||||
self.logger = Logger(
|
|
||||||
f"hacs.repository.{self.information.category}.{self.information.full_name}"
|
|
||||||
)
|
|
||||||
self.logger.info("Uninstalling")
|
self.logger.info("Uninstalling")
|
||||||
await self.remove_local_directory()
|
await self.remove_local_directory()
|
||||||
self.status.installed = False
|
self.status.installed = False
|
||||||
if self.information.category == "integration":
|
if self.data.category == "integration":
|
||||||
if self.config_flow:
|
if self.config_flow:
|
||||||
await self.reload_custom_components()
|
await self.reload_custom_components()
|
||||||
else:
|
else:
|
||||||
self.pending_restart = True
|
self.pending_restart = True
|
||||||
elif self.information.category == "theme":
|
elif self.data.category == "theme":
|
||||||
try:
|
try:
|
||||||
await self.hass.services.async_call("frontend", "reload_themes", {})
|
await self.hacs.hass.services.async_call(
|
||||||
|
"frontend", "reload_themes", {}
|
||||||
|
)
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
pass
|
pass
|
||||||
if self.information.full_name in self.common.installed:
|
if self.data.full_name in self.hacs.common.installed:
|
||||||
self.common.installed.remove(self.information.full_name)
|
self.hacs.common.installed.remove(self.data.full_name)
|
||||||
self.versions.installed = None
|
self.versions.installed = None
|
||||||
self.versions.installed_commit = None
|
self.versions.installed_commit = None
|
||||||
self.hass.bus.async_fire(
|
self.hacs.hass.bus.async_fire(
|
||||||
"hacs/repository",
|
"hacs/repository",
|
||||||
{
|
{"id": 1337, "action": "uninstall", "repository": self.data.full_name},
|
||||||
"id": 1337,
|
|
||||||
"action": "uninstall",
|
|
||||||
"repository": self.information.full_name,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def remove_local_directory(self):
|
async def remove_local_directory(self):
|
||||||
@@ -576,13 +399,11 @@ class HacsRepository(Hacs):
|
|||||||
from asyncio import sleep
|
from asyncio import sleep
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.information.category == "python_script":
|
if self.data.category == "python_script":
|
||||||
local_path = "{}/{}.py".format(
|
local_path = "{}/{}.py".format(self.content.path.local, self.data.name)
|
||||||
self.content.path.local, self.information.name
|
elif self.data.category == "theme":
|
||||||
)
|
|
||||||
elif self.information.category == "theme":
|
|
||||||
local_path = "{}/{}.yaml".format(
|
local_path = "{}/{}.yaml".format(
|
||||||
self.content.path.local, self.information.name
|
self.content.path.local, self.data.name
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
local_path = self.content.path.local
|
local_path = self.content.path.local
|
||||||
@@ -590,7 +411,7 @@ class HacsRepository(Hacs):
|
|||||||
if os.path.exists(local_path):
|
if os.path.exists(local_path):
|
||||||
self.logger.debug(f"Removing {local_path}")
|
self.logger.debug(f"Removing {local_path}")
|
||||||
|
|
||||||
if self.information.category in ["python_script", "theme"]:
|
if self.data.category in ["python_script", "theme"]:
|
||||||
os.remove(local_path)
|
os.remove(local_path)
|
||||||
else:
|
else:
|
||||||
shutil.rmtree(local_path)
|
shutil.rmtree(local_path)
|
||||||
|
82
config/custom_components/hacs/repositories/repositorydata.py
Normal file
82
config/custom_components/hacs/repositories/repositorydata.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
"""Repository data."""
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class RepositoryData:
|
||||||
|
"""RepositoryData class."""
|
||||||
|
|
||||||
|
id: int = 0
|
||||||
|
full_name: str = ""
|
||||||
|
pushed_at: str = ""
|
||||||
|
category: str = ""
|
||||||
|
archived: bool = False
|
||||||
|
description: str = ""
|
||||||
|
manifest_name: str = None
|
||||||
|
topics: List[str] = []
|
||||||
|
fork: bool = False
|
||||||
|
domain: str = ""
|
||||||
|
default_branch: str = None
|
||||||
|
stargazers_count: int = 0
|
||||||
|
last_commit: str = ""
|
||||||
|
file_name: str = ""
|
||||||
|
content_in_root: bool = False
|
||||||
|
zip_release: bool = False
|
||||||
|
filename: str = ""
|
||||||
|
render_readme: bool = False
|
||||||
|
hide_default_branch: bool = False
|
||||||
|
domains: List[str] = []
|
||||||
|
country: List[str] = []
|
||||||
|
authors: List[str] = []
|
||||||
|
homeassistant: str = None # Minimum Home Assistant version
|
||||||
|
hacs: str = None # Minimum HACS version
|
||||||
|
persistent_directory: str = None
|
||||||
|
iot_class: str = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name."""
|
||||||
|
if self.category in ["integration", "netdaemon"]:
|
||||||
|
return self.domain
|
||||||
|
return self.full_name.split("/")[-1]
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
"""Export to json."""
|
||||||
|
return self.__dict__
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_from_dict(source: dict):
|
||||||
|
"""Set attributes from dicts."""
|
||||||
|
data = RepositoryData()
|
||||||
|
for key in source:
|
||||||
|
if key in data.__dict__:
|
||||||
|
if key == "pushed_at":
|
||||||
|
setattr(
|
||||||
|
data, key, datetime.strptime(source[key], "%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
)
|
||||||
|
elif key == "county":
|
||||||
|
if isinstance(source[key], str):
|
||||||
|
setattr(data, key, [source[key]])
|
||||||
|
else:
|
||||||
|
setattr(data, key, source[key])
|
||||||
|
else:
|
||||||
|
setattr(data, key, source[key])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def update_data(self, data: dict):
|
||||||
|
"""Update data of the repository."""
|
||||||
|
for key in data:
|
||||||
|
if key in self.__dict__:
|
||||||
|
if key == "pushed_at":
|
||||||
|
setattr(
|
||||||
|
self, key, datetime.strptime(data[key], "%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
)
|
||||||
|
elif key == "county":
|
||||||
|
if isinstance(data[key], str):
|
||||||
|
setattr(self, key, [data[key]])
|
||||||
|
else:
|
||||||
|
setattr(self, key, data[key])
|
||||||
|
else:
|
||||||
|
setattr(self, key, data[key])
|
72
config/custom_components/hacs/repositories/theme.py
Executable file → Normal file
72
config/custom_components/hacs/repositories/theme.py
Executable file → Normal file
@@ -1,23 +1,22 @@
|
|||||||
"""Class for themes in HACS."""
|
"""Class for themes in HACS."""
|
||||||
from .repository import HacsRepository, register_repository_class
|
from integrationhelper import Logger
|
||||||
|
from .repository import HacsRepository
|
||||||
from ..hacsbase.exceptions import HacsException
|
from ..hacsbase.exceptions import HacsException
|
||||||
from ..helpers.filters import filter_content_return_one_of_type, find_first_of_filetype
|
from ..helpers.information import find_file_name
|
||||||
|
|
||||||
|
|
||||||
@register_repository_class
|
|
||||||
class HacsTheme(HacsRepository):
|
class HacsTheme(HacsRepository):
|
||||||
"""Themes in HACS."""
|
"""Themes in HACS."""
|
||||||
|
|
||||||
category = "theme"
|
|
||||||
|
|
||||||
def __init__(self, full_name):
|
def __init__(self, full_name):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.information.full_name = full_name
|
self.data.full_name = full_name
|
||||||
self.information.category = self.category
|
self.data.category = "theme"
|
||||||
self.content.path.remote = "themes"
|
self.content.path.remote = "themes"
|
||||||
self.content.path.local = f"{self.system.config_path}/themes"
|
self.content.path.local = f"{self.hacs.system.config_path}/themes/"
|
||||||
self.content.single = False
|
self.content.single = False
|
||||||
|
self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}")
|
||||||
|
|
||||||
async def validate_repository(self):
|
async def validate_repository(self):
|
||||||
"""Validate."""
|
"""Validate."""
|
||||||
@@ -27,7 +26,6 @@ class HacsTheme(HacsRepository):
|
|||||||
# Custom step 1: Validate content.
|
# Custom step 1: Validate content.
|
||||||
compliant = False
|
compliant = False
|
||||||
for treefile in self.treefiles:
|
for treefile in self.treefiles:
|
||||||
self.logger.debug(treefile)
|
|
||||||
if treefile.startswith("themes/") and treefile.endswith(".yaml"):
|
if treefile.startswith("themes/") and treefile.endswith(".yaml"):
|
||||||
compliant = True
|
compliant = True
|
||||||
break
|
break
|
||||||
@@ -36,25 +34,13 @@ class HacsTheme(HacsRepository):
|
|||||||
f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant"
|
f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.repository_manifest:
|
if self.data.content_in_root:
|
||||||
if self.repository_manifest.content_in_root:
|
self.content.path.remote = ""
|
||||||
self.content.path.remote = ""
|
|
||||||
|
|
||||||
self.content.objects = await self.repository_object.get_contents(
|
|
||||||
self.content.path.remote, self.ref
|
|
||||||
)
|
|
||||||
|
|
||||||
if not isinstance(self.content.objects, list):
|
|
||||||
self.validate.errors.append("Repostitory structure not compliant")
|
|
||||||
|
|
||||||
self.content.files = filter_content_return_one_of_type(
|
|
||||||
self.treefiles, "themes", "yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Handle potential errors
|
# Handle potential errors
|
||||||
if self.validate.errors:
|
if self.validate.errors:
|
||||||
for error in self.validate.errors:
|
for error in self.validate.errors:
|
||||||
if not self.system.status.startup:
|
if not self.hacs.system.status.startup:
|
||||||
self.logger.error(error)
|
self.logger.error(error)
|
||||||
return self.validate.success
|
return self.validate.success
|
||||||
|
|
||||||
@@ -67,44 +53,20 @@ class HacsTheme(HacsRepository):
|
|||||||
await self.common_registration()
|
await self.common_registration()
|
||||||
|
|
||||||
# Set name
|
# Set name
|
||||||
if self.repository_manifest.filename is not None:
|
find_file_name(self)
|
||||||
self.information.file_name = self.repository_manifest.filename
|
self.content.path.local = f"{self.hacs.system.config_path}/themes/{self.data.file_name.replace('.yaml', '')}"
|
||||||
else:
|
|
||||||
self.information.file_name = find_first_of_filetype(
|
|
||||||
self.content.files, "yaml"
|
|
||||||
).split("/")[-1]
|
|
||||||
self.information.name = self.information.file_name.replace(".yaml", "")
|
|
||||||
self.content.path.local = (
|
|
||||||
f"{self.system.config_path}/themes/{self.information.name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_repository(self): # lgtm[py/similar-function]
|
async def update_repository(self): # lgtm[py/similar-function]
|
||||||
"""Update."""
|
"""Update."""
|
||||||
if self.github.ratelimits.remaining == 0:
|
if self.hacs.github.ratelimits.remaining == 0:
|
||||||
return
|
return
|
||||||
# Run common update steps.
|
# Run common update steps.
|
||||||
await self.common_update()
|
await self.common_update()
|
||||||
|
|
||||||
# Get theme objects.
|
# Get theme objects.
|
||||||
if self.repository_manifest:
|
if self.data.content_in_root:
|
||||||
if self.repository_manifest.content_in_root:
|
self.content.path.remote = ""
|
||||||
self.content.path.remote = ""
|
|
||||||
self.content.objects = await self.repository_object.get_contents(
|
|
||||||
self.content.path.remote, self.ref
|
|
||||||
)
|
|
||||||
|
|
||||||
self.content.files = filter_content_return_one_of_type(
|
|
||||||
self.treefiles, "themes", "yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update name
|
# Update name
|
||||||
if self.repository_manifest.filename is not None:
|
find_file_name(self)
|
||||||
self.information.file_name = self.repository_manifest.filename
|
self.content.path.local = f"{self.hacs.system.config_path}/themes/{self.data.file_name.replace('.yaml', '')}"
|
||||||
else:
|
|
||||||
self.information.file_name = find_first_of_filetype(
|
|
||||||
self.content.files, "yaml"
|
|
||||||
).split("/")[-1]
|
|
||||||
self.information.name = self.information.file_name.replace(".yaml", "")
|
|
||||||
self.content.path.local = (
|
|
||||||
f"{self.system.config_path}/themes/{self.information.name}"
|
|
||||||
)
|
|
||||||
|
9
config/custom_components/hacs/sensor.py
Executable file → Normal file
9
config/custom_components/hacs/sensor.py
Executable file → Normal file
@@ -48,7 +48,7 @@ class HACSSensor(HACSDevice):
|
|||||||
for repository in hacs.repositories:
|
for repository in hacs.repositories:
|
||||||
if (
|
if (
|
||||||
repository.pending_upgrade
|
repository.pending_upgrade
|
||||||
and repository.category in hacs.common.categories
|
and repository.data.category in hacs.common.categories
|
||||||
):
|
):
|
||||||
self.repositories.append(repository)
|
self.repositories.append(repository)
|
||||||
self._state = len(self.repositories)
|
self._state = len(self.repositories)
|
||||||
@@ -87,13 +87,10 @@ class HACSSensor(HACSDevice):
|
|||||||
for repository in self.repositories:
|
for repository in self.repositories:
|
||||||
data.append(
|
data.append(
|
||||||
{
|
{
|
||||||
"name": repository.information.full_name,
|
"name": repository.data.full_name,
|
||||||
"display_name": repository.display_name,
|
"display_name": repository.display_name,
|
||||||
"installed version": repository.display_installed_version,
|
"installed version": repository.display_installed_version,
|
||||||
"available version": repository.display_available_version,
|
"available version": repository.display_available_version,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return {
|
return {"repositories": data}
|
||||||
"repositories": data,
|
|
||||||
"attribution": "It is expected to see [object Object] here, for more info see https://hacs.xyz/docs/basic/sensor",
|
|
||||||
}
|
|
||||||
|
0
config/custom_components/hacs/services.yaml
Executable file → Normal file
0
config/custom_components/hacs/services.yaml
Executable file → Normal file
59
config/custom_components/hacs/setup.py
Executable file → Normal file
59
config/custom_components/hacs/setup.py
Executable file → Normal file
@@ -1,51 +1,57 @@
|
|||||||
"""Setup functions for HACS."""
|
"""Setup functions for HACS."""
|
||||||
# pylint: disable=bad-continuation
|
# pylint: disable=bad-continuation
|
||||||
|
from hacs_frontend.version import VERSION as FE_VERSION
|
||||||
|
from homeassistant.helpers import discovery
|
||||||
|
|
||||||
|
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||||
|
from custom_components.hacs.const import VERSION, DOMAIN
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
|
from custom_components.hacs.helpers.information import get_repository
|
||||||
|
from custom_components.hacs.helpers.register_repository import register_repository
|
||||||
|
|
||||||
|
|
||||||
async def load_hacs_repository(hacs):
|
async def load_hacs_repository():
|
||||||
"""Load HACS repositroy."""
|
"""Load HACS repositroy."""
|
||||||
from .const import VERSION
|
hacs = get_hacs()
|
||||||
from aiogithubapi import (
|
|
||||||
AIOGitHubAuthentication,
|
|
||||||
AIOGitHubException,
|
|
||||||
AIOGitHubRatelimit,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
repository = hacs().get_by_name("hacs/integration")
|
repository = hacs.get_by_name("hacs/integration")
|
||||||
if repository is None:
|
if repository is None:
|
||||||
await hacs().register_repository("hacs/integration", "integration")
|
await register_repository("hacs/integration", "integration")
|
||||||
repository = hacs().get_by_name("hacs/integration")
|
repository = hacs.get_by_name("hacs/integration")
|
||||||
if repository is None:
|
if repository is None:
|
||||||
raise AIOGitHubException("Unknown error")
|
raise HacsException("Unknown error")
|
||||||
repository.status.installed = True
|
repository.status.installed = True
|
||||||
repository.versions.installed = VERSION
|
repository.versions.installed = VERSION
|
||||||
repository.status.new = False
|
repository.status.new = False
|
||||||
hacs.repo = repository.repository_object
|
hacs.repo = repository.repository_object
|
||||||
hacs.data_repo = await hacs().github.get_repo("hacs/default")
|
hacs.data_repo = await get_repository(
|
||||||
except (
|
hacs.session, hacs.configuration.token, "hacs/default"
|
||||||
AIOGitHubException,
|
)
|
||||||
AIOGitHubRatelimit,
|
except HacsException as exception:
|
||||||
AIOGitHubAuthentication,
|
if "403" in f"{exception}":
|
||||||
) as exception:
|
hacs.logger.critical("GitHub API is ratelimited, or the token is wrong.")
|
||||||
hacs.logger.critical(f"[{exception}] - Could not load HACS!")
|
else:
|
||||||
|
hacs.logger.critical(f"[{exception}] - Could not load HACS!")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def setup_extra_stores(hacs):
|
def setup_extra_stores():
|
||||||
"""Set up extra stores in HACS if enabled in Home Assistant."""
|
"""Set up extra stores in HACS if enabled in Home Assistant."""
|
||||||
|
hacs = get_hacs()
|
||||||
if "python_script" in hacs.hass.config.components:
|
if "python_script" in hacs.hass.config.components:
|
||||||
hacs.common.categories.append("python_script")
|
if "python_script" not in hacs.common.categories:
|
||||||
|
hacs.common.categories.append("python_script")
|
||||||
|
|
||||||
if hacs.hass.services.services.get("frontend", {}).get("reload_themes") is not None:
|
if hacs.hass.services.services.get("frontend", {}).get("reload_themes") is not None:
|
||||||
hacs.common.categories.append("theme")
|
if "theme" not in hacs.common.categories:
|
||||||
|
hacs.common.categories.append("theme")
|
||||||
|
|
||||||
|
|
||||||
def add_sensor(hacs):
|
def add_sensor():
|
||||||
"""Add sensor."""
|
"""Add sensor."""
|
||||||
from .const import DOMAIN
|
hacs = get_hacs()
|
||||||
from homeassistant.helpers import discovery
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if hacs.configuration.config_type == "yaml":
|
if hacs.configuration.config_type == "yaml":
|
||||||
@@ -64,11 +70,12 @@ def add_sensor(hacs):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def setup_frontend(hacs):
|
async def setup_frontend():
|
||||||
"""Configure the HACS frontend elements."""
|
"""Configure the HACS frontend elements."""
|
||||||
from .http import HacsFrontend, HacsPluginViewLegacy
|
from .http import HacsFrontend, HacsPluginViewLegacy
|
||||||
from .ws_api_handlers import setup_ws_api
|
from .ws_api_handlers import setup_ws_api
|
||||||
from hacs_frontend.version import VERSION as FE_VERSION
|
|
||||||
|
hacs = get_hacs()
|
||||||
|
|
||||||
hacs.hass.http.register_view(HacsFrontend())
|
hacs.hass.http.register_view(HacsFrontend())
|
||||||
hacs.frontend.version_running = FE_VERSION
|
hacs.frontend.version_running = FE_VERSION
|
||||||
|
0
config/custom_components/hacs/store.py
Executable file → Normal file
0
config/custom_components/hacs/store.py
Executable file → Normal file
126
config/custom_components/hacs/ws_api_handlers.py
Executable file → Normal file
126
config/custom_components/hacs/ws_api_handlers.py
Executable file → Normal file
@@ -6,10 +6,12 @@ import voluptuous as vol
|
|||||||
from aiogithubapi import AIOGitHubException
|
from aiogithubapi import AIOGitHubException
|
||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from .hacsbase import Hacs
|
|
||||||
from .hacsbase.exceptions import HacsException
|
from .hacsbase.exceptions import HacsException
|
||||||
from .store import async_load_from_store, async_save_to_store
|
from .store import async_load_from_store, async_save_to_store
|
||||||
|
|
||||||
|
from custom_components.hacs.globals import get_hacs
|
||||||
|
from custom_components.hacs.helpers.register_repository import register_repository
|
||||||
|
|
||||||
|
|
||||||
async def setup_ws_api(hass):
|
async def setup_ws_api(hass):
|
||||||
"""Set up WS API handlers."""
|
"""Set up WS API handlers."""
|
||||||
@@ -34,74 +36,76 @@ async def setup_ws_api(hass):
|
|||||||
)
|
)
|
||||||
async def hacs_settings(hass, connection, msg):
|
async def hacs_settings(hass, connection, msg):
|
||||||
"""Handle get media player cover command."""
|
"""Handle get media player cover command."""
|
||||||
|
hacs = get_hacs()
|
||||||
action = msg["action"]
|
action = msg["action"]
|
||||||
Hacs().logger.debug(f"WS action '{action}'")
|
hacs.logger.debug(f"WS action '{action}'")
|
||||||
|
|
||||||
if action == "set_fe_grid":
|
if action == "set_fe_grid":
|
||||||
Hacs().configuration.frontend_mode = "Grid"
|
hacs.configuration.frontend_mode = "Grid"
|
||||||
|
|
||||||
elif action == "onboarding_done":
|
elif action == "onboarding_done":
|
||||||
Hacs().configuration.onboarding_done = True
|
hacs.configuration.onboarding_done = True
|
||||||
|
|
||||||
elif action == "set_fe_table":
|
elif action == "set_fe_table":
|
||||||
Hacs().configuration.frontend_mode = "Table"
|
hacs.configuration.frontend_mode = "Table"
|
||||||
|
|
||||||
elif action == "set_fe_compact_true":
|
elif action == "set_fe_compact_true":
|
||||||
Hacs().configuration.frontend_compact = False
|
hacs.configuration.frontend_compact = False
|
||||||
|
|
||||||
elif action == "set_fe_compact_false":
|
elif action == "set_fe_compact_false":
|
||||||
Hacs().configuration.frontend_compact = True
|
hacs.configuration.frontend_compact = True
|
||||||
|
|
||||||
elif action == "reload_data":
|
elif action == "reload_data":
|
||||||
Hacs().system.status.reloading_data = True
|
hacs.system.status.reloading_data = True
|
||||||
hass.bus.async_fire("hacs/status", {})
|
hass.bus.async_fire("hacs/status", {})
|
||||||
await Hacs().recuring_tasks_all()
|
await hacs.recuring_tasks_all()
|
||||||
Hacs().system.status.reloading_data = False
|
hacs.system.status.reloading_data = False
|
||||||
hass.bus.async_fire("hacs/status", {})
|
hass.bus.async_fire("hacs/status", {})
|
||||||
|
|
||||||
elif action == "upgrade_all":
|
elif action == "upgrade_all":
|
||||||
Hacs().system.status.upgrading_all = True
|
hacs.system.status.upgrading_all = True
|
||||||
Hacs().system.status.background_task = True
|
hacs.system.status.background_task = True
|
||||||
hass.bus.async_fire("hacs/status", {})
|
hass.bus.async_fire("hacs/status", {})
|
||||||
for repository in Hacs().repositories:
|
for repository in hacs.repositories:
|
||||||
if repository.pending_upgrade:
|
if repository.pending_upgrade:
|
||||||
repository.status.selected_tag = None
|
repository.status.selected_tag = None
|
||||||
await repository.install()
|
await repository.install()
|
||||||
Hacs().system.status.upgrading_all = False
|
hacs.system.status.upgrading_all = False
|
||||||
Hacs().system.status.background_task = False
|
hacs.system.status.background_task = False
|
||||||
hass.bus.async_fire("hacs/status", {})
|
hass.bus.async_fire("hacs/status", {})
|
||||||
hass.bus.async_fire("hacs/repository", {})
|
hass.bus.async_fire("hacs/repository", {})
|
||||||
|
|
||||||
elif action == "clear_new":
|
elif action == "clear_new":
|
||||||
for repo in Hacs().repositories:
|
for repo in hacs.repositories:
|
||||||
if msg.get("category") == repo.information.category:
|
if msg.get("category") == repo.data.category:
|
||||||
if repo.status.new:
|
if repo.status.new:
|
||||||
Hacs().logger.debug(
|
hacs.logger.debug(
|
||||||
f"Clearing new flag from '{repo.information.full_name}'"
|
f"Clearing new flag from '{repo.data.full_name}'"
|
||||||
)
|
)
|
||||||
repo.status.new = False
|
repo.status.new = False
|
||||||
else:
|
else:
|
||||||
Hacs().logger.error(f"WS action '{action}' is not valid")
|
hacs.logger.error(f"WS action '{action}' is not valid")
|
||||||
hass.bus.async_fire("hacs/config", {})
|
hass.bus.async_fire("hacs/config", {})
|
||||||
await Hacs().data.async_write()
|
await hacs.data.async_write()
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
@websocket_api.websocket_command({vol.Required("type"): "hacs/config"})
|
@websocket_api.websocket_command({vol.Required("type"): "hacs/config"})
|
||||||
async def hacs_config(hass, connection, msg):
|
async def hacs_config(hass, connection, msg):
|
||||||
"""Handle get media player cover command."""
|
"""Handle get media player cover command."""
|
||||||
config = Hacs().configuration
|
hacs = get_hacs()
|
||||||
|
config = hacs.configuration
|
||||||
|
|
||||||
content = {}
|
content = {}
|
||||||
content["frontend_mode"] = config.frontend_mode
|
content["frontend_mode"] = config.frontend_mode
|
||||||
content["frontend_compact"] = config.frontend_compact
|
content["frontend_compact"] = config.frontend_compact
|
||||||
content["onboarding_done"] = config.onboarding_done
|
content["onboarding_done"] = config.onboarding_done
|
||||||
content["version"] = Hacs().version
|
content["version"] = hacs.version
|
||||||
content["dev"] = config.dev
|
content["dev"] = config.dev
|
||||||
content["debug"] = config.debug
|
content["debug"] = config.debug
|
||||||
content["country"] = config.country
|
content["country"] = config.country
|
||||||
content["experimental"] = config.experimental
|
content["experimental"] = config.experimental
|
||||||
content["categories"] = Hacs().common.categories
|
content["categories"] = hacs.common.categories
|
||||||
|
|
||||||
connection.send_message(websocket_api.result_message(msg["id"], content))
|
connection.send_message(websocket_api.result_message(msg["id"], content))
|
||||||
|
|
||||||
@@ -110,13 +114,14 @@ async def hacs_config(hass, connection, msg):
|
|||||||
@websocket_api.websocket_command({vol.Required("type"): "hacs/status"})
|
@websocket_api.websocket_command({vol.Required("type"): "hacs/status"})
|
||||||
async def hacs_status(hass, connection, msg):
|
async def hacs_status(hass, connection, msg):
|
||||||
"""Handle get media player cover command."""
|
"""Handle get media player cover command."""
|
||||||
|
hacs = get_hacs()
|
||||||
content = {
|
content = {
|
||||||
"startup": Hacs().system.status.startup,
|
"startup": hacs.system.status.startup,
|
||||||
"background_task": Hacs().system.status.background_task,
|
"background_task": hacs.system.status.background_task,
|
||||||
"lovelace_mode": Hacs().system.lovelace_mode,
|
"lovelace_mode": hacs.system.lovelace_mode,
|
||||||
"reloading_data": Hacs().system.status.reloading_data,
|
"reloading_data": hacs.system.status.reloading_data,
|
||||||
"upgrading_all": Hacs().system.status.upgrading_all,
|
"upgrading_all": hacs.system.status.upgrading_all,
|
||||||
"disabled": Hacs().system.disabled,
|
"disabled": hacs.system.disabled,
|
||||||
}
|
}
|
||||||
connection.send_message(websocket_api.result_message(msg["id"], content))
|
connection.send_message(websocket_api.result_message(msg["id"], content))
|
||||||
|
|
||||||
@@ -125,30 +130,31 @@ async def hacs_status(hass, connection, msg):
|
|||||||
@websocket_api.websocket_command({vol.Required("type"): "hacs/repositories"})
|
@websocket_api.websocket_command({vol.Required("type"): "hacs/repositories"})
|
||||||
async def hacs_repositories(hass, connection, msg):
|
async def hacs_repositories(hass, connection, msg):
|
||||||
"""Handle get media player cover command."""
|
"""Handle get media player cover command."""
|
||||||
repositories = Hacs().repositories
|
hacs = get_hacs()
|
||||||
|
repositories = hacs.repositories
|
||||||
content = []
|
content = []
|
||||||
for repo in repositories:
|
for repo in repositories:
|
||||||
if repo.information.category in Hacs().common.categories:
|
if repo.data.category in hacs.common.categories:
|
||||||
data = {
|
data = {
|
||||||
"additional_info": repo.information.additional_info,
|
"additional_info": repo.information.additional_info,
|
||||||
"authors": repo.information.authors,
|
"authors": repo.data.authors,
|
||||||
"available_version": repo.display_available_version,
|
"available_version": repo.display_available_version,
|
||||||
"beta": repo.status.show_beta,
|
"beta": repo.status.show_beta,
|
||||||
"can_install": repo.can_install,
|
"can_install": repo.can_install,
|
||||||
"category": repo.information.category,
|
"category": repo.data.category,
|
||||||
"country": repo.repository_manifest.country,
|
"country": repo.data.country,
|
||||||
"config_flow": repo.config_flow,
|
"config_flow": repo.config_flow,
|
||||||
"custom": repo.custom,
|
"custom": repo.custom,
|
||||||
"default_branch": repo.information.default_branch,
|
"default_branch": repo.data.default_branch,
|
||||||
"description": repo.information.description,
|
"description": repo.data.description,
|
||||||
"domain": repo.manifest.get("domain"),
|
"domain": repo.integration_manifest.get("domain"),
|
||||||
"downloads": repo.releases.last_release_object_downloads,
|
"downloads": repo.releases.downloads,
|
||||||
"file_name": repo.information.file_name,
|
"file_name": repo.data.file_name,
|
||||||
"first_install": repo.status.first_install,
|
"first_install": repo.status.first_install,
|
||||||
"full_name": repo.information.full_name,
|
"full_name": repo.data.full_name,
|
||||||
"hide": repo.status.hide,
|
"hide": repo.status.hide,
|
||||||
"hide_default_branch": repo.repository_manifest.hide_default_branch,
|
"hide_default_branch": repo.data.hide_default_branch,
|
||||||
"homeassistant": repo.repository_manifest.homeassistant,
|
"homeassistant": repo.data.homeassistant,
|
||||||
"id": repo.information.uid,
|
"id": repo.information.uid,
|
||||||
"info": repo.information.info,
|
"info": repo.information.info,
|
||||||
"installed_version": repo.display_installed_version,
|
"installed_version": repo.display_installed_version,
|
||||||
@@ -162,11 +168,11 @@ async def hacs_repositories(hass, connection, msg):
|
|||||||
"pending_upgrade": repo.pending_upgrade,
|
"pending_upgrade": repo.pending_upgrade,
|
||||||
"releases": repo.releases.published_tags,
|
"releases": repo.releases.published_tags,
|
||||||
"selected_tag": repo.status.selected_tag,
|
"selected_tag": repo.status.selected_tag,
|
||||||
"stars": repo.information.stars,
|
"stars": repo.data.stargazers_count,
|
||||||
"state": repo.state,
|
"state": repo.state,
|
||||||
"status_description": repo.display_status_description,
|
"status_description": repo.display_status_description,
|
||||||
"status": repo.display_status,
|
"status": repo.display_status,
|
||||||
"topics": repo.information.topics,
|
"topics": repo.data.topics,
|
||||||
"updated_info": repo.status.updated_info,
|
"updated_info": repo.status.updated_info,
|
||||||
"version_or_commit": repo.display_version_or_commit,
|
"version_or_commit": repo.display_version_or_commit,
|
||||||
}
|
}
|
||||||
@@ -186,6 +192,7 @@ async def hacs_repositories(hass, connection, msg):
|
|||||||
)
|
)
|
||||||
async def hacs_repository(hass, connection, msg):
|
async def hacs_repository(hass, connection, msg):
|
||||||
"""Handle get media player cover command."""
|
"""Handle get media player cover command."""
|
||||||
|
hacs = get_hacs()
|
||||||
try:
|
try:
|
||||||
repo_id = msg.get("repository")
|
repo_id = msg.get("repository")
|
||||||
action = msg.get("action")
|
action = msg.get("action")
|
||||||
@@ -193,8 +200,8 @@ async def hacs_repository(hass, connection, msg):
|
|||||||
if repo_id is None or action is None:
|
if repo_id is None or action is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
repository = Hacs().get_by_id(repo_id)
|
repository = hacs.get_by_id(repo_id)
|
||||||
Hacs().logger.debug(f"Running {action} for {repository.information.full_name}")
|
hacs.logger.debug(f"Running {action} for {repository.data.full_name}")
|
||||||
|
|
||||||
if action == "update":
|
if action == "update":
|
||||||
await repository.update_repository()
|
await repository.update_repository()
|
||||||
@@ -230,17 +237,17 @@ async def hacs_repository(hass, connection, msg):
|
|||||||
repository.remove()
|
repository.remove()
|
||||||
|
|
||||||
elif action == "set_version":
|
elif action == "set_version":
|
||||||
if msg["version"] == repository.information.default_branch:
|
if msg["version"] == repository.data.default_branch:
|
||||||
repository.status.selected_tag = None
|
repository.status.selected_tag = None
|
||||||
else:
|
else:
|
||||||
repository.status.selected_tag = msg["version"]
|
repository.status.selected_tag = msg["version"]
|
||||||
await repository.update_repository()
|
await repository.update_repository()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
Hacs().logger.error(f"WS action '{action}' is not valid")
|
hacs.logger.error(f"WS action '{action}' is not valid")
|
||||||
|
|
||||||
repository.state = None
|
repository.state = None
|
||||||
await Hacs().data.async_write()
|
await hacs.data.async_write()
|
||||||
except AIOGitHubException as exception:
|
except AIOGitHubException as exception:
|
||||||
hass.bus.async_fire("hacs/error", {"message": str(exception)})
|
hass.bus.async_fire("hacs/error", {"message": str(exception)})
|
||||||
except AttributeError as exception:
|
except AttributeError as exception:
|
||||||
@@ -262,6 +269,7 @@ async def hacs_repository(hass, connection, msg):
|
|||||||
)
|
)
|
||||||
async def hacs_repository_data(hass, connection, msg):
|
async def hacs_repository_data(hass, connection, msg):
|
||||||
"""Handle get media player cover command."""
|
"""Handle get media player cover command."""
|
||||||
|
hacs = get_hacs()
|
||||||
repo_id = msg.get("repository")
|
repo_id = msg.get("repository")
|
||||||
action = msg.get("action")
|
action = msg.get("action")
|
||||||
data = msg.get("data")
|
data = msg.get("data")
|
||||||
@@ -273,12 +281,12 @@ async def hacs_repository_data(hass, connection, msg):
|
|||||||
if "github." in repo_id:
|
if "github." in repo_id:
|
||||||
repo_id = repo_id.split("github.com/")[1]
|
repo_id = repo_id.split("github.com/")[1]
|
||||||
|
|
||||||
if repo_id in Hacs().common.skip:
|
if repo_id in hacs.common.skip:
|
||||||
Hacs().common.skip.remove(repo_id)
|
hacs.common.skip.remove(repo_id)
|
||||||
|
|
||||||
if not Hacs().get_by_name(repo_id):
|
if not hacs.get_by_name(repo_id):
|
||||||
try:
|
try:
|
||||||
registration = await Hacs().register_repository(repo_id, data.lower())
|
registration = await register_repository(repo_id, data.lower())
|
||||||
if registration is not None:
|
if registration is not None:
|
||||||
raise HacsException(registration)
|
raise HacsException(registration)
|
||||||
except Exception as exception: # pylint: disable=broad-except
|
except Exception as exception: # pylint: disable=broad-except
|
||||||
@@ -299,15 +307,15 @@ async def hacs_repository_data(hass, connection, msg):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
repository = Hacs().get_by_name(repo_id)
|
repository = hacs.get_by_name(repo_id)
|
||||||
else:
|
else:
|
||||||
repository = Hacs().get_by_id(repo_id)
|
repository = hacs.get_by_id(repo_id)
|
||||||
|
|
||||||
if repository is None:
|
if repository is None:
|
||||||
hass.bus.async_fire("hacs/repository", {})
|
hass.bus.async_fire("hacs/repository", {})
|
||||||
return
|
return
|
||||||
|
|
||||||
Hacs().logger.debug(f"Running {action} for {repository.information.full_name}")
|
hacs.logger.debug(f"Running {action} for {repository.data.full_name}")
|
||||||
|
|
||||||
if action == "set_state":
|
if action == "set_state":
|
||||||
repository.state = data
|
repository.state = data
|
||||||
@@ -322,9 +330,9 @@ async def hacs_repository_data(hass, connection, msg):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
repository.state = None
|
repository.state = None
|
||||||
Hacs().logger.error(f"WS action '{action}' is not valid")
|
hacs.logger.error(f"WS action '{action}' is not valid")
|
||||||
|
|
||||||
await Hacs().data.async_write()
|
await hacs.data.async_write()
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
|
@@ -11,13 +11,15 @@ cover:
|
|||||||
covers:
|
covers:
|
||||||
large_garage:
|
large_garage:
|
||||||
device: !secret large_garage_id
|
device: !secret large_garage_id
|
||||||
username: !secret garadget_username
|
#username: !secret garadget_username
|
||||||
password: !secret garadget_password
|
#password: !secret garadget_password
|
||||||
|
access_token: !secret garadget_access_token
|
||||||
name: large_garage
|
name: large_garage
|
||||||
small_garage:
|
small_garage:
|
||||||
device: !secret small_garage_id
|
device: !secret small_garage_id
|
||||||
username: !secret garadget_username
|
#username: !secret garadget_username
|
||||||
password: !secret garadget_password
|
#password: !secret garadget_password
|
||||||
|
access_token: !secret garadget_access_token
|
||||||
name: small_garage
|
name: small_garage
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
|
@@ -6,17 +6,11 @@
|
|||||||
###################################
|
###################################
|
||||||
|
|
||||||
homeassistant:
|
homeassistant:
|
||||||
customize_glob:
|
|
||||||
"sensor.skybell_*":
|
|
||||||
icon: mdi:camera-front
|
|
||||||
|
|
||||||
hidden: False
|
|
||||||
homebridge_hidden: True
|
|
||||||
|
|
||||||
group:
|
group:
|
||||||
skybell:
|
skybell:
|
||||||
name: Skybell HD Front Door
|
name: Skybell HD Front Door
|
||||||
|
|
||||||
entities:
|
entities:
|
||||||
- binary_sensor.skybell_front_door_button
|
- binary_sensor.skybell_front_door_button
|
||||||
- binary_sensor.skybell_front_door_motion
|
- binary_sensor.skybell_front_door_motion
|
||||||
@@ -63,7 +57,7 @@ switch:
|
|||||||
## Doorbell Press
|
## Doorbell Press
|
||||||
automation:
|
automation:
|
||||||
- alias: 'Log SkyBell Pressed Activity'
|
- alias: 'Log SkyBell Pressed Activity'
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
- platform: state
|
- platform: state
|
||||||
entity_id:
|
entity_id:
|
||||||
@@ -87,7 +81,7 @@ automation:
|
|||||||
|
|
||||||
# Motion Sensing
|
# Motion Sensing
|
||||||
- alias: 'Log SkyBell Motion detection'
|
- alias: 'Log SkyBell Motion detection'
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
- platform: event
|
- platform: event
|
||||||
event_type: skybell_motion
|
event_type: skybell_motion
|
||||||
@@ -98,7 +92,7 @@ automation:
|
|||||||
# Turn SkyBell Light and Neato Schedule back on if it's turned off. Like any Good Watchdog.
|
# Turn SkyBell Light and Neato Schedule back on if it's turned off. Like any Good Watchdog.
|
||||||
|
|
||||||
- alias: Automated Mismatch WatchDog!
|
- alias: Automated Mismatch WatchDog!
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
- platform: state
|
- platform: state
|
||||||
entity_id:
|
entity_id:
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Reference in New Issue
Block a user