<?phpnamespace App\Model;use JumPHP\Kernel\Engine;/** * Everything the windows share: the servers (saved in var/servers.json), their connections, the Events log, the navigation * history (Back / Forward). One for the whole app (a service). */final class Session{ /** @var list<Connection> */ public array $connections = []; /** @var list<LogEntry> newest last */ public array $log = []; public float $clock = 0; /** the one of the app: the dialogs tick it while they are on top of the window */ public static ?Session $current = null; public function __construct() { self::$current = $this; } private bool $loaded = false; /** the saved passwords' keys (src/Model/Keyring.php) */ private ?Keyring $keyring = null; public function keyring(): Keyring { return $this->keyring ??= new Keyring(Engine::get()->storage); } public function load(): void { if ($this->loaded) { return; } $this->loaded = true; foreach ((array)Engine::get()->storage->readJson('servers.json', []) as $row) { if (is_array($row) && ($row['host'] ?? '') !== '') { $this->connections[] = new Connection(ServerProfile::fromArray($row), $this); } } $this->keyring()->restore($this); // the sealed passwords opened (or asked for: MasterPasswordForm), clear ones of old files sealed } /** Forgets everything (tests). */ public function reset(): void { foreach ($this->connections as $c) { $c->disconnect(false); } $this->connections = []; $this->log = []; $this->loaded = true; $this->keyring = null; } public function save(): void { Engine::get()->storage->writeJson('servers.json', array_map(fn (Connection $c) => $c->profile->toArray($this->keyring()), $this->connections)); } /** A server added (or found again): connected at once. */ public function add(ServerProfile $profile): Connection { foreach ($this->connections as $c) { if ($c->profile->key() === $profile->key()) { $c->profile = $profile; $this->save(); return $c; } } $connection = new Connection($profile, $this); $this->connections[] = $connection; $this->save(); return $connection; } public function remove(Connection $connection): void { $connection->disconnect(); $this->connections = array_values(array_filter($this->connections, fn ($c) => $c !== $connection)); $this->save(); } public function tick(float $dt): void { $this->clock += $dt; foreach ($this->connections as $c) { $c->tick($dt); } } public function log(string $title, string $server, ?string $target = null, string $status = LogEntry::DONE, string $detail = ''): LogEntry { $entry = new LogEntry($title, $server, $target, $this->clock); $entry->status = $status; $entry->detail = $detail; if ($status !== LogEntry::RUNNING) { $entry->progress = 1; $entry->finished = $this->clock; } $this->log[] = $entry; if (count($this->log) > 300) { array_shift($this->log); } return $entry; } public function running(): int { return count(array_filter($this->log, fn (LogEntry $e) => $e->status === LogEntry::RUNNING)); } /** The connection that holds an object. */ public function connectionOf(string $ref): ?Connection { foreach ($this->connections as $c) { if ($c->cache->get($ref) !== null) { return $c; } } return null; } public function version(): int { $v = count($this->connections) * 1000003; foreach ($this->log as $e) { $v += $e->id * 7 + (int)($e->progress * 100) + strlen($e->status) * 13; } foreach ($this->connections as $i => $c) { $v += $c->cache->version * ($i + 1) + strlen($c->state) * 31; } return $v; }}