Skip to content

Commit d0d1a3a

Browse files
fedemkrmatt-livefrontKatherineInCode
authored
[PM-18285] Vault repository refactor to improve performance (#1677)
Co-authored-by: Matt Czech <[email protected]> Co-authored-by: Katherine Bertelsen <[email protected]>
1 parent df1e16e commit d0d1a3a

File tree

52 files changed

+4630
-1388
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+4630
-1388
lines changed

Bitwarden/Application/Support/Settings.bundle/Acknowledgements.plist

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@
122122
<key>Type</key>
123123
<string>PSChildPaneSpecifier</string>
124124
</dict>
125+
<dict>
126+
<key>File</key>
127+
<string>Acknowledgements/Sourcery</string>
128+
<key>Title</key>
129+
<string>Sourcery</string>
130+
<key>Type</key>
131+
<string>PSChildPaneSpecifier</string>
132+
</dict>
125133
<dict>
126134
<key>File</key>
127135
<string>Acknowledgements/swift-custom-dump</string>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>PreferenceSpecifiers</key>
6+
<array>
7+
<dict>
8+
<key>FooterText</key>
9+
<string>MIT License
10+
11+
Copyright (c) 2016-2021 Krzysztof Zabłocki
12+
13+
Permission is hereby granted, free of charge, to any person obtaining a copy
14+
of this software and associated documentation files (the "Software"), to deal
15+
in the Software without restriction, including without limitation the rights
16+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
copies of the Software, and to permit persons to whom the Software is
18+
furnished to do so, subject to the following conditions:
19+
20+
The above copyright notice and this permission notice shall be included in all
21+
copies or substantial portions of the Software.
22+
23+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29+
SOFTWARE.
30+
</string>
31+
<key>License</key>
32+
<string>MIT</string>
33+
<key>Type</key>
34+
<string>PSGroupSpecifier</string>
35+
</dict>
36+
</array>
37+
</dict>
38+
</plist>

BitwardenShared/Core/Autofill/Utilities/AutofillListMode.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// The mode in which the autofil list presents its items.
2-
public enum AutofillListMode {
2+
public enum AutofillListMode: Sendable {
33
/// The autofill list shows all ciphers for autofill.
44
/// This is used on autofill with text to insert.
55
/// Only filters deleted items.

BitwardenShared/Core/Platform/Services/ServiceContainer.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
489489
)
490490

491491
let policyService = DefaultPolicyService(
492+
configService: configService,
492493
organizationService: organizationService,
493494
policyDataStore: dataStore,
494495
stateService: stateService
@@ -693,6 +694,32 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
693694
vaultTimeoutService: vaultTimeoutService
694695
)
695696

697+
let vaultListDirectorStrategyFactory = DefaultVaultListDirectorStrategyFactory(
698+
cipherService: cipherService,
699+
collectionService: collectionService,
700+
folderService: folderService,
701+
vaultListBuilderFactory: DefaultVaultListSectionsBuilderFactory(
702+
clientService: clientService,
703+
errorReporter: errorReporter
704+
),
705+
vaultListDataPreparator: DefaultVaultListDataPreparator(
706+
ciphersClientWrapperService: DefaultCiphersClientWrapperService(
707+
clientService: clientService,
708+
errorReporter: errorReporter
709+
),
710+
clientService: clientService,
711+
errorReporter: errorReporter,
712+
policyService: policyService,
713+
stateService: stateService,
714+
vaultListPreparedDataBuilderFactory: DefaultVaultListPreparedDataBuilderFactory(
715+
clientService: clientService,
716+
errorReporter: errorReporter,
717+
stateService: stateService,
718+
timeProvider: timeProvider
719+
)
720+
)
721+
)
722+
696723
let vaultRepository = DefaultVaultRepository(
697724
cipherService: cipherService,
698725
clientService: clientService,
@@ -707,6 +734,7 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
707734
stateService: stateService,
708735
syncService: syncService,
709736
timeProvider: timeProvider,
737+
vaultListDirectorStrategyFactory: vaultListDirectorStrategyFactory,
710738
vaultTimeoutService: vaultTimeoutService
711739
)
712740

BitwardenShared/Core/Platform/Utilities/Constants.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ extension Constants {
1414
/// The minimum server version required to have cipher key encryption on.
1515
static let cipherKeyEncryptionMinServerVersion = "2024.2.0"
1616

17+
/// The size of the slice to decrypt ciphers in batch using the SDK.
18+
static let decryptCiphersBatchSize: Int = 100
19+
1720
/// The default type for a Fido2 public key credential.
1821
static let defaultFido2PublicKeyCredentialType = "public-key"
1922

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import Foundation
2+
3+
// MARK: - SortDescriptorWrapper<T>
4+
5+
/// A wrapper of `SortDescriptor<T>` that uses the native descriptor if available
6+
/// and fallbacks to `BackportSortDescriptor` otherwise.
7+
/// This will no longer be necessary if the minimum iOS version is bumped to iOS 17.
8+
struct SortDescriptorWrapper<T> {
9+
/// Comparator to use to sort.
10+
private let _compare: (T, T) -> ComparisonResult
11+
12+
/// Initializes the sort descriptor.
13+
/// - Parameters:
14+
/// - keyPath: The key path to use to sort.
15+
/// - comparator: The comparator to use to sort the string.
16+
/// - order: The sort order to use.
17+
init(
18+
_ keyPath: KeyPath<T, String>,
19+
comparator: String.StandardComparator,
20+
order: SortOrder = .forward
21+
) {
22+
if #available(iOS 17, *) {
23+
// Use native SortDescriptor on iOS 17+
24+
let native = SortDescriptor(keyPath, comparator: comparator, order: order)
25+
_compare = { lhs, rhs in native.compare(lhs, rhs) }
26+
} else if #available(iOS 16, *) {
27+
let comparison = { (lhs: String, rhs: String) in
28+
comparator.compare(lhs, rhs)
29+
}
30+
// iOS 16 fallback using BackportSortDescriptor
31+
let backport = BackportSortDescriptor<T>(comparator: { lhs, rhs in
32+
let result = comparison(lhs[keyPath: keyPath], rhs[keyPath: keyPath])
33+
return order == .forward ? result : result.reversed()
34+
})
35+
_compare = backport.compare
36+
} else {
37+
// iOS 15 fallback using `localizedStandardCompare`.
38+
let backport = BackportSortDescriptor<T>(comparator: { lhs, rhs in
39+
let lhsStr = lhs[keyPath: keyPath]
40+
let rhsStr = rhs[keyPath: keyPath]
41+
let result = lhsStr.localizedStandardCompare(rhsStr)
42+
return order == .forward ? result : result.reversed()
43+
})
44+
_compare = backport.compare
45+
}
46+
}
47+
48+
/// Initializes the sort descriptor.
49+
/// - Parameters:
50+
/// - keyPath: The key path to use to sort.
51+
/// - comparator: The comparator to use to sort the string.
52+
/// - order: The sort order to use.
53+
init(
54+
_ keyPath: KeyPath<T, String?>,
55+
comparator: String.StandardComparator,
56+
order: SortOrder = .forward
57+
) {
58+
if #available(iOS 17, *) {
59+
let native = SortDescriptor<T>(keyPath, comparator: comparator, order: order)
60+
_compare = { lhs, rhs in native.compare(lhs, rhs) }
61+
} else if #available(iOS 16, *) {
62+
let comparison: (String?, String?) -> ComparisonResult = { lhs, rhs in
63+
switch (lhs, rhs) {
64+
case let (lhsValue?, rhsValue?):
65+
return comparator.compare(lhsValue, rhsValue)
66+
case (nil, nil):
67+
return .orderedSame
68+
case (nil, _):
69+
return .orderedAscending
70+
case (_, nil):
71+
return .orderedDescending
72+
}
73+
}
74+
let backport = BackportSortDescriptor<T>(comparator: { lhs, rhs in
75+
let result = comparison(lhs[keyPath: keyPath], rhs[keyPath: keyPath])
76+
return order == .forward ? result : result.reversed()
77+
})
78+
_compare = backport.compare
79+
} else {
80+
// iOS 15 fallback using localizedStandardCompare
81+
let backport = BackportSortDescriptor<T>(comparator: { lhs, rhs in
82+
let lhsStr = lhs[keyPath: keyPath]
83+
let rhsStr = rhs[keyPath: keyPath]
84+
85+
let result: ComparisonResult
86+
switch (lhsStr, rhsStr) {
87+
case let (lhsValue?, rhsValue?):
88+
result = lhsValue.localizedStandardCompare(rhsValue)
89+
case (nil, nil):
90+
result = .orderedSame
91+
case (nil, _):
92+
result = .orderedAscending
93+
case (_, nil):
94+
result = .orderedDescending
95+
}
96+
97+
return order == .forward ? result : result.reversed()
98+
})
99+
_compare = backport.compare
100+
}
101+
}
102+
103+
/// Initializes the sort descriptor.
104+
/// - Parameters:
105+
/// - keyPath: The key path to use to sort.
106+
/// - ascending: Whether the order should be ascending.
107+
init<Value: Comparable>(_ keyPath: KeyPath<T, Value>, ascending: Bool = true) {
108+
if #available(iOS 17, *) {
109+
let native = SortDescriptor(keyPath, order: ascending ? .forward : .reverse)
110+
_compare = { lhs, rhs in native.compare(lhs, rhs) }
111+
} else {
112+
let backport = BackportSortDescriptor<T>(key: keyPath, ascending: ascending)
113+
_compare = backport.compare
114+
}
115+
}
116+
117+
/// Compares two values.
118+
func compare(_ lhs: T, _ rhs: T) -> ComparisonResult {
119+
_compare(lhs, rhs)
120+
}
121+
}
122+
123+
// MARK: - BackportSortDescriptor<T>
124+
125+
/// Backport version of `SortDescriptor<T>` so it can be used on older than iOS 17 devices.
126+
struct BackportSortDescriptor<T> {
127+
/// Comparator to use to sort.
128+
private let comparator: (T, T) -> ComparisonResult
129+
130+
/// Initializes the sort descriptor.
131+
/// - Parameter comparator: The comparator closure to use to sort.
132+
init(comparator: @escaping (T, T) -> ComparisonResult) {
133+
self.comparator = comparator
134+
}
135+
136+
/// Initializes the sort descriptor.
137+
/// - Parameters:
138+
/// - key: The key path to use to sort.
139+
/// - ascending: Whether the order should be ascending.
140+
init<Value: Comparable>(key: KeyPath<T, Value>, ascending: Bool = true) {
141+
comparator = { lhs, rhs in
142+
let lhsValue = lhs[keyPath: key]
143+
let rhsValue = rhs[keyPath: key]
144+
if lhsValue == rhsValue {
145+
return .orderedSame
146+
}
147+
return ascending
148+
? (lhsValue < rhsValue ? .orderedAscending : .orderedDescending)
149+
: (lhsValue > rhsValue ? .orderedAscending : .orderedDescending)
150+
}
151+
}
152+
153+
/// Compares two values.
154+
func compare(_ lhs: T, _ rhs: T) -> ComparisonResult {
155+
comparator(lhs, rhs)
156+
}
157+
}
158+
159+
// MARK: - ComparisonResult
160+
161+
extension ComparisonResult {
162+
/// Inverts the order of a `ComparisonResult`.
163+
func reversed() -> ComparisonResult {
164+
switch self {
165+
case .orderedAscending:
166+
return .orderedDescending
167+
case .orderedDescending:
168+
return .orderedAscending
169+
case .orderedSame:
170+
return .orderedSame
171+
}
172+
}
173+
}
174+
175+
// MARK: - Array extension
176+
177+
extension Array {
178+
/// Sorts using descriptors.
179+
/// - Parameter descriptor: The descriptor to use to sort the array.
180+
/// - Returns: The sorted array.
181+
func sorted(using descriptor: SortDescriptorWrapper<Element>) -> [Element] {
182+
sorted(using: [descriptor])
183+
}
184+
185+
/// Sorts using descriptors.
186+
/// - Parameter descriptors: The descriptors to use to sort the array.
187+
/// - Returns: The sorted array.
188+
func sorted(using descriptors: [SortDescriptorWrapper<Element>]) -> [Element] {
189+
sorted { lhs, rhs in
190+
for descriptor in descriptors {
191+
let result = descriptor.compare(lhs, rhs)
192+
if result != .orderedSame {
193+
return result == .orderedAscending
194+
}
195+
}
196+
return false
197+
}
198+
}
199+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import XCTest
2+
3+
@testable import BitwardenShared
4+
5+
// MARK: - SortDescriptorWrapperTests
6+
7+
class SortDescriptorWrapperTests: BitwardenTestCase {
8+
// MARK: Tests
9+
10+
/// Using a `SortDescriptorWrapper` sorts an array of object in ascending manner for non-optional string value.
11+
func test_sortDescriptor_sortsAscending() {
12+
let sortDescriptor = SortDescriptorWrapper<FooToSort>(\.sortValue, comparator: .localizedStandard)
13+
let values = [
14+
FooToSort(sortValue: "one value"),
15+
FooToSort(sortValue: "2nd value"),
16+
FooToSort(sortValue: "old value"),
17+
FooToSort(sortValue: "1234567"),
18+
FooToSort(sortValue: "zzzzzzzz"),
19+
FooToSort(sortValue: "aaa"),
20+
FooToSort(sortValue: "bbbb"),
21+
]
22+
XCTAssertEqual(values.sorted(using: sortDescriptor).map(\.sortValue), [
23+
"2nd value",
24+
"1234567",
25+
"aaa",
26+
"bbbb",
27+
"old value",
28+
"one value",
29+
"zzzzzzzz",
30+
])
31+
}
32+
33+
/// Using a `SortDescriptorWrapper` sort an array of objects in descending manner for optional string value.
34+
func test_sortDescriptor_sortsDescending() {
35+
let sortDescriptor = SortDescriptorWrapper<FooToSort>(
36+
\.optionalSortValue,
37+
comparator: .localizedStandard,
38+
order: .reverse
39+
)
40+
let values = [
41+
FooToSort(sortValue: "", optionalSortValue: "one value"),
42+
FooToSort(sortValue: "", optionalSortValue: "2nd value"),
43+
FooToSort(sortValue: "", optionalSortValue: "old value"),
44+
FooToSort(sortValue: "", optionalSortValue: "1234567"),
45+
FooToSort(sortValue: "", optionalSortValue: "zzzzzzzz"),
46+
FooToSort(sortValue: "", optionalSortValue: "aaa"),
47+
FooToSort(sortValue: "", optionalSortValue: "bbbb"),
48+
]
49+
XCTAssertEqual(values.sorted(using: sortDescriptor).map(\.optionalSortValue), [
50+
"zzzzzzzz",
51+
"one value",
52+
"old value",
53+
"bbbb",
54+
"aaa",
55+
"1234567",
56+
"2nd value",
57+
])
58+
}
59+
}
60+
61+
/// A stub object to sort.
62+
struct FooToSort {
63+
/// The value to use for sorting.
64+
let sortValue: String
65+
/// The value to use for sorting but as optional type.
66+
let optionalSortValue: String?
67+
68+
init(sortValue: String, optionalSortValue: String? = nil) {
69+
self.sortValue = sortValue
70+
self.optionalSortValue = optionalSortValue
71+
}
72+
}

0 commit comments

Comments
 (0)