M2: full backup runs (database, config, data) with occ and cron
BackupService starts a run per location (database dump in one transaction, then config + data directory as a resumable file run), stores the state in ngb_runs after every step, locks a run while a step executes and records the snapshot. Previews, logs, updater backups and per-user cache/uploads are excluded (TreeWalker now supports patterns). BackupJob (every 5 min) starts the daily schedule and steps running runs with a short budget under web cron and a long one under system cron. occ: backup:run, backup:status, backup:list. Tested on NC34/PostgreSQL: first run 128 files / 1 GiB in 64 s (443 MiB uploaded to S3), unchanged second run 2 s / 0 bytes; a background run to SFTP completed through cron.php.
This commit is contained in:
+18
-13
@@ -17,7 +17,8 @@ namespace OCA\NgBackup\Job;
|
||||
final class TreeWalker {
|
||||
/**
|
||||
* @param array<string, string> $roots name => absolute directory, walked in this order
|
||||
* @param list<string> $exclude path prefixes ("<root>/<relative>") that are skipped entirely
|
||||
* @param list<string> $exclude path prefixes ("<root>/<relative>") that are skipped entirely;
|
||||
* a pattern with "*" matches one path segment (fnmatch, FNM_PATHNAME)
|
||||
*/
|
||||
public function __construct(
|
||||
private array $roots,
|
||||
@@ -51,10 +52,8 @@ final class TreeWalker {
|
||||
}
|
||||
|
||||
private function walkDir(string $abs, string $logical, ?string $after): \Generator {
|
||||
foreach ($this->exclude as $prefix) {
|
||||
if ($logical !== '' && ($logical === $prefix || str_starts_with($logical, $prefix . '/'))) {
|
||||
return;
|
||||
}
|
||||
if ($logical !== '' && $this->isExcluded($logical, true)) {
|
||||
return;
|
||||
}
|
||||
$names = @scandir($abs);
|
||||
if ($names === false) {
|
||||
@@ -78,20 +77,26 @@ final class TreeWalker {
|
||||
if ($after !== null && self::compare($childLogical, $after) <= 0) {
|
||||
continue;
|
||||
}
|
||||
$excluded = false;
|
||||
foreach ($this->exclude as $prefix) {
|
||||
if ($childLogical === $prefix) {
|
||||
$excluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$excluded) {
|
||||
if (!$this->isExcluded($childLogical, false)) {
|
||||
yield $childLogical => $childAbs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isExcluded(string $logical, bool $isDir): bool {
|
||||
foreach ($this->exclude as $pattern) {
|
||||
if (str_contains($pattern, '*')) {
|
||||
if (fnmatch($pattern, $logical, FNM_PATHNAME)) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($logical === $pattern || ($isDir && str_starts_with($logical, $pattern . '/'))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function isPrefix(string $dir, string $path): bool {
|
||||
return str_starts_with($path, $dir . '/');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user