Files
mypkg/mypkg/core/package_manager.py

268 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Главный модуль менеджера пакетов.
Содержит класс 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())