181 lines
6.0 KiB
Python
181 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import os
|
||
import socket
|
||
import threading
|
||
from typing import Optional, Dict, Any, Callable
|
||
import tftpy
|
||
|
||
from core.config import AppConfig
|
||
from core.exceptions import TFTPError
|
||
from .base_server import BaseServer
|
||
|
||
class TFTPServer(BaseServer):
|
||
"""TFTP сервер для передачи файлов."""
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self._server: Optional[tftpy.TftpServer] = None
|
||
self._root_dir = AppConfig.CONFIGS_DIR
|
||
self._host = "0.0.0.0"
|
||
self._port = AppConfig.TFTP_PORT
|
||
self._timeout = AppConfig.TFTP_TIMEOUT
|
||
|
||
# Создаем директорию, если её нет
|
||
os.makedirs(self._root_dir, exist_ok=True)
|
||
|
||
def configure(self, root_dir: Optional[str] = None, host: Optional[str] = None,
|
||
port: Optional[int] = None, timeout: Optional[int] = None) -> None:
|
||
"""
|
||
Конфигурация TFTP сервера.
|
||
|
||
Args:
|
||
root_dir: Корневая директория для файлов
|
||
host: IP-адрес для прослушивания
|
||
port: Порт сервера
|
||
timeout: Таймаут операций
|
||
"""
|
||
if root_dir is not None:
|
||
self._root_dir = root_dir
|
||
os.makedirs(self._root_dir, exist_ok=True)
|
||
if host is not None:
|
||
self._host = host
|
||
if port is not None:
|
||
self._port = port
|
||
if timeout is not None:
|
||
self._timeout = timeout
|
||
|
||
def start(self) -> None:
|
||
"""
|
||
Запуск TFTP сервера.
|
||
|
||
Raises:
|
||
TFTPError: При ошибке запуска сервера
|
||
"""
|
||
if self.is_running:
|
||
self._logger.warning("TFTP сервер уже запущен")
|
||
return
|
||
|
||
try:
|
||
# Создаем серверный объект
|
||
self._server = tftpy.TftpServer(self._root_dir)
|
||
|
||
# Запускаем сервер в отдельном потоке
|
||
self._start_in_thread(self._serve)
|
||
|
||
self._notify_started({
|
||
"host": self._host,
|
||
"port": self._port,
|
||
"root_dir": self._root_dir
|
||
})
|
||
|
||
self._logger.info(f"TFTP сервер запущен на {self._host}:{self._port}")
|
||
|
||
except Exception as e:
|
||
self._notify_error(e)
|
||
raise TFTPError(f"Ошибка запуска TFTP сервера: {e}")
|
||
|
||
def stop(self) -> None:
|
||
"""Остановка TFTP сервера."""
|
||
if not self.is_running:
|
||
return
|
||
|
||
try:
|
||
if self._server:
|
||
self._server.stop()
|
||
self._server = None
|
||
|
||
self._stop_thread()
|
||
self._notify_stopped()
|
||
|
||
self._logger.info("TFTP сервер остановлен")
|
||
|
||
except Exception as e:
|
||
self._logger.error(f"Ошибка при остановке TFTP сервера: {e}")
|
||
|
||
def _serve(self) -> None:
|
||
"""Основной цикл сервера."""
|
||
try:
|
||
self._server.listen(self._host, self._port, timeout=self._timeout)
|
||
except Exception as e:
|
||
self._notify_error(e)
|
||
self._logger.error(f"Ошибка в работе TFTP сервера: {e}")
|
||
self.stop()
|
||
|
||
def get_server_info(self) -> Dict[str, Any]:
|
||
"""
|
||
Получение информации о сервере.
|
||
|
||
Returns:
|
||
Dict[str, Any]: Информация о сервере
|
||
"""
|
||
return {
|
||
"running": self.is_running,
|
||
"host": self._host,
|
||
"port": self._port,
|
||
"root_dir": self._root_dir,
|
||
"timeout": self._timeout
|
||
}
|
||
|
||
def list_files(self) -> list[str]:
|
||
"""
|
||
Получение списка файлов в корневой директории.
|
||
|
||
Returns:
|
||
list[str]: Список файлов
|
||
"""
|
||
try:
|
||
return os.listdir(self._root_dir)
|
||
except Exception as e:
|
||
self._logger.error(f"Ошибка при получении списка файлов: {e}")
|
||
return []
|
||
|
||
def get_file_info(self, filename: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
Получение информации о файле.
|
||
|
||
Args:
|
||
filename: Имя файла
|
||
|
||
Returns:
|
||
Optional[Dict[str, Any]]: Информация о файле или None
|
||
"""
|
||
file_path = os.path.join(self._root_dir, filename)
|
||
if not os.path.exists(file_path):
|
||
return None
|
||
|
||
try:
|
||
stat = os.stat(file_path)
|
||
return {
|
||
"name": filename,
|
||
"size": stat.st_size,
|
||
"modified": stat.st_mtime,
|
||
"created": stat.st_ctime
|
||
}
|
||
except Exception as e:
|
||
self._logger.error(f"Ошибка при получении информации о файле: {e}")
|
||
return None
|
||
|
||
def delete_file(self, filename: str) -> bool:
|
||
"""
|
||
Удаление файла из корневой директории.
|
||
|
||
Args:
|
||
filename: Имя файла
|
||
|
||
Returns:
|
||
bool: True если файл удален успешно
|
||
"""
|
||
file_path = os.path.join(self._root_dir, filename)
|
||
if not os.path.exists(file_path):
|
||
return False
|
||
|
||
try:
|
||
os.remove(file_path)
|
||
self._logger.info(f"Файл удален: {filename}")
|
||
return True
|
||
except Exception as e:
|
||
self._logger.error(f"Ошибка при удалении файла: {e}")
|
||
return False
|