Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add install screen to samples #528

Merged
merged 7 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Samples/Common/Package.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// swift-tools-version:5.3
// swift-tools-version:5.7

import PackageDescription

let package = Package(
name: "KSCrashSamplesCommon",
platforms: [
.iOS(.v14),
.tvOS(.v14),
.watchOS(.v7),
.macOS(.v11),
.iOS(.v15),
.tvOS(.v15),
.watchOS(.v8),
.macOS(.v13),
],
products: [
.library(
Expand Down
158 changes: 158 additions & 0 deletions Samples/Common/Sources/LibraryBridge/InstallBridge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//
// InstallBridge.swift
//
// Created by Nikolay Volosatov on 2024-07-07.
//
// Copyright (c) 2012 Karl Stenerud. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall remain in place
// in this source code.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import Foundation
import Combine
import SwiftUI
import KSCrashRecording

public enum BasePath: String, CaseIterable {
case `default`
case cache
case applicationSupport
}

public class InstallBridge: ObservableObject {
public typealias MonitorType = KSCrashRecording.MonitorType

public enum InstallationError: Error, LocalizedError {
case kscrashError(String)
case unexpectedError(String)
case alreadyInstalled

public var errorDescription: String? {
switch self {
case .kscrashError(let message), .unexpectedError(let message):
return message
case .alreadyInstalled:
return "KSCrash is already installed"
}
}
}

private static func setBasePath(_ value: BasePath) {
let basePath = value.basePaths.first.flatMap { $0 + "/KSCrash" }
print("Setting KSCrash base path to: \(basePath ?? "<default>")")
bamx23 marked this conversation as resolved.
Show resolved Hide resolved
KSCrash.setBasePath(basePath)
}

private var config: KSCrashConfiguration
private var disposables = Set<AnyCancellable>()

@Published public var basePath: BasePath = .default
@Published public var installed: Bool = false
@Published public var error: InstallationError?

public init() {
config = .init()

$basePath
.removeDuplicates()
.sink(receiveValue: Self.setBasePath(_:))
.store(in: &disposables)
}

public func install() {
guard !installed else {
error = .alreadyInstalled
return
}

do {
try KSCrash.shared.install(with: config)
installed = true
} catch let error as KSCrashInstallError {
let message = error.localizedDescription
print("Failed to install KSCrash: \(message)")
self.error = .kscrashError(message)
} catch {
let message = error.localizedDescription
print("Unexpected error during KSCrash installation: \(message)")
self.error = .unexpectedError(message)
}
}
}

// An utility method to simplify binding of config fields
extension InstallBridge {
public func configBinding<T>(for keyPath: WritableKeyPath<KSCrashConfiguration, T>) -> Binding<T> {
.init { [config] in
config[keyPath: keyPath]
} set: { [weak self] val in
self?.objectWillChange.send()
self?.config[keyPath: keyPath] = val
}
}
}

// Monitor types are specified here
extension InstallBridge {
public static let allRawMonitorTypes: [(MonitorType, String, String)] = [
bamx23 marked this conversation as resolved.
Show resolved Hide resolved
(.machException, "Mach Exception", "Low-level system exceptions"),
(.signal, "Signal", "UNIX-style signals indicating abnormal program termination"),
(.cppException, "C++ Exception", "Unhandled exceptions in C++ code"),
(.nsException, "NSException", "Unhandled Objective-C exceptions"),
(.mainThreadDeadlock, "Main Thread Deadlock", "Situations where the main thread becomes unresponsive"),
(.memoryTermination, "Memory Termination", "Termination due to excessive memory usage"),
(.zombie, "Zombie", "Attempts to access deallocated objects"),
(.userReported, "User Reported", "Custom crash reports"),
(.system, "System", "Additional system information added to reports"),
(.applicationState, "Application State", "Application lifecycle added to report"),
]

public static let allCompositeMonitorTypes: [(MonitorType, String)] = [
(.all, "All"),
(.fatal, "Fatal"),

(.productionSafe, "Production-safe"),
(.productionSafeMinimal, "Production-safe Minimal"),
(.experimental, "Experimental"),

(.required, "Required"),
(.optional, "Optional"),

(.debuggerSafe, "Debugger-safe"),
(.debuggerUnsafe, "Debugger-unsafe"),

(.asyncSafe, "Async-safe"),
(.asyncUnsafe, "Async-unsafe"),

(.manual, "Manual"),
]
}

extension BasePath {
var basePaths: [String] {
switch self {
case .default:
return []
case .cache:
return NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
case .applicationSupport:
return NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)
}
}
}
59 changes: 0 additions & 59 deletions Samples/Common/Sources/LibraryBridge/RecordingSample.swift

This file was deleted.

82 changes: 82 additions & 0 deletions Samples/Common/Sources/SampleUI/Components/MonitorTypeView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// MonitorTypeView.swift
//
// Created by Nikolay Volosatov on 2024-07-07.
//
// Copyright (c) 2012 Karl Stenerud. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall remain in place
// in this source code.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import Foundation
import SwiftUI
import LibraryBridge

struct MonitorTypeView: View {

@Binding var monitors: InstallBridge.MonitorType

private func monitorBinding(_ monitor: InstallBridge.MonitorType) -> Binding<Bool> {
return .init(get: {
monitors.contains(monitor)
}, set: { flag in
if flag {
monitors.insert(monitor)
} else {
monitors.remove(monitor)
}
})
}

var body: some View {
List {
Section(header: Text("Monitors")) {
ForEach(InstallBridge.allRawMonitorTypes, id: \.0.rawValue) { (monitor, name, description) in
Toggle(isOn: monitorBinding(monitor)) {
VStack(alignment: .leading) {
Text(name)
Text(description)
.font(.caption)
.foregroundStyle(Color.secondary)
}
}
}
}
Section(header: Text("Composite")) {
ForEach(InstallBridge.allCompositeMonitorTypes, id: \.1) { (monitor, name) in
HStack {
Text(name)
Spacer()
Group {
Button("+") { monitors.formUnion(monitor) }
.disabled(monitors.intersection(monitor) == monitor)
.tint(Color.green)
Button("-") { monitors.subtract(monitor) }
.disabled(monitors.intersection(monitor).isEmpty)
.tint(Color.red)
}
.buttonStyle(.bordered)
.font(.subheadline.monospaced())
}
}
}
}
.navigationTitle("Monitors")
}
}
Loading