AutoBackups/autobackups/src/utils/everything_wrapper.py

273 lines
10 KiB
Python

"""
Everything API Wrapper
Integración con Everything para búsqueda rápida de archivos .s7p
"""
import os
import ctypes
from pathlib import Path
from typing import List, Optional, Tuple
import logging
class EverythingSearcher:
"""Wrapper para la API de Everything"""
def __init__(self, dll_path: str = None):
self.logger = logging.getLogger(__name__)
self.dll = None
self.is_initialized = False
if dll_path:
self.load_dll(dll_path)
def load_dll(self, dll_path: str) -> bool:
"""Cargar la DLL de Everything"""
try:
if not os.path.exists(dll_path):
self.logger.error(f"DLL de Everything no encontrada: {dll_path}")
return False
self.dll = ctypes.WinDLL(dll_path)
# Definir funciones de la API
self._setup_api_functions()
# Inicializar Everything
self.is_initialized = self._initialize_everything()
if self.is_initialized:
self.logger.info("Everything API inicializada correctamente")
else:
self.logger.error("Error inicializando Everything API")
return self.is_initialized
except Exception as e:
self.logger.error(f"Error cargando DLL de Everything: {e}")
return False
def _setup_api_functions(self):
"""Configurar las funciones de la API"""
try:
# Everything_SetSearchW - Establecer consulta de búsqueda
self.dll.Everything_SetSearchW.argtypes = [ctypes.c_wchar_p]
self.dll.Everything_SetSearchW.restype = None
# Everything_SetMatchPath - Habilitar coincidencia de ruta completa
self.dll.Everything_SetMatchPath.argtypes = [ctypes.c_bool]
self.dll.Everything_SetMatchPath.restype = None
# Everything_SetMatchCase - Habilitar coincidencia de mayúsculas/minúsculas
self.dll.Everything_SetMatchCase.argtypes = [ctypes.c_bool]
self.dll.Everything_SetMatchCase.restype = None
# Everything_QueryW - Ejecutar consulta
self.dll.Everything_QueryW.argtypes = [ctypes.c_bool]
self.dll.Everything_QueryW.restype = ctypes.c_bool
# Everything_GetNumResults - Obtener número de resultados
self.dll.Everything_GetNumResults.restype = ctypes.c_uint
# Everything_GetResultFullPathNameW - Obtener ruta completa del resultado
self.dll.Everything_GetResultFullPathNameW.argtypes = [
ctypes.c_uint, ctypes.c_wchar_p, ctypes.c_uint
]
self.dll.Everything_GetResultFullPathNameW.restype = ctypes.c_uint
# Everything_GetLastError - Obtener último error
self.dll.Everything_GetLastError.restype = ctypes.c_uint
self.logger.debug("Funciones API configuradas correctamente")
except Exception as e:
self.logger.error(f"Error configurando funciones API: {e}")
raise
def _initialize_everything(self) -> bool:
"""Inicializar Everything con configuraciones por defecto"""
try:
# Configurar opciones de búsqueda
self.dll.Everything_SetMatchPath(True)
self.dll.Everything_SetMatchCase(False)
return True
except Exception as e:
self.logger.error(f"Error inicializando Everything: {e}")
return False
def search_s7p_files(self, search_directories: List[str]) -> List[str]:
"""
Buscar archivos .s7p en los directorios especificados
Retorna lista de rutas completas a archivos .s7p encontrados
"""
if not self.is_initialized:
self.logger.error("Everything no está inicializado")
return []
try:
all_s7p_files = []
for directory in search_directories:
s7p_files = self._search_s7p_in_directory(directory)
all_s7p_files.extend(s7p_files)
# Eliminar duplicados manteniendo el orden
unique_files = []
seen = set()
for file_path in all_s7p_files:
if file_path not in seen:
seen.add(file_path)
unique_files.append(file_path)
self.logger.info(f"Encontrados {len(unique_files)} archivos .s7p únicos")
return unique_files
except Exception as e:
self.logger.error(f"Error buscando archivos S7P: {e}")
return []
def _search_s7p_in_directory(self, directory: str) -> List[str]:
"""Buscar archivos .s7p en un directorio específico"""
try:
# Normalizar la ruta del directorio
directory = os.path.normpath(directory)
# Construir consulta de búsqueda
# Buscar archivos .s7p en el directorio y subdirectorios
search_query = f'"{directory}" ext:s7p'
self.logger.debug(f"Búsqueda en Everything: {search_query}")
# Establecer consulta
self.dll.Everything_SetSearchW(search_query)
# Ejecutar búsqueda
if not self.dll.Everything_QueryW(True):
error_code = self.dll.Everything_GetLastError()
self.logger.error(f"Error en consulta Everything: código {error_code}")
return []
# Obtener número de resultados
num_results = self.dll.Everything_GetNumResults()
self.logger.debug(f"Everything encontró {num_results} resultados para {directory}")
if num_results == 0:
return []
# Extraer rutas de los resultados
results = []
buffer_size = 4096 # Tamaño del buffer para rutas
for i in range(num_results):
# Buffer para almacenar la ruta
path_buffer = ctypes.create_unicode_buffer(buffer_size)
# Obtener ruta completa
chars_copied = self.dll.Everything_GetResultFullPathNameW(
i, path_buffer, buffer_size
)
if chars_copied > 0:
file_path = path_buffer.value
if file_path and os.path.exists(file_path):
results.append(file_path)
self.logger.debug(f"Archivo S7P encontrado: {file_path}")
return results
except Exception as e:
self.logger.error(f"Error buscando en directorio {directory}: {e}")
return []
def search_files_by_pattern(self, pattern: str, max_results: int = 1000) -> List[str]:
"""
Buscar archivos usando un patrón específico
"""
if not self.is_initialized:
self.logger.error("Everything no está inicializado")
return []
try:
self.logger.debug(f"Búsqueda por patrón: {pattern}")
# Establecer consulta
self.dll.Everything_SetSearchW(pattern)
# Ejecutar búsqueda
if not self.dll.Everything_QueryW(True):
error_code = self.dll.Everything_GetLastError()
self.logger.error(f"Error en consulta Everything: código {error_code}")
return []
# Obtener número de resultados
num_results = self.dll.Everything_GetNumResults()
actual_results = min(num_results, max_results)
self.logger.debug(f"Procesando {actual_results} de {num_results} resultados")
results = []
buffer_size = 4096
for i in range(actual_results):
path_buffer = ctypes.create_unicode_buffer(buffer_size)
chars_copied = self.dll.Everything_GetResultFullPathNameW(
i, path_buffer, buffer_size
)
if chars_copied > 0:
file_path = path_buffer.value
if file_path and os.path.exists(file_path):
results.append(file_path)
return results
except Exception as e:
self.logger.error(f"Error buscando patrón {pattern}: {e}")
return []
def is_everything_available(self) -> bool:
"""Verificar si Everything está disponible y funcionando"""
return self.is_initialized
def get_project_directory_from_s7p(self, s7p_file_path: str,
observation_directory: str) -> Tuple[str, str]:
"""
Determinar el directorio del proyecto y la ruta relativa desde un archivo .s7p
Retorna: (directorio_proyecto, ruta_relativa)
"""
try:
s7p_path = Path(s7p_file_path)
obs_path = Path(observation_directory)
# El directorio del proyecto es el padre del archivo .s7p
project_dir = s7p_path.parent
# Calcular ruta relativa desde el directorio de observación
try:
relative_path = project_dir.relative_to(obs_path)
return str(project_dir), str(relative_path)
except ValueError:
# Si no se puede calcular la ruta relativa, usar ruta absoluta
self.logger.warning(f"No se puede calcular ruta relativa para {s7p_file_path}")
return str(project_dir), str(project_dir)
except Exception as e:
self.logger.error(f"Error determinando directorio de proyecto para {s7p_file_path}: {e}")
return "", ""
# Funciones de utilidad para crear instancias
def create_everything_searcher(dll_path: str) -> Optional[EverythingSearcher]:
"""Crear una instancia de EverythingSearcher con manejo de errores"""
try:
searcher = EverythingSearcher(dll_path)
if searcher.is_everything_available():
return searcher
else:
return None
except Exception as e:
logging.getLogger(__name__).error(f"Error creando EverythingSearcher: {e}")
return None