MongoView/MongoView/Node.swift

239 lines
6.6 KiB
Swift

//
// Node.swift
// MongoView
//
// Created by Shadowfacts on 1/9/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Foundation
import MongoSwift
// Node needs to be an NSObject, since NSOutlineView uses NSObject.isEqual(_:) and NSObject.hash to determine item equality
// which is necessary to prevent items from collapsing when refreshing the view
class Node: NSObject {
let key: Key?
let value: BSON
weak var parent: Node?
lazy private(set) var children: [Node] = {
switch value {
case let .array(array):
return array.enumerated().map { (index, val) in
Node(key: .index(index), value: val, parent: self)
}
case let .document(doc):
return doc.map { (key, val) in
Node(key: .name(key), value: val, parent: self)
}
default:
return []
}
}()
var numberOfChildren: Int {
children.count
}
var hasChildren: Bool {
numberOfChildren > 0
}
init(key: Key? = nil, value: BSON, parent: Node? = nil) {
self.value = value
self.parent = parent
if key == nil,
case let .document(doc) = value,
case let .objectID(id) = doc["_id"] {
self.key = .objectID(id)
} else {
self.key = key
}
}
convenience init(document: BSONDocument) {
if case let .objectID(id) = document["_id"] {
self.init(key: .objectID(id), value: .document(document))
} else {
self.init(key: nil, value: .document(document))
}
}
override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Node else { return false }
return self.parent == object.parent && self.key == object.key
}
override var hash: Int {
var hasher = Hasher()
hasher.combine(parent)
hasher.combine(key)
return hasher.finalize()
}
}
extension Node {
enum Key: Equatable, Hashable {
case index(Int)
case name(String)
case objectID(BSONObjectID)
}
}
extension Node {
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .current
formatter.setLocalizedDateFormatFromTemplate("yyyy-MM-dd HH:mm:ss ZZ")
return formatter
}()
var keyString: String {
switch key {
case nil:
return ""
case let .index(index):
return index.description
case let .name(name):
return name
case let .objectID(id):
return id.description
}
}
var valueString: String {
switch value {
case let .double(value):
return value.description
case let .string(value):
return value
case let .document(doc):
return "(\(doc.count) field\(doc.count == 1 ? "" : "s"))"
case let .array(array):
return "(\(array.count) element\(array.count == 1 ? "" : "s"))"
case let .binary(value):
switch value.subtype {
case .generic:
return "(generic binary data)"
case .function:
return "(function binary data)"
case .binaryDeprecated:
return "(binary data)"
case .uuidDeprecated:
fallthrough
case .uuid:
return try! value.toUUID().description
case .md5:
return "(MD5 binary data)"
default:
return "(unknown binary data))"
}
case .undefined:
return "undefined"
case let .objectID(value):
return value.description
case let .bool(value):
return value.description
case let .datetime(value):
return value.description
case .null:
return "null"
case let .regex(value):
return value.pattern
case let .dbPointer(value):
return "\(value.ref)(\(value.id))"
case let .symbol(value):
return value.description
case let .code(value):
return value.code
case let .codeWithScope(value):
return value.code
case let .int32(value):
return value.description
case let .timestamp(value):
let date = Date(timeIntervalSince1970: TimeInterval(value.timestamp))
return Node.dateFormatter.string(from: date)
case let .int64(value):
return value.description
case let .decimal128(value):
return value.description
case .minKey:
return "(min key)"
case .maxKey:
return "(max key)"
}
}
var typeString: String {
switch value {
case .double(_):
return "Double"
case .string(_):
return "String"
case .document(_):
return "Document"
case .array(_):
return "Array"
case let .binary(value):
switch value.subtype {
case .generic:
return "Generic binary data"
case .function:
return "Function binary data"
case .binaryDeprecated:
return "Binary data"
case .uuidDeprecated:
fallthrough
case .uuid:
return "UUID"
case .md5:
return "MD5 hash"
default:
return "Unknown binary data"
}
case .undefined:
return "Undefined"
case .objectID(_):
return "ObjectId"
case .bool(_):
return "Bool"
case .datetime(_):
return "DateTime"
case .null:
return "Null"
case .regex(_):
return "RegEx"
case .dbPointer(_):
return "DBRef"
case .symbol(_):
return "Symbol"
case .code(_):
return "Code"
case .codeWithScope(_):
return "Code with scope"
case .int32(_):
return "Int32"
case .timestamp(_):
return "Timestamp"
case .int64(_):
return "Int64"
case .decimal128(_):
return "Decimal128"
case .minKey:
return "MinKey"
case .maxKey:
return "MaxKey"
}
}
var isValueCopyable: Bool {
switch value {
case .document(_), .array(_), .binary(_), .minKey, .maxKey:
return false
default:
return true
}
}
}