Files
Pulse/Sources/PulseUI/Features/Console/ConsoleNetworkRequestViewModel.swift
T

164 lines
5.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The MIT License (MIT)
//
// Copyright (c) 20202022 Alexander Grebenyuk (github.com/kean).
import SwiftUI
import PulseCore
import Combine
import CoreData
final class ConsoleNetworkRequestViewModel: Pinnable, ObservableObject {
#if os(iOS)
lazy var time = ConsoleMessageViewModel.timeFormatter.string(from: request.createdAt)
var badgeColor: UIColor = .gray
#else
var badgeColor: Color = .gray
#endif
var status: String = ""
var title: String = ""
var text: String = ""
var state: LoggerNetworkRequestEntity.State = .pending
private let request: LoggerNetworkRequestEntity
private let store: LoggerStore
private var cancellable: AnyCancellable?
static let timeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss.SSS"
return formatter
}()
init(request: LoggerNetworkRequestEntity, store: LoggerStore) {
self.request = request
self.store = store
self.refresh()
self.cancellable = request.objectWillChange.sink { [weak self] in
withAnimation {
self?.refresh()
self?.objectWillChange.send()
}
}
}
private func refresh() {
let time = ConsoleMessageViewModel.timeFormatter.string(from: request.createdAt)
let state = request.state
var prefix: String
switch request.state {
case .pending:
prefix = "PENDING"
if request.totalUnitCount > 0 {
func format(_ bytes: Int64) -> String {
ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
}
prefix += " · \(format(request.completedUnitCount)) / \(format(request.totalUnitCount))"
}
case .success:
prefix = StatusCodeFormatter.string(for: Int(request.statusCode))
case .failure:
if request.errorCode != 0 {
prefix = "\(request.errorCode) \(descriptionForURLErrorCode(Int(request.errorCode)))"
} else {
prefix = StatusCodeFormatter.string(for: Int(request.statusCode))
}
}
#if os(iOS)
self.status = ""
var title = prefix
if request.duration > 0 {
title += " · \(DurationFormatter.string(from: request.duration))"
}
if request.responseBodySize > 0 {
let size = ByteCountFormatter.string(fromByteCount: request.responseBodySize, countStyle: .file)
title += " · \(request.isFromCache ? "Cache" : size)"
}
self.title = title
switch state {
case .pending: self.badgeColor = .systemYellow
case .success: self.badgeColor = .systemGreen
case .failure: self.badgeColor = .systemRed
}
#else
if request.responseBodySize > 0 {
let size = ByteCountFormatter.string(fromByteCount: request.responseBodySize, countStyle: .file)
prefix += " · \(request.isFromCache ? "Cache" : size)"
}
self.status = prefix
var title = "\(time)"
if request.duration > 0 {
title += " · \(DurationFormatter.string(from: request.duration))"
}
self.title = title
switch state {
case .pending: self.badgeColor = .yellow
case .success: self.badgeColor = .green
case .failure: self.badgeColor = .red
}
#endif
let method = request.httpMethod ?? "GET"
self.text = method + " " + (request.url ?? "")
self.state = request.state
}
// MARK: Pins
lazy var pinViewModel = PinButtonViewModel(store: store, request: request)
// MARK: Context Menu
func shareAsPlainText() -> ShareItems {
ShareItems([ConsoleShareService(store: store).share(request, output: .plainText)])
}
func shareAsMarkdown() -> ShareItems {
let text = ConsoleShareService(store: store).share(request, output: .markdown)
let directory = TemporaryDirectory()
let fileURL = directory.write(text: text, extension: "markdown")
return ShareItems([fileURL], cleanup: directory.remove)
}
func shareAsHTML() -> ShareItems {
let text = ConsoleShareService(store: store).share(request, output: .html)
let directory = TemporaryDirectory()
let fileURL = directory.write(text: text, extension: "html")
return ShareItems([fileURL], cleanup: directory.remove)
}
func shareAsCURL() -> ShareItems {
let summary = NetworkLoggerSummary(request: request, store: store)
return ShareItems([summary.cURLDescription()])
}
var containsResponseData: Bool {
request.responseBodyKey != nil
}
// WARNING: This call is relatively expensive.
var responseString: String? {
request.responseBodyKey
.flatMap(store.getData)
.flatMap { String(data: $0, encoding: .utf8) }
}
var url: String? {
request.url
}
var host: String? {
request.host
}
var cURLDescription: String {
NetworkLoggerSummary(request: request, store: store).cURLDescription()
}
}