archive-ccc8458/video/lib/chunked.php 2.6 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
<?php
declare(strict_types=1);
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/hash.php';

// Outside the videos dir; blocked from web access by routes in .htaccess
// plus a deny .htaccess we drop into the directory at creation time.
const UPLOADS_DIR = __DIR__ . '/../.uploads';

function ensure_uploads_dir(): void {
    if (!is_dir(UPLOADS_DIR)) {
        @mkdir(UPLOADS_DIR, 0700, true);
    }
    $deny = UPLOADS_DIR . '/.htaccess';
    if (!is_file($deny)) {
        @file_put_contents($deny, "Require all denied\nOptions -Indexes\n");
    }
}

function is_valid_upload_id(string $id): bool {
    return (bool) preg_match('/^[a-f0-9]{32}$/', $id);
}

function upload_dir(string $id): ?string {
    if (!is_valid_upload_id($id)) return null;
    return UPLOADS_DIR . '/' . $id;
}

function read_meta(string $id): ?array {
    $dir = upload_dir($id);
    if ($dir === null) return null;
    $f = $dir . '/meta.json';
    if (!is_file($f)) return null;
    $j = json_decode((string) file_get_contents($f), true);
    return is_array($j) ? $j : null;
}

function write_meta(string $id, array $meta): bool {
    $dir = upload_dir($id);
    if ($dir === null) return false;
    if (!is_dir($dir)) @mkdir($dir, 0700, true);
    $bytes = file_put_contents($dir . '/meta.json', json_encode($meta), LOCK_EX);
    return $bytes !== false;
}

function chunk_path(string $id, int $index): ?string {
    $dir = upload_dir($id);
    if ($dir === null) return null;
    if ($index < 0 || $index >= MAX_CHUNKS) return null;
    return $dir . '/chunk-' . str_pad((string) $index, 5, '0', STR_PAD_LEFT) . '.bin';
}

function delete_upload_dir(string $id): void {
    $dir = upload_dir($id);
    if ($dir === null || !is_dir($dir)) return;
    foreach (glob($dir . '/*') ?: [] as $f) @unlink($f);
    @rmdir($dir);
}

function gc_orphan_uploads(): void {
    if (!is_dir(UPLOADS_DIR)) return;
    $cutoff = time() - UPLOAD_TTL_SEC;
    foreach (scandir(UPLOADS_DIR) ?: [] as $entry) {
        if ($entry === '.' || $entry === '..' || !is_valid_upload_id($entry)) continue;
        $dir = UPLOADS_DIR . '/' . $entry;
        if (!is_dir($dir)) continue;
        $meta = $dir . '/meta.json';
        $mtime = is_file($meta) ? filemtime($meta) : filemtime($dir);
        if ($mtime !== false && $mtime < $cutoff) {
            foreach (glob($dir . '/*') ?: [] as $f) @unlink($f);
            @rmdir($dir);
        }
    }
}

function chunked_uploaded_indices(string $id, int $expected): array {
    $present = [];
    for ($i = 0; $i < $expected; $i++) {
        $p = chunk_path($id, $i);
        if ($p !== null && is_file($p)) $present[] = $i;
    }
    return $present;
}