Make CrashReporter a static struct. Add CrashLog immutable struct.

This commit is contained in:
Brent Simmons 2018-12-22 12:01:31 -08:00
parent 22e17a6d92
commit f2de9f1a60
2 changed files with 66 additions and 76 deletions

View File

@ -178,7 +178,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
} }
#endif #endif
CrashReporter.shared.check(appName: "NetNewsWire") CrashReporter.check(appName: "NetNewsWire")
} }
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {

View File

@ -17,119 +17,109 @@ import Foundation
// future apps can use it. // future apps can use it.
class CrashReporter { struct CrashLog {
static let shared = CrashReporter() let path: String
let modificationDate: Date
let content: String
let contentHash: String
private struct DefaultsKey { 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.rs_md5Hash()
self.path = path
self.modificationDate = modificationDate
}
}
struct CrashReporter {
struct DefaultsKey {
static let lastSeenCrashLogDateKey = "LastSeenCrashLogDate" static let lastSeenCrashLogDateKey = "LastSeenCrashLogDate"
static let hashOfLastSeenCrashLogKey = "LastSeenCrashLogHash" static let hashOfLastSeenCrashLogKey = "LastSeenCrashLogHash"
static let sendCrashLogsAutomaticallyKey = "SendCrashLogsAutomatically" static let sendCrashLogsAutomaticallyKey = "SendCrashLogsAutomatically"
} }
private var sendCrashLogsAutomatically: Bool {
get {
return UserDefaults.standard.bool(forKey: DefaultsKey.sendCrashLogsAutomaticallyKey)
}
set {
UserDefaults.standard.set(newValue, forKey: DefaultsKey.sendCrashLogsAutomaticallyKey)
}
}
private var hashOfLastSeenCrashLog: String? {
get {
return UserDefaults.standard.string(forKey: DefaultsKey.hashOfLastSeenCrashLogKey)
}
set {
UserDefaults.standard.set(newValue, forKey: DefaultsKey.hashOfLastSeenCrashLogKey)
}
}
private var lastSeenCrashLogDate: Date? {
get {
let lastSeenCrashLogDouble = UserDefaults.standard.double(forKey: DefaultsKey.lastSeenCrashLogDateKey)
if lastSeenCrashLogDouble < 1.0 {
return nil
}
return Date(timeIntervalSince1970: lastSeenCrashLogDouble)
}
set {
UserDefaults.standard.set(newValue!.timeIntervalSince1970, forKey: DefaultsKey.hashOfLastSeenCrashLogKey)
}
}
/// Look in ~/Library/Logs/DiagnosticReports/ for a new crash log for this app. /// Look in ~/Library/Logs/DiagnosticReports/ for a new crash log for this app.
/// Show a crash log catcher window if found. /// Show a crash log reporter window if found.
func check(appName: String) { static func check(appName: String) {
let folder = ("~/Library/Logs/DiagnosticReports/" as NSString).expandingTildeInPath let folder = ("~/Library/Logs/DiagnosticReports/" as NSString).expandingTildeInPath
let crashSuffix = ".crash" guard let filenames = try? FileManager.default.contentsOfDirectory(atPath: folder) else {
let lowerAppName = appName.lowercased()
var filenames = [String]()
do {
filenames = try FileManager.default.contentsOfDirectory(atPath: folder)
}
catch {
return 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 mostRecentFilePath: String? = nil
var mostRecentFileDate = Date.distantPast var mostRecentFileDate = Date.distantPast
for filename in filenames { for filename in filenames {
if !filename.lowercased().hasPrefix(lowerAppName) { if !filename.lowercased().hasPrefix(lowerAppName) || !filename.hasSuffix(crashSuffix) {
continue
}
if !filename.hasSuffix(crashSuffix) {
continue continue
} }
let path = (folder as NSString).appendingPathComponent(filename) let path = (folder as NSString).appendingPathComponent(filename)
var fileAttributes = [FileAttributeKey: Any]() let fileAttributes = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [FileAttributeKey: Any]()
do {
fileAttributes = try FileManager.default.attributesOfItem(atPath: path)
}
catch {
continue
}
if let fileModificationDate = fileAttributes[.modificationDate] as? Date { if let fileModificationDate = fileAttributes[.modificationDate] as? Date {
if fileModificationDate > mostRecentFileDate { if fileModificationDate > lastSeenCrashLogDate && fileModificationDate > mostRecentFileDate { // Ignore if previously seen
mostRecentFileDate = fileModificationDate mostRecentFileDate = fileModificationDate
mostRecentFilePath = path mostRecentFilePath = path
} }
} }
} }
guard let crashLogPath = mostRecentFilePath else { guard let crashLogPath = mostRecentFilePath, let crashLog = CrashLog(path: crashLogPath, modificationDate: mostRecentFileDate) else {
return return
} }
if let lastSeenCrashLogDate = lastSeenCrashLogDate, lastSeenCrashLogDate >= mostRecentFileDate { if hasSeen(crashLog) {
return return
} }
remember(crashLog)
guard let crashLog = try? NSString(contentsOfFile: crashLogPath, usedEncoding: nil) as String else { if shouldSendCrashLogsAutomatically() {
return
}
let hashOfFoundLog = crashLog.rs_md5Hash()
// Check to see if weve already reported this crash log. Just in case date comparison fails.
if let lastSeenHash = hashOfLastSeenCrashLog, lastSeenHash == hashOfFoundLog {
return
}
hashOfLastSeenCrashLog = hashOfFoundLog
lastSeenCrashLogDate = mostRecentFileDate
// Run crash log window.
if sendCrashLogsAutomatically {
sendCrashLog(crashLog) sendCrashLog(crashLog)
return }
else {
runCrashReporterWindow(crashLog)
} }
} }
static func sendCrashLog(_ crashLog: CrashLog) {
// TODO
}
static func runCrashReporterWindow(_ crashLog: CrashLog) {
// TODO
}
} }
private extension CrashReporter { private extension CrashReporter {
func sendCrashLog(_ crashLog: String) { static func hasSeen(_ crashLog: CrashLog) -> Bool {
// TODO // No need to compare dates, because thats done in the file loop.
// Check to see if weve 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 dont 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)
} }
} }