Files
ShhShell/ShhShell/Host/HostsManager.swift
neon443 af912f234f add suport for rewritten authwithpubkey in sshhandler
added removefromkeychain
added renamekey
added deletekey
updatekymanagerview to add deleting and ui uodates
remove publickey and passphrase from host
remove key related texboxes in connectionview
added a passwordstore instance
made keytypes and names published
added savekeypairs
updatedsavetokeychain to remove and readd if it exists in the keychain
update getkeys
remove authwithbiometrics from hostmanager
trying to add key renaming support
remove Key (unused)
cleanup
2025-07-02 21:22:21 +01:00

191 lines
5.0 KiB
Swift

//
// HostsManager.swift
// ShhShell
//
// Created by neon443 on 20/06/2025.
//
import Foundation
import LocalAuthentication
import SwiftUI
class HostsManager: ObservableObject, @unchecked Sendable {
private let userDefaults = NSUbiquitousKeyValueStore.default
@Published var hosts: [Host] = []
@Published var themes: [Theme] = []
@Published var selectedTheme: Theme = Theme.defaultTheme
init() {
loadHosts()
loadThemes()
}
func loadThemes() {
userDefaults.synchronize()
guard let dataTheme = userDefaults.data(forKey: "themes") else { return }
guard let decodedThemes = try? JSONDecoder().decode([ThemeCodable].self, from: dataTheme) else { return }
for index in 0..<decodedThemes.count {
guard let encoded = try? JSONEncoder().encode(decodedThemes[index]) else { return }
guard let synthedTheme = Theme.decodeTheme(data: encoded) else { return }
self.themes.append(synthedTheme)
}
guard let dataSelTheme = userDefaults.data(forKey: "selectedTheme") else { return }
guard let decodedSelTheme = Theme.decodeTheme(data: dataSelTheme) else { return }
//name doesnt matter
self.selectedTheme = decodedSelTheme
}
func downloadTheme(fromUrl: URL?) {
guard let fromUrl else { return }
let task = URLSession.shared.dataTask(with: fromUrl) { data, response, error in
guard let data else { return }
DispatchQueue.main.async {
self.importTheme(data: data, fromUrl: fromUrl)
}
}
task.resume()
}
func selectTheme(_ selectedTheme: Theme) {
withAnimation { self.selectedTheme = selectedTheme }
saveThemes()
}
func isThemeSelected(_ themeInQuestion: Theme) -> Bool {
return themeInQuestion == self.selectedTheme
}
func renameTheme(_ theme: Theme?, to newName: String) {
guard let theme else { return }
guard theme.name != newName else { return }
guard let index = themes.firstIndex(where: {$0.id == theme.id}) else { return }
var newTheme = themes[index]
newTheme.name = newName
newTheme.id = UUID().uuidString
withAnimation { themes[index] = newTheme }
saveThemes()
}
func deleteTheme(_ themeToDel: Theme) {
guard let index = themes.firstIndex(where: {$0 == themeToDel}) else { return }
themes.remove(at: index)
saveThemes()
}
@MainActor
func importTheme(data: Data?, fromUrl: URL? = nil) {
guard let data else { return }
guard var theme = Theme.decodeTheme(data: data) else { return }
theme.name = fromUrl?.lastPathComponent.replacingOccurrences(of: ".itermcolors", with: "") ?? ""
self.themes.append(theme)
saveThemes()
}
func saveThemes() {
let encoder = JSONEncoder()
// map the theme to themecodable
guard let encodedThemes = try? encoder.encode(themes.map({$0.themeCodable})) else { return }
userDefaults.set(encodedThemes, forKey: "themes")
guard let encodedSelectedTheme = try? encoder.encode(selectedTheme.themeCodable) else { return }
userDefaults.set(encodedSelectedTheme, forKey: "selectedTheme")
userDefaults.synchronize()
}
func getHostIndexMatching(_ hostSearchingFor: Host) -> Int? {
if let index = hosts.firstIndex(where: { $0.id == hostSearchingFor.id }) {
return index
} else {
return nil
}
}
func getHostMatching(_ HostSearchingFor: Host) -> Host? {
guard let index = getHostIndexMatching(HostSearchingFor) else { return nil }
return hosts[index]
}
func updateHost(_ updatedHost: Host) {
let oldID = updatedHost.id
if let index = hosts.firstIndex(where: { $0.id == updatedHost.id }) {
var updateHostWithNewID = updatedHost
updateHostWithNewID.id = UUID()
withAnimation { hosts[index] = updateHostWithNewID }
updateHostWithNewID.id = oldID
withAnimation { hosts[index] = updateHostWithNewID }
saveHosts()
}
}
func duplicateHost(_ hostToDup: Host) {
var hostNewID = hostToDup
hostNewID.id = UUID()
if let index = hosts.firstIndex(where: { $0 == hostToDup }) {
hosts.insert(hostNewID, at: index+1)
}
}
func moveHost(from: IndexSet, to: Int) {
hosts.move(fromOffsets: from, toOffset: to)
saveHosts()
}
func loadHosts() {
userDefaults.synchronize()
let decoder = JSONDecoder()
guard let data = userDefaults.data(forKey: "savedHosts") else { return }
if let decoded = try? decoder.decode([Host].self, from: data) {
self.hosts = decoded
}
}
func saveHosts() {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(hosts) {
userDefaults.set(encoded, forKey: "savedHosts")
userDefaults.synchronize()
}
}
func addHostIfNeeded(_ hostToAdd: Host) {
if !hosts.contains(hostToAdd) {
hosts.append(hostToAdd)
}
}
func removeHost(_ host: Host) {
if let index = hosts.firstIndex(where: { $0.id == host.id }) {
let _ = withAnimation { hosts.remove(at: index) }
saveHosts()
}
}
func getKeys() -> [Keypair] {
var result: [Keypair] = []
for host in hosts {
guard let keyID = host.privateKeyID else { continue }
}
return result
}
func getHostsUsingKeys(_ keys: [Keypair]) -> [Host] {
var result: [Host] = []
for key in keys {
let hosts = hosts.filter({
$0.privateKeyID == key.id
})
result += hosts
}
return result
}
}