mirror of https://github.com/oxen-io/session-ios
Fixed a number of bugs found in the internal release
• Moved the 'getSwarm' behaviour into a distinct job to prevent duplicate API calls • Updated to the latest libSession (fix libQuic crash) • Updated the JobRunner to support the `runOnceTransient` behaviour and be able to run transient jobs in the app extensions • Reworked the extension file logging to be written directly to the file in a single operation rather than line-by-line via the logger • Fixed a bug where community invites has the wrong author • Fixed a bug where the title on the disappearing messages settings screen was clipping vertically • Fixed a bug where tapping on the disappearing messages setting subtitle could incorrectly appear in read-only state for admins • Fixed a log which contained notification content • Tweaks to extension logging logicpull/960/head
parent
2cffda17bc
commit
a3188ebea4
@ -1 +1 @@
|
|||||||
Subproject commit 1c4667ba0c56c924d4e957743d1324be2c899040
|
Subproject commit 7651967104845db16e6a58f70635c01f7f4c2033
|
@ -1,18 +1,20 @@
|
|||||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SessionMessagingKit
|
import SessionMessagingKit
|
||||||
|
|
||||||
enum NotificationError: LocalizedError {
|
enum NotificationError: Error, CustomStringConvertible {
|
||||||
case processing(PushNotificationAPI.ProcessResult)
|
case processing(PushNotificationAPI.ProcessResult)
|
||||||
case messageProcessing
|
case messageProcessing
|
||||||
case messageHandling(MessageReceiverError)
|
case messageHandling(MessageReceiverError)
|
||||||
|
|
||||||
public var errorDescription: String? {
|
public var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .processing(let result): return "Failed to process notification (\(result))"
|
case .processing(let result): return "Failed to process notification (\(result)) (NotificationError.processing)."
|
||||||
case .messageProcessing: return "Failed to process message"
|
case .messageProcessing: return "Failed to process message (NotificationError.messageProcessing)."
|
||||||
case .messageHandling(let error): return "Failed to handle message (\(error))"
|
case .messageHandling(let error): return "Failed to handle message (\(error)) (NotificationError.messageHandling)."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import GRDB
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
|
public enum GetSwarmJob: JobExecutor {
|
||||||
|
public static let maxFailureCount: Int = 0
|
||||||
|
public static let requiresThreadId: Bool = false
|
||||||
|
public static let requiresInteractionId: Bool = false
|
||||||
|
|
||||||
|
/// The minimum number of snodes in a swarm.
|
||||||
|
private static let minSwarmSnodeCount: Int = 3
|
||||||
|
|
||||||
|
public static func run(
|
||||||
|
_ job: Job,
|
||||||
|
queue: DispatchQueue,
|
||||||
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
using dependencies: Dependencies
|
||||||
|
) {
|
||||||
|
guard
|
||||||
|
let detailsData: Data = job.details,
|
||||||
|
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||||
|
else {
|
||||||
|
SNLog("[GetSwarmJob] Failing due to missing details.")
|
||||||
|
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
SNLog("[GetSwarmJob] Retrieving swarm for \(details.swarmPublicKey).")
|
||||||
|
return SnodeAPI
|
||||||
|
.getSwarm(for: details.swarmPublicKey, using: dependencies)
|
||||||
|
.subscribe(on: queue, using: dependencies)
|
||||||
|
.receive(on: queue, using: dependencies)
|
||||||
|
.sinkUntilComplete(
|
||||||
|
receiveCompletion: { result in
|
||||||
|
switch result {
|
||||||
|
case .finished: break
|
||||||
|
case .failure(let error):
|
||||||
|
SNLog("[GetSwarmJob] Failed due to error: \(error)")
|
||||||
|
failure(job, error, false, dependencies)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receiveValue: { (snodes: Set<Snode>) in
|
||||||
|
// Store the swarm and update the 'loadedSwarms' state so we don't fetch it again from the
|
||||||
|
// database the next time it's used
|
||||||
|
SnodeAPI.setSwarm(to: snodes, for: details.swarmPublicKey)
|
||||||
|
SnodeAPI.loadedSwarms.mutate { $0.insert(details.swarmPublicKey) }
|
||||||
|
|
||||||
|
SNLog("[GetSwarmJob] Complete.")
|
||||||
|
success(job, false, dependencies)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func run(
|
||||||
|
for swarmPublicKey: String,
|
||||||
|
using dependencies: Dependencies
|
||||||
|
) -> AnyPublisher<Set<Snode>, Error> {
|
||||||
|
// Try to load the swarm from the database if we haven't already
|
||||||
|
if !SnodeAPI.loadedSwarms.wrappedValue.contains(swarmPublicKey) {
|
||||||
|
let updatedCacheForKey: Set<Snode> = dependencies.storage
|
||||||
|
.read { db in try Snode.fetchSet(db, publicKey: swarmPublicKey) }
|
||||||
|
.defaulting(to: [])
|
||||||
|
|
||||||
|
SnodeAPI.swarmCache.mutate { $0[swarmPublicKey] = updatedCacheForKey }
|
||||||
|
SnodeAPI.loadedSwarms.mutate { $0.insert(swarmPublicKey) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already have a cached version of the swarm which is large enough then use that
|
||||||
|
if let cachedSwarm = SnodeAPI.swarmCache.wrappedValue[swarmPublicKey], cachedSwarm.count >= minSwarmSnodeCount {
|
||||||
|
return Just(cachedSwarm)
|
||||||
|
.setFailureType(to: Error.self)
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise trigger the job
|
||||||
|
return Deferred {
|
||||||
|
Future<Set<Snode>, Error> { resolver in
|
||||||
|
let targetJob: Job? = dependencies.storage.write(using: dependencies) { db in
|
||||||
|
return dependencies.jobRunner.upsert(
|
||||||
|
db,
|
||||||
|
job: Job(
|
||||||
|
variant: .getSwarm,
|
||||||
|
behaviour: .runOnceTransient,
|
||||||
|
shouldBeUnique: true,
|
||||||
|
details: Details(swarmPublicKey: swarmPublicKey)
|
||||||
|
),
|
||||||
|
canStartJob: true,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let job: Job = targetJob else {
|
||||||
|
SNLog("[GetSwarmJob] Failed to retrieve existing job or schedule a new one.")
|
||||||
|
return resolver(Result.failure(JobRunnerError.generic))
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies.jobRunner.afterJob(job) { result in
|
||||||
|
switch result {
|
||||||
|
case .succeeded:
|
||||||
|
guard
|
||||||
|
let cachedSwarm = SnodeAPI.swarmCache.wrappedValue[swarmPublicKey],
|
||||||
|
cachedSwarm.count >= minSwarmSnodeCount
|
||||||
|
else {
|
||||||
|
SNLog("[GetSwarmJob] Failed to find swarm in cache after job.")
|
||||||
|
return resolver(Result.failure(JobRunnerError.generic))
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver(Result.success(cachedSwarm))
|
||||||
|
|
||||||
|
case .failed(let error, _): resolver(Result.failure(error ?? JobRunnerError.generic))
|
||||||
|
case .deferred, .notFound: resolver(Result.failure(JobRunnerError.generic))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - GetSwarmJob.Details
|
||||||
|
|
||||||
|
extension GetSwarmJob {
|
||||||
|
public struct Details: Codable {
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case swarmPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate let swarmPublicKey: String
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue