Commit fd20e820 authored by Roman Alifanov's avatar Roman Alifanov

init is a minimal dependency checking system

parent f5e6275f
...@@ -9,4 +9,6 @@ src/main.py ...@@ -9,4 +9,6 @@ src/main.py
src/window.py src/window.py
src/window.blp src/window.blp
src/settings/main.py src/settings/main.py
src/settings/widgets/service_dialog.py src/settings/deps.py
\ No newline at end of file src/settings/widgets/deps_alert_dialog.blp
src/settings/widgets/service_dialog.py
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: tuneit\n" "Project-Id-Version: tuneit\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-21 13:16+0300\n" "POT-Creation-Date: 2025-02-11 15:59+0300\n"
"PO-Revision-Date: 2025-01-21 13:18+0300\n" "PO-Revision-Date: 2025-02-11 16:01+0300\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: ru_RU\n" "Language: ru_RU\n"
...@@ -26,7 +26,7 @@ msgstr "" ...@@ -26,7 +26,7 @@ msgstr ""
msgid "GTK;" msgid "GTK;"
msgstr "" msgstr ""
#: data/ru.ximperlinux.TuneIt.metainfo.xml.in:7 src/window.blp:92 #: data/ru.ximperlinux.TuneIt.metainfo.xml.in:7 src/window.blp:9
msgid "TuneIt" msgid "TuneIt"
msgstr "" msgstr ""
...@@ -52,30 +52,46 @@ msgstr "Показывать модули которым нужны root пра ...@@ -52,30 +52,46 @@ msgstr "Показывать модули которым нужны root пра
msgid "translator-credits" msgid "translator-credits"
msgstr "ximper@etersoft.ru" msgstr "ximper@etersoft.ru"
#: src/window.blp:26 #: src/window.blp:40 src/window.blp:66 src/window.blp:87
msgid "Main Menu" msgid "Main Menu"
msgstr "" msgstr ""
#: src/window.blp:72 #: src/window.blp:48
msgid "Settings" msgid "Settings"
msgstr "Настройки" msgstr "Настройки"
#: src/window.blp:79 #: src/window.blp:75
msgid "Sections"
msgstr ""
#: src/window.blp:113
msgid "Shop" msgid "Shop"
msgstr "Магазин" msgstr "Магазин"
#: src/window.blp:99 #: src/window.blp:131
msgid "_Preferences" msgid "_Preferences"
msgstr "" msgstr ""
#: src/window.blp:104 #: src/window.blp:136
msgid "_Keyboard Shortcuts" msgid "_Keyboard Shortcuts"
msgstr "" msgstr ""
#: src/window.blp:109 #: src/window.blp:141
msgid "_About TuneIt" msgid "_About TuneIt"
msgstr "О программе" msgstr "О программе"
#: src/settings/widgets/deps_alert_dialog.blp:5
msgid "The module has unmet dependencies"
msgstr "Модуль имеет неудовлетворенные зависимости"
#: src/settings/widgets/deps_alert_dialog.blp:8
msgid "Ignore"
msgstr "Игнорировать"
#: src/settings/widgets/deps_alert_dialog.blp:9
msgid "Skip module"
msgstr "Пропустить модуль"
#: src/settings/widgets/service_dialog.py:13 #: src/settings/widgets/service_dialog.py:13
msgid "Dbus service is disabled or unresponsive." msgid "Dbus service is disabled or unresponsive."
msgstr "Dbus сервис отключен или не отвечает." msgstr "Dbus сервис отключен или не отвечает."
......
from .os import OSReleaseChecker
from .path import PathChecker
from .binary import BinaryChecker
class DependencyCheckerFactory:
def __init__(self):
self._checkers = {
'os': OSReleaseChecker,
'path': PathChecker,
'binary': BinaryChecker,
}
def create_checker(self, dependency_type):
checker_class = self._checkers.get(dependency_type)
if not checker_class:
raise ValueError(f"Unfinished type of dependence: {dependency_type}")
return checker_class()
class DependencyManager:
def __init__(self):
self.factory = DependencyCheckerFactory()
def _verify(self, items, check_type):
results = []
for item_type, expected_value in items.items():
try:
checker = self.factory.create_checker(item_type)
result = checker.check(expected_value, is_conflict=(check_type == "conflict"))
results.append({
'type': check_type,
'name': item_type,
'success': result['success'],
'actual': result['actual'],
'expected': result['expected'],
'error': result.get('error', '')
})
except Exception as e:
print(f"Error when checking {item_type}: {str(e)}")
results.append({
'type': check_type,
'name': item_type,
'success': False,
'error': str(e)
})
return results
def verify_deps(self, dependencies):
return self._verify(dependencies, "dependency")
def verify_conflicts(self, conflicts):
return self._verify(conflicts, "conflict")
def format_results(self, results):
message = ""
for result in results:
label = {
'dependency': f"{result['name']} {_("dependency")}",
'conflict': f"{result['name']} {_("conflict")}"
}[result['type']]
status = "✓" if result['success'] else "✕"
message += f"{label} {status}\n"
if 'actual' in result:
message += f" {_("Actual")} {result['actual']}\n"
message += f" {_("Expected")}: {result['expected']}\n"
if result['error']:
message += f" {_("Error")} {result['error']}\n"
return message
class DependencyChecker:
def check(self, expected_value, is_conflict=False):
raise NotImplementedError("check() method must be implemented in the subclass!")
from .base import DependencyChecker
class BinaryChecker(DependencyChecker):
def check(self, expected_value, is_conflict=False):
from shutil import which
binary_path = which(expected_value)
if isinstance(expected_value, (str, bytes)):
expected_values = (expected_value,)
else:
expected_values = tuple(expected_value)
if is_conflict:
success = binary_path is None
else:
success = binary_path is not None
return {
'success': success,
'actual': binary_path,
'expected': expected_values
}
from .base import DependencyChecker
class OSReleaseChecker(DependencyChecker):
def check(self, expected_value='Etersoft Ximper', is_conflict=False):
from ..backends.file import FileBackend
actual_name = FileBackend({'file_path': '/etc/os-release'}).get_value('NAME', 's')
if isinstance(expected_value, (str, bytes)):
expected_values = (expected_value,)
else:
expected_values = tuple(expected_value)
if is_conflict:
success = actual_name not in expected_values
else:
success = actual_name in expected_values
return {
'success': success,
'actual': actual_name,
'expected': expected_values
}
from .base import DependencyChecker
class PathChecker(DependencyChecker):
def check(self, expected_value, is_conflict=False):
from os import path
file_path = path.expanduser(expected_value)
file_path = path.expandvars(file_path)
file_exists = path.exists(file_path)
if isinstance(expected_value, (str, bytes)):
expected_values = (expected_value,)
else:
expected_values = tuple(expected_value)
if is_conflict:
success = not file_exists
else:
success = file_exists
return {
'success': success,
'actual': file_path,
'expected': expected_values,
'exists': file_exists
}
...@@ -3,7 +3,9 @@ from .page import Page ...@@ -3,7 +3,9 @@ from .page import Page
from .sections import SectionFactory from .sections import SectionFactory
from .tools.yml_tools import load_modules from .tools.yml_tools import load_modules
from .widgets.deps_alert_dialog import TuneItDepsAlertDialog
from .deps import DependencyManager
def init_settings_stack(stack, listbox, split_view): def init_settings_stack(stack, listbox, split_view):
yaml_data = load_modules() yaml_data = load_modules()
...@@ -11,6 +13,8 @@ def init_settings_stack(stack, listbox, split_view): ...@@ -11,6 +13,8 @@ def init_settings_stack(stack, listbox, split_view):
modules_dict = {} modules_dict = {}
pages_dict = {} pages_dict = {}
dep_manager = DependencyManager()
if stack.get_pages(): if stack.get_pages():
print("Clear pages...") print("Clear pages...")
listbox.remove_all() listbox.remove_all()
...@@ -18,8 +22,33 @@ def init_settings_stack(stack, listbox, split_view): ...@@ -18,8 +22,33 @@ def init_settings_stack(stack, listbox, split_view):
stack.remove(page) stack.remove(page)
else: else:
print("First init...") print("First init...")
for module_data in yaml_data: for module_data in yaml_data:
deps_results = dep_manager.verify_deps(module_data.get('deps', {}))
conflicts_results = dep_manager.verify_conflicts(module_data.get('conflicts', {}))
deps_message = dep_manager.format_results(deps_results)
conflicts_message = dep_manager.format_results(conflicts_results)
all_deps_ok = all(r['success'] for r in deps_results)
all_conflicts_ok = all(r['success'] for r in conflicts_results)
if all_deps_ok and all_conflicts_ok:
print("Deps: OK")
else:
dialog = TuneItDepsAlertDialog()
dialog.set_body(module_data['name'])
dialog.deps_message_textbuffer.set_text(
f"{deps_message}\n{conflicts_message}"
)
response = dialog.user_question(listbox.get_root())
print(f"RESPONSE: {response}")
if response == "skip":
break
module = Module(module_data) module = Module(module_data)
modules_dict[module.name] = module modules_dict[module.name] = module
......
using Gtk 4.0;
using Adw 1;
template $TuneItDepsAlertDialog: Adw.AlertDialog {
heading: _("The module has unmet dependencies");
responses [
close: _("Ignore") destructive,
skip: _("Skip module") suggested,
]
close-response: "close";
extra-child: Gtk.TextView{
wrap-mode: word_char;
buffer: Gtk.TextBuffer deps_message_textbuffer {};
};
}
from gi.repository import GObject, Adw, Gtk
from time import sleep
@Gtk.Template(resource_path='/ru.ximperlinux.TuneIt/settings/widgets/deps_alert_dialog.ui')
class TuneItDepsAlertDialog(Adw.AlertDialog):
__gtype_name__ = "TuneItDepsAlertDialog"
deps_message_textbuffer = Gtk.Template.Child()
response = ""
def user_question(self, window):
self.present(window)
def on_response(dialog, response):
self.response = response
self.connect('response', on_response)
while True:
if self.response != "":
return self.response
else:
sleep(0.1)
continue
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
<gresource prefix="/ru.ximperlinux.TuneIt"> <gresource prefix="/ru.ximperlinux.TuneIt">
<file preprocess="xml-stripblanks">window.ui</file> <file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">settings/widgets/panel_row.ui</file> <file preprocess="xml-stripblanks">settings/widgets/panel_row.ui</file>
<file preprocess="xml-stripblanks">settings/widgets/deps_alert_dialog.ui</file>
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file> <file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
</gresource> </gresource>
</gresources> </gresources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment