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.
 | 
			
		||||
//
 | 
			
		||||
// stringlint:disable
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
import SessionMessagingKit
 | 
			
		||||
 | 
			
		||||
enum NotificationError: LocalizedError {
 | 
			
		||||
enum NotificationError: Error, CustomStringConvertible {
 | 
			
		||||
    case processing(PushNotificationAPI.ProcessResult)
 | 
			
		||||
    case messageProcessing
 | 
			
		||||
    case messageHandling(MessageReceiverError)
 | 
			
		||||
    
 | 
			
		||||
    public var errorDescription: String? {
 | 
			
		||||
    public var description: String {
 | 
			
		||||
        switch self {
 | 
			
		||||
            case .processing(let result): return "Failed to process notification (\(result))"
 | 
			
		||||
            case .messageProcessing: return "Failed to process message"
 | 
			
		||||
            case .messageHandling(let error): return "Failed to handle message (\(error))"
 | 
			
		||||
            case .processing(let result): return "Failed to process notification (\(result)) (NotificationError.processing)."
 | 
			
		||||
            case .messageProcessing: return "Failed to process message (NotificationError.messageProcessing)."
 | 
			
		||||
            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