Export…: where to save the XVA file (typed, or the system's Save dialog when this target has one: a desktop app), compressed * or not. A browser cannot write a file of that size anywhere: the dialog says so. Closes with ['xva' => ['op' => 'export', ...]], * src/Feature/Xva/XvaTransfers streams it. * * @property Label $heading * @property Label $where * @property TextBox $path * @property Button $browse * @property CheckBox $compress * @property Label $size * @property Label $error * @property Button $export * @property Button $cancel */ final class ExportForm extends Form { private array $params = []; public function __construct(private Session $session) { parent::__construct(); } /** XAPI's answers, events and the console go on while this dialog is open. */ protected function tick(float $dt): void { $this->session->tick($dt); } /** @return array */ public static function paramsFor(Connection $c, string $vm): array { $size = 0.0; foreach ($c->cache->disksOf($vm) as $d) { if ($d['vdi'] !== null && ($d['vbd']['type'] ?? '') !== 'CD') { $size += (float)($d['vdi']['physical_utilisation'] ?? 0); } } $name = $c->cache->name($vm); $dir = rtrim((string)(getenv('HOME') ?: sys_get_temp_dir()), '/'); return ['target' => $vm, 'name' => $name, 'server' => $c->name(), 'size' => $size, 'file' => $dir . '/' . (trim((string)preg_replace('/[^A-Za-z0-9._-]+/', '-', $name), '-') ?: 'vm') . '.xva', 'halted' => in_array($c->cache->field($vm, 'power_state', ''), ['Halted'], true) || !empty($c->cache->field($vm, 'is_a_snapshot'))]; } protected function loaded(array $params = []): void { $this->params = $params; $this->heading->text = "Export VM '" . ($params['name'] ?? '') . "'"; $this->where->text = "In '" . ($params['server'] ?? '') . "'"; $this->path->text = (string)($params['file'] ?? ''); $this->browse->enabled = Native::supports('save'); $this->size->text = 'Its disks hold ' . Units::bytes((float)($params['size'] ?? 0)) . ': the file is about that size (less when compressed).'; $this->error->text = ''; $this->export->enabled = true; if (Platform::isWasm()) { // a web page can only hand a file to the browser's downloads, all in memory: not for a VM's disks $this->error->text = 'Exporting to a file is not possible in a browser (a VM does not fit in the page\'s memory): use XenCenter on the desktop, or xe vm-export on the server.'; $this->export->enabled = false; } elseif (empty($params['halted'])) { $this->error->text = 'Shut the VM down first: XAPI exports halted VMs (or snapshots) only.'; $this->export->enabled = false; } $this->setFocus($this->path); } public function onBrowseClick(Button $sender): void { if (!Native::supports('save')) { return; } FilePicker::save(['title' => 'Export VM', 'file_name' => basename($this->path->text), 'directory' => dirname($this->path->text), 'filters' => ['XVA files' => ['xva']]])->then(function (?string $file): void { if ($file !== null) { $this->path->text = str_ends_with(strtolower($file), '.xva') ? $file : $file . '.xva'; } }, fn (\Throwable $e) => $this->error->text = $e->getMessage()); } public function onExportClick(mixed $sender = null): void { if (!$this->export->enabled) { return; } $file = trim($this->path->text); if ($file === '' || str_ends_with($file, '/')) { $this->error->text = 'Enter the file to write.'; return; } if (!is_dir(dirname($file)) || !is_writable(dirname($file))) { $this->error->text = 'The folder ' . dirname($file) . ' does not exist or cannot be written.'; return; } $this->close(['xva' => ['op' => 'export', 'target' => $this->params['target'] ?? '', 'file' => $file, 'compress' => $this->compress->checked]]); } public function onCancelClick(Button $sender): void { $this->close(); } }