diff --git a/Session/Path/PathVC.swift b/Session/Path/PathVC.swift index 54d8d8bc9..d2a8605d1 100644 --- a/Session/Path/PathVC.swift +++ b/Session/Path/PathVC.swift @@ -129,7 +129,10 @@ final class PathVC: BaseVC { // Register for path country updates cacheUpdateId = IP2Country.onCacheLoaded { [weak self] in DispatchQueue.main.async { - self?.update(paths: (self?.lastPath.map { [$0] } ?? []), force: true) + switch (self?.lastPath, self?.lastPath.isEmpty == true) { + case (.none, _), (_, true): self?.update(paths: [], force: true) + case (.some(let lastPath), _): self?.update(paths: [lastPath], force: true) + } } } } diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index 887625f1d..73185c9e8 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -178,6 +178,50 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa targetView: UIView? = nil, animated: Bool = true, onShareComplete: (() -> ())? = nil + ) { + guard + let latestLogFilePath: String = Log.logFilePath(), + Singleton.hasAppContext, + let viewController: UIViewController = Singleton.appContext.frontmostViewController + else { return } + + #if targetEnvironment(simulator) + let modal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: "Export Logs", // stringlint:disable + body: .text( + "How would you like to export the logs?\n\n(This modal only appears on the Simulator)" // stringlint:disable + ), + confirmTitle: "Copy Path", // stringlint:disable + cancelTitle: "Share", // stringlint:disable + cancelStyle: .alert_text, + onConfirm: { _ in UIPasteboard.general.string = latestLogFilePath }, + onCancel: { _ in + HelpViewModel.shareLogsInternal( + viewControllerToDismiss: viewControllerToDismiss, + targetView: targetView, + animated: animated, + onShareComplete: onShareComplete + ) + } + ) + ) + viewController.present(modal, animated: animated, completion: nil) + #else + HelpViewModel.shareLogsInternal( + viewControllerToDismiss: viewControllerToDismiss, + targetView: targetView, + animated: animated, + onShareComplete: onShareComplete + ) + #endif + } + + private static func shareLogsInternal( + viewControllerToDismiss: UIViewController? = nil, + targetView: UIView? = nil, + animated: Bool = true, + onShareComplete: (() -> ())? = nil ) { Log.info("[Version] \(SessionApp.versionInfo)") Log.flush() diff --git a/Session/Utilities/IP2Country.swift b/Session/Utilities/IP2Country.swift index 986e28a28..9a7f3a792 100644 --- a/Session/Utilities/IP2Country.swift +++ b/Session/Utilities/IP2Country.swift @@ -46,6 +46,10 @@ public enum IP2Country { static func populateCacheIfNeededAsync() { DispatchQueue.global(qos: .utility).async { + // Ensure the caches get loaded in the background + _ = ipv4Table + _ = countryNamesCache + pathsChangedCallbackId.mutate { pathsChangedCallbackId in guard pathsChangedCallbackId == nil else { return } diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index 3609e3bd0..9f42372df 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -299,7 +299,10 @@ public extension LinkPreview { return Fail(error: LinkPreviewError.featureDisabled) .eraseToAnyPublisher() } - guard let previewUrl: String = previewUrl else { + + // Force the url to lowercase to ensure we casing doesn't result in redownloading the + // details + guard let previewUrl: String = previewUrl?.lowercased() else { return Fail(error: LinkPreviewError.invalidInput) .eraseToAnyPublisher() } diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index a485dee5a..811a8aa70 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -491,7 +491,14 @@ public final class JobRunner: JobRunnerType { // Add and start any blocking jobs blockingQueue.wrappedValue?.appDidFinishLaunching( - with: jobsToRun.blocking, + with: jobsToRun.blocking.map { job -> Job in + guard job.behaviour == .recurringOnLaunch else { return job } + + // If the job is a `recurringOnLaunch` job then we reset the `nextRunTimestamp` + // value on the instance because the assumption is that `recurringOnLaunch` will + // run a job regardless of how many times it previously failed + return job.with(nextRunTimestamp: 0) + }, canStart: true, using: dependencies ) @@ -503,7 +510,14 @@ public final class JobRunner: JobRunnerType { jobsByVariant.forEach { variant, jobs in jobQueues[variant]?.appDidFinishLaunching( - with: jobs, + with: jobs.map { job -> Job in + guard job.behaviour == .recurringOnLaunch else { return job } + + // If the job is a `recurringOnLaunch` job then we reset the `nextRunTimestamp` + // value on the instance because the assumption is that `recurringOnLaunch` will + // run a job regardless of how many times it previously failed + return job.with(nextRunTimestamp: 0) + }, canStart: false, using: dependencies ) @@ -562,7 +576,12 @@ public final class JobRunner: JobRunnerType { } .forEach { queue, jobs in queue.appDidBecomeActive( - with: jobs, + with: jobs.map { job -> Job in + // We reset the `nextRunTimestamp` value on the instance because the + // assumption is that `recurringOnActive` will run a job regardless + // of how many times it previously failed + job.with(nextRunTimestamp: 0) + }, canStart: !blockingQueueIsRunning, using: dependencies ) diff --git a/SessionUtilitiesKit/Networking/Request.swift b/SessionUtilitiesKit/Networking/Request.swift index f9fc5baf9..179709527 100644 --- a/SessionUtilitiesKit/Networking/Request.swift +++ b/SessionUtilitiesKit/Networking/Request.swift @@ -70,6 +70,9 @@ public struct Request { case let bodyBytes as [UInt8]: return Data(bodyBytes) + + case let bodyDirectData as Data: + return bodyDirectData default: // Having no body is fine so just return nil diff --git a/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift b/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift index 5e2146d82..84ede4729 100644 --- a/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift +++ b/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift @@ -749,6 +749,33 @@ class JobRunnerSpec: QuickSpec { jobRunner.appDidFinishLaunching(using: dependencies) expect(jobRunner.allJobInfo()).to(beEmpty()) } + + // MARK: -------- runs the job regardless of the nextRunTimestamp value it has + it("runs the job regardless of the nextRunTimestamp value it has") { + job1 = Job( + id: 100, + failureCount: 0, + variant: .messageSend, + behaviour: .recurringOnLaunch, + shouldBlock: false, + shouldBeUnique: false, + shouldSkipLaunchBecomeActive: false, + nextRunTimestamp: Date.distantFuture.timeIntervalSince1970, + threadId: nil, + interactionId: nil, + details: try? JSONEncoder() + .with(outputFormatting: .sortedKeys) + .encode(TestDetails(completeTime: 1)) + ) + + mockStorage.write { db in + try job1.upsert(db) + } + + jobRunner.appDidFinishLaunching(using: dependencies) + jobRunner.appDidBecomeActive(using: dependencies) + expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue()) + } } // MARK: ------ by being notified of app becoming active @@ -1058,6 +1085,33 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue()) expect(jobRunner.isCurrentlyRunning(job2)).to(beTrue()) } + + // MARK: -------- runs the job regardless of the nextRunTimestamp value it has + it("runs the job regardless of the nextRunTimestamp value it has") { + job1 = Job( + id: 100, + failureCount: 0, + variant: .messageSend, + behaviour: .recurringOnActive, + shouldBlock: false, + shouldBeUnique: false, + shouldSkipLaunchBecomeActive: false, + nextRunTimestamp: Date.distantFuture.timeIntervalSince1970, + threadId: nil, + interactionId: nil, + details: try? JSONEncoder() + .with(outputFormatting: .sortedKeys) + .encode(TestDetails(completeTime: 1)) + ) + + mockStorage.write { db in + try job1.upsert(db) + } + + jobRunner.appDidFinishLaunching(using: dependencies) + jobRunner.appDidBecomeActive(using: dependencies) + expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue()) + } } // MARK: ------ by checking if a job can be added to the queue