file manager
This commit is contained in:
512
.history/src/api/filestation_20250830141415.py
Normal file
512
.history/src/api/filestation_20250830141415.py
Normal file
@@ -0,0 +1,512 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user