clock = $clock ?? 'time'; } /** @return array{outcome: string, message: string} */ public function run(string $trigger): array { if (!$this->config->enabled) { return ['outcome' => 'off', 'message' => __('Backups are off on this site, so nothing ran.', 'freshet-backup')]; } if (!$this->lock->acquire()) { return ['outcome' => 'busy', 'message' => __('A backup is already running, so this one was skipped. The screen shows its result when it finishes.', 'freshet-backup')]; } try { return $this->record($trigger, $this->work()); } catch (\Throwable $e) { return $this->record($trigger, ['outcome' => 'failed', 'message' => $e instanceof DumpError || $e instanceof DestinationError ? $e->getMessage() : sprintf( /* translators: %s: the error */ __('The backup failed with an unexpected error (%s), so nothing was saved from this run. Try Run backup now; if it happens again, send this message to support.', 'freshet-backup'), $e->getMessage() )]); } finally { $this->lock->release(); } } /** @return array */ private function work(): array { $this->buffer->clearConfigCopies(); $refusal = $this->buffer->prepare($this->loopback); if ($refusal !== null) { return ['outcome' => 'refused', 'message' => $refusal]; } $this->buffer->clearPartials(); $stranded = $this->buffer->adopt(); $note = $stranded === [] ? '' : ' ' . sprintf( /* translators: 1: number of files, 2: folder path, 3: folder path */ _n( '%1$d older backup is still in %2$s, the folder this plugin used before, because it could not be moved to %3$s. It was not sent to Dropbox. Move it there by hand and the next run sends it.', '%1$d older backups are still in %2$s, the folder this plugin used before, because they could not be moved to %3$s. They were not sent to Dropbox. Move them there by hand and the next run sends them.', count($stranded), 'freshet-backup' ), count($stranded), (string) $this->buffer->legacy, $this->buffer->dir ); $path = $this->buffer->newPath(($this->clock)()); $started = microtime(true); $dump = $this->dumper->dump($path, ['Site' => $this->host, 'Plugin version' => defined('FRESHET_BACKUP_VERSION') ? FRESHET_BACKUP_VERSION : '']); $run = [ 'file' => basename($path), 'bytes' => $dump['bytes'], 'tables' => $dump['tables'], 'rows' => $dump['rows'], 'seconds' => round(microtime(true) - $started, 1), ]; $sent = []; $error = null; if ($this->destination !== null) { foreach ($this->buffer->files() as $file) { try { $this->destination->store($file, basename($file)); } catch (DestinationError $e) { $error = $e->getMessage(); break; } wp_delete_file($file); $sent[] = basename($file); } } $this->buffer->prune($this->config->keepLocal); if ($error === null && $this->destination !== null) { try { $this->destination->prune($this->config->keepRemote, $this->buffer->filePattern()); } catch (DestinationError $e) { $error = $e->getMessage(); } } $run['sent'] = $sent; if ($error !== null) { return $run + ['outcome' => 'kept', 'message' => $error . $note]; } if ($this->destination === null) { return $run + ['outcome' => 'ok', 'message' => sprintf( /* translators: %s: file name */ __('Backup %s saved on this server only, because Dropbox is not connected.', 'freshet-backup'), $run['file'] ) . $note]; } return $run + ['outcome' => 'ok', 'message' => sprintf( /* translators: 1: file name, 2: destination name */ __('Backup %1$s is in %2$s and verified.', 'freshet-backup'), $run['file'], $this->destination->label() ) . $note]; } /** * @param array $run * @return array{outcome: string, message: string} */ private function record(string $trigger, array $run): array { $this->status->record($run + ['trigger' => $trigger, 'time' => ($this->clock)()]); return ['outcome' => (string) $run['outcome'], 'message' => (string) $run['message']]; } }