diff --git a/LibSession-Util b/LibSession-Util index 714230c0c..9a867d563 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit 714230c0c8867ac8662603fd6debb5b4c4369ef1 +Subproject commit 9a867d563266b875144285cd49b07b3aacf7206e diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 75cc072d5..455cf72a1 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -870,6 +870,8 @@ FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; }; FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */; }; + FDDD554C2C1FC812006CBF03 /* CheckForAppUpdatesJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDD554B2C1FC812006CBF03 /* CheckForAppUpdatesJob.swift */; }; + FDDD554E2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */; }; FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; }; FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; }; FDE125232A837E4E002DA685 /* MainAppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE125222A837E4E002DA685 /* MainAppContext.swift */; }; @@ -2065,6 +2067,8 @@ FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = ""; }; FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessResult.swift; sourceTree = ""; }; FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionTypeConversionUtilitiesSpec.swift; sourceTree = ""; }; + FDDD554B2C1FC812006CBF03 /* CheckForAppUpdatesJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckForAppUpdatesJob.swift; sourceTree = ""; }; + FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _019_ScheduleAppUpdateCheckJob.swift; sourceTree = ""; }; FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = ""; }; FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerSpec.swift; sourceTree = ""; }; FDE125222A837E4E002DA685 /* MainAppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAppContext.swift; sourceTree = ""; }; @@ -3755,6 +3759,7 @@ FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */, FD428B222B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift */, 7B5233C5290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift */, + FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */, ); path = Migrations; sourceTree = ""; @@ -4454,6 +4459,7 @@ FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */, 7B7E5B512A4D024C00A8208E /* ExpirationUpdateJob.swift */, 7B7AD41E2A5512CA00469FB1 /* GetExpirationJob.swift */, + FDDD554B2C1FC812006CBF03 /* CheckForAppUpdatesJob.swift */, ); path = Types; sourceTree = ""; @@ -6177,6 +6183,7 @@ 7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */, 7B7E5B522A4D024C00A8208E /* ExpirationUpdateJob.swift in Sources */, FD09799727FFA84A00936362 /* RecipientState.swift in Sources */, + FDDD554E2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift in Sources */, FDA8EB00280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift in Sources */, FD09798927FD1C5A00936362 /* OpenGroup.swift in Sources */, FD848B9628422A2A000E298B /* MessageViewModel.swift in Sources */, @@ -6279,6 +6286,7 @@ C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */, FD716E682850318E00C96BF4 /* CallMode.swift in Sources */, FD09799527FE7B8E00936362 /* Interaction.swift in Sources */, + FDDD554C2C1FC812006CBF03 /* CheckForAppUpdatesJob.swift in Sources */, FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */, 7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */, FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */, @@ -8034,7 +8042,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 450; + CURRENT_PROJECT_VERSION = 451; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -8112,7 +8120,7 @@ CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 450; + CURRENT_PROJECT_VERSION = 451; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 70fcf5bbb..12b244c7c 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -321,7 +321,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // the app after this closure is registered but before it's actually triggered - this can // result in the `BackgroundPoller` incorrectly getting called in the foreground, this check // is here to prevent that - guard Singleton.hasAppContext && Singleton.appContext.isInBackground else { return } + guard Singleton.hasAppContext && Singleton.appContext.isInBackground else { + BackgroundPoller.isValid = false + return + } BackgroundPoller.poll { result in guard BackgroundPoller.isValid else { return } diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 83d167986..db31e6d27 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -35,7 +35,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API _015_BlockCommunityMessageRequests.self, _016_MakeBrokenProfileTimestampsNullable.self, _017_RebuildFTSIfNeeded_2_4_5.self, - _018_DisappearingMessagesConfiguration.self + _018_DisappearingMessagesConfiguration.self, + _019_ScheduleAppUpdateCheckJob.self ] ] ) @@ -60,5 +61,6 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API JobRunner.setExecutor(ConfigMessageReceiveJob.self, for: .configMessageReceive) JobRunner.setExecutor(ExpirationUpdateJob.self, for: .expirationUpdate) JobRunner.setExecutor(GetExpirationJob.self, for: .getExpiration) + JobRunner.setExecutor(CheckForAppUpdatesJob.self, for: .checkForAppUpdates) } } diff --git a/SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift b/SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift new file mode 100644 index 000000000..504a2d87d --- /dev/null +++ b/SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift @@ -0,0 +1,25 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +enum _019_ScheduleAppUpdateCheckJob: Migration { + static let target: TargetMigrations.Identifier = .messagingKit + static let identifier: String = "ScheduleAppUpdateCheckJob" // stringlint:disable + static let needsConfigSync: Bool = false + static let minExpectedRunDuration: TimeInterval = 0.1 + static var requirements: [MigrationRequirement] = [.libSessionStateLoaded] + static let fetchedTables: [(TableRecord & FetchableRecord).Type] = [] + static let createdOrAlteredTables: [(TableRecord & FetchableRecord).Type] = [] + static let droppedTables: [(TableRecord & FetchableRecord).Type] = [] + + static func migrate(_ db: GRDB.Database) throws { + _ = try Job( + variant: .checkForAppUpdates, + behaviour: .recurring + ).migrationSafeInserted(db) + + Storage.update(progress: 1, for: self, in: target) // In case this is the last migration + } +} diff --git a/SessionMessagingKit/Jobs/Types/CheckForAppUpdatesJob.swift b/SessionMessagingKit/Jobs/Types/CheckForAppUpdatesJob.swift new file mode 100644 index 000000000..da6f380ed --- /dev/null +++ b/SessionMessagingKit/Jobs/Types/CheckForAppUpdatesJob.swift @@ -0,0 +1,55 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Combine +import SessionUtilitiesKit +import SessionSnodeKit + +public enum CheckForAppUpdatesJob: JobExecutor { + public static var maxFailureCount: Int = -1 + public static var requiresThreadId: Bool = false + public static let requiresInteractionId: Bool = false + + 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 + ) { + dependencies.storage + .readPublisher(using: dependencies) { db -> [UInt8]? in Identity.fetchUserEd25519KeyPair(db)?.secretKey } + .subscribe(on: queue) + .receive(on: queue) + .tryFlatMap { maybeEd25519SecretKey in + guard let ed25519SecretKey: [UInt8] = maybeEd25519SecretKey else { throw StorageError.objectNotFound } + + return LibSession.checkClientVersion( + ed25519SecretKey: ed25519SecretKey, + using: dependencies + ) + } + .sinkUntilComplete( + receiveCompletion: { _ in + var updatedJob: Job = job.with( + failureCount: 0, + nextRunTimestamp: (dependencies.dateNow.timeIntervalSince1970 + (24 * 60 * 60)) + ) + + dependencies.storage.write(using: dependencies) { db in + try updatedJob.save(db) + } + + success(updatedJob, false, dependencies) + }, + receiveValue: { _, versionInfo in + switch versionInfo.prerelease { + case .none: Log.info("[CheckForAppUpdatesJob] Latest version: \(versionInfo.version)") + case .some(let prerelease): + Log.info("[CheckForAppUpdatesJob] Latest version: \(versionInfo.version), pre-release version: \(prerelease.version)") + } + } + ) + } +} diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 55fa3cf8b..4b6abd2e8 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -28,15 +28,15 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension override public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler self.request = request - - guard let notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent else { - Log.info("didReceive called with no content.") - return self.completeSilenty() - } // Abort if the main app is running guard !(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { Log.info("didReceive called while main app running.") + return self.completeSilenty(isMainAppAndActive: true) + } + + guard let notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent else { + Log.info("didReceive called with no content.") return self.completeSilenty() } @@ -328,7 +328,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension completeSilenty() } - private func completeSilenty() { + private func completeSilenty(isMainAppAndActive: Bool = false) { // Ensure we only run this once guard hasCompleted.mutate({ hasCompleted in @@ -343,8 +343,11 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension .read { db in try Interaction.fetchUnreadCount(db) } .map { NSNumber(value: $0) } .defaulting(to: NSNumber(value: 0)) + Log.info("Complete silently.") - Storage.suspendDatabaseAccess() + if !isMainAppAndActive { + Storage.suspendDatabaseAccess() + } Log.flush() self.contentHandler!(silentContent) diff --git a/SessionSnodeKit/LibSession/LibSession+Networking.swift b/SessionSnodeKit/LibSession/LibSession+Networking.swift index 472c93870..f497246d9 100644 --- a/SessionSnodeKit/LibSession/LibSession+Networking.swift +++ b/SessionSnodeKit/LibSession/LibSession+Networking.swift @@ -358,6 +358,7 @@ public extension LibSession { } static func checkClientVersion( + ed25519SecretKey: [UInt8], using dependencies: Dependencies = Dependencies() ) -> AnyPublisher<(ResponseInfoType, AppVersionResponse), Error> { typealias Output = (success: Bool, timeout: Bool, statusCode: Int, data: Data?) @@ -366,9 +367,12 @@ public extension LibSession { .tryFlatMap { network in return CallbackWrapper .create { wrapper in + var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) + network_get_client_version( network, CLIENT_PLATFORM_IOS, + &cEd25519SecretKey, Int64(floor(Network.fileDownloadTimeout * 1000)), { success, timeout, statusCode, dataPtr, dataLen, ctx in let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } @@ -422,7 +426,7 @@ public extension LibSession { return nil } - guard network_init(&network, cCachePath, Features.useTestnet, true, &error) else { + guard network_init(&network, cCachePath, Features.useTestnet, !Singleton.appContext.isMainApp, true, &error) else { Log.error("[LibQuic] Unable to create network object: \(String(cString: error))") return nil } diff --git a/SessionSnodeKit/Models/AppVersionResponse.swift b/SessionSnodeKit/Models/AppVersionResponse.swift index eea0eae0b..ae5c33739 100644 --- a/SessionSnodeKit/Models/AppVersionResponse.swift +++ b/SessionSnodeKit/Models/AppVersionResponse.swift @@ -2,10 +2,96 @@ import Foundation -public struct AppVersionResponse: Codable { +public class AppVersionResponse: AppVersionInfo { + enum CodingKeys: String, CodingKey { + case prerelease + } + + public let prerelease: AppVersionInfo? + + public init( + version: String, + updated: TimeInterval?, + name: String?, + notes: String?, + assets: [Asset]?, + prerelease: AppVersionInfo? + ) { + self.prerelease = prerelease + + super.init( + version: version, + updated: updated, + name: name, + notes: notes, + assets: assets + ) + } + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + self.prerelease = try? container.decode(AppVersionInfo?.self, forKey: .prerelease) + + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(prerelease, forKey: .prerelease) + } +} + +// MARK: - AppVersionInfo + +public class AppVersionInfo: Codable { enum CodingKeys: String, CodingKey { case version = "result" + case updated + case name + case notes + case assets + } + + public struct Asset: Codable { + enum CodingKeys: String, CodingKey { + case name + case url + } + + public let name: String + public let url: String } public let version: String + public let updated: TimeInterval? + public let name: String? + public let notes: String? + public let assets: [Asset]? + + public init( + version: String, + updated: TimeInterval?, + name: String?, + notes: String?, + assets: [Asset]? + ) { + self.version = version + self.updated = updated + self.name = name + self.notes = notes + self.assets = assets + } + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(version, forKey: .version) + try container.encodeIfPresent(updated, forKey: .updated) + try container.encodeIfPresent(name, forKey: .name) + try container.encodeIfPresent(notes, forKey: .notes) + try container.encodeIfPresent(assets, forKey: .assets) + } } diff --git a/SessionUtilitiesKit/Database/Models/Job.swift b/SessionUtilitiesKit/Database/Models/Job.swift index 2744ed2b6..3dc4aca4c 100644 --- a/SessionUtilitiesKit/Database/Models/Job.swift +++ b/SessionUtilitiesKit/Database/Models/Job.swift @@ -128,6 +128,9 @@ public struct Job: Codable, Equatable, Hashable, Identifiable, FetchableRecord, /// This is a job that runs once whenever a message is marked as read because of syncing from user config and /// needs to get expiration from network case getExpiration + + /// This is a job that runs at most once every 24 hours in order to check if there is a new update available on GitHub + case checkForAppUpdates = 3011 } public enum Behaviour: Int, Codable, DatabaseValueConvertible, CaseIterable { diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index 20868c13b..6ad692b68 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -37,8 +37,7 @@ public enum Log { public static func appResumedExecution() { guard logger.wrappedValue != nil else { return } - Log.empty() - Log.empty() + logger.wrappedValue?.loadExtensionLogsAndResumeLogging() } public static func logFilePath() -> String? { @@ -46,7 +45,51 @@ public enum Log { let logger: Logger = logger.wrappedValue else { return nil } - return logger.fileLogger.logFileManager.sortedLogFilePaths.first + let logFiles: [String] = logger.fileLogger.logFileManager.sortedLogFilePaths + + guard !logFiles.isEmpty else { return nil } + + // If the latest log file is too short (ie. less that ~100kb) then we want to create a temporary file + // which contains the previous log file logs plus the logs from the newest file so we don't miss info + // that might be relevant for debugging + guard + logFiles.count > 1, + let attributes: [FileAttributeKey: Any] = try? FileManager.default.attributesOfItem(atPath: logFiles[0]), + let fileSize: UInt64 = attributes[.size] as? UInt64, + fileSize < (100 * 1024) + else { return logFiles[0] } + + // The file is too small so lets create a temp file to share instead + let tempDirectory: String = NSTemporaryDirectory() + let tempFilePath: String = URL(fileURLWithPath: tempDirectory) + .appendingPathComponent(URL(fileURLWithPath: logFiles[1]).lastPathComponent) + .path + + do { + try FileManager.default.copyItem( + atPath: logFiles[1], + toPath: tempFilePath + ) + + guard let fileHandle: FileHandle = FileHandle(forWritingAtPath: tempFilePath) else { + throw StorageError.objectNotFound + } + + // Ensure we close the file handle + defer { fileHandle.closeFile() } + + // Move to the end of the file to insert the logs + if #available(iOS 13.4, *) { try fileHandle.seekToEnd() } + else { fileHandle.seekToEndOfFile() } + + // Append the data from the newest log to the temp file + let newestLogData: Data = try Data(contentsOf: URL(fileURLWithPath: logFiles[0])) + if #available(iOS 13.4, *) { try fileHandle.write(contentsOf: newestLogData) } + else { fileHandle.write(newestLogData) } + } + catch { return logFiles[0] } + + return tempFilePath } public static func flush() { @@ -129,7 +172,7 @@ public enum Log { ) { guard let logger: Logger = logger.wrappedValue, - logger.startupCompleted.wrappedValue + !logger.isSuspended.wrappedValue else { return pendingStartupLogs.mutate { $0.append((level, message, withPrefixes, silenceForTests)) } } logger.log(level, message, withPrefixes: withPrefixes, silenceForTests: silenceForTests) @@ -143,7 +186,7 @@ public class Logger { private let primaryPrefix: String private let forceNSLog: Bool fileprivate let fileLogger: DDFileLogger - fileprivate let startupCompleted: Atomic = Atomic(false) + fileprivate let isSuspended: Atomic = Atomic(true) fileprivate var retrievePendingStartupLogs: (() -> [Log.LogInfo])? public init( @@ -178,19 +221,22 @@ public class Logger { // Now that we are setup we should load the extension logs which will then // complete the startup process when completed - self.loadExtensionLogs() + self.loadExtensionLogsAndResumeLogging() } // MARK: - Functions - private func loadExtensionLogs() { + fileprivate func loadExtensionLogsAndResumeLogging() { + // Pause logging while we load the extension logs (want to avoid interleaving them where possible) + isSuspended.mutate { $0 = true } + // The extensions write their logs to the app shared directory but the main app writes // to a local directory (so they can be exported via XCode) - the below code reads any // logs from the shared directly and attempts to add them to the main app logs to make // debugging user issues in extensions easier DispatchQueue.global(qos: .utility).async { [weak self] in guard let currentLogFileInfo: DDLogFileInfo = self?.fileLogger.currentLogFileInfo else { - self?.completeStartup(error: "Unable to retrieve current log file.") + self?.completeResumeLogging(error: "Unable to retrieve current log file.") return } @@ -257,21 +303,27 @@ public class Logger { } } catch { - self?.completeStartup(error: "Unable to write extension logs to current log file") + self?.completeResumeLogging(error: "Unable to write extension logs to current log file") return } - self?.completeStartup() + self?.completeResumeLogging() } } } - private func completeStartup(error: String? = nil) { - let pendingLogs: [Log.LogInfo] = startupCompleted.mutate { startupCompleted in - startupCompleted = true + private func completeResumeLogging(error: String? = nil) { + let pendingLogs: [Log.LogInfo] = isSuspended.mutate { isSuspended in + isSuspended = false return (retrievePendingStartupLogs?() ?? []) } + // If we had an error loading the extension logs then actually log it + if let error: String = error { + Log.empty() + log(.error, error, withPrefixes: true, silenceForTests: false) + } + // After creating a new logger we want to log two empty lines to make it easier to read Log.empty() Log.empty() diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index d91006046..ba75a639b 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -156,7 +156,7 @@ public final class JobRunner: JobRunnerType { case (.failed(let lhsError, let lhsPermanent), .failed(let rhsError, let rhsPermanent)): return ( // Not a perfect solution but should be good enough - "\(lhsError ?? JobRunnerError.generic)" == "\(rhsError ?? JobRunnerError.generic)" && + "\(lhsError ?? JobRunnerError.unknown)" == "\(rhsError ?? JobRunnerError.unknown)" && lhsPermanent == rhsPermanent ) @@ -299,7 +299,8 @@ public final class JobRunner: JobRunnerType { jobVariants: [ jobVariants.remove(.expirationUpdate), jobVariants.remove(.getExpiration), - jobVariants.remove(.disappearingMessages) + jobVariants.remove(.disappearingMessages), + jobVariants.remove(.checkForAppUpdates) // Don't want this to block other jobs ].compactMap { $0 } ), @@ -1632,7 +1633,7 @@ public final class JobQueue: Hashable { // immediately (in this case we don't trigger any job callbacks because the // job isn't actually done, it's going to try again immediately) if self.type == .blocking && job.shouldBlock { - SNLog("[JobRunner] \(queueContext) \(job.variant) job failed; retrying immediately") + SNLog("[JobRunner] \(queueContext) \(job.variant) job failed due to error: \(error ?? JobRunnerError.unknown); retrying immediately") // If it was a possible deferral loop then we don't actually want to // retry the job (even if it's a blocking one, this gives a small chance @@ -1664,7 +1665,7 @@ public final class JobQueue: Hashable { let maxFailureCount: Int = (executorMap.wrappedValue[job.variant]?.maxFailureCount ?? 0) let nextRunTimestamp: TimeInterval = (dependencies.dateNow.timeIntervalSince1970 + JobRunner.getRetryInterval(for: job)) var dependantJobIds: [Int64] = [] - var failureText: String = "failed" + var failureText: String = "failed due to error: \(error ?? JobRunnerError.unknown)" dependencies.storage.write(using: dependencies) { db in /// Retrieve a list of dependant jobs so we can clear them from the queue @@ -1683,8 +1684,8 @@ public final class JobQueue: Hashable { ) else { failureText = (maxFailureCount >= 0 && updatedFailureCount > maxFailureCount ? - "failed permanently; too many retries" : - "failed permanently" + "failed permanently due to error: \(error ?? JobRunnerError.unknown); too many retries" : + "failed permanently due to error: \(error ?? JobRunnerError.unknown)" ) // If the job permanently failed or we have performed all of our retry attempts @@ -1696,7 +1697,7 @@ public final class JobQueue: Hashable { return } - failureText = "failed; scheduling retry (failure count is \(updatedFailureCount))" + failureText = "failed due to error: \(error ?? JobRunnerError.unknown); scheduling retry (failure count is \(updatedFailureCount))" try job .with( diff --git a/SessionUtilitiesKit/JobRunner/JobRunnerError.swift b/SessionUtilitiesKit/JobRunner/JobRunnerError.swift index 0932187ed..51ab05775 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunnerError.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunnerError.swift @@ -3,8 +3,6 @@ import Foundation public enum JobRunnerError: Error { - case generic - case executorMissing case jobIdMissing case requiredThreadIdMissing @@ -14,4 +12,6 @@ public enum JobRunnerError: Error { case missingDependencies case possibleDeferralLoop + + case unknown }