Huge refactor to add better structure to the project. Separating features with their associated view and model structure. This should be better organization and will allow us to improve the overall architecture in the future. I forsee many more improvements that can follow this change. e.g. MVVM Arch As well as cleaning up duplicate, unused, functionality. Many files have global functions that can also be moved or be renamed. damus/ ├── Features/ │ ├── <Feature>/ │ │ ├── Views/ │ │ └── Models/ ├── Shared/ │ ├── Components/ │ ├── Media/ │ ├── Buttons/ │ ├── Extensions/ │ ├── Empty Views/ │ ├── ErrorHandling/ │ ├── Modifiers/ │ └── Utilities/ ├── Core/ │ ├── Nostr/ │ ├── NIPs/ │ ├── DIPs/ │ ├── Types/ │ ├── Networking/ │ └── Storage/ Signed-off-by: ericholguin <ericholguin@apache.org>
147 lines
4.7 KiB
Swift
147 lines
4.7 KiB
Swift
//
|
|
// 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: RelayURL?
|
|
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: RelayURL? = 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) {
|
|
Task {
|
|
await addLine(content)
|
|
await publishChanges()
|
|
}
|
|
}
|
|
|
|
@MainActor private func addLine(_ line: String) {
|
|
let line = "\(formatter.string(from: .now)) - \(line)"
|
|
lines.insert(line, at: 0)
|
|
truncateLines()
|
|
}
|
|
|
|
/// 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)")
|
|
}
|
|
}
|
|
}
|