<?php
/**
 * STEP 查看器 - 上传接口（PHP 版，虚拟主机可用）
 * 接收原始字节流 + X-Filename 头，加密存储为 uploads/xxx.enc
 * 生产环境请修改 SECRET！
 */
header('Content-Type: application/json; charset=utf-8');

$SECRET = '7175a8598ff7fa1c182f57f0c750e4f8';
$MAX_SIZE = 500 * 1024 * 1024;
// 私有目录：Web 目录外的绝对路径，模型只存这里，web 目录里根本没有模型文件可下载。
// 群晖 File Station 在 web 目录外建 /volume1/stepviewer_private 并给 http 用户组读写权限；
// 路径不同就改成你的实际路径。
$PRIVATE_DIR = '/volume1/stepviewer_private';

function json_out($code, $obj) {
    http_response_code($code);
    echo json_encode($obj, JSON_UNESCAPED_UNICODE);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') json_out(405, ['error' => 'method not allowed']);

$len = (int)($_SERVER['CONTENT_LENGTH'] ?? 0);
if ($len <= 0) json_out(400, ['error' => 'empty body']);
if ($len > $MAX_SIZE) json_out(413, ['error' => 'file too large (max 500MB)']);

$raw = urldecode($_SERVER['HTTP_X_FILENAME'] ?? 'model.step');
$base = basename($raw);
$safe = preg_replace('/[^\w.\-\x{4e00}-\x{9fff}]/u', '_', $base);
if (!$safe || !preg_match('/\.(step|stp)$/i', $safe)) $safe .= '.stp';
$stem = pathinfo($safe, PATHINFO_FILENAME);
$ext = pathinfo($safe, PATHINFO_EXTENSION);
$name = $stem . '_' . substr(bin2hex(random_bytes(4)), 0, 8) . '.' . $ext;

$data = file_get_contents('php://input');
if ($data === false || strlen($data) !== $len) json_out(400, ['error' => 'incomplete upload']);

// 客户端可选 gzip 压缩上传（STEP 文本约可压到 1/6，节省带宽）
if (($_SERVER['HTTP_X_ENCODING'] ?? '') === 'gzip') {
    $dec = gzdecode($data);
    if ($dec === false) json_out(400, ['error' => 'bad gzip body']);
    if (strlen($dec) > $MAX_SIZE) json_out(413, ['error' => 'file too large (max 500MB)']);
    $data = $dec;
}

// 存储层也压缩：先 gzip 再加密，磁盘占用约 1/6（读取端靠 gzip 魔数自动识别）
$rawSize = strlen($data);
$data = gzencode($data, 6);
if ($data === false) json_out(500, ['error' => 'gzip failed']);

$dir = $PRIVATE_DIR !== '' ? $PRIVATE_DIR : (__DIR__ . '/../uploads');
if (!is_dir($dir)) @mkdir($dir, 0755, true);
if (!is_dir($dir)) json_out(500, ['error' => 'upload dir not writable']);

$nonce = random_bytes(16);
$key = hash('sha256', $SECRET . $nonce, true);
$enc = '';
for ($i = 0; $i < strlen($data); $i++) {
    $enc .= $data[$i] ^ $key[$i % strlen($key)];
}

file_put_contents($dir . '/' . $name . '.enc', $nonce . $enc);
// 私有目录模式下返回裸文件名：嵌入链接就是 ?file=xxx.enc，读取端按 basename 在私有目录找
$entryPath = ($PRIVATE_DIR !== '' ? '' : 'uploads/') . $name . '.enc';

// 记入首页数模库（入口目录的 library.json，2D/3D 共享）；库写失败不影响上传本身
try {
    add_to_library($entryPath, $safe, '3d');
} catch (Throwable $e) { /* 忽略 */ }

json_out(200, ['path' => $entryPath, 'size' => $rawSize]);

/**
 * 把刚上传的文件追加进首页数模库（../../library.json）：
 * 同路径已有记录就刷新名称和时间，否则插到最前。
 */
function add_to_library($path, $name, $type) {
    $libFile = dirname(__DIR__, 2) . '/library.json';
    $records = [];
    if (is_file($libFile)) {
        $old = json_decode((string)file_get_contents($libFile), true);
        if (is_array($old) && isset($old['records']) && is_array($old['records'])) $records = $old['records'];
    }
    $found = false;
    foreach ($records as $i => $rec) {
        if (isset($rec['path']) && $rec['path'] === $path) {
            $records[$i]['name'] = $name;
            $records[$i]['time'] = time();
            $found = true;
            break;
        }
    }
    if (!$found) {
        array_unshift($records, ['path' => $path, 'name' => $name, 'type' => $type, 'note' => '', 'time' => time()]);
    }
    @file_put_contents($libFile, json_encode(['records' => $records], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
}
