Make CrashReporter a static struct. Add CrashLog immutable struct.
This commit is contained in:
parent
22e17a6d92
commit
f2de9f1a60
|
@ -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 {
|
||||||
|
|
|
@ -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 we’ve 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 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue