Files
Termind/TFM/Features/Workspaces/ClosedWorkspaceRecord.swift
T
Kaede Akino 9738605dd3 feat(terminal): add Mosh and Eternal Terminal transports
Add SSH-authenticated transports with independent settings, protocol-aware menus and snippets, and tmux session persistence.

Restore eligible iOS workspaces from encrypted transport checkpoints without adding a macOS recovery entry. Include schema migrations and regression coverage.
2026-10-16 08:36:56 +08:00

61 lines
2.5 KiB
Swift

import Foundation
import TermindApplication
import TermindDomain
/// Only saved-host references and presentation state survive an app restart.
struct ClosedWorkspaceRecord: Codable, Identifiable {
let id: UUID
let title: String
let symbol: String
let layout: WorkspaceTemplateLayout?
let paneNames: [String?]
let focusedPaneIndex: Int
var remoteSessions: [RemoteSessionReference?]? = nil
@MainActor
init(_ workspace: Workspace, hosts: HostLibraryModel) {
let layout =
workspace.panes.contains { $0.sshSession?.isExternalTemporaryConnection == true }
? nil : workspace.workspaceTemplateLayout(in: hosts)
self.remoteSessions = workspace.panes.map { $0.sshSession?.sessionPersistence.attachment }
self.id = workspace.id
self.title = workspace.title(in: hosts)
self.symbol = workspace.symbol
self.layout = layout
self.paneNames = workspace.panes.map(\.customTitle)
self.focusedPaneIndex = workspace.panes.firstIndex { $0.id == workspace.focusedPaneID } ?? 0
}
@MainActor
func canRestore(in hosts: HostLibraryModel) -> Bool {
guard let layout else { return false }
return layout.isValid
&& layout.panes.allSatisfy { pane in
switch pane {
case .empty: true
case .ssh(let id), .sftp(let id): hosts.host(id)?.sshEnabled == true
case .telnet(let id): hosts.host(id)?.telnetEnabled == true
case .mosh(let id): hosts.host(id)?.isEnabled(.mosh) == true
case .eternalTerminal(let id): hosts.host(id)?.isEnabled(.eternalTerminal) == true
}
}
}
@MainActor
func restore(in hosts: HostLibraryModel) -> Workspace? {
guard canRestore(in: hosts), let layout else { return nil }
let node = layout.makeSplitNode(using: hosts).node
let workspace = Workspace(pane: node.firstPane)
workspace.layout = node
for (pane, name) in zip(workspace.panes, paneNames) { pane.customTitle = name }
for (pane, reference) in zip(workspace.panes, remoteSessions ?? []) {
pane.sshSession?.sessionPersistence.attachment = reference
}
if workspace.isSplit { workspace.customTitle = title } else { workspace.automaticTitle = title }
if workspace.panes.indices.contains(focusedPaneIndex) {
workspace.focusedPaneID = workspace.panes[focusedPaneIndex].id
}
return workspace
}
}