settings['files'] ?? null); } public static function any(Job $job): bool { return FileParts::any(self::parts($job)); } /** The site whose files these are: this one, or on a network the job's subsite. */ public static function scope(Job $job): Scope { return Scope::for_settings($job->settings); } /** @return array the job's file groups' folders on this site (a subsite's uploads are its own) */ public static function roots(Job $job): array { return Groups::roots(self::scope($job)); } /** The file list: built here (this site has the files) or received (this site gets them). */ public static function list(Job $job): FileList { return new FileList(Paths::job_dir($job->id) . '/manifest.jsonl'); } /** Comparison of the list with this site's files; its needed() list is also where a pushing site keeps the receiver's answer. */ public static function diff(Job $job): FileDiff { $scope = self::scope($job); return new FileDiff(self::list($job), Groups::roots($scope), self::parts($job), Paths::job_dir($job->id), SubsiteFiles::policies($scope), SubsiteFiles::untouched($scope)); } /** * The copy's progress for the screens (both directions): files and bytes copied so far, and which group — themes, * plugins, uploads or other — comes next; null once everything is copied. * * @param array $c the files:transfer cursor */ public static function report(Job $job, array $c, FileList $list, IndexList $needed): void { $job->stats['transfer']['copied'] = ['count' => (int) $c['count'], 'bytes' => (int) $c['bytes'], 'gone' => (int) $c['gone']]; $next = empty($c['done']) ? ($needed->slice((int) $c['pos'], 1)[0] ?? null) : null; $job->stats['transfer']['now'] = $next !== null ? ($list->get($next)['g'] ?? null) : null; } public static function unpacker(Job $job): Unpacker { return new Unpacker(self::list($job), Paths::staging_dir($job->id), self::parts($job)['low_disk'] ? self::roots($job)['uploads'] : null); } public static function swap(Job $job): FileSwap { return new FileSwap(Paths::journal($job->id)); } public static function placer(Job $job): FilePlacer { $diff = self::diff($job); $scope = self::scope($job); return new FilePlacer(self::list($job), $diff->needed(), $diff->extras_path(), Groups::roots($scope), Paths::staging_dir($job->id), self::swap($job), Paths::aside($job->id), SubsiteFiles::policies($scope)); } /** The file restore point to record with the job's restore point, when any file was moved. */ public static function point(Job $job): ?array { return is_file(Paths::journal($job->id)) ? ['journal' => Paths::journal($job->id), 'dir' => Paths::aside($job->id)] : null; } /** Undo file moves after the first $keep, and tidy the restore point folder. */ public static function put_back(Job $job, int $keep = 0): void { if (is_file(Paths::journal($job->id))) { self::swap($job)->rollback($keep); } Fs::remove_empty_dirs(Paths::aside($job->id)); } }