Integrate PLCrashReporter. Fixes #2524
This commit is contained in:
parent
43a01c5501
commit
3022f78434
@ -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
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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" */;
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user