Files
Termind/TFI/Application/Workspaces/MobileConnectionHistory.swift

118 lines
4.1 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import TermindApplication
import TermindDomain
struct MobileConnectionHistoryRow: Identifiable {
let entry: ConnectionHistoryEntry
let host: Host
let loginAddress: String
let sourceDeviceName: String?
var id: ConnectionHistoryEntry.ID { entry.id }
var canOpen: Bool {
MobileConnectionHistoryActionPolicy.isAvailable(entry.kind, for: host)
}
}
struct MobileRecentHostRow: Identifiable {
let entry: ConnectionHistoryEntry
let host: Host
let loginAddress: String
let preferredKind: ConnectionHistoryEntry.Kind?
var id: Host.ID { host.id }
var canOpen: Bool { preferredKind != nil }
}
enum MobileConnectionHistoryActionPolicy {
static let menuKinds: [ConnectionHistoryEntry.Kind] = [.ssh, .mosh, .eternalTerminal, .telnet, .sftp]
// Trailing swipe actions are declared from the edge outwards, so reversing
// the menu order keeps the revealed protocol order consistent.
static let swipeKinds: [ConnectionHistoryEntry.Kind] = [.sftp, .telnet, .eternalTerminal, .mosh, .ssh]
static func availableMenuKinds(for host: Host) -> [ConnectionHistoryEntry.Kind] {
menuKinds.filter { isAvailable($0, for: host) }
}
static func availableSwipeKinds(for host: Host) -> [ConnectionHistoryEntry.Kind] {
swipeKinds.filter { isAvailable($0, for: host) }
}
static func isAvailable(_ kind: ConnectionHistoryEntry.Kind, for host: Host) -> Bool {
switch kind {
case .ssh, .sftp: host.sshEnabled
case .telnet: host.telnetEnabled
case .mosh: host.isEnabled(.mosh)
case .eternalTerminal: host.isEnabled(.eternalTerminal)
}
}
}
@MainActor
enum MobileConnectionHistory {
static func recentHostRows(
history: ConnectionHistoryModel,
library: HostLibraryModel,
limit: Int
) -> [MobileRecentHostRow] {
history.recentLocalHosts(in: library, limit: limit).map { recent in
let host = recent.host
let preferredKind = preferredKind(for: host)
return MobileRecentHostRow(
entry: recent.entry,
host: host,
loginAddress: library.preferredTerminalLoginAddress(for: host),
preferredKind: preferredKind
)
}
}
static func eventRows(
history: ConnectionHistoryModel,
devices: DeviceDirectoryModel,
library: HostLibraryModel,
query: String = "",
limit: Int
) -> [MobileConnectionHistoryRow] {
guard limit > 0 else { return [] }
var rows: [MobileConnectionHistoryRow] = []
for entry in history.entries() {
guard let host = library.host(entry.hostID) else { continue }
let loginAddress = library.loginAddress(
for: host, protocol: entry.kind == .telnet ? .telnet : .ssh)
let sourceDeviceName = devices.name(for: entry.sourceDeviceID)
guard
query.isEmpty
|| matches(
entry, host: host, loginAddress: loginAddress,
sourceDeviceName: sourceDeviceName, query: query)
else {
continue
}
rows.append(
MobileConnectionHistoryRow(
entry: entry, host: host, loginAddress: loginAddress,
sourceDeviceName: sourceDeviceName))
if rows.count == limit { break }
}
return rows
}
static func preferredKind(for host: Host) -> ConnectionHistoryEntry.Kind? {
.preferredTerminal(for: host)
}
private static func matches(
_ entry: ConnectionHistoryEntry, host: Host, loginAddress: String,
sourceDeviceName: String?, query: String
) -> Bool {
host.matches(query)
|| loginAddress.localizedCaseInsensitiveContains(query)
|| entry.kind.label.localizedCaseInsensitiveContains(query)
|| sourceDeviceName?.localizedCaseInsensitiveContains(query) == true
}
}