-
-
Notifications
You must be signed in to change notification settings - Fork 143
Expand file tree
/
Copy pathConnectionManager.swift
More file actions
132 lines (113 loc) · 4.29 KB
/
ConnectionManager.swift
File metadata and controls
132 lines (113 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import Foundation
import TableProModels
public final class ConnectionManager: @unchecked Sendable {
private let driverFactory: DriverFactory
private let secureStore: SecureStore
private let sshProvider: SSHProvider?
private let lock = NSLock()
private var sessions: [UUID: ConnectionSession] = [:]
public init(
driverFactory: DriverFactory,
secureStore: SecureStore,
sshProvider: SSHProvider? = nil
) {
self.driverFactory = driverFactory
self.secureStore = secureStore
self.sshProvider = sshProvider
}
public func connect(_ connection: DatabaseConnection) async throws -> ConnectionSession {
let password = try secureStore.retrieve(forKey: Self.passwordKey(for: connection.id))
var effectiveHost = connection.host
var effectivePort = connection.port
if connection.sshEnabled, let ssh = connection.sshConfiguration {
guard let provider = sshProvider else {
throw ConnectionError.sshNotSupported
}
let tunnel = try await provider.createTunnel(
config: ssh,
remoteHost: connection.host,
remotePort: connection.port
)
effectiveHost = tunnel.localHost
effectivePort = tunnel.localPort
}
do {
var effectiveConnection = connection
effectiveConnection.host = effectiveHost
effectiveConnection.port = effectivePort
let driver = try driverFactory.createDriver(for: effectiveConnection, password: password)
try await driver.connect()
let session = ConnectionSession(
connectionId: connection.id,
driver: driver,
activeDatabase: connection.database,
status: .connected
)
storeSession(session, for: connection.id)
return session
} catch {
if connection.sshEnabled, let provider = sshProvider {
try? await provider.closeTunnel(for: connection.id)
}
throw error
}
}
public func storePassword(_ password: String, for connectionId: UUID) throws {
try secureStore.store(password, forKey: Self.passwordKey(for: connectionId))
}
public func deletePassword(for connectionId: UUID) throws {
try secureStore.delete(forKey: Self.passwordKey(for: connectionId))
}
private static func passwordKey(for connectionId: UUID) -> String {
"com.TablePro.password.\(connectionId.uuidString)"
}
public func disconnect(_ connectionId: UUID) async {
let session = removeSession(for: connectionId)
guard let session else { return }
try? await session.driver.disconnect()
if let sshProvider {
try? await sshProvider.closeTunnel(for: connectionId)
}
}
public func disconnectAll() async {
let ids = allSessionIds()
for id in ids {
await disconnect(id)
}
}
private func allSessionIds() -> [UUID] {
lock.lock()
defer { lock.unlock() }
return Array(sessions.keys)
}
public func updateSession(_ connectionId: UUID, _ mutation: (inout ConnectionSession) -> Void) {
lock.lock()
defer { lock.unlock() }
guard var session = sessions[connectionId] else { return }
mutation(&session)
sessions[connectionId] = session
}
public func switchDatabase(_ connectionId: UUID, to database: String) async throws {
guard let session = session(for: connectionId) else {
throw ConnectionError.notConnected
}
try await session.driver.switchDatabase(to: database)
updateSession(connectionId) { $0.activeDatabase = database }
}
public func session(for connectionId: UUID) -> ConnectionSession? {
lock.lock()
defer { lock.unlock() }
return sessions[connectionId]
}
private func storeSession(_ session: ConnectionSession, for id: UUID) {
lock.lock()
sessions[id] = session
lock.unlock()
}
private func removeSession(for id: UUID) -> ConnectionSession? {
lock.lock()
let session = sessions.removeValue(forKey: id)
lock.unlock()
return session
}
}