Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance tuning #131

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import SMBClient

class ServersViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
private let tableView = UITableView(frame: .zero, style: .insetGrouped)

private var services = [Service]()
private var sessions = [String: SMBClient]()

override func viewDidLoad() {
Expand Down Expand Up @@ -40,6 +42,7 @@ class ServersViewController: UIViewController, UITableViewDataSource, UITableVie

@objc
private func serviceDidDiscover(_ notification: Notification) {
services = ServiceDiscovery.shared.services.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }
tableView.reloadData()
}

Expand Down Expand Up @@ -117,11 +120,9 @@ class ServersViewController: UIViewController, UITableViewDataSource, UITableVie

switch indexPath.section {
case 0:
let services = ServiceDiscovery.shared.services
let service = services[indexPath.row]

cell.textLabel?.text = service.name

if let _ = sessions[service.id.rawValue] {
cell.accessoryType = .disclosureIndicator
} else {
Expand All @@ -132,7 +133,6 @@ class ServersViewController: UIViewController, UITableViewDataSource, UITableVie
let server = servers[indexPath.row]

cell.textLabel?.text = server.displayName

if let _ = sessions[server.id.rawValue] {
cell.accessoryType = .disclosureIndicator
} else {
Expand All @@ -149,7 +149,6 @@ class ServersViewController: UIViewController, UITableViewDataSource, UITableVie
Task { @MainActor in
switch indexPath.section {
case 0:
let services = ServiceDiscovery.shared.services
let service = services[indexPath.row]

if let client = sessions[service.id.rawValue] {
Expand Down
43 changes: 0 additions & 43 deletions Examples/FileBrowser/FileBrowser (iOS)/ServiceDiscovery.swift

This file was deleted.

6 changes: 3 additions & 3 deletions Examples/FileBrowser/FileBrowser (macOS)/DataRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ class DataRepository {

private init() {}

func set(_ path: String, nodes: [Node]) {
func set<Item: Node>(_ path: String, nodes: [Item]) {
data[path] = nodes.map { $0.detach() }
}

func nodes(_ path: String) -> [Node]? {
data[path]
func nodes<Item: Node>(_ path: String) -> [Item]? {
data[path] as? [Item]
}
}
120 changes: 51 additions & 69 deletions Examples/FileBrowser/FileBrowser (macOS)/DirectoryStructure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,34 @@ class DirectoryStructure {
private let server: String
private let path: String

private var tree = Tree()
private var viewTree = Tree()
private var tree = Tree<FileNode>()
private var viewTree = Tree<FileNode>()

private var searchText = ""
private var sortDescriptor = NSSortDescriptor(key: "NameColumn", ascending: true)

private let client: SMBClient

var useCache = false {
didSet {
if !useCache {
cache.removeAll()
}
}
}
private var cache = [FileNode: [FileNode]]()

init(server: String, path: String, client: SMBClient) {
self.server = server
self.path = path
self.client = client
}

func viewTree(_ tree: Tree) -> Tree {
func viewTree(_ tree: Tree<FileNode>) -> Tree<FileNode> {
let nodes = tree.nodes
.compactMap {
guard let node = $0 as? FileNode else { return nil }
return node
}
.sorted(sortDescriptor)

let viewTree: Tree
let viewTree: Tree<FileNode>
if !searchText.isEmpty {
let filteredNodes = nodes.filter {
$0.name.localizedCaseInsensitiveContains(searchText)
Expand All @@ -43,9 +48,7 @@ class DirectoryStructure {

func reload() async {
let nodes = await listDirectory(path: path, parent: nil)

tree.nodes.removeAll()
tree.nodes.append(contentsOf: nodes)
tree.nodes = nodes

viewTree = viewTree(tree)
}
Expand All @@ -57,11 +60,7 @@ class DirectoryStructure {
let nodes = await listDirectory(path: path, parent: nil)
let rootNodes = tree.rootNodes()

let newNodes = mergeNodes(oldNodes: rootNodes, newNodes: nodes)

let set = Set(rootNodes)
tree.nodes = tree.nodes.filter { !set.contains($0) } + newNodes

tree.nodes = Array(Set(rootNodes).union(nodes))
viewTree = viewTree(tree)

outlineView.reloadData()
Expand All @@ -72,52 +71,41 @@ class DirectoryStructure {
let path = resolvePath(fileNode)

let nodes = await listDirectory(path: path, parent: fileNode)
let childlen = children(of: fileNode)

let (deleted, inserted) = nodeDelta(oldNodes: childlen, newNodes: nodes)
let newNodes = mergeNodes(oldNodes: childlen, newNodes: nodes)
let children = children(of: fileNode)

let set = Set(childlen)
tree.nodes = tree.nodes.filter { !set.contains($0) } + newNodes
let (deleted, inserted) = nodeDelta(oldNodes: children, newNodes: nodes)
tree.nodes = Array(
Set(tree.nodes)
.subtracting(children)
.union(nodes)
)

viewTree = viewTree(tree)

outlineView.beginUpdates()
outlineView.removeItems(at: IndexSet(deleted), inParent: fileNode)
outlineView.insertItems(at: IndexSet(inserted), inParent: fileNode, withAnimation: childlen.isEmpty ? .slideDown : [])
outlineView.insertItems(at: IndexSet(inserted), inParent: fileNode, withAnimation: children.isEmpty ? .slideDown : [])
outlineView.endUpdates()
}

func update(_ outlineView: NSOutlineView) {
guard let newRootNodes = DataRepository.shared.nodes(join(server, path)) else {
guard let rootNodes: [FileNode] = DataRepository.shared.nodes(join(server, path)) else {
return
}
let expandedNodes = tree.nodes
.compactMap {
$0 as? FileNode
}

let childNodes = tree.nodes
.filter {
return $0.isDirectory && outlineView.isItemExpanded($0)
}
var newChildNodes = [Node]()
for expandedNode in expandedNodes {
if let nodes = DataRepository.shared.nodes(join(server, expandedNode.path)) {
for case let node as FileNode in nodes {
newChildNodes.append(FileNode(path: node.path, file: node.file, parent: ID(expandedNode.path)))
.reduce(into: [FileNode]()) {
guard let nodes: [FileNode] = DataRepository.shared.nodes(join(server, $1.path)) else {
return
}
let parent = $1.id
$0 += nodes.map { FileNode(path: $0.path, file: $0.file, parent: parent) }
}
}

let oldNodes = Set(tree.nodes)
let newNodes = Set(newRootNodes + newChildNodes)

let common = oldNodes.intersection(newNodes)
let added = newNodes.subtracting(oldNodes)

var merged = Array(common)
merged.append(contentsOf: added)

tree.nodes = merged
tree.nodes = rootNodes + childNodes

viewTree = viewTree(tree)
outlineView.reloadData()
Expand All @@ -133,9 +121,9 @@ class DirectoryStructure {
viewTree = viewTree(tree)
}

func resolvePath(_ node: Node) -> String {
func resolvePath(_ node: FileNode) -> String {
var subpath = node.name
var current: Node = node
var current: FileNode = node
while let parent = tree.parent(of: current) {
subpath = join(parent.name, subpath)
current = parent
Expand All @@ -148,16 +136,24 @@ class DirectoryStructure {
viewTree.nodes.count
}

func parent(of node: Node) -> Node? {
func parent(of node: FileNode) -> FileNode? {
viewTree.parent(of: node)
}

func children(of node: Node) -> [Node] {
viewTree.children(of: node)
func children(of node: FileNode) -> [FileNode] {
if useCache, let children = cache[node] {
return children
} else {
let children = viewTree.children(of: node)
if useCache {
cache[node] = children
}
return children
}
}

func node(_ id: ID) -> FileNode? {
return viewTree.nodes.first { $0.id == id } as? FileNode
viewTree.nodes.first { $0.id == id }
}

func node(_ fileURL: URL) -> FileNode? {
Expand All @@ -168,7 +164,7 @@ class DirectoryStructure {
func numberOfChildren(of item: Any?) -> Int {
if let node = item as? FileNode {
if node.isExpandable {
return viewTree.children(of: node).count
return children(of: node).count
} else {
return 0
}
Expand All @@ -179,7 +175,8 @@ class DirectoryStructure {

func child(index: Int, of item: Any?) -> Any {
if let node = item as? FileNode {
return viewTree.children(of: node)[index]
let children = children(of: node)
return children[index]
} else {
return viewTree.rootNodes()[index]
}
Expand All @@ -192,7 +189,7 @@ class DirectoryStructure {
return false
}

private func listDirectory(path: String, parent: FileNode?) async -> [Node] {
private func listDirectory(path: String, parent: FileNode?) async -> [FileNode] {
do {
let files = try await client.listDirectory(path: path)
.filter { $0.name != "." && $0.name != ".." && !$0.isHidden }
Expand All @@ -210,7 +207,7 @@ class DirectoryStructure {
return DataRepository.shared.nodes(join(server, path)) ?? []
}

private func nodeDelta(oldNodes: [Node], newNodes: [Node]) -> (deleted: [Int], inserted: [Int]) {
private func nodeDelta(oldNodes: [FileNode], newNodes: [FileNode]) -> (deleted: [Int], inserted: [Int]) {
let oldSet = Set(oldNodes)
let newSet = Set(newNodes)

Expand All @@ -224,21 +221,6 @@ class DirectoryStructure {

return (deleted, inserted)
}

private func mergeNodes(oldNodes: [Node], newNodes: [Node]) -> [Node] {
var mergedNodes = [Node]()
let oldDict = Dictionary(uniqueKeysWithValues: oldNodes.map { ($0, $0) })

for newNode in newNodes {
if let oldNode = oldDict[newNode] {
mergedNodes.append(oldNode)
} else {
mergedNodes.append(newNode)
}
}

return mergedNodes
}
}

private extension Array where Element == FileNode {
Expand Down
20 changes: 14 additions & 6 deletions Examples/FileBrowser/FileBrowser (macOS)/FilesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ class FilesViewController: NSViewController {
private var tabGroupObserving: NSKeyValueObservation?
private var scrollViewObserving: NSKeyValueObservation?

private let semaphore = Semaphore(value: 1)

private var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
Expand Down Expand Up @@ -198,7 +196,7 @@ class FilesViewController: NSViewController {
Task {
let name = NSLocalizedString("untitled folder", comment: "")
if let parent {
await dirTree.reload(directory: join(parent, name), outlineView)
await dirTree.reload(directory: parent, outlineView)
} else {
try await client.createDirectory(path: join(path, name))
await dirTree.reload(directory: path, outlineView)
Expand Down Expand Up @@ -338,7 +336,7 @@ class FilesViewController: NSViewController {

navigationController.push(filesViewController)
} else {
guard let shares = DataRepository.shared.nodes(serverNode.path) else { return }
guard let shares: [ShareNode] = DataRepository.shared.nodes(serverNode.path) else { return }
let sharesViewController = SharesViewController.instantiate(serverNode: serverNode, shares: Tree(nodes: shares))
navigationController.push(sharesViewController)
}
Expand Down Expand Up @@ -585,16 +583,26 @@ extension FilesViewController: NSOutlineViewDelegate {
guard let fileNode = userInfo["NSObject"] as? FileNode else { return }
guard fileNode.isDirectory else { return }

dirTree.useCache = true

Task { @MainActor in
await semaphore.wait()
defer { Task { await semaphore.signal() } }
dirTree.useCache = false

await dirTree.expand(fileNode, outlineView)
updateItemCount()
}
}

func outlineViewItemDidExpand(_ notification: Notification) {
updateItemCount()
}

func outlineViewItemWillCollapse(_ notification: Notification) {
dirTree.useCache = true
}

func outlineViewItemDidCollapse(_ notification: Notification) {
dirTree.useCache = false
updateItemCount()
}
}
Expand Down
Loading
Loading