151 lines
5.7 KiB
Python
151 lines
5.7 KiB
Python
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.1"
|
|
|
|
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/<path:key>", 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/<path:key>", 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/<path:key>", 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/<sid>/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/<path:filename>")
|
|
def upload_frontend(filename):
|
|
"""Отдаёт исходные модули фронтенда из каталога upload/frontend."""
|
|
return send_from_directory(ROOT / "upload" / "frontend", filename)
|
|
|
|
|
|
@app.get("/file-picker/<path:filename>")
|
|
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)
|