add RelayLog class

Signed-off-by: Bryan Montz <bryanmontz@me.com>
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
Bryan Montz 2023-07-09 08:45:33 -05:00 committed by William Casarin
parent 13f98659a4
commit ef4aeb40e0
2 changed files with 147 additions and 0 deletions

View File

@ -295,6 +295,7 @@
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */; };
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
50A60D142A28BEEE00186190 /* RelayLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A60D132A28BEEE00186190 /* RelayLog.swift */; };
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; };
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
@ -765,6 +766,7 @@
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = "<group>"; };
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
50A60D132A28BEEE00186190 /* RelayLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayLog.swift; sourceTree = "<group>"; };
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = "<group>"; };
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
@ -1132,6 +1134,7 @@
children = (
501F8C5329FF5EE2001AFC1D /* CoreData */,
4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */,
50A60D132A28BEEE00186190 /* RelayLog.swift */,
4C75EFA527FF87A20006080F /* Nostr.swift */,
4C75EFAE28049D340006080F /* NostrFilter.swift */,
4C75EFB028049D510006080F /* NostrResponse.swift */,
@ -1980,6 +1983,7 @@
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */,
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
50A60D142A28BEEE00186190 /* RelayLog.swift in Sources */,
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,

143
damus/Nostr/RelayLog.swift Normal file
View File

@ -0,0 +1,143 @@
//
// RelayLog.swift
// damus
//
// Created by Bryan Montz on 6/1/23.
//
import Combine
import Foundation
import UIKit
/// Stores a running list of events and state changes related to a relay, so that users
/// will have information to help developers debug issues.
final class RelayLog: ObservableObject {
private static let line_limit = 250
private let relay_url: URL?
private lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
private(set) var lines = [String]()
private var notification_token: AnyCancellable?
/// Creates a RelayLog
/// - Parameter relay_url: the relay url the log represents. Pass nil for the url to create
/// a RelayLog that does nothing. This is required to allow RelayLog to be used as a StateObject,
/// because they cannot be Optional.
init(_ relay_url: URL? = nil) {
self.relay_url = relay_url
setUp()
}
private var log_files_directory: URL {
FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("RelayLogs", isDirectory: true)
}
private var log_file_url: URL? {
guard let file_name = relay_url?.absoluteString.data(using: .utf8) else {
return nil
}
return log_files_directory.appendingPathComponent(file_name.base64EncodedString())
}
/// Sets up the log file and prepares to listen to app state changes
private func setUp() {
guard let log_file_url else {
return
}
try? FileManager.default.createDirectory(at: log_files_directory, withIntermediateDirectories: false)
if !FileManager.default.fileExists(atPath: log_file_url.path) {
// create the log file if it doesn't exist yet
FileManager.default.createFile(atPath: log_file_url.path, contents: nil)
} else {
// otherwise load it into memory
readFromDisk()
}
let willResignPublisher = NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)
let willTerminatePublisher = NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)
notification_token = Publishers.Merge(willResignPublisher, willTerminatePublisher)
.sink { [weak self] _ in
self?.writeToDisk()
}
}
/// The current contents of the log
var contents: String? {
guard !lines.isEmpty else {
return nil
}
return lines.joined(separator: "\n")
}
/// Adds content to the log
/// - Parameter content: what to add to the log. The date and time are prepended to the content.
func add(_ content: String) {
let line = "\(formatter.string(from: .now)) - \(content)"
lines.insert(line, at: 0)
truncateLines()
Task {
await publishChanges()
}
}
/// Tells views that our log has been updated
@MainActor private func publishChanges() {
objectWillChange.send()
}
private func truncateLines() {
lines = Array(lines.prefix(RelayLog.line_limit))
}
/// Reads the contents of the log file from disk into memory
private func readFromDisk() {
guard let log_file_url else {
return
}
do {
let handle = try FileHandle(forReadingFrom: log_file_url)
let data = try handle.readToEnd()
try handle.close()
guard let data, let content = String(data: data, encoding: .utf8) else {
return
}
lines = content.components(separatedBy: "\n")
truncateLines()
} catch {
print("⚠️ Warning: RelayLog failed to read from \(log_file_url)")
}
}
/// Writes the contents of the lines in memory to disk
private func writeToDisk() {
guard let log_file_url, let relay_url,
!lines.isEmpty,
let content = lines.joined(separator: "\n").data(using: .utf8) else {
return
}
do {
let handle = try FileHandle(forWritingTo: log_file_url)
try handle.truncate(atOffset: 0)
try handle.write(contentsOf: content)
try handle.close()
} catch {
print("⚠️ Warning: RelayLog(\(relay_url)) failed to write to file: \(error)")
}
}
}