-
Notifications
You must be signed in to change notification settings - Fork 384
/
Copy pathAllLocationDataSource.swift
115 lines (97 loc) · 3.99 KB
/
AllLocationDataSource.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//
// AllLocationDataSource.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-02-22.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadREST
import MullvadTypes
class AllLocationDataSource: LocationDataSourceProtocol {
private(set) var nodes = [LocationNode]()
var searchableNodes: [LocationNode] {
nodes
}
/// Constructs a collection of node trees from relays fetched from the API.
/// ``RelayLocation.city`` is of special import since we use it to get country
/// and city names.
func reload(_ relays: LocationRelays) {
let rootNode = RootLocationNode()
for relay in relays.relays {
guard case
let .city(countryCode, cityCode) = RelayLocation(dashSeparatedString: relay.location),
let serverLocation = relays.locations[relay.location]
else { continue }
let relayLocation = RelayLocation.hostname(countryCode, cityCode, relay.hostname)
for ancestorOrSelf in relayLocation.ancestors + [relayLocation] {
addLocation(ancestorOrSelf, rootNode: rootNode, serverLocation: serverLocation, relay: relay)
}
}
nodes = rootNode.children
}
func node(by location: RelayLocation) -> LocationNode? {
let rootNode = RootLocationNode(children: nodes)
return switch location {
case let .country(countryCode):
rootNode.descendantNodeFor(codes: [countryCode])
case let .city(countryCode, cityCode):
rootNode.descendantNodeFor(codes: [countryCode, cityCode])
case let .hostname(_, _, hostCode):
rootNode.descendantNodeFor(codes: [hostCode])
}
}
private func addLocation(
_ location: RelayLocation,
rootNode: LocationNode,
serverLocation: REST.ServerLocation,
relay: REST.ServerRelay
) {
switch location {
case let .country(countryCode):
let countryNode = LocationNode(
name: serverLocation.country,
code: LocationNode.combineNodeCodes([countryCode]),
locations: [location],
isActive: true // Defaults to true, updated when children are populated.
)
if !rootNode.children.contains(countryNode) {
rootNode.children.append(countryNode)
rootNode.children.sort()
}
case let .city(countryCode, cityCode):
let cityNode = LocationNode(
name: serverLocation.city,
code: LocationNode.combineNodeCodes([countryCode, cityCode]),
locations: [location],
isActive: true // Defaults to true, updated when children are populated.
)
if let countryNode = rootNode.countryFor(code: countryCode),
!countryNode.children.contains(cityNode) {
cityNode.parent = countryNode
countryNode.children.append(cityNode)
countryNode.children.sort()
}
case let .hostname(countryCode, cityCode, hostCode):
let hostNode = LocationNode(
name: relay.hostname,
code: LocationNode.combineNodeCodes([hostCode]),
locations: [location],
isActive: relay.active
)
if let countryNode = rootNode.countryFor(code: countryCode),
let cityNode = countryNode.cityFor(codes: [countryCode, cityCode]),
!cityNode.children.contains(hostNode) {
hostNode.parent = cityNode
cityNode.children.append(hostNode)
cityNode.children.sort()
cityNode.isActive = cityNode.children.contains(where: { hostNode in
hostNode.isActive
})
countryNode.isActive = countryNode.children.contains(where: { cityNode in
cityNode.isActive
})
}
}
}
}