Mac OSX Launcher: Router management/healthcheck code updates.

This commit is contained in:
meeh
2019-05-02 22:50:55 +00:00
parent 66deb5dc7e
commit 7cb0c9bbb4
5 changed files with 267 additions and 0 deletions

View File

@ -0,0 +1,50 @@
//
// NetworkUtil.swift
// I2PLauncher
//
// Created by Mikal Villa on 07/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
class NetworkUtil {
static func checkTcpPortForListen(host: String = "127.0.0.1", port: in_port_t = 7657) -> (Bool, descr: String){
let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)
if socketFileDescriptor == -1 {
return (false, "SocketCreationFailed, \(descriptionOfLastError())")
}
var addr = sockaddr_in()
let sizeOfSockkAddr = MemoryLayout<sockaddr_in>.size
addr.sin_len = __uint8_t(sizeOfSockkAddr)
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port
addr.sin_addr = in_addr(s_addr: inet_addr(host))
addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
var bind_addr = sockaddr()
memcpy(&bind_addr, &addr, Int(sizeOfSockkAddr))
if Darwin.bind(socketFileDescriptor, &bind_addr, socklen_t(sizeOfSockkAddr)) == -1 {
let details = descriptionOfLastError()
release(socket: socketFileDescriptor)
return (false, "\(port), BindFailed, \(details)")
}
if listen(socketFileDescriptor, SOMAXCONN ) == -1 {
let details = descriptionOfLastError()
release(socket: socketFileDescriptor)
return (false, "\(port), ListenFailed, \(details)")
}
release(socket: socketFileDescriptor)
return (true, "\(port) is free for use")
}
static func release(socket: Int32) {
Darwin.shutdown(socket, SHUT_RDWR)
close(socket)
}
static func descriptionOfLastError() -> String {
return String(cString: UnsafePointer(strerror(errno)))
}
}

View File

@ -0,0 +1,42 @@
//
// HttpTunnelService.swift
// I2PLauncher
//
// Created by Mikal Villa on 03/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
import Kanna
class HttpTunnelService : Service {
let dataURL: URL = URL(string: "http://127.0.0.1:7657/i2ptunnel/")!
override func updateStatus(callback: @escaping (BaseService) -> Void) {
URLSession.shared.dataTask(with: dataURL) { [weak self] data, _, error in
guard let strongSelf = self else { return }
defer { callback(strongSelf) }
guard let doc = try? HTML(html: data!, encoding: .utf8) else { return /*strongSelf._fail("Couldn't parse response")*/ }
_ = doc.css("table#clientTunnels > tr.tunnelProperties > td.tunnelStatus").first
let maxStatus: ServiceStatus = .started
strongSelf.status = maxStatus
switch maxStatus {
case .waiting:
strongSelf.message = "Waiting on router"
case .started:
strongSelf.message = "Started"
case .stopped:
strongSelf.message = "Stopped"
case .undetermined:
strongSelf.message = "Undetermined"
default:
strongSelf.message = "Undetermined" /*downComponents.map { $0["name"] as? String }.compactMap { $0 }.joined(separator: ", ")*/
}
}.resume()
}
}

View File

@ -0,0 +1,23 @@
//
// I2PRouterService.swift
// I2PLauncher
//
// Created by Mikal Villa on 03/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
class I2PRouterService : Service {
override func updateStatus(callback: @escaping (BaseService) -> Void) {
//guard let strongSelf = self else { return }
defer { callback(self) }
DispatchQueue.main.async {
self.status = ServiceStatus(rawValue: 0)!
self.message = "Dead"
}
}
}

View File

@ -0,0 +1,20 @@
//
// IrcTunnelService.swift
// I2PLauncher
//
// Created by Mikal Villa on 03/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
class IrcTunnelService : Service {
override func updateStatus(callback: @escaping (BaseService) -> Void) {
defer { callback(self) }
self.status = ServiceStatus(rawValue: 0)!
self.message = "Dead"
}
}

View File

@ -0,0 +1,132 @@
//
// Service.swift
// I2PLauncher
//
// Created by Mikal Villa on 12/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
public enum ServiceStatus: Int, Comparable {
case undetermined
case waiting
case started
case notice
case killed
case crashed
case stopped
case restarting
public static func < (lhs: ServiceStatus, rhs: ServiceStatus) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
protocol ComparableStatus: Comparable {
var serviceStatus: ServiceStatus { get }
}
extension ComparableStatus {
public static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.serviceStatus < rhs.serviceStatus
}
}
typealias Service = BaseService & RequiredServiceProperties
protocol RequiredServiceProperties {
var name: String { get }
//var url: URL { get }
}
extension RequiredServiceProperties {
// Default implementation of the property `name` is to return the class name
var name: String { return "\(type(of: self))" }
}
public class BaseService {
public var status: ServiceStatus = .undetermined {
didSet {
if oldValue == .undetermined || status == .undetermined || oldValue == status {
self.shouldNotify = false
} else if Preferences.shared().notifyOnStatusChange {
self.shouldNotify = true
}
}
}
var message: String = "Loading…"
var shouldNotify = false
public static func all() -> [BaseService] {
guard let servicesPlist = Bundle.main.path(forResource: "RouterServices", ofType: "plist"),
let services = NSDictionary(contentsOfFile: servicesPlist)?["services"] as? [String] else {
fatalError("The RouterServices.plist file does not exist. The build phase script might have failed.")
}
return services.map(BaseService.named).compactMap { $0 }
}
static func named(_ name: String) -> BaseService? {
return (NSClassFromString("I2PLauncher.\(name)") as? Service.Type)?.init()
}
public required init() {}
public func updateStatus(callback: @escaping (BaseService) -> Void) {}
func _fail(_ error: Error?) {
self.status = .undetermined
self.message = error.debugDescription// ?? "Unexpected error"
}
func _fail(_ message: String) {
self.status = .undetermined
self.message = message
}
func notifyIfNecessary() {
guard let realSelf = self as? Service else { fatalError("BaseService should not be used directly.") }
guard shouldNotify else { return }
self.shouldNotify = false
let notification = NSUserNotification()
let possessiveS = realSelf.name.hasSuffix("s") ? "'" : "'s"
notification.title = "\(realSelf.name)\(possessiveS) status has changed"
notification.informativeText = message
NSUserNotificationCenter.default.deliver(notification)
}
}
extension BaseService: Equatable {
public static func == (lhs: BaseService, rhs: BaseService) -> Bool {
guard
let lhs = lhs as? Service,
let rhs = rhs as? Service
else {
fatalError("BaseService should not be used directly.")
}
return lhs.name == rhs.name
}
}
extension BaseService: Comparable {
public static func < (lhs: BaseService, rhs: BaseService) -> Bool {
guard
let lhs = lhs as? Service,
let rhs = rhs as? Service
else {
fatalError("BaseService should not be used directly.")
}
let sameStatus = lhs.status == rhs.status
let differentStatus =
lhs.status != .started && lhs.status != .notice
&& rhs.status == .started || rhs.status == .notice
return ((lhs.name < rhs.name) && sameStatus) || differentStatus
}
}