.zip.part, the central-directory sidecar * ..cd and the file list ..list. After every request the archive length and entry count are * saved in the job, so an interrupted request can be cut back to the last saved state. */ final class BackupArchive { public static function part(Job $job): string { return Paths::backups_dir() . '/' . $job->stats['backup']['file'] . '.part'; } public static function final_path(Job $job): string { return Paths::backups_dir() . '/' . $job->stats['backup']['file']; } public static function cd(Job $job): string { return Paths::backups_dir() . '/.' . $job->id . '.cd'; } public static function list(Job $job): string { return Paths::backups_dir() . '/.' . $job->id . '.list'; } public static function writer(Job $job): ZipWriter { return new ZipWriter(self::part($job), self::cd($job)); } public static function save(Job $job, ZipWriter $writer): void { $job->progress['zip'] = ['bytes' => $writer->length(), 'entries' => $writer->entry_count()]; } public static function rewind(Job $job): void { if (!empty($job->stats['backup']['file']) && is_file(self::part($job))) { self::writer($job)->rewind((int) ($job->progress['zip']['bytes'] ?? 0), (int) ($job->progress['zip']['entries'] ?? 0)); } } /** Remove everything an unfinished backup left behind. */ public static function discard(Job $job): void { if (!empty($job->stats['backup']['file'])) { wp_delete_file(self::part($job)); } wp_delete_file(self::cd($job)); wp_delete_file(self::list($job)); } }