diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 44b983214..dd31e23ac 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -16,6 +16,7 @@ import RSCore import RSCoreResources import Secrets import OSLog +import CrashReporter // If we're not going to import Sparkle, provide dummy protocols to make it easy // for AppDelegate to comply @@ -101,12 +102,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, private let appMovementMonitor = RSAppMovementMonitor() #if !MAC_APP_STORE && !TEST private var softwareUpdater: SPUUpdater! + private var crashReporter: PLCrashReporter! #endif override init() { NSWindow.allowsAutomaticWindowTabbing = false super.init() + #if !MAC_APP_STORE + let crashReporterConfig = PLCrashReporterConfig.defaultConfiguration() + crashReporter = PLCrashReporter(configuration: crashReporterConfig) + crashReporter.enable() + #endif + SecretsManager.provider = Secrets() AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!) FeedProviderManager.shared.delegate = ExtensionPointManager.shared @@ -257,9 +265,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } #if !MAC_APP_STORE - DispatchQueue.main.async { - CrashReporter.check(appName: "NetNewsWire") - } + DispatchQueue.main.async { + CrashReporter.check(crashReporter: self.crashReporter) + } #endif } diff --git a/Mac/CrashReporter/CrashReporter.swift b/Mac/CrashReporter/CrashReporter.swift index ddd3e5ad5..06aa3f5e6 100644 --- a/Mac/CrashReporter/CrashReporter.swift +++ b/Mac/CrashReporter/CrashReporter.swift @@ -8,39 +8,17 @@ import Foundation import RSWeb +import CrashReporter -// Based originally on Uli Kusterer's UKCrashReporter: http://www.zathras.de/angelweb/blog-ukcrashreporter-oh-one.htm -// Then based on the crash reporter included in NetNewsWire 3 and NetNewsWire Lite 4. // Displays a window that shows the crash log — gives the user the chance to add data. // (Or just decide not to send it.) // This code is not included in the MAS build. // At some point this code should probably move into RSCore, so Rainier and any other // future apps can use it. - -struct CrashLog { - - let path: String - let modificationDate: Date - let content: String - let contentHash: String - - init?(path: String, modificationDate: Date) { - guard let s = try? NSString(contentsOfFile: path, usedEncoding: nil) as String, !s.isEmpty else { - return nil - } - self.content = s - self.contentHash = s.md5String - self.path = path - self.modificationDate = modificationDate - } -} - struct CrashReporter { struct DefaultsKey { - static let lastSeenCrashLogDateKey = "LastSeenCrashLogDate" - static let hashOfLastSeenCrashLogKey = "LastSeenCrashLogHash" static let sendCrashLogsAutomaticallyKey = "SendCrashLogsAutomatically" } @@ -48,51 +26,19 @@ struct CrashReporter { /// Look in ~/Library/Logs/DiagnosticReports/ for a new crash log for this app. /// Show a crash log reporter window if found. - static func check(appName: String) { - let folder = ("~/Library/Logs/DiagnosticReports/" as NSString).expandingTildeInPath - guard let filenames = try? FileManager.default.contentsOfDirectory(atPath: folder) else { - return - } - - let crashSuffix = ".crash" - let lowerAppName = appName.lowercased() - let lastSeenCrashLogDate: Date = { - let lastSeenCrashLogDouble = UserDefaults.standard.double(forKey: DefaultsKey.lastSeenCrashLogDateKey) - return Date(timeIntervalSince1970: lastSeenCrashLogDouble) - }() - - var mostRecentFilePath: String? = nil - var mostRecentFileDate = Date.distantPast - for filename in filenames { - if !filename.lowercased().hasPrefix(lowerAppName) || !filename.hasSuffix(crashSuffix) { - continue - } - - let path = (folder as NSString).appendingPathComponent(filename) - let fileAttributes = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [FileAttributeKey: Any]() - if let fileModificationDate = fileAttributes[.modificationDate] as? Date { - if fileModificationDate > lastSeenCrashLogDate && fileModificationDate > mostRecentFileDate { // Ignore if previously seen - mostRecentFileDate = fileModificationDate - mostRecentFilePath = path - } - } - } - - guard let crashLogPath = mostRecentFilePath, let crashLog = CrashLog(path: crashLogPath, modificationDate: mostRecentFileDate) else { - return - } - - if hasSeen(crashLog) { - return - } - remember(crashLog) + static func check(crashReporter: PLCrashReporter) { + guard crashReporter.hasPendingCrashReport(), + let crashData = crashReporter.loadPendingCrashReportData(), + let crashReport = try? PLCrashReport(data: crashData), + let crashLogText = PLCrashReportTextFormatter.stringValue(for: crashReport, with: PLCrashReportTextFormatiOS) else { return } if shouldSendCrashLogsAutomatically() { - sendCrashLogText(crashLog.content) - } - else { - runCrashReporterWindow(crashLog) + sendCrashLogText(crashLogText) + } else { + runCrashReporterWindow(crashLogText) } + + crashReporter.purgePendingCrashReport() } static func sendCrashLogText(_ crashLogText: String) { @@ -113,29 +59,14 @@ struct CrashReporter { } } - static func runCrashReporterWindow(_ crashLog: CrashLog) { - crashReportWindowController = CrashReportWindowController(crashLogText: crashLog.content) + static func runCrashReporterWindow(_ crashLogText: String) { + crashReportWindowController = CrashReportWindowController(crashLogText: crashLogText) crashReportWindowController!.showWindow(self) } } private extension CrashReporter { - static func hasSeen(_ crashLog: CrashLog) -> Bool { - // No need to compare dates, because that’s done in the file loop. - // Check to see if we’ve already reported this exact crash log. - guard let hashOfLastSeenCrashLog = UserDefaults.standard.string(forKey: DefaultsKey.hashOfLastSeenCrashLogKey) else { - return false - } - return hashOfLastSeenCrashLog == crashLog.contentHash - } - - static func remember(_ crashLog: CrashLog) { - // Save the modification date and hash, so we don’t send duplicates. - UserDefaults.standard.set(crashLog.contentHash, forKey: DefaultsKey.hashOfLastSeenCrashLogKey) - UserDefaults.standard.set(crashLog.modificationDate.timeIntervalSince1970, forKey: DefaultsKey.lastSeenCrashLogDateKey) - } - static func shouldSendCrashLogsAutomatically() -> Bool { return UserDefaults.standard.bool(forKey: DefaultsKey.sendCrashLogsAutomaticallyKey) } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 57305f1ba..e567cd561 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -400,6 +400,7 @@ 5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; }; 5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194737024BBCAF4001A2939 /* TimelineSortOrderView.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; + 519CA8E525841DB700EB079A /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 519CA8E425841DB700EB079A /* CrashReporter */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; }; 519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */; }; @@ -2101,6 +2102,7 @@ 51E4DAED2425F6940091EB5B /* CloudKit.framework in Frameworks */, 514C16E124D2EF38009A3AFA /* RSCoreResources in Frameworks */, 514C16CE24D2E63F009A3AFA /* Account in Frameworks */, + 519CA8E525841DB700EB079A /* CrashReporter in Frameworks */, 51A737BF24DB197F0015FA66 /* RSDatabase in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3888,6 +3890,7 @@ 51A737C424DB19B50015FA66 /* RSWeb */, 51A737C724DB19CC0015FA66 /* RSParser */, 17192AD92567B3D500AAEACA /* RSSparkle */, + 519CA8E425841DB700EB079A /* CrashReporter */, ); productName = NetNewsWire; productReference = 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */; @@ -4012,6 +4015,7 @@ 51B0DF0D24D24E3B000AD99E /* XCRemoteSwiftPackageReference "RSDatabase" */, 51B0DF2324D2C7FA000AD99E /* XCRemoteSwiftPackageReference "RSParser" */, 17192AD82567B3D500AAEACA /* XCRemoteSwiftPackageReference "Sparkle-Binary" */, + 519CA8E325841DB700EB079A /* XCRemoteSwiftPackageReference "plcrashreporter" */, ); productRefGroup = 849C64611ED37A5D003D8FC0 /* Products */; projectDirPath = ""; @@ -5940,6 +5944,14 @@ minimumVersion = "1.0.0-beta1"; }; }; + 519CA8E325841DB700EB079A /* XCRemoteSwiftPackageReference "plcrashreporter" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/microsoft/plcrashreporter.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.8.1; + }; + }; 51B0DF0D24D24E3B000AD99E /* XCRemoteSwiftPackageReference "RSDatabase" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Ranchero-Software/RSDatabase.git"; @@ -6089,6 +6101,11 @@ isa = XCSwiftPackageProductDependency; productName = Account; }; + 519CA8E425841DB700EB079A /* CrashReporter */ = { + isa = XCSwiftPackageProductDependency; + package = 519CA8E325841DB700EB079A /* XCRemoteSwiftPackageReference "plcrashreporter" */; + productName = CrashReporter; + }; 51A737AD24DB19730015FA66 /* RSCore */ = { isa = XCSwiftPackageProductDependency; package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */; diff --git a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0eb5dba64..672882924 100644 --- a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -46,6 +46,15 @@ "version": "2.1.2" } }, + { + "package": "PLCrashReporter", + "repositoryURL": "https://github.com/microsoft/plcrashreporter.git", + "state": { + "branch": null, + "revision": "de6b8f9db4b2a0aa859a5507550a70548e4da936", + "version": "1.8.1" + } + }, { "package": "RSCore", "repositoryURL": "https://github.com/Ranchero-Software/RSCore.git",