Files
racket-chatgpt-bootstrap/private/racketzip.php
T
www-data 2f2e8869d6 Reorganize PHP internals and static assets
Move shared PHP code into private/, move JavaScript files into js/, and block direct access to private/. Remove unused API key and cache artifacts from the working tree.
2026-05-26 11:32:36 +02:00

197 lines
5.4 KiB
PHP

<?php
/*
* Shared handling for the Racket installation zip and its binary parts.
*/
define('RACKET_ZIP_FILE', dirname(__DIR__) . '/config/racket.zip');
define('RACKET_ZIP_DATA_DIR', dirname(__DIR__) . '/data');
define('RACKET_ZIP_PART_PREFIX', 'racket-part-');
define('RACKET_ZIP_MANIFEST_FILE', RACKET_ZIP_DATA_DIR . '/racket-parts.json');
function racket_zip_binary_chunk_bytes_for_base64_kib($kib)
{
return intdiv(((int)$kib) * 1024, 4) * 3;
}
function racket_zip_part_name($nr)
{
return RACKET_ZIP_PART_PREFIX . sprintf('%06d', $nr);
}
function racket_zip_part_file($partNumber)
{
return RACKET_ZIP_DATA_DIR . '/' . RACKET_ZIP_PART_PREFIX . $partNumber;
}
function racket_zip_ensure_data_dir()
{
if (!is_dir(RACKET_ZIP_DATA_DIR)) {
if (!mkdir(RACKET_ZIP_DATA_DIR, 0755, true)) {
throw new RuntimeException('Kan data directory niet maken: ' . RACKET_ZIP_DATA_DIR);
}
}
if (!is_writable(RACKET_ZIP_DATA_DIR)) {
throw new RuntimeException('Data directory is niet schrijfbaar: ' . RACKET_ZIP_DATA_DIR);
}
}
function racket_zip_remove_old_parts()
{
foreach (glob(RACKET_ZIP_DATA_DIR . '/' . RACKET_ZIP_PART_PREFIX . '*') ?: array() as $file) {
if (is_file($file)) {
@unlink($file);
}
}
if (is_file(RACKET_ZIP_MANIFEST_FILE)) {
@unlink(RACKET_ZIP_MANIFEST_FILE);
}
}
function split_racket_zip_parts($maxBase64Kib)
{
$maxBase64Bytes = (int)$maxBase64Kib * 1024;
$binaryChunkBytes = racket_zip_binary_chunk_bytes_for_base64_kib($maxBase64Kib);
if ($binaryChunkBytes < 3) {
throw new RuntimeException('Ongeldige racket.zip base64 chunk-instelling.');
}
if (!is_file(RACKET_ZIP_FILE) || !is_readable(RACKET_ZIP_FILE)) {
throw new RuntimeException('racket.zip ontbreekt of is niet leesbaar: ' . RACKET_ZIP_FILE);
}
racket_zip_ensure_data_dir();
racket_zip_remove_old_parts();
$in = fopen(RACKET_ZIP_FILE, 'rb');
if ($in === false) {
throw new RuntimeException('Kan racket.zip niet openen.');
}
$parts = array();
$nr = 1;
$totalBinaryBytes = 0;
while (!feof($in)) {
$buf = fread($in, $binaryChunkBytes);
if ($buf === false) {
fclose($in);
throw new RuntimeException('Fout bij lezen van racket.zip.');
}
if ($buf === '') {
break;
}
$name = racket_zip_part_name($nr);
$file = RACKET_ZIP_DATA_DIR . '/' . $name;
if (file_put_contents($file, $buf, LOCK_EX) === false) {
fclose($in);
throw new RuntimeException('Kan part niet schrijven: ' . $file);
}
$n = sprintf('%06d', $nr);
$binaryBytes = strlen($buf);
$parts[] = array(
'number' => $n,
'name' => $name,
'file' => $file,
'size' => $binaryBytes,
);
$totalBinaryBytes += $binaryBytes;
$nr++;
}
fclose($in);
if (count($parts) === 0) {
throw new RuntimeException('racket.zip lijkt leeg te zijn.');
}
$manifest = array(
'source_file' => RACKET_ZIP_FILE,
'source_bytes' => filesize(RACKET_ZIP_FILE),
'source_mtime' => filemtime(RACKET_ZIP_FILE),
'source_sha256' => hash_file('sha256', RACKET_ZIP_FILE),
'max_base64_kib' => (int)$maxBase64Kib,
'max_base64_bytes' => $maxBase64Bytes,
'binary_chunk_bytes' => $binaryChunkBytes,
'part_count' => count($parts),
'total_binary_bytes' => $totalBinaryBytes,
'parts' => $parts,
'created_at' => gmdate('c'),
);
if (file_put_contents(
RACKET_ZIP_MANIFEST_FILE,
json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n",
LOCK_EX
) === false) {
throw new RuntimeException('Kan racket.zip manifest niet schrijven: ' . RACKET_ZIP_MANIFEST_FILE);
}
return $manifest;
}
function load_racket_zip_parts_manifest()
{
if (!is_file(RACKET_ZIP_MANIFEST_FILE) || !is_readable(RACKET_ZIP_MANIFEST_FILE)) {
throw new RuntimeException('racket.zip parts-manifest ontbreekt: ' . RACKET_ZIP_MANIFEST_FILE);
}
$raw = file_get_contents(RACKET_ZIP_MANIFEST_FILE);
if ($raw === false) {
throw new RuntimeException('Kan racket.zip parts-manifest niet lezen: ' . RACKET_ZIP_MANIFEST_FILE);
}
$manifest = json_decode($raw, true);
if (!is_array($manifest)) {
throw new RuntimeException('racket.zip parts-manifest is geen geldige JSON: ' . RACKET_ZIP_MANIFEST_FILE);
}
return $manifest;
}
function racket_zip_parts_current($manifest, $maxBase64Kib)
{
if (!is_array($manifest)) {
return false;
}
if ((int)($manifest['max_base64_kib'] ?? 0) !== (int)$maxBase64Kib) {
return false;
}
if (!is_file(RACKET_ZIP_FILE)) {
return false;
}
if ((int)($manifest['source_bytes'] ?? -1) !== (int)filesize(RACKET_ZIP_FILE)) {
return false;
}
if ((int)($manifest['source_mtime'] ?? -1) !== (int)filemtime(RACKET_ZIP_FILE)) {
return false;
}
foreach (($manifest['parts'] ?? array()) as $part) {
$number = (string)($part['number'] ?? '');
$file = racket_zip_part_file($number);
if (!preg_match('/^[0-9]{6}$/', $number) || !is_file($file) || !is_readable($file)) {
return false;
}
}
return count($manifest['parts'] ?? array()) > 0;
}