Skip to content

Commit feaeef3

Browse files
authored
Merge branch 'fix-dependency-links' into debugcamerasview
2 parents 36e17cf + 70f3c51 commit feaeef3

8 files changed

Lines changed: 136 additions & 55 deletions

File tree

.githooks/pre-commit

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/sh
2+
#
3+
# Pre-commit hook: auto-format the code before committing.
4+
# Runs `bundle exec fastlane autocorrect` and re-stages the files it changed.
5+
#
6+
# Install with: bundle exec fastlane install_git_hooks
7+
#
8+
9+
set -e
10+
11+
cd "$(git rev-parse --show-toplevel)"
12+
13+
staged=$(git diff --cached --name-only --diff-filter=ACMR)
14+
15+
[ -n "$staged" ] || exit 0
16+
17+
partial=$(git diff --name-only --diff-filter=ACMR -- $staged)
18+
if [ -n "$partial" ]; then
19+
echo "pre-commit: these staged files also have unstaged changes:" >&2
20+
echo "$partial" | sed 's/^/ /' >&2
21+
echo "pre-commit: stage the whole file, or run 'bundle exec fastlane autocorrect' and stage it yourself." >&2
22+
exit 1
23+
fi
24+
25+
echo "pre-commit: running bundle exec fastlane autocorrect"
26+
bundle exec fastlane autocorrect
27+
28+
IFS='
29+
'
30+
for file in $staged; do
31+
[ -e "$file" ] && git add -- "$file"
32+
done

.github/workflows/ci.yml

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ jobs:
146146
```
147147
148148
test:
149-
needs: check-swiftlint-disables
149+
needs: lint
150150
runs-on: macos-26
151151
timeout-minutes: 45
152152
steps:
@@ -167,21 +167,23 @@ jobs:
167167
env.DEVELOPER_DIR,
168168
hashFiles('.ruby-version', '**/Gemfile.lock')) }}
169169
170-
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
171-
name: "Cache: DerivedData"
170+
# DerivedData is warmed on `main` (the save step below) and only restored
171+
# on PRs, so pinned SPM dependencies are compiled once per Package.resolved
172+
# and reused instead of recompiled from scratch on every run.
173+
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
174+
name: "Restore: DerivedData"
175+
id: derived_data
172176
with:
173177
path: ~/Library/Developer/Xcode/DerivedData
174178
key: >-
175-
${{ format('{0}-deriveddata-{1}-{2}-{3}',
179+
${{ format('{0}-deriveddata-{1}-{2}',
176180
runner.os,
177181
env.DEVELOPER_DIR,
178-
hashFiles('**/Package.resolved'),
179-
github.run_id) }}
182+
hashFiles('**/Package.resolved')) }}
180183
restore-keys: >-
181-
${{ format('{0}-deriveddata-{1}-{2}-',
184+
${{ format('{0}-deriveddata-{1}-',
182185
runner.os,
183-
env.DEVELOPER_DIR,
184-
hashFiles('**/Package.resolved')) }}
186+
env.DEVELOPER_DIR) }}
185187
186188
- name: Install Brews
187189
# right now, we don't need anything from brew for tests, so save some time
@@ -194,9 +196,25 @@ jobs:
194196

195197
- name: Run tests
196198
run: bundle exec fastlane test
199+
env:
200+
CI_ENABLE_COVERAGE: ${{ github.ref == 'refs/heads/main' && 'true' || 'false' }}
201+
202+
# Save the warmed DerivedData only from `main`, and only on a fresh key,
203+
# so PR runs never evict the shared base cache other PRs restore from.
204+
- uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
205+
name: "Save: DerivedData"
206+
if: github.ref == 'refs/heads/main' && steps.derived_data.outputs.cache-hit != 'true'
207+
with:
208+
path: ~/Library/Developer/Xcode/DerivedData
209+
key: >-
210+
${{ format('{0}-deriveddata-{1}-{2}',
211+
runner.os,
212+
env.DEVELOPER_DIR,
213+
hashFiles('**/Package.resolved')) }}
197214
198215
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
199216
name: "Upload Code Coverage"
217+
if: github.ref == 'refs/heads/main'
200218
with:
201219
xcode: true
202220
xcode_archive_path: fastlane/test_output/Tests-Unit.xcresult

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ bundle exec fastlane lint
8888
bundle exec fastlane autocorrect
8989
```
9090

91+
To run `autocorrect` automatically before each commit, install the git pre-commit hook once:
92+
93+
```bash
94+
bundle exec fastlane install_git_hooks
95+
```
96+
9197
In the Xcode project, the autocorrectable linters will not modify your source code but will provide warnings. This project uses several linters:
9298

9399
- [SwiftFormat](https://github.com/nicklockwood/SwiftFormat)

Sources/App/LifecycleManager.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import GRDB
23
import PromiseKit
34
import Shared
45
import UIKit
@@ -80,6 +81,7 @@ class LifecycleManager {
8081

8182
@objc private func willEnterForeground() {
8283
isActive = true
84+
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
8385
refreshNetworkInformation()
8486
syncLiveActivities()
8587
}
@@ -96,6 +98,7 @@ class LifecycleManager {
9698

9799
@objc private func didEnterBackground() {
98100
isActive = false
101+
NotificationCenter.default.post(name: Database.suspendNotification, object: self)
99102
Current.backgroundTask(withName: BackgroundTask.lifecycleManagerDidEnterBackground.rawValue) { _ in
100103
when(fulfilled: Current.apis.map { api in
101104
api.CreateEvent(

Sources/Shared/API/ServerManager.swift

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -92,38 +92,8 @@ private extension Identifier where ObjectType == Server {
9292

9393
private struct ServerCache {
9494
var restrictCaching: Bool = false
95-
var deletedServers: Set<Identifier<Server>> {
96-
get {
97-
let identifiers = Current.settingsStore.prefs.array(forKey: "deletedServers") as? [String] ?? []
98-
return Set(identifiers.map { Identifier<Server>(rawValue: $0) })
99-
}
100-
set {
101-
Current.settingsStore.prefs.set(newValue.map(\.rawValue), forKey: "deletedServers")
102-
}
103-
}
104-
105-
var info: [Identifier<Server>: ServerInfo] = [:] {
106-
didSet {
107-
if !deletedServers.isDisjoint(with: info.keys) {
108-
Current.Log
109-
.error(
110-
"Stale server(s) in info cache overlapping with deleted servers, info keys: \(info.keys), deleted servers: \(deletedServers)"
111-
)
112-
}
113-
}
114-
}
115-
116-
var server: [Identifier<Server>: Server] = [:] {
117-
didSet {
118-
if !deletedServers.isDisjoint(with: server.keys) {
119-
Current.Log
120-
.error(
121-
"There are server(s) in cache that are deleted also in deleted servers set, servers: \(server.keys), deleted servers: \(deletedServers)"
122-
)
123-
}
124-
}
125-
}
126-
95+
var info: [Identifier<Server>: ServerInfo] = [:]
96+
var server: [Identifier<Server>: Server] = [:]
12797
var all: [Server]?
12898

12999
mutating func remove(identifier: Identifier<Server>) {
@@ -164,6 +134,16 @@ final class ServerManagerImpl: ServerManager {
164134

165135
private let cache = HAProtected<ServerCache>(value: .init())
166136

137+
private var deletedServers: Set<Identifier<Server>> {
138+
get {
139+
let identifiers = Current.settingsStore.prefs.array(forKey: "deletedServers") as? [String] ?? []
140+
return Set(identifiers.map { Identifier<Server>(rawValue: $0) })
141+
}
142+
set {
143+
Current.settingsStore.prefs.set(newValue.map(\.rawValue), forKey: "deletedServers")
144+
}
145+
}
146+
167147
// MARK: Lifecycle
168148

169149
init(
@@ -202,10 +182,10 @@ final class ServerManagerImpl: ServerManager {
202182
}
203183

204184
public var all: [Server] {
185+
let deletedServers = deletedServers
205186
let snapshot = cache.read { cache in
206187
(
207188
restrictCaching: cache.restrictCaching,
208-
deletedServers: cache.deletedServers,
209189
cachedServers: cache.all
210190
)
211191
}
@@ -216,17 +196,18 @@ final class ServerManagerImpl: ServerManager {
216196

217197
// Read from Keychain and GRDB outside the cache lock so persistence I/O
218198
// does not block unrelated server-manager operations.
219-
let persistedServers = mergedServerInfo(deletedServers: snapshot.deletedServers)
199+
let persistedServers = mergedServerInfo(deletedServers: deletedServers)
220200
.sorted(by: { lhs, rhs -> Bool in
221201
lhs.1.sortOrder < rhs.1.sortOrder
222202
})
223203

204+
let deletedServersUnchanged = self.deletedServers == deletedServers
224205
if let cachedOrFreshServers = cache.mutate(using: { cache -> [Server]? in
225206
if !cache.restrictCaching, let cachedServers = cache.all {
226207
return cachedServers
227208
}
228209

229-
guard cache.deletedServers == snapshot.deletedServers else {
210+
guard deletedServersUnchanged else {
230211
return nil
231212
}
232213

@@ -241,7 +222,7 @@ final class ServerManagerImpl: ServerManager {
241222

242223
// Avoid retrying forever when another thread keeps mutating the server set.
243224
// In that case we return a best-effort fresh view and let a later access cache it.
244-
let latestDeletedServers = cache.read(\.deletedServers)
225+
let latestDeletedServers = self.deletedServers
245226
let latestPersistedServers = mergedServerInfo(deletedServers: latestDeletedServers)
246227
.sorted(by: { lhs, rhs -> Bool in
247228
lhs.1.sortOrder < rhs.1.sortOrder
@@ -272,8 +253,12 @@ final class ServerManagerImpl: ServerManager {
272253
}
273254
}
274255

256+
var deletedServers = deletedServers
257+
if deletedServers.remove(identifier) != nil {
258+
self.deletedServers = deletedServers
259+
}
260+
275261
let result = cache.mutate { cache -> Server in
276-
cache.deletedServers.remove(identifier)
277262
keychain.set(serverInfo: setValue, key: identifier.keychainKey, encoder: encoder)
278263
cache.info[identifier] = setValue
279264
cache.all = nil
@@ -293,8 +278,11 @@ final class ServerManagerImpl: ServerManager {
293278
}
294279

295280
public func remove(identifier: Identifier<Server>) {
281+
var deletedServers = deletedServers
282+
deletedServers.insert(identifier)
283+
self.deletedServers = deletedServers
284+
296285
cache.mutate { cache in
297-
cache.deletedServers.insert(identifier)
298286
keychain.deleteServerInfo(key: identifier.keychainKey)
299287
cache.remove(identifier: identifier)
300288
}
@@ -310,8 +298,11 @@ final class ServerManagerImpl: ServerManager {
310298

311299
public func removeAll() {
312300
let allKeys = Set(keychain.allKeys() + mirrorStore.allKeys())
301+
var deletedServers = deletedServers
302+
deletedServers.formUnion(Set(allKeys.map { Identifier<Server>(keychainKey: $0) }))
303+
self.deletedServers = deletedServers
304+
313305
cache.mutate { cache in
314-
cache.deletedServers.formUnion(Set(allKeys.map { Identifier<Server>(keychainKey: $0) }))
315306
cache.reset()
316307
_ = try? keychain.removeAll()
317308
}
@@ -344,7 +335,14 @@ final class ServerManagerImpl: ServerManager {
344335
fallback: ServerInfo
345336
) -> () -> ServerInfo {
346337
{
347-
cache.mutate { cache -> ServerInfo in
338+
if let cached = cache.read({ cache -> ServerInfo? in
339+
cache.restrictCaching ? nil : cache.info[identifier]
340+
}) {
341+
return cached
342+
}
343+
344+
let deletedServers = self.deletedServers
345+
return cache.mutate { cache -> ServerInfo in
348346
if !cache.restrictCaching, let info = cache.info[identifier] {
349347
return info
350348
} else {
@@ -357,7 +355,7 @@ final class ServerManagerImpl: ServerManager {
357355
let info = keychainInfo
358356
?? (shouldUseMirrorFallback ? mirroredInfo : nil)
359357
?? fallback
360-
if !cache.deletedServers.contains(identifier) {
358+
if !deletedServers.contains(identifier) {
361359
cache.info[identifier] = info
362360
}
363361
return info
@@ -373,15 +371,16 @@ final class ServerManagerImpl: ServerManager {
373371
encoder: JSONEncoder,
374372
notify: @escaping () -> Void
375373
) -> (ServerInfo) -> Bool {
376-
{ baseServerInfo in
374+
{ [weak self] baseServerInfo in
377375
var serverInfo = baseServerInfo
378376

379377
// update active URL so we can update just once if it's different than the save is doing
380378
// intentionally not in the lock
381379
_ = serverInfo.connection.evaluateActiveURL()
382380

381+
let deletedServers = self?.deletedServers ?? []
383382
return cache.mutate { cache in
384-
guard !cache.deletedServers.contains(identifier) else {
383+
guard !deletedServers.contains(identifier) else {
385384
Current.Log.verbose("ignoring update to deleted server \(identifier)")
386385
return false
387386
}
@@ -491,7 +490,7 @@ final class ServerManagerImpl: ServerManager {
491490
}
492491

493492
private func pruneDeletedMirroredServers() {
494-
let deletedKeys = Set(cache.read(\.deletedServers).map(\.keychainKey))
493+
let deletedKeys = Set(deletedServers.map(\.keychainKey))
495494
guard !deletedKeys.isEmpty else { return }
496495

497496
let mirrorKeys = Set(mirrorStore.allKeys())
@@ -506,7 +505,7 @@ final class ServerManagerImpl: ServerManager {
506505
}
507506

508507
private func restorableMirroredServers(excludingPreviouslyRestored: Bool = false) -> [(String, ServerInfo)] {
509-
let deletedServers = cache.read(\.deletedServers)
508+
let deletedServers = deletedServers
510509
let restoredMirroredServers = excludingPreviouslyRestored ? restoredMirroredServers : []
511510
return mirrorStore.allServerInfo().filter { key, _ in
512511
!deletedServers.contains(.init(keychainKey: key)) && !restoredMirroredServers.contains(key)
@@ -578,7 +577,7 @@ final class ServerManagerImpl: ServerManager {
578577
public func restorableState() -> Data {
579578
var state = [String: ServerInfo]()
580579

581-
for (id, info) in mergedServerInfo(deletedServers: cache.read({ $0.deletedServers })) {
580+
for (id, info) in mergedServerInfo(deletedServers: deletedServers) {
582581
state[id] = info
583582
}
584583

Sources/Shared/Database/GRDB+Initialization.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public extension DatabaseQueue {
77
static var appDatabase: DatabaseQueue = {
88
var configuration = Configuration()
99
configuration.busyMode = .timeout(3)
10+
configuration.observesSuspensionNotifications = true
1011

1112
let database: DatabaseQueue
1213
var isInMemoryFallback = false

fastlane/lanes/quality.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,22 @@
1010
sh('../Tools/build_tool swiftformat ..')
1111
sh('bundle exec rubocop -a ..')
1212
end
13+
14+
desc 'Install the git pre-commit hook that runs autocorrect before each commit'
15+
lane :install_git_hooks do |options|
16+
current = sh('cd .. ; git config --default "" core.hooksPath', log: false).strip
17+
18+
if current == '.githooks'
19+
UI.success('Git hooks already installed (core.hooksPath is .githooks).')
20+
next
21+
end
22+
23+
unless current.empty? || options[:force]
24+
message = "core.hooksPath is already set to '#{current}'. " \
25+
'Re-run with `install_git_hooks force:true` to override it.'
26+
UI.user_error!(message)
27+
end
28+
29+
sh('cd .. ; git config core.hooksPath .githooks')
30+
UI.success('Installed git hooks. `bundle exec fastlane autocorrect` will run before each commit.')
31+
end

fastlane/lanes/testing.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@
3434

3535
desc 'Run tests'
3636
lane :test do
37+
code_coverage = ENV.key?('CI_ENABLE_COVERAGE') ? (ENV['CI_ENABLE_COVERAGE'] == 'true') : nil
3738
run_tests(
3839
project: 'HomeAssistant.xcodeproj',
3940
scheme: 'Tests-Unit',
4041
result_bundle: true,
4142
skip_package_dependencies_resolution: true,
4243
skip_detect_devices: true,
44+
code_coverage: code_coverage,
45+
xcargs: 'COMPILER_INDEX_STORE_ENABLE=NO',
4346
destination: 'platform=iOS Simulator,name=iPhone 17,OS=latest'
4447
)
4548
end

0 commit comments

Comments
 (0)