minimal
This commit is contained in:
parent
b0eea9d62e
commit
b44bcd4810
12 changed files with 44 additions and 606 deletions
|
|
@ -1 +0,0 @@
|
||||||
from .app import run
|
|
||||||
13
gfd/app.py
13
gfd/app.py
|
|
@ -1,13 +0,0 @@
|
||||||
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())
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
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]}")
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
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")
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
"""
|
|
||||||
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()
|
|
||||||
116
gfd/core/sfd.py
116
gfd/core/sfd.py
|
|
@ -1,116 +0,0 @@
|
||||||
"""
|
|
||||||
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 <select> element)
|
|
||||||
and the associated MD5 hashes (embedded within inline JavaScript).
|
|
||||||
|
|
||||||
The parsed results are used by the UI layer to display which installers
|
|
||||||
are available and to match system OS types to the appropriate installer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import unicodedata
|
|
||||||
import requests
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
SFD_URL = "https://soportefirmadigital.com/sfdj/dl.aspx"
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_text(text: str) -> str:
|
|
||||||
"""
|
|
||||||
Normalize text for consistent matching.
|
|
||||||
|
|
||||||
Steps:
|
|
||||||
1. Remove accents and diacritics using Unicode normalization.
|
|
||||||
2. Replace consecutive spaces with a single space.
|
|
||||||
3. Convert to lowercase and strip leading/trailing whitespace.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str): Raw string to normalize.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Cleaned, normalized string suitable for fuzzy matching.
|
|
||||||
"""
|
|
||||||
text = unicodedata.normalize("NFKD", text)
|
|
||||||
text = "".join(c for c in text if not unicodedata.combining(c))
|
|
||||||
return re.sub(r"\s+", " ", text).strip().lower()
|
|
||||||
|
|
||||||
|
|
||||||
def fetchInstallerOptions(url: str = SFD_URL):
|
|
||||||
"""
|
|
||||||
Fetch and parse the installer list from the Soporte Firma Digital website.
|
|
||||||
|
|
||||||
Example output:
|
|
||||||
[
|
|
||||||
{"name": "Usuarios Linux - Ubuntu 24.04 LTS (DEB 64bits) - 78 MB",
|
|
||||||
"md5": "bdc871e15f2096f930b285f0ed799aa0"},
|
|
||||||
{"name": "Usuarios Windows JCOP3 - 189 MB",
|
|
||||||
"md5": "596c347e409d00388c3c1ee0a72b96c0"},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url (str, optional): Target URL to fetch. Defaults to SFD_URL.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[dict]: Each dict contains:
|
|
||||||
- "name" (str): Installer display name.
|
|
||||||
- "md5" (str | None): MD5 checksum (if found), else None.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
r = requests.get(url, timeout=8)
|
|
||||||
r.raise_for_status()
|
|
||||||
except requests.RequestException:
|
|
||||||
# Network error, timeout, or unreachable server
|
|
||||||
return []
|
|
||||||
|
|
||||||
soup = BeautifulSoup(r.text, "html.parser")
|
|
||||||
|
|
||||||
# Extract installer names from the dropdown list
|
|
||||||
installers = [
|
|
||||||
opt.text.strip()
|
|
||||||
for opt in soup.select("#ctl00_certContents_ddlInstaladores option")
|
|
||||||
]
|
|
||||||
if not installers:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Parse embedded JavaScript to extract name, MD5 mapping
|
|
||||||
md5s = {}
|
|
||||||
for name, md5 in re.findall(
|
|
||||||
r"text\s*==\s*'([^']+)'.*?MD5\s*[:=]\s*([A-Za-z0-9]+)",
|
|
||||||
r.text,
|
|
||||||
re.DOTALL,
|
|
||||||
):
|
|
||||||
md5 = md5.lower()
|
|
||||||
if re.fullmatch(r"[0-9a-f]{32}", md5): # Only valid 32-character MD5
|
|
||||||
md5s[_normalize_text(name)] = md5
|
|
||||||
|
|
||||||
# Merge name list with MD5 map
|
|
||||||
results = []
|
|
||||||
for inst in installers:
|
|
||||||
n = _normalize_text(inst)
|
|
||||||
md5 = md5s.get(n)
|
|
||||||
|
|
||||||
# Try fuzzy match if exact normalization key not found
|
|
||||||
if not md5:
|
|
||||||
for key, val in md5s.items():
|
|
||||||
if key in n or n in key:
|
|
||||||
md5 = val
|
|
||||||
break
|
|
||||||
|
|
||||||
results.append({"name": inst, "md5": md5})
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
"""
|
|
||||||
Fetch and print all installers to stdout.
|
|
||||||
"""
|
|
||||||
installers = fetchInstallerOptions()
|
|
||||||
print(f"Fetched {len(installers)} installers from {SFD_URL}\n")
|
|
||||||
for item in installers:
|
|
||||||
print(f"{item['name']:<70} MD5={item['md5'] or 'N/A'}")
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
translations = {
|
translations = {
|
||||||
"window_title": "Digital Signature Manager",
|
"window_title": "Digital Signature Manager",
|
||||||
"no_install_message": "Digital signature not installed",
|
|
||||||
"recommended_version": "Recommended version:",
|
|
||||||
"install": "Install",
|
|
||||||
"installed_version": "Installed version:",
|
|
||||||
"latest_version_installed": "Latest version installed",
|
|
||||||
"update_digital_signature": "Update Digital Signature",
|
|
||||||
"no_installers_found": "Could not fetch the installer list",
|
|
||||||
"loading_installers": "Loading installers",
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
translations = {
|
translations = {
|
||||||
"window_title": "Gestor de Firma Digital",
|
"window_title": "Gestor de Firma Digital",
|
||||||
"no_install_message": "No tiene la firma digital instalada",
|
|
||||||
"recommended_version": "Versión recomendada:",
|
|
||||||
"install": "Instalar",
|
|
||||||
"installed_version": "Versión instalada:",
|
|
||||||
"latest_version_installed": "Última versión instalada",
|
|
||||||
"update_digital_signature": "Actualizar Firma Digital",
|
|
||||||
"no_installers_found": "No se pudo obtener la lista de instaladores", # ES
|
|
||||||
"loading_installers": "Cargando instaladores",
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
gfd/sfd.py
Normal file
44
gfd/sfd.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import requests as req
|
||||||
|
from bs4 import BeautifulSoup as bs
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
SFD_INSTALLERS_URL = "https://soportefirmadigital.com/sfdj/dl.aspx"
|
||||||
|
SFD_INSTALLERS_SELECT_ID = "#ctl00_certContents_ddlInstaladores"
|
||||||
|
|
||||||
|
|
||||||
|
def get_installers(
|
||||||
|
url: str = SFD_INSTALLERS_URL,
|
||||||
|
select: str = SFD_INSTALLERS_SELECT_ID,
|
||||||
|
timeout: int = 10,
|
||||||
|
) -> dict[str, str]:
|
||||||
|
try:
|
||||||
|
r = req.get(url, timeout=timeout)
|
||||||
|
r.raise_for_status()
|
||||||
|
except req.exceptions.RequestException as e:
|
||||||
|
raise RuntimeError(f"Error fetching installer list: {e}")
|
||||||
|
|
||||||
|
html = bs(r.text, "html.parser")
|
||||||
|
|
||||||
|
opts = html.select(f"{select} option")
|
||||||
|
|
||||||
|
if not opts:
|
||||||
|
raise RuntimeError("Empty options")
|
||||||
|
|
||||||
|
return {
|
||||||
|
cast(str, key): value
|
||||||
|
for opt in opts
|
||||||
|
if (key := opt.get("value")) and (value := opt.text.strip())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import json
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = get_installers()
|
||||||
|
|
||||||
|
if data:
|
||||||
|
print(json.dumps(data, indent=2, ensure_ascii=False), flush=True)
|
||||||
|
|
||||||
|
except RuntimeError as e:
|
||||||
|
raise SystemExit(e)
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
from PySide6 import QtWidgets, QtCore
|
|
||||||
from gfd.core.osinfo import get_os_type
|
|
||||||
from gfd.core.installers import get_available_installers, get_installed_version
|
|
||||||
|
|
||||||
|
|
||||||
def check_installation_status():
|
|
||||||
"""
|
|
||||||
Determine system support and installer availability.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(installed: [version, md5] or [],
|
|
||||||
available: list[[version, md5]],
|
|
||||||
recommended: [version, md5] or None)
|
|
||||||
"""
|
|
||||||
os_type = get_os_type()
|
|
||||||
|
|
||||||
if not os_type:
|
|
||||||
return [], [], None
|
|
||||||
|
|
||||||
available = get_available_installers(os_type)
|
|
||||||
installed = get_installed_version()
|
|
||||||
|
|
||||||
if not available:
|
|
||||||
return installed, [], None
|
|
||||||
|
|
||||||
# Assume the first installer in the list is the recommended one
|
|
||||||
recommended = available[0]
|
|
||||||
|
|
||||||
return installed, available, recommended
|
|
||||||
|
|
||||||
|
|
||||||
class GFDWidget(QtWidgets.QWidget):
|
|
||||||
def __init__(self, tr):
|
|
||||||
super().__init__()
|
|
||||||
self.tr = tr
|
|
||||||
self.setWindowTitle(self.tr("window_title"))
|
|
||||||
self.resize(600, 400)
|
|
||||||
|
|
||||||
self.main_layout = QtWidgets.QVBoxLayout(self)
|
|
||||||
self.main_layout.setContentsMargins(20, 20, 20, 20)
|
|
||||||
|
|
||||||
# Card container
|
|
||||||
self.card = QtWidgets.QFrame()
|
|
||||||
self.card_layout = QtWidgets.QVBoxLayout(self.card)
|
|
||||||
self.card_layout.setSpacing(12)
|
|
||||||
|
|
||||||
# Title
|
|
||||||
self.title = QtWidgets.QLabel(self.tr("window_title"))
|
|
||||||
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
||||||
self.title.setStyleSheet("font-weight: bold; font-size: 16px;")
|
|
||||||
self.card_layout.addWidget(self.title)
|
|
||||||
|
|
||||||
# Content area
|
|
||||||
self.content_area = QtWidgets.QWidget()
|
|
||||||
self.content_layout = QtWidgets.QVBoxLayout(self.content_area)
|
|
||||||
self.content_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
||||||
self.card_layout.addWidget(self.content_area)
|
|
||||||
|
|
||||||
# Footer
|
|
||||||
self.footer = QtWidgets.QHBoxLayout()
|
|
||||||
self.footer.addStretch()
|
|
||||||
self.refresh_btn = QtWidgets.QPushButton("⟳")
|
|
||||||
self.refresh_btn.setFixedWidth(36)
|
|
||||||
self.refresh_btn.clicked.connect(self.refresh_state)
|
|
||||||
self.footer.addWidget(self.refresh_btn)
|
|
||||||
self.card_layout.addLayout(self.footer)
|
|
||||||
|
|
||||||
self.main_layout.addWidget(self.card)
|
|
||||||
self.setStyleSheet("""
|
|
||||||
QPushButton {
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
QPushButton[primary="true"] {
|
|
||||||
background-color: #2f80ed;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
QPushButton[primary="true"]:disabled {
|
|
||||||
background-color: #a0c4ff;
|
|
||||||
}
|
|
||||||
QLabel, QComboBox {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
self.states = ["not_installed", "installed", "update_available"]
|
|
||||||
self.current_state = 0
|
|
||||||
|
|
||||||
# Run initial refresh
|
|
||||||
self.refresh_state()
|
|
||||||
|
|
||||||
# --- LOGIC ---
|
|
||||||
|
|
||||||
def refresh_state(self):
|
|
||||||
"""Fetch installer list and determine status."""
|
|
||||||
self.refresh_btn.setEnabled(False)
|
|
||||||
self.clear_content()
|
|
||||||
loading_label = QtWidgets.QLabel(self.tr("loading_installers") + "…")
|
|
||||||
loading_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
||||||
self.content_layout.addWidget(loading_label)
|
|
||||||
|
|
||||||
QtCore.QTimer.singleShot(150, self._refresh_check)
|
|
||||||
|
|
||||||
def _refresh_check(self):
|
|
||||||
"""Evaluate OS type, installed version, and available installers."""
|
|
||||||
self.installed, self.available, self.recommended = check_installation_status()
|
|
||||||
|
|
||||||
print("\n=== INSTALLER STATUS DEBUG ===")
|
|
||||||
if not self.available:
|
|
||||||
print("No available installers for this OS.")
|
|
||||||
else:
|
|
||||||
for i, (name, md5) in enumerate(self.available, start=1):
|
|
||||||
print(f"{i:2d}. {name:<70} MD5={md5 or 'N/A'}")
|
|
||||||
print("==============================\n")
|
|
||||||
|
|
||||||
# 🧩 Print simulated installed state
|
|
||||||
if not self.installed:
|
|
||||||
print("→ Status: NOT INSTALLED")
|
|
||||||
else:
|
|
||||||
print(f"→ Installed: {self.installed[0]} MD5={self.installed[1]}")
|
|
||||||
|
|
||||||
if not self.available:
|
|
||||||
self.show_error()
|
|
||||||
elif not self.recommended:
|
|
||||||
print("→ Decision: OS NOT SUPPORTED")
|
|
||||||
self.show_error()
|
|
||||||
elif not self.installed:
|
|
||||||
print(f"→ Decision: NOT INSTALLED (Recommended: {self.recommended[0]})")
|
|
||||||
self.set_state("not_installed")
|
|
||||||
else:
|
|
||||||
installed_tuple = tuple(self.installed)
|
|
||||||
available_tuples = [tuple(a) for a in self.available]
|
|
||||||
|
|
||||||
if installed_tuple in available_tuples:
|
|
||||||
print("→ Decision: INSTALLED (latest version matches)")
|
|
||||||
self.set_state("installed")
|
|
||||||
else:
|
|
||||||
print("→ Decision: UPDATE AVAILABLE (installed not in available list)")
|
|
||||||
self.set_state("update_available")
|
|
||||||
|
|
||||||
self.refresh_btn.setEnabled(True)
|
|
||||||
|
|
||||||
def clear_content(self):
|
|
||||||
"""Remove all widgets from content area."""
|
|
||||||
for i in reversed(range(self.content_layout.count())):
|
|
||||||
widget = self.content_layout.itemAt(i).widget()
|
|
||||||
if widget:
|
|
||||||
widget.setParent(None)
|
|
||||||
|
|
||||||
# --- UI STATES ---
|
|
||||||
|
|
||||||
def update_state(self):
|
|
||||||
"""Render the current UI state."""
|
|
||||||
self.clear_content()
|
|
||||||
state = self.states[self.current_state]
|
|
||||||
|
|
||||||
if state == "not_installed":
|
|
||||||
self.show_not_installed()
|
|
||||||
elif state == "installed":
|
|
||||||
self.show_installed()
|
|
||||||
elif state == "update_available":
|
|
||||||
self.show_update_available()
|
|
||||||
|
|
||||||
def show_error(self):
|
|
||||||
"""Shown when no installer data is available."""
|
|
||||||
self.clear_content()
|
|
||||||
lbl = QtWidgets.QLabel(self.tr("no_installers_found"))
|
|
||||||
lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
||||||
self.content_layout.addWidget(lbl)
|
|
||||||
|
|
||||||
def show_not_installed(self):
|
|
||||||
recommended_name = self.recommended[0] if self.recommended else "?"
|
|
||||||
lbl = QtWidgets.QLabel(
|
|
||||||
f"{self.tr('no_install_message')}<br>"
|
|
||||||
f"{self.tr('recommended_version')}<br><b>{recommended_name}</b>"
|
|
||||||
)
|
|
||||||
lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
||||||
self.content_layout.addWidget(lbl)
|
|
||||||
|
|
||||||
btn = QtWidgets.QPushButton(self.tr("install"))
|
|
||||||
btn.setProperty("primary", True)
|
|
||||||
self.content_layout.addWidget(btn)
|
|
||||||
|
|
||||||
combo = QtWidgets.QComboBox()
|
|
||||||
for version, md5 in self.available:
|
|
||||||
combo.addItem(f"{version} (MD5: {md5 or 'N/A'})")
|
|
||||||
self.content_layout.addWidget(combo)
|
|
||||||
|
|
||||||
def show_installed(self):
|
|
||||||
version, md5 = self.installed
|
|
||||||
lbl = QtWidgets.QLabel(
|
|
||||||
f"{self.tr('installed_version')}<br><b>{version}</b><br>MD5: {md5 or 'N/A'}"
|
|
||||||
)
|
|
||||||
lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
||||||
self.content_layout.addWidget(lbl)
|
|
||||||
|
|
||||||
btn = QtWidgets.QPushButton(self.tr("latest_version_installed"))
|
|
||||||
btn.setEnabled(False)
|
|
||||||
self.content_layout.addWidget(btn)
|
|
||||||
|
|
||||||
def show_update_available(self):
|
|
||||||
version, md5 = self.installed
|
|
||||||
lbl = QtWidgets.QLabel(
|
|
||||||
f"{self.tr('installed_version')}<br><b>{version}</b><br>MD5: {md5 or 'N/A'}"
|
|
||||||
)
|
|
||||||
lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
||||||
self.content_layout.addWidget(lbl)
|
|
||||||
|
|
||||||
btn = QtWidgets.QPushButton(self.tr("update_digital_signature"))
|
|
||||||
btn.setProperty("primary", True)
|
|
||||||
self.content_layout.addWidget(btn)
|
|
||||||
|
|
||||||
combo = QtWidgets.QComboBox()
|
|
||||||
for version, md5 in self.available:
|
|
||||||
combo.addItem(f"{version} (MD5: {md5 or 'N/A'})")
|
|
||||||
self.content_layout.addWidget(combo)
|
|
||||||
|
|
||||||
def set_state(self, state):
|
|
||||||
if state in self.states:
|
|
||||||
self.current_state = self.states.index(state)
|
|
||||||
self.update_state()
|
|
||||||
4
main.py
4
main.py
|
|
@ -1,4 +0,0 @@
|
||||||
import gfd
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
gfd.run("es")
|
|
||||||
|
|
@ -1,12 +1,2 @@
|
||||||
beautifulsoup4==4.14.2
|
beautifulsoup4==4.14.2
|
||||||
certifi==2025.10.5
|
|
||||||
charset-normalizer==3.4.3
|
|
||||||
idna==3.10
|
|
||||||
PySide6==6.9.2
|
|
||||||
PySide6_Addons==6.9.2
|
|
||||||
PySide6_Essentials==6.9.2
|
|
||||||
requests==2.32.5
|
requests==2.32.5
|
||||||
shiboken6==6.9.2
|
|
||||||
soupsieve==2.8
|
|
||||||
typing_extensions==4.15.0
|
|
||||||
urllib3==2.5.0
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue