138 lines
4.6 KiB
Python
138 lines
4.6 KiB
Python
import json
|
||
import traceback
|
||
from abc import ABC, abstractmethod
|
||
from pathlib import Path
|
||
from typing import Any, Dict, Literal
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, field_validator
|
||
from rich.console import Console
|
||
|
||
from utils.tools import CONFIGS
|
||
|
||
|
||
class ToolsConfig(BaseModel):
|
||
apktool_jar_url: str
|
||
apktool_wrapper_url: str
|
||
|
||
@field_validator("apktool_jar_url", "apktool_wrapper_url")
|
||
@classmethod
|
||
def validate_url(cls, v: str) -> str:
|
||
if not v.startswith(("http://", "https://")):
|
||
raise ValueError("URL должен начинаться с http:// или https://")
|
||
return v
|
||
|
||
|
||
class SigningConfig(BaseModel):
|
||
keystore: Path = Field(default=Path("keystore.jks"))
|
||
keystore_pass_file: Path = Field(default=Path("keystore.pass"))
|
||
v1_signing: bool = False
|
||
v2_signing: bool = True
|
||
v3_signing: bool = True
|
||
|
||
|
||
class BuildConfig(BaseModel):
|
||
verbose: bool = False
|
||
force: bool = False
|
||
clean_after_build: bool = True
|
||
|
||
|
||
class Config(BaseModel):
|
||
tools: ToolsConfig
|
||
signing: SigningConfig = Field(default_factory=SigningConfig)
|
||
build: BuildConfig = Field(default_factory=BuildConfig)
|
||
base: Dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
def load_config(console: Console) -> Config:
|
||
"""Загружает и валидирует конфигурацию"""
|
||
config_path = Path("config.json")
|
||
|
||
if not config_path.exists():
|
||
console.print("[red]Файл config.json не найден")
|
||
raise typer.Exit(1)
|
||
|
||
try:
|
||
return Config.model_validate_json(config_path.read_text())
|
||
except ValidationError as e:
|
||
console.print(f"[red]Ошибка валидации config.json:\n{e}")
|
||
raise typer.Exit(1)
|
||
|
||
|
||
class PatchTemplate(BaseModel, ABC):
|
||
model_config = ConfigDict(arbitrary_types_allowed=True, validate_default=True)
|
||
|
||
enabled: bool = Field(default=True, description="Включить или отключить патч")
|
||
priority: int = Field(default=0, description="Приоритет применения патча")
|
||
|
||
_name: str = PrivateAttr()
|
||
_applied: bool = PrivateAttr(default=False)
|
||
_console: Console | None = PrivateAttr(default=None)
|
||
|
||
def __init__(self, name: str, console: Console, **data):
|
||
loaded_data = self._load_config_static(name, console)
|
||
|
||
merged_data = {**loaded_data, **data}
|
||
|
||
valid_fields = set(self.model_fields.keys())
|
||
filtered_data = {k: v for k, v in merged_data.items() if k in valid_fields}
|
||
|
||
super().__init__(**filtered_data)
|
||
self._name = name
|
||
self._console = console
|
||
self._applied = False
|
||
|
||
@staticmethod
|
||
def _load_config_static(name: str, console: Console | None) -> Dict[str, Any]:
|
||
"""Загружает конфигурацию из файла (статический метод)"""
|
||
config_path = CONFIGS / f"{name}.json"
|
||
try:
|
||
if config_path.exists():
|
||
return json.loads(config_path.read_text())
|
||
except Exception as e:
|
||
if console:
|
||
console.print(
|
||
f"[red]Ошибка при загрузке конфигурации патча {name}: {e}"
|
||
)
|
||
console.print(f"[yellow]Используются значения по умолчанию")
|
||
return {}
|
||
|
||
def save_config(self) -> None:
|
||
"""Сохраняет конфигурацию в файл"""
|
||
config_path = CONFIGS / f"{self._name}.json"
|
||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||
config_path.write_text(self.model_dump_json(indent=2))
|
||
|
||
@property
|
||
def name(self) -> str:
|
||
return self._name
|
||
|
||
@property
|
||
def applied(self) -> bool:
|
||
return self._applied
|
||
|
||
@applied.setter
|
||
def applied(self, value: bool) -> None:
|
||
self._applied = value
|
||
|
||
@property
|
||
def console(self) -> Console | None:
|
||
return self._console
|
||
|
||
@abstractmethod
|
||
def apply(self, base: Dict[str, Any]) -> Any:
|
||
raise NotImplementedError(
|
||
"Попытка применения шаблона патча, а не его реализации"
|
||
)
|
||
|
||
def safe_apply(self, base: Dict[str, Any]) -> bool:
|
||
"""Безопасно применяет патч с обработкой ошибок"""
|
||
try:
|
||
self._applied = self.apply(base)
|
||
return self._applied
|
||
except Exception as e:
|
||
if self._console:
|
||
self._console.print(f"[red]Ошибка в патче {self._name}: {e}")
|
||
if base.get("verbose"):
|
||
self._console.print_exception()
|
||
return False
|