268 lines
11 KiB
Python
268 lines
11 KiB
Python
"""
|
||
Главный модуль менеджера пакетов.
|
||
|
||
Содержит класс PackageManager, который управляет:
|
||
- установкой пакетов (с зависимостями)
|
||
- удалением пакетов
|
||
- получением информации о пакетах
|
||
- поиском пакетов в репозитории
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import shutil
|
||
from typing import List
|
||
|
||
from .dependency_resolver import DependencyResolver
|
||
from ..models.package import Package
|
||
from ..database.db_manager import DatabaseManager
|
||
from ..utils.colors import (
|
||
print_success, print_error, print_warning,
|
||
print_info, print_package, print_step, print_sandbox_mode
|
||
)
|
||
|
||
|
||
class PackageManager:
|
||
"""
|
||
Главный класс менеджера пакетов.
|
||
|
||
Координирует работу всех компонентов:
|
||
- DatabaseManager — для хранения установленных пакетов
|
||
- DependencyResolver — для разрешения зависимостей
|
||
|
||
Позволяет устанавливать, удалять, искать пакеты и получать о них информацию.
|
||
"""
|
||
|
||
def __init__(self, repo_path: str = "repo", sandbox: str = None):
|
||
"""
|
||
Инициализация менеджера пакетов.
|
||
|
||
Args:
|
||
repo_path: путь к папке с репозиторием (по умолчанию "repo")
|
||
sandbox: путь к песочнице (если указан — установка в эту папку,
|
||
иначе в реальную систему "/")
|
||
"""
|
||
self.repo_path = repo_path
|
||
|
||
# Настройка режима песочницы
|
||
if sandbox:
|
||
self.root = sandbox # корень для установки
|
||
self.sandbox_mode = True
|
||
print_sandbox_mode(self.root)
|
||
else:
|
||
self.root = "/" # реальная файловая система
|
||
self.sandbox_mode = False
|
||
|
||
# Создаём компоненты менеджера
|
||
self.db = DatabaseManager(self.root)
|
||
self.resolver = DependencyResolver(repo_path, self.db.installed)
|
||
|
||
def install(self, package_name: str, dry_run: bool = False) -> bool:
|
||
"""
|
||
Устанавливает пакет и все его зависимости.
|
||
|
||
Аргументы:
|
||
package_name: имя пакета для установки (например, "nano")
|
||
dry_run: если True — только показать, что будет сделано
|
||
|
||
Возвращает:
|
||
True — установка успешна, False — ошибка
|
||
"""
|
||
# === РЕЖИМ DRY-RUN (только показать) ===
|
||
if dry_run:
|
||
print("🔍 РЕЖИМ DRY-RUN (ничего не устанавливается)")
|
||
try:
|
||
to_install = self.resolver.resolve(package_name)
|
||
except Exception as e:
|
||
print_error(f"Ошибка: {e}")
|
||
return False
|
||
|
||
print(f"\n📦 Будет установлено:")
|
||
for pkg in to_install:
|
||
print(f" {pkg.full_name()}")
|
||
if pkg.depends:
|
||
print(f" Зависимости: {', '.join(pkg.depends)}")
|
||
print(f"\n✅ Установка не произведена (dry-run)")
|
||
return True
|
||
|
||
# === РЕАЛЬНАЯ УСТАНОВКА ===
|
||
print_step(f"Поиск пакета {package_name}...")
|
||
|
||
# 1. Разрешаем зависимости (получаем список пакетов в нужном порядке)
|
||
try:
|
||
to_install = self.resolver.resolve(package_name)
|
||
except Exception as e:
|
||
print_error(f"Ошибка разрешения зависимостей: {e}")
|
||
return False
|
||
|
||
# 2. Устанавливаем каждый пакет из списка (сначала зависимости)
|
||
for pkg in to_install:
|
||
# Если пакет уже установлен — пропускаем
|
||
if self.db.is_installed(pkg.name):
|
||
print_info(f"{pkg.name} уже установлен")
|
||
continue
|
||
|
||
print_step(f"Установка {pkg.full_name()}...")
|
||
|
||
# Путь к папке с файлами пакета
|
||
source_dir = os.path.join(
|
||
self.repo_path, "packages", pkg.name, "files"
|
||
)
|
||
|
||
# Копируем все файлы пакета в целевую систему
|
||
for file_info in pkg.files:
|
||
src = os.path.join(source_dir, file_info["source"])
|
||
dst = os.path.join(
|
||
self.root,
|
||
file_info["destination"].lstrip("/")
|
||
)
|
||
|
||
# Создаём папку назначения (если её нет)
|
||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||
|
||
# Копируем файл
|
||
shutil.copy2(src, dst)
|
||
|
||
# Устанавливаем права доступа (если указаны)
|
||
if "permissions" in file_info:
|
||
os.chmod(dst, file_info["permissions"])
|
||
|
||
# Сохраняем информацию о пакете в базу данных
|
||
self.db.add_package(pkg)
|
||
print_success(f"{pkg.name} установлен")
|
||
|
||
# Если включена песочница — сообщаем, куда установили
|
||
if self.sandbox_mode:
|
||
print_info(f"Файлы в: {self.root}")
|
||
|
||
print_success(f"Пакет {package_name} и его зависимости установлены")
|
||
return True
|
||
|
||
def remove(self, package_name: str, force: bool = False, dry_run: bool = False) -> bool:
|
||
"""
|
||
Удаляет установленный пакет.
|
||
|
||
Аргументы:
|
||
package_name: имя пакета для удаления
|
||
force: принудительное удаление (игнорировать зависимости)
|
||
dry_run: если True — только показать, что будет удалено
|
||
|
||
Возвращает:
|
||
True — удаление успешно, False — ошибка
|
||
"""
|
||
print_step(f"Поиск пакета {package_name}...")
|
||
|
||
# Проверяем, установлен ли пакет
|
||
if not self.db.is_installed(package_name):
|
||
print_error(f"Пакет {package_name} не установлен")
|
||
return False
|
||
|
||
pkg = self.db.get_package(package_name)
|
||
|
||
# === РЕЖИМ DRY-RUN (только показать) ===
|
||
if dry_run:
|
||
print("🔍 РЕЖИМ DRY-RUN (ничего не удаляется)")
|
||
print(f"\n🗑️ Будет удалён: {pkg.full_name()}")
|
||
print(f" Файлы ({len(pkg.files)}):")
|
||
for f in pkg.files:
|
||
dst = os.path.join(self.root, f["destination"].lstrip("/"))
|
||
if os.path.exists(dst):
|
||
print(f" {dst} (существует)")
|
||
else:
|
||
print(f" {dst} (не найден)")
|
||
print(f"\n✅ Удаление не произведено (dry-run)")
|
||
return True
|
||
|
||
# === РЕАЛЬНОЕ УДАЛЕНИЕ ===
|
||
print_step(f"Удаление {pkg.full_name()}...")
|
||
|
||
# Удаляем все файлы пакета
|
||
for file_info in pkg.files:
|
||
dst = os.path.join(self.root, file_info["destination"].lstrip("/"))
|
||
if os.path.exists(dst):
|
||
os.remove(dst)
|
||
print_step(f" Удалён: {dst}")
|
||
|
||
# Удаляем запись о пакете из базы данных
|
||
self.db.remove_package(package_name)
|
||
print_success(f"Пакет {package_name} удалён")
|
||
return True
|
||
|
||
def info(self, package_name: str):
|
||
"""
|
||
Возвращает информацию о пакете (из репозитория или из установленных).
|
||
|
||
Аргументы:
|
||
package_name: имя пакета
|
||
|
||
Возвращает:
|
||
словарь с информацией о пакете (name, version, description,
|
||
depends, files, installed, install_date) или None, если не найден
|
||
"""
|
||
from ..utils.colors import print_error
|
||
|
||
# Сначала ищем среди установленных пакетов
|
||
pkg = self.db.get_package(package_name)
|
||
|
||
# Если не установлен — ищем в репозитории
|
||
if not pkg:
|
||
meta_path = os.path.join(
|
||
self.repo_path, "packages", package_name, "meta.json"
|
||
)
|
||
if not os.path.exists(meta_path):
|
||
print_error(f"Пакет {package_name} не найден")
|
||
return None
|
||
with open(meta_path, 'r') as f:
|
||
pkg_data = json.load(f)
|
||
pkg = Package(**pkg_data)
|
||
|
||
return {
|
||
'name': pkg.name,
|
||
'version': pkg.version,
|
||
'description': pkg.description,
|
||
'depends': pkg.depends,
|
||
'files': pkg.files,
|
||
'installed': pkg.installed,
|
||
'install_date': pkg.install_date
|
||
}
|
||
|
||
def search(self, keyword: str) -> List[Package]:
|
||
"""
|
||
Ищет пакеты в репозитории по ключевому слову.
|
||
|
||
Аргументы:
|
||
keyword: ключевое слово для поиска (ищет в имени и описании)
|
||
|
||
Возвращает:
|
||
список объектов Package, подходящих под критерий поиска
|
||
"""
|
||
results = []
|
||
packages_dir = os.path.join(self.repo_path, "packages")
|
||
|
||
if not os.path.exists(packages_dir):
|
||
return results
|
||
|
||
# Перебираем все папки пакетов в репозитории
|
||
for pkg_name in os.listdir(packages_dir):
|
||
meta_path = os.path.join(packages_dir, pkg_name, "meta.json")
|
||
if os.path.exists(meta_path):
|
||
with open(meta_path, 'r') as f:
|
||
pkg_data = json.load(f)
|
||
|
||
# Проверяем, содержится ли ключевое слово в имени или описании
|
||
keyword_lower = keyword.lower()
|
||
if (keyword_lower in pkg_data.get('name', '').lower() or
|
||
keyword_lower in pkg_data.get('description', '').lower()):
|
||
pkg = Package(**pkg_data)
|
||
results.append(pkg)
|
||
|
||
return results
|
||
|
||
def list_installed(self) -> List[Package]:
|
||
"""
|
||
Возвращает список всех установленных пакетов.
|
||
|
||
Возвращает:
|
||
список объектов Package
|
||
"""
|
||
return list(self.db.installed.values()) |