import json import logging from pathlib import Path import sys import threading from flask import Blueprint, Flask, Response, jsonify, render_template, request, send_from_directory ROOT = Path(__file__).resolve().parent.parent if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from upload.backend.upload_refs import create_upload_refs_blueprint from upload.backend.session import get_files, file_count, cleanup logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s") log = logging.getLogger("upload-platform") # Конфигурация стенда: разрешённые расширения, буфер и лимиты DEFAULT_CONFIG = { "allowedExt": [".pdf", ".doc", ".docx", ".txt", ".md"], "vmUploadUrl": "/mock-buffer/", "vmUploadPrefix": "/mock-buffer/", } try: with (ROOT / "config.json").open(encoding="utf-8") as config_file: CONFIG = {**DEFAULT_CONFIG, **json.load(config_file)} except (OSError, json.JSONDecodeError): CONFIG = DEFAULT_CONFIG VERSION = "0.2.2" app = Flask( __name__, template_folder=str(ROOT / "site" / "templates"), static_folder=str(ROOT / "site" / "static"), ) # ----------------------------------------------------------------------------- # In-memory Mock Buffer (эмуляция WebDAV буфера на ВМ для локальных тестов) # ----------------------------------------------------------------------------- _mock_storage = {} _mock_storage_lock = threading.Lock() mock_buffer_bp = Blueprint("mock_buffer", __name__) @mock_buffer_bp.route("/mock-buffer/", methods=["PUT"]) def mock_put(key): """Принимает файл от браузера в RAM mock-буфера.""" data = request.get_data() with _mock_storage_lock: _mock_storage[key] = data log.info("MockBuffer: stored key=%s (%d bytes)", key, len(data)) return "", 201 @mock_buffer_bp.route("/mock-buffer/", methods=["GET"]) def mock_get(key): """Отдаёт файл по GET-запросу бэкенда (исходящий pull).""" with _mock_storage_lock: data = _mock_storage.get(key) if data is None: log.warning("MockBuffer: key not found key=%s", key) return "Not found", 404 return Response(data, mimetype="application/octet-stream") @mock_buffer_bp.route("/mock-buffer/", methods=["DELETE"]) def mock_delete(key): """Удаляет файл из RAM mock-буфера после успешного pull.""" with _mock_storage_lock: existed = _mock_storage.pop(key, None) is not None log.info("MockBuffer: deleted key=%s (existed=%s)", key, existed) return "", 204 @mock_buffer_bp.route("/mock-buffer/status", methods=["GET"]) def mock_status(): """Диагностика mock-буфера: количество и размер находящихся в нём файлов.""" with _mock_storage_lock: items = [{"key": k, "size": len(v)} for k, v in _mock_storage.items()] return jsonify({"count": len(items), "items": items}) # ----------------------------------------------------------------------------- # Регистрация Слоя 2 (upload_refs) с эмуляцией передачи в Слой 3 # ----------------------------------------------------------------------------- def on_file_received_for_layer3(sid: str, name: str, content: bytes): """Эмуляция отдачи файла в Слой 3 бизнес-логики (обфускация/парсинг).""" log.info("[Layer 3 Emulation] Ready to process file in session %s: name=%s, size=%d bytes", sid, name, len(content)) upload_refs_bp = create_upload_refs_blueprint({ "apiPrefix": "/api", "vmUploadPrefix": CONFIG.get("vmUploadPrefix", "/mock-buffer/"), "maxFileBytes": CONFIG.get("maxFileBytes", 50 * 1024 * 1024), "maxSessionBytes": CONFIG.get("maxSessionBytes", 500 * 1024 * 1024), "ttlSeconds": CONFIG.get("ttlSeconds", 30 * 60), "pullRetries": 2, "pullRetryDelay": 0.3, "onFileReceived": on_file_received_for_layer3, }) app.register_blueprint(mock_buffer_bp) app.register_blueprint(upload_refs_bp) # ----------------------------------------------------------------------------- # Роуты тестового стенда # ----------------------------------------------------------------------------- @app.route("/") def index(): """Возвращает demo-страницу со Слой 1 (picker) и Слой 2 (transit upload).""" return render_template("index.html", version=VERSION, config=CONFIG) @app.route("/health") def health(): """Liveness-проверка платформы.""" return "ok", 200 @app.route("/api/session//files", methods=["GET"]) def session_files(sid): """Проверка содержимого сессии в RAM бэкенда (для тестов и UI).""" files = get_files(sid) if files is None: return jsonify({"ok": False, "error": "Session not found"}), 404 return jsonify({ "ok": True, "session": sid, "files": [{"name": name, "size": len(content)} for name, content in files] }) @app.get("/upload-frontend/") def upload_frontend(filename): """Отдаёт исходные модули фронтенда из каталога upload/frontend.""" return send_from_directory(ROOT / "upload" / "frontend", filename) @app.get("/file-picker/") def file_picker_dist(filename): """Отдаёт собранный бандл из каталога dist.""" return send_from_directory(ROOT / "dist", filename) if __name__ == "__main__": app.run(debug=False, host="0.0.0.0", port=5000)