From 35e137af83e4453f7c1c91cf73fbb94c7157dcbf Mon Sep 17 00:00:00 2001 From: Nikolay Volosatov Date: Tue, 27 Aug 2024 13:51:19 +0100 Subject: [PATCH] Add reports-only install --- .../Sources/LibraryBridge/InstallBridge.swift | 16 +++++ .../Common/Sources/SampleUI/SampleView.swift | 9 +-- .../SampleUI/Screens/InstallView.swift | 4 +- Sources/KSCrashRecording/KSCrash.m | 15 +++++ Sources/KSCrashRecording/KSCrashC.c | 61 +++++++++++++++---- Sources/KSCrashRecording/KSCrashReportStore.c | 5 +- Sources/KSCrashRecording/KSCrashReportStore.h | 3 +- Sources/KSCrashRecording/include/KSCrash.h | 4 ++ Sources/KSCrashRecording/include/KSCrashC.h | 4 ++ .../KSCrashReportStore_Tests.m | 2 +- 10 files changed, 95 insertions(+), 28 deletions(-) diff --git a/Samples/Common/Sources/LibraryBridge/InstallBridge.swift b/Samples/Common/Sources/LibraryBridge/InstallBridge.swift index 06defa08..e8454774 100644 --- a/Samples/Common/Sources/LibraryBridge/InstallBridge.swift +++ b/Samples/Common/Sources/LibraryBridge/InstallBridge.swift @@ -67,6 +67,7 @@ public class InstallBridge: ObservableObject { @Published public var basePath: BasePath = .default @Published public var installed: Bool = false + @Published public var installSkipped: Bool = false @Published public var error: InstallationError? public init() { @@ -97,6 +98,21 @@ public class InstallBridge: ObservableObject { self.error = .unexpectedError(message) } } + + public func installReportsOnly() { + do { + try KSCrash.shared.installReportsOnly(withInstallPath: config.installPath) + installSkipped = true + } catch let error as KSCrashInstallError { + let message = error.localizedDescription + Self.logger.error("Failed to install KSCrash: \(message)") + self.error = .kscrashError(message) + } catch { + let message = error.localizedDescription + Self.logger.error("Unexpected error during KSCrash installation: \(message)") + self.error = .unexpectedError(message) + } + } } // An utility method to simplify binding of config fields diff --git a/Samples/Common/Sources/SampleUI/SampleView.swift b/Samples/Common/Sources/SampleUI/SampleView.swift index 771b08bb..a34b844f 100644 --- a/Samples/Common/Sources/SampleUI/SampleView.swift +++ b/Samples/Common/Sources/SampleUI/SampleView.swift @@ -34,19 +34,16 @@ public struct SampleView: View { @ObservedObject var installBridge = InstallBridge() - @State private var installSkipped = false - public var body: some View { NavigationView { - if installBridge.installed || installSkipped { + if installBridge.installed || installBridge.installSkipped { MainView( - installSkipped: $installSkipped + installSkipped: $installBridge.installSkipped ) .navigationTitle("KSCrash Sample") } else { InstallView( - bridge: installBridge, - installSkipped: $installSkipped + bridge: installBridge ) .navigationTitle("Install KSCrash") } diff --git a/Samples/Common/Sources/SampleUI/Screens/InstallView.swift b/Samples/Common/Sources/SampleUI/Screens/InstallView.swift index 6fae0245..f527aa9a 100644 --- a/Samples/Common/Sources/SampleUI/Screens/InstallView.swift +++ b/Samples/Common/Sources/SampleUI/Screens/InstallView.swift @@ -31,8 +31,6 @@ import LibraryBridge struct InstallView: View { @ObservedObject var bridge: InstallBridge - @Binding var installSkipped: Bool - @State private var showingInstallAlert = false var body: some View { @@ -82,7 +80,7 @@ struct InstallView: View { } Button("Skip install") { - installSkipped = true + bridge.installReportsOnly() } .foregroundStyle(Color.red) } diff --git a/Sources/KSCrashRecording/KSCrash.m b/Sources/KSCrashRecording/KSCrash.m index 9420c9dc..bf2ab275 100644 --- a/Sources/KSCrashRecording/KSCrash.m +++ b/Sources/KSCrashRecording/KSCrash.m @@ -235,6 +235,21 @@ - (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration error:(NS return YES; } +- (BOOL)installReportsOnlyWithInstallPath:(NSString *)installPath error:(NSError **)error +{ + KSCrashInstallErrorCode result = + kscrash_installReports(self.bundleName.UTF8String, (installPath ?: getDefaultInstallPath()).UTF8String); + + if (result != KSCrashInstallErrorNone) { + if (error != NULL) { + *error = [self errorForInstallErrorCode:result]; + } + return NO; + } + + return YES; +} + - (void)sendAllReportsWithCompletion:(KSCrashReportFilterCompletion)onCompletion { NSArray *reports = [self allReports]; diff --git a/Sources/KSCrashRecording/KSCrashC.c b/Sources/KSCrashRecording/KSCrashC.c index efa1b580..433f0e51 100644 --- a/Sources/KSCrashRecording/KSCrashC.c +++ b/Sources/KSCrashRecording/KSCrashC.c @@ -86,6 +86,8 @@ static const size_t g_monitorMappingCount = sizeof(g_monitorMappings) / sizeof(g /** True if KSCrash has been installed. */ static volatile bool g_installed = 0; +/** True if the reports store has been installed. */ +static volatile bool g_reportsInstalled = 0; static bool g_shouldAddConsoleLogToReport = false; static bool g_shouldPrintPreviousLog = false; @@ -209,6 +211,21 @@ void handleConfiguration(KSCrashCConfiguration *configuration) #pragma mark - API - // ============================================================================ +static KSCrashInstallErrorCode installReportsStore(const char *appName, const char *const installPath) +{ + char path[KSFU_MAX_PATH_LENGTH]; + if (snprintf(path, sizeof(path), "%s/Reports", installPath) >= (int)sizeof(path)) { + KSLOG_ERROR("Reports path is too long."); + return KSCrashInstallErrorPathTooLong; + } + if (ksfu_makePath(path) == false) { + KSLOG_ERROR("Could not create path: %s", path); + return KSCrashInstallErrorCouldNotCreatePath; + } + kscrs_initialize(appName, path); + return KSCrashInstallErrorNone; +} + KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const installPath, KSCrashCConfiguration configuration) { @@ -226,19 +243,16 @@ KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const i handleConfiguration(&configuration); - char path[KSFU_MAX_PATH_LENGTH]; - if (snprintf(path, sizeof(path), "%s/Reports", installPath) >= (int)sizeof(path)) { - KSLOG_ERROR("Path too long."); - return KSCrashInstallErrorPathTooLong; - } - if (ksfu_makePath(path) == false) { - KSLOG_ERROR("Could not create path: %s", path); - return KSCrashInstallErrorCouldNotCreatePath; + if (g_reportsInstalled == false) { + KSCrashInstallErrorCode result = installReportsStore(appName, installPath); + if (result != KSCrashInstallErrorNone) { + return result; + } } - kscrs_initialize(appName, installPath, path); + char path[KSFU_MAX_PATH_LENGTH]; if (snprintf(path, sizeof(path), "%s/Data", installPath) >= (int)sizeof(path)) { - KSLOG_ERROR("Path too long."); + KSLOG_ERROR("Data path is too long."); return KSCrashInstallErrorPathTooLong; } if (ksfu_makePath(path) == false) { @@ -248,14 +262,14 @@ KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const i ksmemory_initialize(path); if (snprintf(path, sizeof(path), "%s/Data/CrashState.json", installPath) >= (int)sizeof(path)) { - KSLOG_ERROR("Path too long."); + KSLOG_ERROR("Crash state path is too long."); return KSCrashInstallErrorPathTooLong; } kscrashstate_initialize(path); if (snprintf(g_consoleLogPath, sizeof(g_consoleLogPath), "%s/Data/ConsoleLog.txt", installPath) >= (int)sizeof(g_consoleLogPath)) { - KSLOG_ERROR("Console log path too long."); + KSLOG_ERROR("Console log path is too long."); return KSCrashInstallErrorPathTooLong; } if (g_shouldPrintPreviousLog) { @@ -279,6 +293,29 @@ KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const i return KSCrashInstallErrorNone; } +KSCrashInstallErrorCode kscrash_installReports(const char *appName, const char *const installPath) +{ + KSLOG_DEBUG("Installing reports store."); + + if (g_reportsInstalled) { + KSLOG_DEBUG("Crash reporter already installed."); + return KSCrashInstallErrorAlreadyInstalled; + } + + if (appName == NULL || installPath == NULL) { + KSLOG_ERROR("Invalid parameters: appName or installPath is NULL."); + return KSCrashInstallErrorInvalidParameter; + } + + KSCrashInstallErrorCode result = installReportsStore(appName, installPath); + if (result != KSCrashInstallErrorNone) { + return result; + } + + g_reportsInstalled = true; + return KSCrashInstallErrorNone; +} + void kscrash_setUserInfoJSON(const char *const userInfoJSON) { kscrashreport_setUserInfoJSON(userInfoJSON); } const char *kscrash_getUserInfoJSON(void) { return kscrashreport_getUserInfoJSON(); } diff --git a/Sources/KSCrashRecording/KSCrashReportStore.c b/Sources/KSCrashRecording/KSCrashReportStore.c index 901d085c..abc4f22f 100644 --- a/Sources/KSCrashRecording/KSCrashReportStore.c +++ b/Sources/KSCrashRecording/KSCrashReportStore.c @@ -45,7 +45,6 @@ static _Atomic(uint32_t) g_nextUniqueIDLow; static int64_t g_nextUniqueIDHigh; static const char *g_appName; static const char *g_reportsPath; -static const char *g_installPath; static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; static int compareInt64(const void *a, const void *b) @@ -157,12 +156,10 @@ static void initializeIDs(void) // Public API -void kscrs_initialize(const char *appName, const char *installPath, const char *reportsPath) +void kscrs_initialize(const char *appName, const char *reportsPath) { pthread_mutex_lock(&g_mutex); g_appName = strdup(appName); - g_installPath = strdup(installPath); - ksfu_makePath(installPath); g_reportsPath = strdup(reportsPath); ksfu_makePath(reportsPath); pruneReports(); diff --git a/Sources/KSCrashRecording/KSCrashReportStore.h b/Sources/KSCrashRecording/KSCrashReportStore.h index a99abab1..6489946d 100644 --- a/Sources/KSCrashRecording/KSCrashReportStore.h +++ b/Sources/KSCrashRecording/KSCrashReportStore.h @@ -38,10 +38,9 @@ extern "C" { /** Initialize the report store. * * @param appName The application's name. - * @param installPath Full path to directory where the crash system writes files. * @param reportsPath Full path to directory where the reports are to be stored (path will be created if needed). */ -void kscrs_initialize(const char *appName, const char *installPath, const char *reportsPath); +void kscrs_initialize(const char *appName, const char *reportsPath); /** Get the next crash report to be generated. * Max length for paths is KSCRS_MAX_PATH_LENGTH diff --git a/Sources/KSCrashRecording/include/KSCrash.h b/Sources/KSCrashRecording/include/KSCrash.h index ca625fb2..b8637cc5 100644 --- a/Sources/KSCrashRecording/include/KSCrash.h +++ b/Sources/KSCrashRecording/include/KSCrash.h @@ -127,6 +127,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration error:(NSError **)error; +/** TODO: Add docs + */ +- (BOOL)installReportsOnlyWithInstallPath:(nullable NSString *)installPath error:(NSError **)error; + /** Send all outstanding crash reports to the current sink. * It will only attempt to send the most recent 5 reports. All others will be * deleted. Once the reports are successfully sent to the server, they may be diff --git a/Sources/KSCrashRecording/include/KSCrashC.h b/Sources/KSCrashRecording/include/KSCrashC.h index 5c109015..a501a228 100644 --- a/Sources/KSCrashRecording/include/KSCrashC.h +++ b/Sources/KSCrashRecording/include/KSCrashC.h @@ -85,6 +85,10 @@ extern "C" { KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const installPath, KSCrashCConfiguration configuration); +/** TODO: Add docs + */ +KSCrashInstallErrorCode kscrash_installReports(const char *appName, const char *const installPath); + /** Set the user-supplied data in JSON format. * * @param userInfoJSON Pre-baked JSON containing user-supplied information. diff --git a/Tests/KSCrashRecordingTests/KSCrashReportStore_Tests.m b/Tests/KSCrashRecordingTests/KSCrashReportStore_Tests.m index 90aa6215..e60ddcb9 100644 --- a/Tests/KSCrashRecordingTests/KSCrashReportStore_Tests.m +++ b/Tests/KSCrashRecordingTests/KSCrashReportStore_Tests.m @@ -62,7 +62,7 @@ - (void)setUp - (void)prepareReportStoreWithPathEnd:(NSString *)pathEnd { self.reportStorePath = [self.tempPath stringByAppendingPathComponent:pathEnd]; - kscrs_initialize(self.appName.UTF8String, self.tempPath.UTF8String, self.reportStorePath.UTF8String); + kscrs_initialize(self.appName.UTF8String, self.reportStorePath.UTF8String); } - (NSArray *)getReportIDs