mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-22 01:22:46 +02:00
Refactor PollHistoryService
This commit is contained in:
@@ -22,25 +22,22 @@ final class PollHistoryService: PollHistoryServiceProtocol {
|
||||
private let room: MXRoom
|
||||
private let timeline: MXEventTimeline
|
||||
private let chunkSizeInDays: UInt
|
||||
private let pollsSubject: PassthroughSubject<TimelinePollDetails, Never> = .init()
|
||||
private let errorSubject: PassthroughSubject<PollHistoryError, Never> = .init()
|
||||
private let isFetchingSubject: PassthroughSubject<Bool, Never> = .init()
|
||||
|
||||
private var timelineListener: Any?
|
||||
|
||||
private let updatesSubject: PassthroughSubject<TimelinePollDetails, Never> = .init()
|
||||
private let updatesErrorsSubject: PassthroughSubject<Error, Never> = .init()
|
||||
|
||||
private var pollAggregators: [String: PollAggregator] = [:]
|
||||
private var targetTimestamp: Date
|
||||
private var oldestEventDate: Date = .distantFuture
|
||||
private var currentBatchSubject: PassthroughSubject<TimelinePollDetails, Error>?
|
||||
|
||||
var pollHistory: AnyPublisher<TimelinePollDetails, Never> {
|
||||
pollsSubject.eraseToAnyPublisher()
|
||||
var updates: AnyPublisher<TimelinePollDetails, Never> {
|
||||
updatesSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
var error: AnyPublisher<PollHistoryError, Never> {
|
||||
errorSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
var isFetching: AnyPublisher<Bool, Never> {
|
||||
isFetchingSubject.eraseToAnyPublisher()
|
||||
var updatesErrors: AnyPublisher<Error, Never> {
|
||||
updatesErrorsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(room: MXRoom, chunkSizeInDays: UInt) {
|
||||
@@ -51,8 +48,8 @@ final class PollHistoryService: PollHistoryServiceProtocol {
|
||||
setup(timeline: timeline)
|
||||
}
|
||||
|
||||
func next() {
|
||||
startPagination()
|
||||
func next() -> AnyPublisher<TimelinePollDetails, Error> {
|
||||
currentBatchSubject?.eraseToAnyPublisher() ?? startPagination()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,22 +61,31 @@ private extension PollHistoryService {
|
||||
|
||||
func setup(timeline: MXEventTimeline) {
|
||||
timelineListener = timeline.listenToEvents { [weak self] event, _, _ in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if event.eventType == .pollStart {
|
||||
self.aggregatePoll(pollStartEvent: event)
|
||||
self?.aggregatePoll(pollStartEvent: event)
|
||||
}
|
||||
|
||||
self.oldestEventDate = min(event.originServerDate, self.oldestEventDate)
|
||||
self?.updateTimestamp(event: event)
|
||||
}
|
||||
}
|
||||
|
||||
func startPagination() {
|
||||
isFetchingSubject.send(true)
|
||||
timeline.resetPagination()
|
||||
paginate(timeline: timeline)
|
||||
func updateTimestamp(event: MXEvent) {
|
||||
oldestEventDate = min(event.originServerDate, oldestEventDate)
|
||||
}
|
||||
|
||||
func startPagination() -> AnyPublisher<TimelinePollDetails, Error> {
|
||||
let batchSubject = PassthroughSubject<TimelinePollDetails, Error>()
|
||||
currentBatchSubject = batchSubject
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
self.timeline.resetPagination()
|
||||
self.paginate(timeline: self.timeline)
|
||||
}
|
||||
|
||||
return batchSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func paginate(timeline: MXEventTimeline) {
|
||||
@@ -93,15 +99,19 @@ private extension PollHistoryService {
|
||||
if timeline.canPaginate(.backwards), self.timestampTargetReached == false {
|
||||
self.paginate(timeline: timeline)
|
||||
} else {
|
||||
self.isFetchingSubject.send(false)
|
||||
self.completeBatch(completion: .finished)
|
||||
}
|
||||
case .failure(let error):
|
||||
self.errorSubject.send(.paginationFailed(error))
|
||||
self.isFetchingSubject.send(false)
|
||||
self.completeBatch(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func completeBatch(completion: Subscribers.Completion<Error>) {
|
||||
currentBatchSubject?.send(completion: completion)
|
||||
currentBatchSubject = nil
|
||||
}
|
||||
|
||||
func aggregatePoll(pollStartEvent: MXEvent) {
|
||||
guard pollAggregators[pollStartEvent.eventId] == nil else {
|
||||
return
|
||||
@@ -131,14 +141,14 @@ extension PollHistoryService: PollAggregatorDelegate {
|
||||
func pollAggregatorDidStartLoading(_ aggregator: PollAggregator) {}
|
||||
|
||||
func pollAggregatorDidEndLoading(_ aggregator: PollAggregator) {
|
||||
pollsSubject.send(.init(poll: aggregator.poll, represent: .started))
|
||||
currentBatchSubject?.send(.init(poll: aggregator.poll, represent: .started))
|
||||
}
|
||||
|
||||
func pollAggregator(_ aggregator: PollAggregator, didFailWithError: Error) {
|
||||
errorSubject.send(.pollAggregationFailed(didFailWithError))
|
||||
updatesErrorsSubject.send(didFailWithError)
|
||||
}
|
||||
|
||||
func pollAggregatorDidUpdateData(_ aggregator: PollAggregator) {
|
||||
pollsSubject.send(.init(poll: aggregator.poll, represent: .started))
|
||||
updatesSubject.send(.init(poll: aggregator.poll, represent: .started))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,54 +17,56 @@
|
||||
import Combine
|
||||
|
||||
final class MockPollHistoryService: PollHistoryServiceProtocol {
|
||||
private let polls: PassthroughSubject<TimelinePollDetails, Never> = .init()
|
||||
|
||||
var pollHistory: AnyPublisher<TimelinePollDetails, Never> {
|
||||
polls.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
var error: AnyPublisher<PollHistoryError, Never> {
|
||||
var updates: AnyPublisher<TimelinePollDetails, Never> {
|
||||
Empty().eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func next() {
|
||||
for poll in activePollsData + pastPollsData {
|
||||
polls.send(poll)
|
||||
}
|
||||
}
|
||||
|
||||
var isLoadingPublisher: AnyPublisher = Just(false).eraseToAnyPublisher()
|
||||
var isFetching: AnyPublisher<Bool, Never> {
|
||||
isLoadingPublisher
|
||||
}
|
||||
|
||||
var activePollsData: [TimelinePollDetails] = (1..<10)
|
||||
.map { index in
|
||||
TimelinePollDetails(id: "a\(index)",
|
||||
question: "Do you like the active poll number \(index)?",
|
||||
answerOptions: [],
|
||||
closed: false,
|
||||
startDate: .init(),
|
||||
totalAnswerCount: 30,
|
||||
type: .disclosed,
|
||||
eventType: .started,
|
||||
maxAllowedSelections: 1,
|
||||
hasBeenEdited: false,
|
||||
hasDecryptionError: false)
|
||||
}
|
||||
|
||||
var pastPollsData: [TimelinePollDetails] = (1..<10)
|
||||
.map { index in
|
||||
TimelinePollDetails(id: "p\(index)",
|
||||
question: "Do you like the active poll number \(index)?",
|
||||
answerOptions: [.init(id: "id", text: "Yes, of course!", count: 20, winner: true, selected: true)],
|
||||
closed: true,
|
||||
startDate: .init(),
|
||||
totalAnswerCount: 30,
|
||||
type: .disclosed,
|
||||
eventType: .started,
|
||||
maxAllowedSelections: 1,
|
||||
hasBeenEdited: false,
|
||||
hasDecryptionError: false)
|
||||
}
|
||||
var updatesErrors: AnyPublisher<Error, Never> {
|
||||
Empty().eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
lazy var nextPublisher: AnyPublisher<TimelinePollDetails, Error> = (activePollsData + pastPollsData)
|
||||
.publisher
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
func next() -> AnyPublisher<TimelinePollDetails, Error> {
|
||||
nextPublisher
|
||||
}
|
||||
}
|
||||
|
||||
private extension MockPollHistoryService {
|
||||
var activePollsData: [TimelinePollDetails] {
|
||||
(1..<10)
|
||||
.map { index in
|
||||
TimelinePollDetails(id: "a\(index)",
|
||||
question: "Do you like the active poll number \(index)?",
|
||||
answerOptions: [],
|
||||
closed: false,
|
||||
startDate: .init(),
|
||||
totalAnswerCount: 30,
|
||||
type: .disclosed,
|
||||
eventType: .started,
|
||||
maxAllowedSelections: 1,
|
||||
hasBeenEdited: false,
|
||||
hasDecryptionError: false)
|
||||
}
|
||||
}
|
||||
|
||||
var pastPollsData: [TimelinePollDetails] {
|
||||
(1..<10)
|
||||
.map { index in
|
||||
TimelinePollDetails(id: "p\(index)",
|
||||
question: "Do you like the active poll number \(index)?",
|
||||
answerOptions: [.init(id: "id", text: "Yes, of course!", count: 20, winner: true, selected: true)],
|
||||
closed: true,
|
||||
startDate: .init(),
|
||||
totalAnswerCount: 30,
|
||||
type: .disclosed,
|
||||
eventType: .started,
|
||||
maxAllowedSelections: 1,
|
||||
hasBeenEdited: false,
|
||||
hasDecryptionError: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,22 +17,8 @@
|
||||
import Combine
|
||||
|
||||
protocol PollHistoryServiceProtocol {
|
||||
/// Publishes poll data as soon they are found in the timeline.
|
||||
/// Updates are also published here, so clients needs to address duplicates.
|
||||
var pollHistory: AnyPublisher<TimelinePollDetails, Never> { get }
|
||||
var updates: AnyPublisher<TimelinePollDetails, Never> { get }
|
||||
var updatesErrors: AnyPublisher<Error, Never> { get }
|
||||
|
||||
/// Publishes whatever errors produced during the sync.
|
||||
var error: AnyPublisher<PollHistoryError, Never> { get }
|
||||
|
||||
/// Ask to fetch the next batch of polls.
|
||||
/// Concrete implementations can decide what a batch is.
|
||||
func next()
|
||||
|
||||
/// Inform whenever the fetch of a new batch of polls starts or ends.
|
||||
var isFetching: AnyPublisher<Bool, Never> { get }
|
||||
}
|
||||
|
||||
enum PollHistoryError: Error {
|
||||
case paginationFailed(Error)
|
||||
case pollAggregationFailed(Error)
|
||||
func next() -> AnyPublisher<TimelinePollDetails, Error>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user