Skip to content

Commit 440ab55

Browse files
committed
Add ActiveLiveLocationsEndTimeTracker to track when the endAt is reached
1 parent 56eed08 commit 440ab55

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

Sources/StreamChat/ChatClient.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,10 @@ public class ChatClient {
640640
database: databaseContainer,
641641
apiClient: apiClient,
642642
attachmentPostProcessor: config.uploadedAttachmentPostProcessor
643+
),
644+
ActiveLiveLocationsEndTimeTracker(
645+
database: databaseContainer,
646+
apiClient: apiClient
643647
)
644648
]
645649
try? backgroundWorker(of: AttachmentQueueUploader.self)

Sources/StreamChat/Database/DTOs/MessageDTO.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,23 @@ class MessageDTO: NSManagedObject {
654654
return request
655655
}
656656

657+
/// Fetches all active location messages in any given channel and from every user.
658+
static func activeLiveLocationMessagesFetchRequest() -> NSFetchRequest<MessageDTO> {
659+
let request = NSFetchRequest<MessageDTO>(entityName: MessageDTO.entityName)
660+
MessageDTO.applyPrefetchingState(to: request)
661+
request.fetchLimit = 50
662+
request.sortDescriptors = [NSSortDescriptor(
663+
keyPath: \MessageDTO.createdAt,
664+
ascending: true
665+
)]
666+
var predicates: [NSPredicate] = [
667+
.init(format: "isActiveLiveLocation == YES"),
668+
.init(format: "localMessageStateRaw == nil")
669+
]
670+
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
671+
return request
672+
}
673+
657674
static func loadCurrentUserActiveLiveLocationMessages(
658675
currentUserId: UserId,
659676
channelId: ChannelId?,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
7+
/// An observer type that observes all active live locations in the database.
8+
typealias ActiveLiveLocationsObserver = StateLayerDatabaseObserver<ListResult, MessageDTO, MessageDTO>
9+
10+
/// A worker that is responsible for tracking when the end time of active locations is reached.
11+
class ActiveLiveLocationsEndTimeTracker: Worker {
12+
private let activeLiveLocationsObserver: ActiveLiveLocationsObserver
13+
private var workItems: [String: DispatchWorkItem] = [:]
14+
private let queue = DispatchQueue(label: "io.getstream.ActiveLiveLocationsEndTimeTracker")
15+
16+
override init(
17+
database: DatabaseContainer,
18+
apiClient: APIClient
19+
) {
20+
activeLiveLocationsObserver = ActiveLiveLocationsObserver(
21+
context: database.backgroundReadOnlyContext,
22+
fetchRequest: MessageDTO.activeLiveLocationMessagesFetchRequest()
23+
)
24+
super.init(database: database, apiClient: apiClient)
25+
startObserving()
26+
}
27+
28+
private func startObserving() {
29+
do {
30+
let items = try activeLiveLocationsObserver.startObserving(
31+
onContextDidChange: { [weak self] _, changes in
32+
self?.handle(changes: changes)
33+
}
34+
)
35+
let changes = items.map { ListChange.insert($0, index: .init(item: 0, section: 0)) }
36+
handle(changes: changes)
37+
} catch {
38+
log.error("Failed to start AttachmentUploader worker. \(error)")
39+
}
40+
}
41+
42+
private func handle(changes: [ListChange<MessageDTO>]) {
43+
guard !changes.isEmpty else {
44+
return
45+
}
46+
47+
for change in changes {
48+
switch change {
49+
case .insert(let message, _):
50+
guard let endAt = message.location?.endAt?.bridgeDate else { continue }
51+
scheduleInactiveLocation(for: message.id, at: endAt)
52+
case .remove(let message, _):
53+
setInactiveLocation(for: message.id)
54+
cancelWorkItem(for: message.id)
55+
case .move, .update:
56+
break
57+
}
58+
}
59+
}
60+
61+
private func scheduleInactiveLocation(for messageId: String, at endAt: Date) {
62+
// Cancel any existing work item for the same messageId
63+
cancelWorkItem(for: messageId)
64+
65+
let workItem = DispatchWorkItem { [weak self] in
66+
self?.setInactiveLocation(for: messageId)
67+
}
68+
workItems[messageId] = workItem
69+
70+
let endAtTime = endAt.timeIntervalSinceNow
71+
queue.asyncAfter(deadline: .now() + endAtTime, execute: workItem)
72+
}
73+
74+
private func setInactiveLocation(for messageId: String) {
75+
database.write { session in
76+
let message = session.message(id: messageId)
77+
message?.isActiveLiveLocation = false
78+
if let location = message?.location {
79+
message?.channel?.activeLiveLocations.remove(location)
80+
}
81+
}
82+
cancelWorkItem(for: messageId)
83+
}
84+
85+
private func cancelWorkItem(for messageId: String) {
86+
workItems[messageId]?.cancel()
87+
workItems.removeValue(forKey: messageId)
88+
}
89+
}

StreamChat.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,8 @@
16381638
AD8C7C662BA46A4A00260715 /* AppEndpoints_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8C7C652BA46A4A00260715 /* AppEndpoints_Tests.swift */; };
16391639
AD8D1809268F7290004E3A5C /* TypingSuggester.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8D1808268F7290004E3A5C /* TypingSuggester.swift */; };
16401640
AD8D180B268F8ED4004E3A5C /* SlackComposerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8D180A268F8ED4004E3A5C /* SlackComposerVC.swift */; };
1641+
AD8E75E62E04963200AE0F70 /* ActiveLiveLocationsEndTimeTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8E75E52E04953C00AE0F70 /* ActiveLiveLocationsEndTimeTracker.swift */; };
1642+
AD8E75E72E04963200AE0F70 /* ActiveLiveLocationsEndTimeTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8E75E52E04953C00AE0F70 /* ActiveLiveLocationsEndTimeTracker.swift */; };
16411643
AD8FEE582AA8E1A100273F88 /* ChatClient+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8FEE572AA8E1A100273F88 /* ChatClient+Environment.swift */; };
16421644
AD8FEE592AA8E1A100273F88 /* ChatClient+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8FEE572AA8E1A100273F88 /* ChatClient+Environment.swift */; };
16431645
AD8FEE5B2AA8E1E400273F88 /* ChatClientFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8FEE5A2AA8E1E400273F88 /* ChatClientFactory.swift */; };
@@ -4412,6 +4414,7 @@
44124414
AD8C7C652BA46A4A00260715 /* AppEndpoints_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEndpoints_Tests.swift; sourceTree = "<group>"; };
44134415
AD8D1808268F7290004E3A5C /* TypingSuggester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingSuggester.swift; sourceTree = "<group>"; };
44144416
AD8D180A268F8ED4004E3A5C /* SlackComposerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlackComposerVC.swift; sourceTree = "<group>"; };
4417+
AD8E75E52E04953C00AE0F70 /* ActiveLiveLocationsEndTimeTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveLiveLocationsEndTimeTracker.swift; sourceTree = "<group>"; };
44154418
AD8FEE572AA8E1A100273F88 /* ChatClient+Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatClient+Environment.swift"; sourceTree = "<group>"; };
44164419
AD8FEE5A2AA8E1E400273F88 /* ChatClientFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatClientFactory.swift; sourceTree = "<group>"; };
44174420
AD90D18425D56196001D03BB /* CurrentUserUpdater_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentUserUpdater_Tests.swift; sourceTree = "<group>"; };
@@ -5651,6 +5654,7 @@
56515654
79280F4524850ECC00CDEB89 /* Background */ = {
56525655
isa = PBXGroup;
56535656
children = (
5657+
AD8E75E52E04953C00AE0F70 /* ActiveLiveLocationsEndTimeTracker.swift */,
56545658
88E26D6D2580F34B00F55AB5 /* AttachmentQueueUploader.swift */,
56555659
F6ED5F7325023EB4005D7327 /* ConnectionRecoveryHandler.swift */,
56565660
F670B50E24FE6EA900003B1A /* MessageEditor.swift */,
@@ -11811,6 +11815,7 @@
1181111815
792A4F482480107A00EAF71D /* Pagination.swift in Sources */,
1181211816
79AF43B42632AF1C00E75CDA /* ChannelVisibilityEventMiddleware.swift in Sources */,
1181311817
DAF1BED525066114003CEDC0 /* MessageController+Combine.swift in Sources */,
11818+
AD8E75E62E04963200AE0F70 /* ActiveLiveLocationsEndTimeTracker.swift in Sources */,
1181411819
ADB8B8EA2D8890B900549C95 /* MessageReminderDTO.swift in Sources */,
1181511820
A36C39F52860680A0004EB7E /* URL+EnrichedURL.swift in Sources */,
1181611821
8A0C3BBC24C0947400CAFD19 /* UserEvents.swift in Sources */,
@@ -12725,6 +12730,7 @@
1272512730
40789D3229F6AC500018C2BB /* AudioRecording.swift in Sources */,
1272612731
C121E89F274544B000023E4C /* MessageSearchController+Combine.swift in Sources */,
1272712732
C121E8A0274544B000023E4C /* MessageSearchController+SwiftUI.swift in Sources */,
12733+
AD8E75E72E04963200AE0F70 /* ActiveLiveLocationsEndTimeTracker.swift in Sources */,
1272812734
C121E8A1274544B000023E4C /* UserController.swift in Sources */,
1272912735
AD0CC01D2BDBD22D005E2C66 /* ReactionEndpoints.swift in Sources */,
1273012736
C121E8A2274544B000023E4C /* UserController+Combine.swift in Sources */,

0 commit comments

Comments
 (0)