273 lines
10 KiB
Python
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
|