import os import json import shutil import zipfile from datetime import datetime import pytz from flask import current_app from werkzeug.utils import secure_filename def ensure_dir_exists(directory): """ Asegurar que un directorio existe, creándolo si es necesario. Args: directory (str): Ruta del directorio """ if not os.path.exists(directory): os.makedirs(directory) def load_json_file(file_path, default=None): """ Cargar un archivo JSON. Args: file_path (str): Ruta al archivo JSON default: Valor por defecto si el archivo no existe o no es válido Returns: dict: Contenido del archivo JSON o valor por defecto """ try: with open(file_path, 'r', encoding='utf-8') as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {} if default is None else default def save_json_file(file_path, data): """ Guardar datos en un archivo JSON. Args: file_path (str): Ruta al archivo JSON data: Datos a guardar (deben ser serializables a JSON) """ # Asegurar que el directorio existe directory = os.path.dirname(file_path) ensure_dir_exists(directory) with open(file_path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) def get_next_id(id_type): """ Obtener el siguiente ID disponible para proyectos o documentos. Args: id_type (str): Tipo de ID ('project' o 'document') Returns: int: Siguiente ID disponible """ storage_path = current_app.config['STORAGE_PATH'] indices_file = os.path.join(storage_path, 'indices.json') # Cargar índices indices = load_json_file(indices_file, {"max_project_id": 0, "max_document_id": 0}) # Incrementar y guardar if id_type == 'project': indices['max_project_id'] += 1 new_id = indices['max_project_id'] elif id_type == 'document': indices['max_document_id'] += 1 new_id = indices['max_document_id'] else: raise ValueError(f"Tipo de ID no válido: {id_type}") save_json_file(indices_file, indices) return new_id def format_project_directory_name(project_id, project_name): """ Formatear nombre de directorio para un proyecto. Args: project_id (int): ID del proyecto project_name (str): Nombre del proyecto Returns: str: Nombre de directorio formateado """ # Sanitizar nombre de proyecto safe_name = secure_filename(project_name) safe_name = safe_name.replace(' ', '_') # Formatear como @id_num_@project_name return f"@{project_id:03d}_@{safe_name}" def format_document_directory_name(document_id, document_name): """ Formatear nombre de directorio para un documento. Args: document_id (int): ID del documento document_name (str): Nombre del documento Returns: str: Nombre de directorio formateado """ # Sanitizar nombre de documento safe_name = secure_filename(document_name) safe_name = safe_name.replace(' ', '_') # Formatear como @id_num_@doc_name return f"@{document_id:03d}_@{safe_name}" def format_version_filename(version, document_name, extension): """ Formatear nombre de archivo para una versión de documento. Args: version (int): Número de versión document_name (str): Nombre base del documento extension (str): Extensión del archivo Returns: str: Nombre de archivo formateado """ # Sanitizar nombre safe_name = secure_filename(document_name) safe_name = safe_name.replace(' ', '_') # Asegurar que la extensión no tiene el punto if extension.startswith('.'): extension = extension[1:] # Formatear como v001_doc_name.ext return f"v{version:03d}_{safe_name}.{extension}" def create_zip_archive(source_dir, files_to_include, output_path): """ Crear un archivo ZIP con los documentos seleccionados. Args: source_dir (str): Directorio fuente files_to_include (list): Lista de archivos a incluir output_path (str): Ruta de salida para el ZIP Returns: str: Ruta al archivo ZIP creado """ with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf: for file_info in files_to_include: zipf.write( os.path.join(source_dir, file_info['path']), arcname=file_info['arcname'] ) return output_path def get_directory_size(directory): """ Calcular el tamaño total de un directorio en bytes. Args: directory (str): Ruta al directorio Returns: int: Tamaño en bytes """ total_size = 0 for dirpath, dirnames, filenames in os.walk(directory): for filename in filenames: file_path = os.path.join(dirpath, filename) total_size += os.path.getsize(file_path) return total_size def get_file_info(file_path): """ Obtener información básica sobre un archivo. Args: file_path (str): Ruta al archivo Returns: dict: Información del archivo """ stat_info = os.stat(file_path) return { 'filename': os.path.basename(file_path), 'size': stat_info.st_size, 'created': datetime.fromtimestamp(stat_info.st_ctime, pytz.UTC).isoformat(), 'modified': datetime.fromtimestamp(stat_info.st_mtime, pytz.UTC).isoformat(), 'path': file_path } def delete_directory_with_content(directory): """ Eliminar un directorio y todo su contenido. Args: directory (str): Ruta al directorio a eliminar """ if os.path.exists(directory): shutil.rmtree(directory)