"""Базовые структуры данных SysDevCleaner.""" from __future__ import annotations import os import time from dataclasses import dataclass, field from enum import IntEnum from typing import Iterable class Risk(IntEnum): """Насколько опасно удалять находку.""" SAFE = 0 # мусор в чистом виде, пересоздаётся сам LOW = 1 # кеш: удаление вызовет лишь замедление первого запуска MEDIUM = 2 # потеря состояния/настроек/сессий, нужна пересборка HIGH = 3 # можно сломать проект или систему — только вручную @property def label(self) -> str: return { Risk.SAFE: "Безопасно", Risk.LOW: "Низкий риск", Risk.MEDIUM: "Средний риск", Risk.HIGH: "Высокий риск", }[self] @property def color(self) -> str: return { Risk.SAFE: "#2e9e5b", Risk.LOW: "#4a90d9", Risk.MEDIUM: "#d99a2b", Risk.HIGH: "#d9534f", }[self] class Category(IntEnum): WINDOWS = 0 BROWSERS = 1 DEV_CACHE = 2 DEV_PROJECT = 3 APPS = 4 LOGS = 5 PACKAGE_MANAGERS = 6 BIG_FILES = 7 DUPLICATES = 8 PROGRAMS = 9 @property def label(self) -> str: return { Category.WINDOWS: "Система Windows", Category.BROWSERS: "Браузеры", Category.DEV_CACHE: "Кеши разработки", Category.DEV_PROJECT: "Артефакты проектов", Category.APPS: "Приложения", Category.LOGS: "Логи и отчёты", Category.PACKAGE_MANAGERS: "Пакетные менеджеры", Category.BIG_FILES: "Крупные файлы", Category.DUPLICATES: "Дубликаты", Category.PROGRAMS: "Установленные программы", }[self] @property def icon(self) -> str: return { Category.WINDOWS: "🪟", Category.BROWSERS: "🌐", Category.DEV_CACHE: "⚙", Category.DEV_PROJECT: "📦", Category.APPS: "🎮", Category.LOGS: "📄", Category.PACKAGE_MANAGERS: "🧰", Category.BIG_FILES: "🐘", Category.DUPLICATES: "👯", Category.PROGRAMS: "🗂", }[self] @dataclass class Finding: """Одна находка: путь (или группа путей), который можно удалить.""" path: str title: str # человекочитаемое имя category: Category risk: Risk size: int = 0 # байты files: int = 0 dirs: int = 0 mtime: float = 0.0 # последнее изменение (unix) detail: str = "" # подсказка что это и чем грозит удаление group: str = "" # имя правила/группы для сводки is_dir: bool = True delete_contents_only: bool = False # чистим содержимое, сам каталог оставляем members: list[str] = field(default_factory=list) # для дубликатов/мульти-путей selected: bool = False extra: dict = field(default_factory=dict) @property def age_days(self) -> float: if not self.mtime: return 0.0 return max(0.0, (time.time() - self.mtime) / 86400.0) @property def key(self) -> str: return os.path.normcase(os.path.abspath(self.path)) @dataclass class Program: """Установленная программа из реестра.""" name: str publisher: str = "" version: str = "" install_date: str = "" install_location: str = "" uninstall_cmd: str = "" registry_size: int = 0 # EstimatedSize из реестра disk_size: int = 0 # реально посчитанный размер last_used: float = 0.0 # mtime самого свежего файла source: str = "" # HKLM / HKCU / WOW64 @property def best_size(self) -> int: return self.disk_size or self.registry_size @property def idle_days(self) -> float: if not self.last_used: return -1.0 return max(0.0, (time.time() - self.last_used) / 86400.0) @dataclass class Recommendation: title: str body: str gain: int = 0 # сколько освободится, байт severity: int = 1 # 0 инфо, 1 совет, 2 важно findings: list[Finding] = field(default_factory=list) @dataclass class ScanResult: findings: list[Finding] = field(default_factory=list) programs: list[Program] = field(default_factory=list) recommendations: list[Recommendation] = field(default_factory=list) disks: list[dict] = field(default_factory=list) errors: list[str] = field(default_factory=list) started: float = 0.0 finished: float = 0.0 scanned_paths: int = 0 @property def duration(self) -> float: return max(0.0, (self.finished or time.time()) - self.started) def total(self, cats: Iterable[Category] | None = None) -> int: cs = set(cats) if cats else None return sum(f.size for f in self.findings if cs is None or f.category in cs) def by_category(self) -> dict[Category, list[Finding]]: out: dict[Category, list[Finding]] = {} for f in self.findings: out.setdefault(f.category, []).append(f) for lst in out.values(): lst.sort(key=lambda x: -x.size) return out