app.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import os
  2. import argparse
  3. import uuid
  4. import shutil
  5. from datetime import datetime, timedelta
  6. from flask import Flask, render_template, request, jsonify, send_file, redirect, url_for
  7. from werkzeug.utils import secure_filename
  8. from dotenv import load_dotenv
  9. import json
  10. from database import init_database, save_file, get_file_by_id, get_all_files, delete_file, hard_delete_file, increment_download_count, cleanup_expired_files, get_file_stats
  11. # Cargar variables de entorno desde .env
  12. load_dotenv()
  13. app = Flask(__name__)
  14. app.config['MAX_CONTENT_LENGTH'] = int(os.getenv('MAX_FILE_SIZE', 16 * 1024 * 1024)) # 16MB max file size
  15. app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', 'uploads')
  16. app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'tu_clave_secreta_aqui')
  17. # Manejador de errores para archivos demasiado grandes
  18. @app.errorhandler(413)
  19. def too_large(e):
  20. return jsonify({
  21. 'error': f'El archivo es demasiado grande. Tamaño máximo: {app.config["MAX_CONTENT_LENGTH"] // (1024*1024)}MB'
  22. }), 413
  23. # Manejador de errores general
  24. @app.errorhandler(500)
  25. def internal_error(e):
  26. return jsonify({'error': 'Error interno del servidor'}), 500
  27. @app.errorhandler(404)
  28. def not_found(e):
  29. return jsonify({'error': 'Página no encontrada'}), 404
  30. # Verificar y crear carpeta de uploads si no existe
  31. def ensure_upload_folder():
  32. if not os.path.exists(app.config['UPLOAD_FOLDER']):
  33. os.makedirs(app.config['UPLOAD_FOLDER'])
  34. print(f"Carpeta '{app.config['UPLOAD_FOLDER']}' creada automáticamente.")
  35. # Inicializar base de datos
  36. def init_app():
  37. ensure_upload_folder()
  38. init_database()
  39. print("Base de datos inicializada correctamente.")
  40. # Configurar argumentos de línea de comandos
  41. def parse_arguments():
  42. parser = argparse.ArgumentParser(description='Servidor de archivos temporales')
  43. parser.add_argument('--host', default=os.getenv('HOST', '127.0.0.1'), help='Host del servidor (default: 127.0.0.1)')
  44. parser.add_argument('--port', type=int, default=int(os.getenv('PORT', 5000)), help='Puerto del servidor (default: 5000)')
  45. parser.add_argument('--https', action='store_true', help='Usar HTTPS')
  46. parser.add_argument('--debug', action='store_true', default=os.getenv('DEBUG', 'False').lower() == 'true', help='Modo debug')
  47. return parser.parse_args()
  48. @app.route('/')
  49. def index():
  50. """Página principal para subir archivos"""
  51. # Pasar configuración al template
  52. max_file_size = app.config['MAX_CONTENT_LENGTH']
  53. max_file_size_mb = max_file_size // (1024 * 1024)
  54. return render_template('index.html',
  55. max_file_size=max_file_size,
  56. max_file_size_mb=max_file_size_mb)
  57. @app.route('/myfiles')
  58. def myfiles():
  59. """Página para ver enlaces guardados en localStorage"""
  60. return render_template('myfiles.html')
  61. @app.route('/upload', methods=['POST'])
  62. def upload_file():
  63. """Manejar la subida de archivos"""
  64. try:
  65. # Verificar si hay archivo en la request
  66. if 'file' not in request.files:
  67. return jsonify({'error': 'No se seleccionó ningún archivo'}), 400
  68. file = request.files['file']
  69. if file.filename == '':
  70. return jsonify({'error': 'No se seleccionó ningún archivo'}), 400
  71. # Obtener tiempo de vida del archivo (por defecto 24 horas)
  72. expires_hours = request.form.get('expires_hours', 24, type=int)
  73. # Validar tiempo de vida (entre 1 hora y 7 días)
  74. if expires_hours < 1 or expires_hours > 168: # 168 horas = 7 días
  75. expires_hours = 24
  76. # Validar tamaño del archivo antes de procesar
  77. max_size = app.config['MAX_CONTENT_LENGTH']
  78. max_size_mb = max_size // (1024 * 1024)
  79. # Verificar content_length si está disponible
  80. if request.content_length and request.content_length > max_size:
  81. return jsonify({
  82. 'error': f'El archivo es demasiado grande. Tamaño máximo: {max_size_mb}MB'
  83. }), 413
  84. # Verificar el tamaño del archivo después de recibirlo
  85. if file:
  86. # Leer el archivo en chunks para verificar el tamaño
  87. file.seek(0, 2) # Ir al final del archivo
  88. file_size = file.tell()
  89. file.seek(0) # Volver al inicio
  90. if file_size > max_size:
  91. return jsonify({
  92. 'error': f'El archivo es demasiado grande. Tamaño máximo: {max_size_mb}MB'
  93. }), 413
  94. # Generar nombre único para el archivo
  95. original_filename = file.filename
  96. if not original_filename:
  97. return jsonify({'error': 'Nombre de archivo no válido'}), 400
  98. filename = secure_filename(original_filename)
  99. if not filename:
  100. return jsonify({'error': 'Nombre de archivo no válido'}), 400
  101. file_id = str(uuid.uuid4())
  102. file_extension = os.path.splitext(filename)[1]
  103. unique_filename = f"{file_id}{file_extension}"
  104. # Guardar archivo
  105. file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
  106. file.save(file_path)
  107. # Guardar en base de datos con el tiempo de vida especificado
  108. save_file(file_id, original_filename, unique_filename, file_size, expires_hours)
  109. # Crear enlace temporal con el tiempo de vida especificado
  110. # Usar el dominio configurado o el host actual
  111. domain = os.getenv('DOMAIN', request.host)
  112. protocol = 'https' if args.https else 'http'
  113. download_url = f"{protocol}://{domain}/download/{file_id}"
  114. return jsonify({
  115. 'success': True,
  116. 'download_url': download_url,
  117. 'file_id': file_id,
  118. 'original_filename': original_filename,
  119. 'expires_at': (datetime.now() + timedelta(hours=expires_hours)).isoformat()
  120. })
  121. else:
  122. return jsonify({'error': 'No se pudo procesar el archivo'}), 400
  123. except Exception as e:
  124. print(f"Error en upload: {str(e)}")
  125. return jsonify({'error': 'Error interno del servidor'}), 500
  126. @app.route('/download/<file_id>')
  127. def download_file(file_id):
  128. """Descargar archivo por ID"""
  129. # Buscar archivo en la base de datos
  130. file_info = get_file_by_id(file_id)
  131. if not file_info:
  132. return "Archivo no encontrado", 404
  133. # Verificar si el archivo ha expirado
  134. expires_at = datetime.fromisoformat(file_info['expires_at'])
  135. if expires_at <= datetime.now():
  136. return "Archivo expirado", 410
  137. # Verificar si el archivo existe físicamente
  138. file_path = os.path.join(app.config['UPLOAD_FOLDER'], file_info['stored_filename'])
  139. if not os.path.exists(file_path):
  140. return "Archivo no encontrado", 404
  141. # Incrementar contador de descargas
  142. increment_download_count(file_id)
  143. # Enviar archivo
  144. return send_file(file_path, as_attachment=True, download_name=file_info['original_filename'])
  145. @app.route('/api/links')
  146. def get_links():
  147. """API para obtener enlaces guardados desde la base de datos"""
  148. try:
  149. files = get_all_files()
  150. links = []
  151. for file_info in files:
  152. # Crear URL de descarga
  153. domain = os.getenv('DOMAIN', request.host)
  154. protocol = 'https' if args.https else 'http'
  155. download_url = f"{protocol}://{domain}/download/{file_info['id']}"
  156. links.append({
  157. 'id': file_info['id'],
  158. 'filename': file_info['original_filename'],
  159. 'url': download_url,
  160. 'created_at': file_info['created_at'],
  161. 'expires_at': file_info['expires_at'],
  162. 'file_size': file_info['file_size'],
  163. 'download_count': file_info['download_count']
  164. })
  165. return jsonify(links)
  166. except Exception as e:
  167. print(f"Error obteniendo enlaces: {str(e)}")
  168. return jsonify({'error': 'Error interno del servidor'}), 500
  169. @app.route('/api/delete/<file_id>', methods=['DELETE'])
  170. def delete_link(file_id):
  171. """API para eliminar un enlace permanentemente"""
  172. try:
  173. # Obtener información del archivo
  174. file_info = get_file_by_id(file_id)
  175. if not file_info:
  176. return jsonify({'error': 'Archivo no encontrado'}), 404
  177. # Eliminar archivo físico
  178. file_path = os.path.join(app.config['UPLOAD_FOLDER'], file_info['stored_filename'])
  179. if os.path.exists(file_path):
  180. os.remove(file_path)
  181. # Eliminar permanentemente de la base de datos
  182. hard_delete_file(file_id)
  183. return jsonify({'success': True, 'message': 'Archivo eliminado permanentemente'})
  184. except Exception as e:
  185. print(f"Error eliminando archivo: {str(e)}")
  186. return jsonify({'error': 'Error interno del servidor'}), 500
  187. @app.route('/api/stats')
  188. def get_stats():
  189. """API para obtener estadísticas"""
  190. try:
  191. stats = get_file_stats()
  192. return jsonify(stats)
  193. except Exception as e:
  194. print(f"Error obteniendo estadísticas: {str(e)}")
  195. return jsonify({'error': 'Error interno del servidor'}), 500
  196. if __name__ == '__main__':
  197. args = parse_arguments()
  198. # Inicializar aplicación (carpeta de uploads y base de datos)
  199. init_app()
  200. # Configurar protocolo
  201. protocol = 'https' if args.https else 'http'
  202. print(f"Servidor iniciando en {protocol}://{args.host}:{args.port}")
  203. print(f"Modo debug: {'Activado' if args.debug else 'Desactivado'}")
  204. app.run(
  205. host=args.host,
  206. port=args.port,
  207. debug=args.debug
  208. )