Files
musicdownload/core/config.py
T
luzadev c2599a72a1 v1.7.7: fix avvio Windows definitivo (rimuove Mark of the Web)
La causa root del "Failed to resolve Python.Runtime.Loader.Initialize"
era il Mark of the Web (Zone.Identifier ADS) che Windows aggiunge ai
file estratti da uno zip scaricato dal browser. .NET Framework
rifiuta SILENZIOSAMENTE di risolvere i symbol export degli assembly
managed con MOTW.

Fix multilivello:
- main.py: al primo avvio cancella :Zone.Identifier da ogni
  .dll/.exe/.pyd nel bundle (via ctypes kernel32.DeleteFileW)
- CI: Unblock-File su dist/ pre-zip
- requirements: pythonnet==3.0.5 + clr-loader==0.2.7.post0 pinnati

Vedi: https://github.com/r0x0r/pywebview/issues/1215
      https://github.com/pyinstaller/pyinstaller/issues/7412
2026-06-09 12:37:48 +02:00

109 lines
3.8 KiB
Python

"""Gestione configurazione persistente (JSON)."""
import json
import os
import sys
from pathlib import Path
VERSION = "v1.7.7"
APP_NAME = "MusicTools"
_LEGACY_NAME = "MusicDownload"
# Endpoint dell'API di licenza/aggiornamenti. Cambialo qui per puntare
# a un ambiente di staging.
LICENSE_API_URL = "https://musictools.djluza.com"
# Quanti giorni puo' restare l'app offline prima di richiedere una
# nuova validazione contro il server.
LICENSE_GRACE_DAYS = 14
# Ogni quanti giorni l'app rivalida la licenza in background quando online.
LICENSE_REVALIDATE_DAYS = 7
def _get_config_dir() -> Path:
"""Ritorna la directory per config.json.
Frozen (bundle PyInstaller):
- macOS: ~/Library/Application Support/MusicTools/
- Windows: %APPDATA%/MusicTools/
Dev (non frozen):
- directory del progetto
Se la cartella nuova non esiste ma quella legacy 'MusicDownload' si',
viene migrata (copia di config.json) cosi' l'utente non perde le
impostazioni dopo il rename.
"""
if getattr(sys, "frozen", False):
if sys.platform == "win32":
base = Path(os.environ.get("APPDATA", Path.home()))
else:
base = Path.home() / "Library" / "Application Support"
config_dir = base / APP_NAME
legacy_dir = base / _LEGACY_NAME
# Migrazione one-shot: se non esiste la nuova ma esiste la vecchia
if not config_dir.exists() and legacy_dir.exists():
try:
config_dir.mkdir(parents=True, exist_ok=True)
legacy_cfg = legacy_dir / "config.json"
if legacy_cfg.exists():
(config_dir / "config.json").write_bytes(legacy_cfg.read_bytes())
except OSError:
pass
config_dir.mkdir(parents=True, exist_ok=True)
return config_dir
return Path(__file__).resolve().parent.parent
CONFIG_PATH = _get_config_dir() / "config.json"
# Default: i path relativi al progetto (se non frozen)
_project_dir = Path(__file__).resolve().parent.parent
DEFAULTS = {
"client_id": "",
"client_secret": "",
"bitrate": "320K",
"hq_threshold": 310,
"cookies_path": str(_project_dir / "cookies.txt"),
"output_dir": str(_project_dir / "MUSICA"),
"theme": "dark",
# ---- Licenza ----
"license_key": "", # chiave fornita all'utente via email
"license_email": "", # email associata all'acquisto
"license_token": "", # JWT firmato dal server (claims offline)
"license_activated_at": 0, # epoch della prima attivazione
"last_validated_at": 0, # epoch dell'ultima revalidate online riuscita
"device_id": "", # UUID generato al primo avvio
# ---- Piano (snapshot dei claims JWT) ----
"plan_code": "", # "basic" | "pro" | "premium" | "annual"
"plan_name": "", # nome user-facing
"plan_features": [], # ["audio", "video", "record", "metadata", "upgrade"]
"plan_daily_limit": None, # None = unlimited (annual), altrimenti int
"plan_is_subscription": False,
"plan_expires_at": 0, # solo per annual one-time
"plan_period_end": 0, # solo per subscription mensili
}
def load_config() -> dict:
"""Carica la configurazione da config.json, con fallback ai default."""
config = dict(DEFAULTS)
if CONFIG_PATH.exists():
try:
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
stored = json.load(f)
config.update(stored)
except (json.JSONDecodeError, OSError):
pass
return config
def save_config(config: dict) -> None:
"""Salva la configurazione su config.json."""
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2, ensure_ascii=False)