86400, 'results' => 604800, 'rows' => 7776000, ); /** * Results must outlive the image links handed out for them. */ public const MIN_RESULTS = 3600; /** * Files deleted per kind and run, so one run stays short on a large backlog. */ public const BATCH = 500; /** * Request storage. * * @var Requests */ private $requests; /** * Private files. * * @var FileStore */ private $files; /** * Seconds to keep each kind. * * @var array{uploads: int, results: int, rows: int} */ private $retention; /** * Returns the current Unix time. * * @var callable */ private $clock; /** * Creates the job. * * @param Requests $requests Request storage. * @param FileStore $files Private files. * @param array{uploads: int, results: int, rows: int} $retention From retention(). * @param callable $clock Returns the current Unix time. */ public function __construct( Requests $requests, FileStore $files, array $retention, callable $clock ) { $this->requests = $requests; $this->files = $files; $this->retention = $retention; $this->clock = $clock; } /** * Retention in seconds, with defaults and safe minimums. * * Photos must outlive their reference and any job using them; rows must outlive their results. * * @param mixed $retention Filtered settings. * @return array{uploads: int, results: int, rows: int} */ public static function retention( $retention ): array { $retention = is_array( $retention ) ? $retention : array(); $value = static function ( string $key ) use ( $retention ): int { $seconds = $retention[ $key ] ?? self::DEFAULTS[ $key ]; return is_numeric( $seconds ) ? (int) $seconds : self::DEFAULTS[ $key ]; }; $results = max( self::MIN_RESULTS, $value( 'results' ) ); return array( 'uploads' => max( TryOnService::PHOTO_LIFETIME + LeasePolicy::MAX_AGE, $value( 'uploads' ) ), 'results' => $results, 'rows' => max( $results, $value( 'rows' ) ), ); } /** * Runs one pass. * * @return array{uploads: int, results: int, expired: int, rows: int} What was removed. */ public function run(): array { $now = (int) ( $this->clock )(); $expired = $this->requests->expire_results( gmdate( 'Y-m-d H:i:s', $now - $this->retention['results'] ) ); $results = $this->delete( FileStore::RESULTS, $now - $this->retention['results'] ); $uploads = $this->delete( FileStore::UPLOADS, $now - $this->retention['uploads'] ); $rows = $this->requests->delete_created_before( gmdate( 'Y-m-d H:i:s', $now - $this->retention['rows'] ) ); return array( 'uploads' => $uploads, 'results' => $results, 'expired' => $expired, 'rows' => $rows, ); } /** * Deletes files of a kind older than $before. * * @param string $kind FileStore kind. * @param int $before Unix time. */ private function delete( string $kind, int $before ): int { $deleted = 0; foreach ( $this->files->stale( $kind, $before, self::BATCH ) as $path ) { if ( $this->files->delete( $path ) ) { ++$deleted; } } return $deleted; } }