session-ios/Session/Utilities/BackgroundPoller.swift

139 lines
6.5 KiB
Swift
Raw Normal View History

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
2020-09-15 03:30:45 +02:00
import PromiseKit
2020-12-07 01:21:24 +01:00
import SessionSnodeKit
import SessionMessagingKit
2020-09-15 03:30:45 +02:00
@objc(LKBackgroundPoller)
public final class BackgroundPoller : NSObject {
2020-09-18 08:12:19 +02:00
private static var closedGroupPoller: ClosedGroupPoller!
2020-12-07 01:21:24 +01:00
private static var promises: [Promise<Void>] = []
2020-09-15 03:30:45 +02:00
private override init() { }
@objc(pollWithCompletionHandler:)
public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
2020-12-07 01:21:24 +01:00
promises = []
promises.append(pollForMessages())
promises.append(contentsOf: pollForClosedGroupMessages())
2021-04-19 06:47:45 +02:00
let v2OpenGroupServers = Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server })
v2OpenGroupServers.forEach { server in
let poller = OpenGroupPollerV2(for: server)
2021-03-24 04:36:26 +01:00
poller.stop()
2021-04-19 06:47:45 +02:00
promises.append(poller.poll(isBackgroundPoll: true))
2021-03-24 04:36:26 +01:00
}
2020-09-15 03:30:45 +02:00
when(resolved: promises).done { _ in
completionHandler(.newData)
2021-05-19 00:49:20 +02:00
}.catch { error in
SNLog("Background poll failed due to error: \(error)")
2020-09-15 03:30:45 +02:00
completionHandler(.failed)
}
}
2020-12-07 01:21:24 +01:00
private static func pollForMessages() -> Promise<Void> {
let userPublicKey = getUserHexEncodedPublicKey()
return getMessages(for: userPublicKey)
}
private static func pollForClosedGroupMessages() -> [Promise<Void>] {
// Fetch all closed groups (excluding any don't contain the current user as a
// GroupMemeber as the user is no longer a member of those)
return GRDBStorage.shared
.read { db in
try ClosedGroup
.select(.threadId)
.joining(
required: ClosedGroup.members
.filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db))
)
.asRequest(of: String.self)
.fetchAll(db)
}
.defaulting(to: [])
.map { groupPublicKey in
getMessages(for: groupPublicKey)
}
2020-12-07 01:21:24 +01:00
}
private static func getMessages(for publicKey: String) -> Promise<Void> {
return SnodeAPI.getSwarm(for: publicKey).then(on: DispatchQueue.main) { swarm -> Promise<Void> in
2020-12-07 01:21:24 +01:00
guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic }
2021-05-19 00:49:20 +02:00
return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) {
return SnodeAPI.getMessages(from: snode, associatedWith: publicKey)
.then(on: DispatchQueue.main) { messages -> Promise<Void> in
guard !messages.isEmpty else { return Promise.value(()) }
var jobsToRun: [Job] = []
GRDBStorage.shared.write { db in
var threadMessages: [String: [MessageReceiveJob.Details.MessageInfo]] = [:]
messages.forEach { message in
do {
let processedMessage: ProcessedMessage? = try Message.processRawReceivedMessage(db, rawMessage: message)
let key: String = (processedMessage?.threadId ?? Message.nonThreadMessageId)
// Persist the received message after the MessageReceiveJob is created
_ = try message.info.saved(db)
threadMessages[key] = (threadMessages[key] ?? [])
.appending(processedMessage?.messageInfo)
}
catch {
switch error {
// Ignore duplicate & selfSend message errors (and don't bother logging
// them as there will be a lot since we each service node duplicates messages)
case DatabaseError.SQLITE_CONSTRAINT_UNIQUE,
MessageReceiverError.duplicateMessage,
MessageReceiverError.duplicateControlMessage,
MessageReceiverError.selfSend:
break
default: SNLog("Failed to deserialize envelope due to error: \(error).")
}
}
}
threadMessages
.forEach { threadId, threadMessages in
let maybeJob: Job? = Job(
variant: .messageReceive,
behaviour: .runOnce,
threadId: threadId,
details: MessageReceiveJob.Details(
messages: threadMessages,
isBackgroundPoll: true
)
)
guard let job: Job = maybeJob else { return }
JobRunner.add(db, job: job)
jobsToRun.append(job)
}
}
let promises = jobsToRun.compactMap { job -> Promise<Void>? in
let (promise, seal) = Promise<Void>.pending()
// Note: In the background we just want jobs to fail silently
MessageReceiveJob.run(
job,
success: { _, _ in seal.fulfill(()) },
failure: { _, _, _ in seal.fulfill(()) },
deferred: { _ in seal.fulfill(()) }
)
return promise
}
return when(fulfilled: promises)
}
2020-12-07 01:21:24 +01:00
}
}
}
2020-09-15 03:30:45 +02:00
}