$s !== '' && $s !== '.' && $s !== '..'); return '/' . implode('/', array_values($parts)); } function rmapiRun(string $bin, array $args): array { $cmd = $bin; foreach ($args as $a) $cmd .= ' ' . escapeshellarg((string)$a); exec($cmd . ' 2>&1', $out, $code); return ['out' => $out, 'ok' => $code === 0]; } function addWebDeleted(string $backup, string $path): void { // Pfad ohne Erweiterung speichern (deckt .pdf und .rmdoc ab) $clean = preg_replace('/\.(pdf|rmdoc)$/i', '', $path); $file = dirname(rtrim($backup, '/')) . '/.web_deleted'; file_put_contents($file, $clean . "\n", FILE_APPEND | LOCK_EX); } function rmapiRmRecursive(string $bin, string $cloudPath): void { // rmapi find gibt Pfade relativ zum Parent zurück, z.B. // find /trash/Scoreblatt → "[f] Scoreblatt/Notebook 4" // Daher Parent-Pfad vorbauen $parent = rtrim(dirname($cloudPath), '/'); $out = []; exec($bin . ' find ' . escapeshellarg($cloudPath) . ' 2>/dev/null', $out); $files = []; $dirs = []; foreach ($out as $line) { if (preg_match('/^\[(f|d)\] (.+)$/', trim($line), $m)) { $abs = $parent . '/' . ltrim($m[2], '/'); if ($m[1] === 'f') $files[] = $abs; else $dirs[] = $abs; } } // Dateien zuerst löschen foreach ($files as $p) rmapiRun($bin, ['rm', $p]); // Ordner von tief nach oben löschen rsort($dirs); // tiefste zuerst via Länge usort($dirs, fn($a,$b) => strlen($b) - strlen($a)); foreach ($dirs as $p) rmapiRun($bin, ['rm', $p]); // Den Pfad selbst löschen (falls nicht schon in $dirs) if (!in_array($cloudPath, $dirs)) rmapiRun($bin, ['rm', $cloudPath]); } function listDir(string $backup, string $path, bool $showRmdoc, bool $showTrash = true): array { $dir = rtrim($backup, '/') . ($path === '/' ? '' : $path); if (!is_dir($dir)) return []; $items = []; foreach (scandir($dir) ?: [] as $entry) { if ($entry === '.' || $entry === '..') continue; if ($entry === 'trash' && $path === '/' ) continue; $full = $dir . '/' . $entry; $lname = strtolower($entry); $isRmdoc = str_ends_with($lname, '.rmdoc'); $isPdf = str_ends_with($lname, '.pdf'); $isDir = is_dir($full); if (!$isDir && !$isPdf && !($showRmdoc && $isRmdoc)) continue; $items[] = [ 'name' => $entry, 'type' => $isDir ? 'folder' : 'file', 'size' => is_file($full) ? filesize($full) : 0, 'mtime' => filemtime($full), 'is_pdf' => $isPdf, 'is_rmdoc' => $isRmdoc, ]; } return $items; } function formatSize(int $bytes): string { if ($bytes < 1024) return $bytes . ' B'; if ($bytes < 1048576) return round($bytes / 1024, 1) . ' KB'; return round($bytes / 1048576, 1) . ' MB'; } $action = $_POST['action'] ?? $_GET['action'] ?? ''; $path = safePath($_POST['path'] ?? $_GET['path'] ?? '/'); $flash = ''; $flashOk = true; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($action === 'delete') { $target = safePath($_POST['target'] ?? ''); $localTarget = realpath($BACKUP . $target); $realBackup = realpath($BACKUP); if ($target !== '/' && $localTarget !== false && $realBackup !== false && str_starts_with($localTarget, $realBackup . '/')) { $wasDir = is_dir($localTarget); $cloudTarget = preg_replace('/\.(pdf|rmdoc)$/i', '', $target); if ($wasDir) { shell_exec('rm -rf ' . escapeshellarg($localTarget)); clearstatcache(true, $localTarget); $ok = !is_dir($localTarget); } else { $ok = @unlink($localTarget); if (str_ends_with(strtolower($target), '.pdf')) { @unlink($BACKUP . substr($target, 0, -4) . '.rmdoc'); @unlink($BACKUP . substr($target, 0, -4) . '.thumb.jpg'); } } if ($wasDir) { rmapiRmRecursive($RMAPI, $cloudTarget); $rmResult = ['ok' => true]; } else { $rmResult = rmapiRun($RMAPI, ['rm', $cloudTarget]); } if ($ok) { addWebDeleted($BACKUP, $target); $flash = 'Gelöscht: ' . $target; $flashOk = true; if (!$rmResult['ok']) $flash .= ' (wird beim nächsten Sync erneut entfernt)'; } else { $flash = 'Fehler beim Löschen.'; $flashOk = false; } } } if ($action === 'delete_multi') { $targets = is_array($_POST['targets'] ?? null) ? $_POST['targets'] : []; $deleted = 0; $realBackup = realpath($BACKUP); foreach ($targets as $t) { $t = safePath($t); $lt = $t !== '/' ? realpath($BACKUP . $t) : false; if (!$lt || !$realBackup || !str_starts_with($lt, $realBackup . '/')) continue; $ct = preg_replace('/\.(pdf|rmdoc)$/i', '', $t); if (is_dir($lt)) { shell_exec('rm -rf ' . escapeshellarg($lt)); clearstatcache(true, $lt); if (!is_dir($lt)) { $deleted++; addWebDeleted($BACKUP, $t); rmapiRmRecursive($RMAPI, $ct); } } else { if (@unlink($lt)) { $deleted++; clearstatcache(true, $lt); addWebDeleted($BACKUP, $t); rmapiRun($RMAPI, ['rm', $ct]); } } } $flash = $deleted . ' Element(e) gelöscht.'; $flashOk = true; } if ($action === 'mkdir') { $name = trim($_POST['name'] ?? ''); $newPath = safePath(rtrim($path, '/') . '/' . $name); if ($name !== '') { $r = rmapiRun($RMAPI, ['mkdir', $newPath]); if ($r['ok']) @mkdir($BACKUP . $newPath, 0755, true); $flash = $r['ok'] ? 'Ordner erstellt: ' . $newPath : 'Fehler: ' . implode(' ', $r['out']); $flashOk = $r['ok']; } } if ($action === 'upload') { $name = trim($_FILES['file']['name'] ?? ''); if ($name !== '' && isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) { $safeName = preg_replace('/[^a-zA-Z0-9_\-\. ]/', '_', $name); $tmpFile = $TMP . '/' . $safeName; move_uploaded_file($_FILES['file']['tmp_name'], $tmpFile); $r = rmapiRun($RMAPI, ['put', $tmpFile, rtrim($path, '/')]); @unlink($tmpFile); $flash = $r['ok'] ? 'Hochgeladen: ' . $name : 'Fehler: ' . implode(' ', $r['out']); $flashOk = $r['ok']; } } if ($action === 'move') { $src = safePath($_POST['src'] ?? ''); $dst = safePath($_POST['dst'] ?? ''); if ($src !== '/' && $dst !== '' && $src !== $dst) { $strip = fn(string $p) => preg_replace('/\.(pdf|rmdoc)$/i', '', $p); $r = rmapiRun($RMAPI, ['mv', $strip($src), $strip($dst)]); if ($r['ok']) { $localSrc = realpath($BACKUP . $src); if ($localSrc) @rename($localSrc, $BACKUP . $dst); $rmdocSrc = realpath($BACKUP . $strip($src) . '.rmdoc'); if ($rmdocSrc) @rename($rmdocSrc, $BACKUP . $strip($dst) . '.rmdoc'); $thumbSrc = realpath($BACKUP . $strip($src) . '.thumb.jpg'); if ($thumbSrc) @rename($thumbSrc, $BACKUP . $strip($dst) . '.thumb.jpg'); } $flash = $r['ok'] ? 'Verschoben nach: ' . $dst : 'Fehler: ' . implode(' ', $r['out']); $flashOk = $r['ok']; } } if ($action === 'move_multi') { $targets = is_array($_POST['targets'] ?? null) ? $_POST['targets'] : []; $destDir = safePath($_POST['destination'] ?? ''); $moved = 0; $strip = fn(string $p) => preg_replace('/\.(pdf|rmdoc)$/i', '', $p); foreach ($targets as $t) { $t = safePath($t); if ($t === '/' || $destDir === '') continue; $name = basename($t); $dst = safePath($destDir . '/' . $name); if ($dst === $t) continue; $r = rmapiRun($RMAPI, ['mv', $strip($t), $strip($dst)]); if ($r['ok']) { $lt = realpath($BACKUP . $t); if ($lt) @rename($lt, $BACKUP . $dst); $rs = realpath($BACKUP . $strip($t) . '.rmdoc'); if ($rs) @rename($rs, $BACKUP . $strip($dst) . '.rmdoc'); $rt = realpath($BACKUP . $strip($t) . '.thumb.jpg'); if ($rt) @rename($rt, $BACKUP . $strip($dst) . '.thumb.jpg'); $moved++; } } $flash = $moved . ' Element(e) verschoben nach: ' . $destDir; $flashOk = true; } } if ($action === 'folders') { header('Content-Type: application/json'); $browsePath = safePath($_GET['path'] ?? '/'); $dir = rtrim($BACKUP, '/') . ($browsePath === '/' ? '' : $browsePath); $folders = []; foreach (scandir($dir) ?: [] as $e) { if ($e === '.' || $e === '..') continue; if (is_dir($dir . '/' . $e)) $folders[] = $e; } echo json_encode(['path' => $browsePath, 'folders' => $folders]); exit; } if ($action === 'view') { $target = safePath($_GET['target'] ?? ''); $realLocal = realpath($BACKUP . $target); $realBackup = realpath($BACKUP); if (!$realLocal || !$realBackup || !str_starts_with($realLocal, $realBackup . '/')) { http_response_code(403); echo 'Zugriff verweigert.'; exit; } if (!is_file($realLocal)) { http_response_code(404); echo 'Nicht gefunden.'; exit; } $ext = strtolower(pathinfo($realLocal, PATHINFO_EXTENSION)); $mime = match($ext) { 'pdf' => 'application/pdf', 'jpg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', default => 'application/octet-stream' }; header('Content-Type: ' . $mime); header('Content-Disposition: inline; filename="' . basename($realLocal) . '"'); header('Content-Length: ' . filesize($realLocal)); readfile($realLocal); exit; } $showRmdoc = isset($_GET['rmdoc']) && $_GET['rmdoc'] === '1'; $showTrash = isset($_GET['trash']) && $_GET['trash'] === '1'; $items = listDir($BACKUP, $path, $showRmdoc, $showTrash); $sort = $_GET['sort'] ?? 'name'; $order = $_GET['order'] ?? 'asc'; usort($items, function ($a, $b) use ($sort, $order) { if ($a['type'] !== $b['type']) return $a['type'] === 'folder' ? -1 : 1; $cmp = strnatcasecmp($a['name'], $b['name']); return $order === 'desc' ? -$cmp : $cmp; }); $breadcrumbs = [['label' => 'reMarkable', 'path' => '/']]; $parts = array_filter(explode('/', $path)); $acc = ''; foreach ($parts as $p) { $acc .= '/' . $p; $breadcrumbs[] = ['label' => $p, 'path' => $acc]; } $parentPath = count($parts) > 0 ? safePath(dirname($path)) : '/'; $folders = array_values(array_filter($items, fn($i) => $i['type'] === 'folder')); $files = array_values(array_filter($items, fn($i) => $i['type'] === 'file')); ?> reMarkable
reMarkable
' : '' ?>
Einträge rmdoc

Keine Dokumente vorhanden

Backup noch nicht ausgeführt oder Ordner ist leer

Ordner

Dokumente

0): ?> ·