Files
nextcloud-ng_backup/lib/Service/RetentionPolicy.php
T
dillard 450e1a5ee6 M5: retention policy, prune with repacking, append-only locations
- RetentionPolicy (last/daily/weekly/monthly) and PruneService: forget
  snapshots the policy drops, delete packs nothing uses and repack mostly
  unused ones by copying encrypted blobs as-is; new packs are indexed
  before old ones are removed
- a location lock keeps prune and backups apart; append-only locations
  are never pruned (cleanup via the location's lifecycle rules)
- weekly prune from the background job; occ backup:prune [--dry-run],
  backup:retention, backup:target:append-only
- tests: forget+prune frees space and the remaining snapshot restores
  identically, repack of mixed packs, dry-run preview, retention buckets
2026-10-02 02:21:48 +02:00

61 lines
1.6 KiB
PHP

<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: 2026 Dillard Blom
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\NgBackup\Service;
/**
* Which snapshots to keep: the newest $last, plus the newest snapshot of each of the most recent
* $daily days, $weekly ISO weeks and $monthly months that have a snapshot (like restic/borg).
*/
final class RetentionPolicy {
public function __construct(
public readonly int $last = 3,
public readonly int $daily = 7,
public readonly int $weekly = 4,
public readonly int $monthly = 6,
) {
}
public static function fromArray(array $a): self {
return new self((int)($a['last'] ?? 3), (int)($a['daily'] ?? 7), (int)($a['weekly'] ?? 4), (int)($a['monthly'] ?? 6));
}
public function toArray(): array {
return ['last' => $this->last, 'daily' => $this->daily, 'weekly' => $this->weekly, 'monthly' => $this->monthly];
}
/**
* @param array<string, int> $snapshots id => unix time
* @return array<string, list<string>> id => reasons it is kept (ids not in the result are removed)
*/
public function keep(array $snapshots): array {
arsort($snapshots);
$keep = [];
$i = 0;
foreach ($snapshots as $id => $time) {
if ($i++ < $this->last) {
$keep[$id][] = 'last';
}
}
foreach (['daily' => 'Y-m-d', 'weekly' => 'o-W', 'monthly' => 'Y-m'] as $kind => $format) {
$seen = [];
foreach ($snapshots as $id => $time) {
$bucket = date($format, $time);
if (isset($seen[$bucket])) {
continue;
}
if (count($seen) >= $this->$kind) {
break;
}
$seen[$bucket] = true;
$keep[$id][] = $kind;
}
}
return $keep;
}
}