From b0eea9d62eb2c7833dd9ba2e8bc26f37eb5d920d Mon Sep 17 00:00:00 2001 From: tavo Date: Tue, 7 Oct 2025 22:58:33 -0600 Subject: [PATCH] updates --- gfd/__init__.py | 109 +------------------- gfd/app.py | 13 +++ gfd/core/installers.py | 98 ++++++++++++++++++ gfd/core/osinfo.py | 74 ++++++++++++++ gfd/core/routines.py | 52 ++++++++++ gfd/core/sfd.py | 116 +++++++++++++++++++++ gfd/i18n/en.py | 12 ++- gfd/i18n/es.py | 10 +- gfd/sfd.py | 68 ------------- gfd/ui/main_window.py | 222 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 591 insertions(+), 183 deletions(-) create mode 100644 gfd/app.py create mode 100644 gfd/core/installers.py create mode 100644 gfd/core/osinfo.py create mode 100644 gfd/core/routines.py create mode 100644 gfd/core/sfd.py delete mode 100644 gfd/sfd.py create mode 100644 gfd/ui/main_window.py diff --git a/gfd/__init__.py b/gfd/__init__.py index 0e0b578..adc7c6c 100644 --- a/gfd/__init__.py +++ b/gfd/__init__.py @@ -1,108 +1 @@ -from PySide6 import QtWidgets, QtCore -import gfd.i18n as i18n -from gfd import sfd - - -class GFDWidget(QtWidgets.QWidget): - def __init__(self, tr): - super().__init__() - self.tr = tr - self.setWindowTitle(self.tr("window_title")) - self.resize(400, 220) - - self.label = QtWidgets.QLabel( - self.tr("hello_world"), - alignment=QtCore.Qt.AlignmentFlag.AlignCenter, - ) - - self.combo = QtWidgets.QComboBox() - self.combo.addItem(self.tr("loading_installers") + "…") - - self.refresh_btn = QtWidgets.QPushButton("⟳ " + self.tr("refresh")) - self.refresh_btn.clicked.connect(self.reload_installers) - - combo_layout = QtWidgets.QHBoxLayout() - combo_layout.addWidget(self.combo) - combo_layout.addWidget(self.refresh_btn) - - self.md5_label = QtWidgets.QLabel( - "", alignment=QtCore.Qt.AlignmentFlag.AlignCenter - ) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(self.label) - layout.addLayout(combo_layout) - layout.addWidget(self.md5_label) - - self.combo.currentIndexChanged.connect(self.on_selection_changed) - - QtCore.QTimer.singleShot(0, self.load_installers_async) - - def reload_installers(self): - """Triggered by the refresh button.""" - self.refresh_btn.setEnabled(False) - self.combo.clear() - self.combo.addItem(self.tr("loading_installers") + "…") - self.load_installers_async() - - def load_installers_async(self): - """Fetch installer options without freezing the UI.""" - self.thread = QtCore.QThread() - self.worker = SFDWorker() - self.worker.moveToThread(self.thread) - - self.thread.started.connect(self.worker.run) - self.worker.finished.connect(self.thread.quit) - self.worker.finished.connect(self.worker.deleteLater) - self.thread.finished.connect(self.thread.deleteLater) - self.worker.result_ready.connect(self.populate_installers) - - self.thread.start() - - @QtCore.Slot(list) - def populate_installers(self, installers): - self.refresh_btn.setEnabled(True) - self.combo.clear() - - if not installers: - self.combo.addItem(self.tr("no_installers_found")) - self.md5_label.setText("") - return - - self.installers = installers - for item in installers: - self.combo.addItem(item["name"], item["md5"]) - - self.combo.setCurrentIndex(0) - self.on_selection_changed(0) - - @QtCore.Slot(int) - def on_selection_changed(self, index): - if not hasattr(self, "installers") or not self.installers: - return - md5 = self.combo.currentData() - name = self.combo.currentText() - self.md5_label.setText(f"{name}
MD5: {md5 or 'N/A'}") - - -class SFDWorker(QtCore.QObject): - """Background worker to fetch installers safely.""" - - finished = QtCore.Signal() - result_ready = QtCore.Signal(list) - - @QtCore.Slot() - def run(self): - data = sfd.fetchInstallerOptions() - self.result_ready.emit(data) - self.finished.emit() - - -def run(lang="es"): - import sys - - app = QtWidgets.QApplication(sys.argv) - tr = i18n.Translator(lang) - w = GFDWidget(tr) - w.show() - sys.exit(app.exec()) +from .app import run diff --git a/gfd/app.py b/gfd/app.py new file mode 100644 index 0000000..2c962b1 --- /dev/null +++ b/gfd/app.py @@ -0,0 +1,13 @@ +from PySide6 import QtWidgets +from gfd.ui.main_window import GFDWidget +from gfd.i18n import Translator + + +def run(lang="es"): + import sys + + app = QtWidgets.QApplication(sys.argv) + tr = Translator(lang) + window = GFDWidget(tr) + window.show() + sys.exit(app.exec()) diff --git a/gfd/core/installers.py b/gfd/core/installers.py new file mode 100644 index 0000000..2fc7d07 --- /dev/null +++ b/gfd/core/installers.py @@ -0,0 +1,98 @@ +import time +import re +import unicodedata +from gfd.core import sfd + +SUPPORTED_INSTALLERS = [ + { + "os_type": "ubuntu24", + "name": "Usuarios Linux - Ubuntu 24.04 LTS (DEB 64bits) - 78 MB", + "md5": "bdc871e15f2096f930b285f0ed799aa0", + }, + { + "os_type": "debian", + "name": "Usuarios Linux - Ubuntu 24.04 LTS (DEB 64bits) - 78 MB", + "md5": "bdc871e15f2096f930b285f0ed799aa0", + }, +] + + +def _normalize(t): + return re.sub(r"\s+", " ", unicodedata.normalize("NFKD", t).lower()).strip() + + +def _fetch(max_attempts=3, delay=2): + for i in range(max_attempts): + data = sfd.fetchInstallerOptions() + + if data: + return data + + if i < max_attempts - 1: + time.sleep(delay) + + return [] + + +def get_available_installers(os_type, installers=None): + """ + Return a list of confirmed available installers for the given OS type. + + If an installer is listed, it means that for a given OS type: + - There is a supported installation routine. + - There is a downloadable archive from Soporte Firma Digital. + """ + src = installers or SUPPORTED_INSTALLERS + locals_ = [i for i in src if i["os_type"] == os_type] + + if not locals_: + return [] + + remote = _fetch() + + if not remote: + return [] + + rmap = {_normalize(r["name"]): r.get("md5") for r in remote} + confirmed = [] + + for local in locals_: + lname, lmd5 = ( + _normalize(local["name"]), + (local.get("md5") or "").lower() or None, + ) + rmd5 = rmap.get(lname) + + if (lmd5 == rmd5) or (lmd5 is None and rmd5 is None): + confirmed.append((local["name"], lmd5)) + + return confirmed + + +# TODO: Routine to check for installed version +def get_installed_version(): + """ + Return the current installed version data, or empty if not installed. + """ + return [] # Return always empty for now + + +if __name__ == "__main__": + os_type = "debian" + print(f"=== TEST: {os_type.upper()} Installer Validation ===") + confirmed = get_available_installers(os_type) + print( + f"{len(confirmed)} confirmed installer(s):" + if confirmed + else "No confirmed installers found." + ) + + for n, m in confirmed: + print(f" - {n} (MD5={m or 'N/A'})") + + installed = get_installed_version() + + if not installed: + print("Status: NOT INSTALLED") + else: + print(f"Installed: {installed[0]} MD5={installed[1]}") diff --git a/gfd/core/osinfo.py b/gfd/core/osinfo.py new file mode 100644 index 0000000..2025b3b --- /dev/null +++ b/gfd/core/osinfo.py @@ -0,0 +1,74 @@ +import platform +import re +from pathlib import Path + + +def _read_os_release(): + """Read and parse /etc/os-release or /usr/lib/os-release if available.""" + paths = [Path("/etc/os-release"), Path("/usr/lib/os-release")] + data = {} + + for path in paths: + if path.exists(): + try: + for line in path.read_text(encoding="utf-8").splitlines(): + if "=" in line: + key, val = line.split("=", 1) + data[key.strip()] = val.strip().strip('"') + break + except Exception: + pass + return data + + +def get_os_type(): + """ + Detect and return a simple OS type string. + + Returns one of: + 'macos', 'windows', 'ubuntu24', 'ubuntu22', 'ubuntu20', + 'debian', 'arch', 'rpm', or None if unsupported. + """ + system = platform.system().lower() + os_type = None + + if system == "darwin": + os_type = "macos" + + elif system == "windows": + os_type = "windows" + + elif system == "linux": + info = _read_os_release() + id_name = info.get("ID", "").lower() + version_id = info.get("VERSION_ID", "") + + # Ubuntu + if id_name == "ubuntu": + if version_id.startswith("24"): + os_type = "ubuntu24" + elif version_id.startswith("22"): + os_type = "ubuntu22" + elif version_id.startswith("20"): + os_type = "ubuntu20" + + # Debian (11 or newer supported) + elif id_name == "debian": + try: + version_num = int(re.findall(r"\d+", version_id or "0")[0]) + except IndexError: + version_num = 0 + if version_num >= 11: + os_type = "debian" + + # Arch / RPM-based + elif "arch" in id_name: + os_type = "arch" + elif id_name in ["fedora", "rhel", "centos", "rocky", "alma", "opensuse"]: + os_type = "rpm" + + return os_type + + +if __name__ == "__main__": + print("Detected OS type:", get_os_type() or "NOT SUPPORTED") diff --git a/gfd/core/routines.py b/gfd/core/routines.py new file mode 100644 index 0000000..21a9342 --- /dev/null +++ b/gfd/core/routines.py @@ -0,0 +1,52 @@ +""" +routines.py - OS-specific installer routines + +Defines a registry of install routines by OS type. +If the current OS is supported and has an associated installer, +the matching routine can be executed to perform installation steps. +""" + +from gfd.core.osinfo import get_os_type + + +def install_ubuntu24(): + """Simulated install routine for Ubuntu 24.04.""" + print("Running Ubuntu 24.04 (DEB) installation routine...") + + +def install_debian(): + """Simulated install routine for Debian.""" + print("Running Debian installation routine (using Ubuntu 24 package)...") + + +SUPPORTED_ROUTINES = { + "ubuntu24": install_ubuntu24, + "debian": install_debian, +} + + +def run_install_routine(os_type=None): + """ + Run the appropriate install routine for the given OS type. + + Args: + os_type (str | None): OS key (e.g. 'ubuntu24', 'macos'). + If None, automatically detects current OS. + + Returns: + bool: True if the OS is supported and a routine was executed, else False. + """ + os_type = os_type or get_os_type() + if not os_type: + return False + + routine = SUPPORTED_ROUTINES.get(os_type) + if not routine: + return False + + routine() + return True + + +if __name__ == "__main__": + run_install_routine() diff --git a/gfd/core/sfd.py b/gfd/core/sfd.py new file mode 100644 index 0000000..5f6aa36 --- /dev/null +++ b/gfd/core/sfd.py @@ -0,0 +1,116 @@ +""" +sfd.py - Installer List Parser for Soporte Firma Digital + +This module is responsible for fetching and parsing the list of available +digital signature installers from the official Soporte Firma Digital website. + +It retrieves both the visible installer names (from the page’s