Files
nas_control_bot/.history/src/api/filestation_20250830141415.py
2025-08-30 14:42:08 +09:00

513 lines
19 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.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Модуль для взаимодействия с файловой системой Synology NAS через API FileStation
"""
import os
import logging
import requests
from typing import Dict, Any, Optional, List, Union
from src.api.synology import SynologyAPI
logger = logging.getLogger(__name__)
def add_file_manager_methods_to_synology_api(api_class):
"""Добавляет методы для работы с файловой системой к классу SynologyAPI"""
def list_files(self, folder_path: str = "/") -> List[Dict[str, Any]]:
"""Получение списка файлов и папок в указанной директории
Args:
folder_path: Путь к директории для просмотра
Returns:
Список файлов и папок в указанной директории
"""
logger.info(f"Listing files in directory: {folder_path}")
# Аутентифицируемся если нужно
if not self.sid and not self.login():
logger.error("Failed to authenticate for file listing")
return []
try:
# Если это корневая папка, получаем список общих папок
if folder_path == "/":
result = self._make_api_request(
"SYNO.FileStation.List",
"list_share",
version=2
)
if not result:
# Пробуем версию 1
result = self._make_api_request(
"SYNO.FileStation.List",
"list_share",
version=1
)
if not result:
logger.error("Failed to list shared folders")
return []
return result.get("shares", [])
else:
# Получаем список файлов в указанной директории
params = {
"folder_path": folder_path,
"sort_by": "name",
"sort_direction": "ASC"
}
result = self._make_api_request(
"SYNO.FileStation.List",
"list",
version=2,
params=params
)
if not result:
# Пробуем версию 1
result = self._make_api_request(
"SYNO.FileStation.List",
"list",
version=1,
params=params
)
if not result:
logger.error(f"Failed to list files in {folder_path}")
return []
return result.get("files", [])
except Exception as e:
logger.error(f"Error listing files in {folder_path}: {str(e)}")
return []
def get_file_info(self, file_path: str) -> Dict[str, Any]:
"""Получение подробной информации о файле
Args:
file_path: Полный путь к файлу
Returns:
Информация о файле
"""
logger.info(f"Getting file info: {file_path}")
# Аутентифицируемся если нужно
if not self.sid and not self.login():
logger.error("Failed to authenticate for file info request")
return {}
try:
params = {
"path": file_path,
"additional": "real_path,size,owner,time,perm"
}
result = self._make_api_request(
"SYNO.FileStation.List",
"getinfo",
version=2,
params=params
)
if not result:
# Пробуем версию 1
result = self._make_api_request(
"SYNO.FileStation.List",
"getinfo",
version=1,
params=params
)
if not result:
logger.error(f"Failed to get file info for {file_path}")
return {}
# Возвращаем информацию о первом файле в результате
files = result.get("files", [])
if files and len(files) > 0:
return files[0]
return {}
except Exception as e:
logger.error(f"Error getting file info for {file_path}: {str(e)}")
return {}
def download_file(self, file_path: str, local_path: str) -> bool:
"""Скачивание файла с NAS
Args:
file_path: Путь к файлу на NAS
local_path: Локальный путь для сохранения файла
Returns:
True если успешно, False в противном случае
"""
logger.info(f"Downloading file from {file_path} to {local_path}")
# Аутентифицируемся если нужно
if not self.sid and not self.login():
logger.error("Failed to authenticate for file download")
return False
try:
# Получаем URL для скачивания файла
params = {
"path": file_path,
"mode": "download"
}
result = self._make_api_request(
"SYNO.FileStation.Download",
"download",
version=2,
params=params
)
if not result:
# Пробуем версию 1
result = self._make_api_request(
"SYNO.FileStation.Download",
"download",
version=1,
params=params
)
if not result:
logger.error(f"Failed to get download URL for {file_path}")
return False
# URL для скачивания
download_url = result.get("url")
if not download_url:
logger.error("No download URL received")
return False
# Добавляем базовый URL, если URL относительный
if not download_url.startswith("http"):
protocol = "https" if self.protocol == "https" else "http"
download_url = f"{protocol}://{self.base_url}/{download_url}"
# Скачиваем файл
response = self.session.get(download_url, stream=True, verify=False)
if response.status_code != 200:
logger.error(f"Failed to download file: HTTP {response.status_code}")
return False
# Сохраняем файл
with open(local_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
logger.info(f"File successfully downloaded to {local_path}")
return True
except Exception as e:
logger.error(f"Error downloading file {file_path}: {str(e)}")
return False
def upload_file(self, local_path: str, folder_path: str) -> bool:
"""Загрузка файла на NAS
Args:
local_path: Локальный путь к файлу
folder_path: Путь на NAS для загрузки файла
Returns:
True если успешно, False в противном случае
"""
logger.info(f"Uploading file from {local_path} to {folder_path}")
# Аутентифицируемся если нужно
if not self.sid and not self.login():
logger.error("Failed to authenticate for file upload")
return False
try:
# Проверяем существование файла
if not os.path.exists(local_path):
logger.error(f"Local file {local_path} not found")
return False
# Формируем URL для загрузки
url = f"{self.base_url}/entry.cgi"
# Извлекаем имя файла из локального пути
file_name = os.path.basename(local_path)
# Подготавливаем параметры для загрузки
params = {
"api": "SYNO.FileStation.Upload",
"version": "2",
"method": "upload",
"path": folder_path,
"_sid": self.sid
}
# Подготавливаем файл для загрузки
files = {
'file': (file_name, open(local_path, 'rb'))
}
# Выполняем запрос
response = self.session.post(url, params=params, files=files, verify=False)
# Закрываем файл
files['file'][1].close()
if response.status_code != 200:
logger.error(f"Failed to upload file: HTTP {response.status_code}")
return False
# Проверяем ответ
try:
data = response.json()
success = data.get("success", False)
if not success:
error_code = data.get("error", {}).get("code", -1)
logger.error(f"Failed to upload file: Error code {error_code}")
return False
logger.info(f"File successfully uploaded to {folder_path}/{file_name}")
return True
except Exception as e:
logger.error(f"Error parsing upload response: {str(e)}")
return False
except Exception as e:
logger.error(f"Error uploading file {local_path}: {str(e)}")
return False
def delete_file(self, file_path: str) -> bool:
"""Удаление файла на NAS
Args:
file_path: Путь к файлу для удаления
Returns:
True если успешно, False в противном случае
"""
logger.info(f"Deleting file: {file_path}")
# Аутентифицируемся если нужно
if not self.sid and not self.login():
logger.error("Failed to authenticate for file deletion")
return False
try:
# Подготавливаем параметры для удаления
params = {
"path": [file_path],
"recursive": True # Удаляем папки рекурсивно
}
result = self._make_api_request(
"SYNO.FileStation.Delete",
"delete",
version=2,
params=params
)
if not result:
# Пробуем версию 1
result = self._make_api_request(
"SYNO.FileStation.Delete",
"delete",
version=1,
params=params
)
if not result:
logger.error(f"Failed to delete file {file_path}")
return False
# Проверяем результат
task_id = result.get("taskid")
if not task_id:
logger.error("No task ID received for deletion")
return False
# Проверяем статус задачи
task_params = {
"taskid": task_id
}
# Ждем завершения задачи
for _ in range(10):
task_result = self._make_api_request(
"SYNO.FileStation.Delete",
"status",
version=2,
params=task_params
)
if not task_result:
task_result = self._make_api_request(
"SYNO.FileStation.Delete",
"status",
version=1,
params=task_params
)
if not task_result:
logger.error(f"Failed to check delete task status for {file_path}")
return False
# Проверяем статус задачи
if task_result.get("finished", False):
return True
# Ждем немного
import time
time.sleep(0.5)
logger.warning(f"Delete task did not complete in time for {file_path}")
return True # Возвращаем True, т.к. задача запущена успешно
except Exception as e:
logger.error(f"Error deleting file {file_path}: {str(e)}")
return False
def create_folder(self, parent_path: str, folder_name: str) -> bool:
"""Создание новой папки на NAS
Args:
parent_path: Родительский путь для новой папки
folder_name: Имя новой папки
Returns:
True если успешно, False в противном случае
"""
logger.info(f"Creating folder {folder_name} in {parent_path}")
# Аутентифицируемся если нужно
if not self.sid and not self.login():
logger.error("Failed to authenticate for folder creation")
return False
try:
# Подготавливаем параметры для создания папки
params = {
"folder_path": parent_path,
"name": folder_name
}
result = self._make_api_request(
"SYNO.FileStation.CreateFolder",
"create",
version=2,
params=params
)
if not result:
# Пробуем версию 1
result = self._make_api_request(
"SYNO.FileStation.CreateFolder",
"create",
version=1,
params=params
)
if not result:
logger.error(f"Failed to create folder {folder_name} in {parent_path}")
return False
# Проверяем результат
folders = result.get("folders", [])
if not folders:
logger.error("No folder information received after creation")
return False
logger.info(f"Folder {folder_name} created successfully in {parent_path}")
return True
except Exception as e:
logger.error(f"Error creating folder {folder_name}: {str(e)}")
return False
def rename_file(self, file_path: str, new_name: str) -> bool:
"""Переименование файла или папки на NAS
Args:
file_path: Путь к файлу для переименования
new_name: Новое имя файла (без пути)
Returns:
True если успешно, False в противном случае
"""
logger.info(f"Renaming {file_path} to {new_name}")
# Аутентифицируемся если нужно
if not self.sid and not self.login():
logger.error("Failed to authenticate for file renaming")
return False
try:
# Получаем путь к родительской директории
parent_path = os.path.dirname(file_path)
# Подготавливаем параметры для переименования
params = {
"path": file_path,
"name": new_name
}
result = self._make_api_request(
"SYNO.FileStation.Rename",
"rename",
version=2,
params=params
)
if not result:
# Пробуем версию 1
result = self._make_api_request(
"SYNO.FileStation.Rename",
"rename",
version=1,
params=params
)
if not result:
logger.error(f"Failed to rename {file_path} to {new_name}")
return False
# Проверяем результат
files = result.get("files", [])
if not files:
logger.error("No file information received after renaming")
return False
logger.info(f"File {file_path} renamed to {new_name} successfully")
return True
except Exception as e:
logger.error(f"Error renaming file {file_path}: {str(e)}")
return False
# Добавляем все методы в класс API
api_class.list_files = list_files
api_class.get_file_info = get_file_info
api_class.download_file = download_file
api_class.upload_file = upload_file
api_class.delete_file = delete_file
api_class.create_folder = create_folder
api_class.rename_file = rename_file
return api_class
# Добавляем методы для работы с файлами к классу SynologyAPI
add_file_manager_methods_to_synology_api(SynologyAPI)