mirror of
https://github.com/signalapp/Signal-iOS.git
synced 2025-12-05 01:10:41 +00:00
Use Cron for periodic backup refresh
This commit is contained in:
@@ -33,8 +33,6 @@
|
||||
04AB61C62E5E37A800405699 /* PollRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AB61C52E5E37A400405699 /* PollRecord.swift */; };
|
||||
04AB61C82E5E399700405699 /* PollOptionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AB61C72E5E399400405699 /* PollOptionRecord.swift */; };
|
||||
04AB61CA2E5E449100405699 /* PollManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AB61C92E5E448A00405699 /* PollManagerTest.swift */; };
|
||||
04B975462E43A4AF00E20364 /* BackupRefreshManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */; };
|
||||
04B975482E43BFE600E20364 /* BackupRefreshManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */; };
|
||||
04BBBE902E259A6900E914B1 /* InactivePrimaryDeviceReminderMegaphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BBBE8F2E259A5F00E914B1 /* InactivePrimaryDeviceReminderMegaphone.swift */; };
|
||||
04BBBE922E26C92D00E914B1 /* InactivePrimaryDeviceStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BBBE912E26C92300E914B1 /* InactivePrimaryDeviceStore.swift */; };
|
||||
04BBBE942E26F00900E914B1 /* InactivePrimaryDeviceStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BBBE932E26F00000E914B1 /* InactivePrimaryDeviceStoreTest.swift */; };
|
||||
@@ -633,6 +631,8 @@
|
||||
50423CA42BBF427900DCB8F5 /* StaleProfileFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50423CA32BBF427900DCB8F5 /* StaleProfileFetcher.swift */; };
|
||||
504271B62BB4C54500E33C01 /* SystemContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504271B52BB4C54500E33C01 /* SystemContact.swift */; };
|
||||
5042EAA3287F96FB00C9B19F /* VisibleBadgeResolverTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5042EAA2287F96FB00C9B19F /* VisibleBadgeResolverTest.swift */; };
|
||||
50438A8E2ECBBDF600FCB28F /* BackupRefreshManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */; };
|
||||
50438A8F2ECBBE1D00FCB28F /* BackupRefreshManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */; };
|
||||
5045F44229E0DB7100058E5F /* LaunchJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C3636233D198300D52012 /* LaunchJobs.swift */; };
|
||||
50468F2529EDD46500948E02 /* ParamParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */; };
|
||||
50468F2929EE130A00948E02 /* InteractionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50468F2829EE130A00948E02 /* InteractionStore.swift */; };
|
||||
@@ -12568,7 +12568,6 @@
|
||||
D923DF9B2DC135A000CDAFC3 /* BackupIdService.swift */,
|
||||
D95D9D2F2E56866200855DE4 /* BackupKeyService.swift */,
|
||||
D98ACAA82E0DA71700FA497F /* BackupPlanManager.swift */,
|
||||
04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */,
|
||||
D9388C902DA4751F0048D4F9 /* BackupSettingsStore.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
@@ -12599,6 +12598,7 @@
|
||||
D93FA5BE2DE77E440013879E /* BackupEnablingManager.swift */,
|
||||
D93BDD932E43063B00779BD8 /* BackupKeepKeySafeSheet.swift */,
|
||||
D98CA2B22DF2450E0060370E /* BackupRecordKeyViewController.swift */,
|
||||
04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */,
|
||||
D932C0EA2E13AD3600FEF9C3 /* BackupSettingsAttachmentDownloadTracker.swift */,
|
||||
D93964B52E038C7B00094117 /* BackupSettingsAttachmentUploadTracker.swift */,
|
||||
D951F5302D9B236100C5EBF3 /* BackupSettingsViewController.swift */,
|
||||
@@ -12901,6 +12901,7 @@
|
||||
D9A952442E0A1C3600C3D1F7 /* Backups */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */,
|
||||
D962FC422E149EC7004DB623 /* BackupSettingsAttachmentDownloadTrackerTest.swift */,
|
||||
D9324F7B2E1492820095D104 /* BackupSettingsAttachmentTrackerTest.swift */,
|
||||
D9A952452E0A1C3C00C3D1F7 /* BackupSettingsAttachmentUploadTrackerTest.swift */,
|
||||
@@ -12968,7 +12969,6 @@
|
||||
D9F027A12E2071D0002994C1 /* BackupDisablingManagerTest.swift */,
|
||||
041A5F082E05D3AC00FAED05 /* BackupEnablementReminderMegaphoneTests.swift */,
|
||||
047A6DCF2E00B5640048EDF4 /* BackupKeyReminderMegaphoneTests.swift */,
|
||||
04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */,
|
||||
04E66D432E00AB3A0059DBAC /* BackupSettingsStoreTests.swift */,
|
||||
D9A36B922C7FEDA100CEC0E7 /* LineByLineStringDiff.swift */,
|
||||
);
|
||||
@@ -16881,6 +16881,7 @@
|
||||
D98CA2AD2DF14A890060370E /* BackupOnboardingKeyIntroViewController.swift in Sources */,
|
||||
B9A53B912CF507FB0000578B /* BackupProgressModal.swift in Sources */,
|
||||
D98CA2B32DF245140060370E /* BackupRecordKeyViewController.swift in Sources */,
|
||||
50438A8E2ECBBDF600FCB28F /* BackupRefreshManager.swift in Sources */,
|
||||
0480F0002E57C51A006CBB29 /* BackupsEnabledNotificationMegaphone.swift in Sources */,
|
||||
D932C0EB2E13AD3F00FEF9C3 /* BackupSettingsAttachmentDownloadTracker.swift in Sources */,
|
||||
D93964B62E038C7B00094117 /* BackupSettingsAttachmentUploadTracker.swift in Sources */,
|
||||
@@ -17588,6 +17589,7 @@
|
||||
F9B93CE028E246D900B3F8A0 /* AppDelegateTest.swift in Sources */,
|
||||
F9B3A92F293554090071EB95 /* ASWebAuthenticationSessionUtilTest.swift in Sources */,
|
||||
50B6BCB42AEC58250010FB3B /* AuthorMergeHelperBuilderTest.swift in Sources */,
|
||||
50438A8F2ECBBE1D00FCB28F /* BackupRefreshManagerTest.swift in Sources */,
|
||||
D962FC432E149ECF004DB623 /* BackupSettingsAttachmentDownloadTrackerTest.swift in Sources */,
|
||||
D9324F7C2E1492920095D104 /* BackupSettingsAttachmentTrackerTest.swift in Sources */,
|
||||
D9A952462E0A1C4200C3D1F7 /* BackupSettingsAttachmentUploadTrackerTest.swift in Sources */,
|
||||
@@ -17851,7 +17853,6 @@
|
||||
D97054202CFE4D0200AC7954 /* BackupPaymentProcessor.swift in Sources */,
|
||||
D98ACAA92E0DA71B00FA497F /* BackupPlanManager.swift in Sources */,
|
||||
66B127FE2E3D352E006FE598 /* BackupPurpose.swift in Sources */,
|
||||
04B975462E43A4AF00E20364 /* BackupRefreshManager.swift in Sources */,
|
||||
C14391132BD1C0DF00ED6FCB /* BackupRequestManager.swift in Sources */,
|
||||
C17310A72BD9D45400B7A151 /* BackupServiceAuth.swift in Sources */,
|
||||
D9388C912DA475230048D4F9 /* BackupSettingsStore.swift in Sources */,
|
||||
@@ -18924,7 +18925,6 @@
|
||||
04AA85BC2E0EFC5C0051C0A7 /* BackupEnablementReminderMegaphoneTests.swift in Sources */,
|
||||
047A6DD02E00B5720048EDF4 /* BackupKeyReminderMegaphoneTests.swift in Sources */,
|
||||
66A1F4EB2E07CEA50095DE4B /* BackupListMediaManagerTests.swift in Sources */,
|
||||
04B975482E43BFE600E20364 /* BackupRefreshManagerTest.swift in Sources */,
|
||||
04E66D452E00AB6A0059DBAC /* BackupSettingsStoreTests.swift in Sources */,
|
||||
F9426283289B1B5600460798 /* BlockingManagerTests.swift in Sources */,
|
||||
D9C42C2F2B6C60600086B142 /* CallRecordDeleteManagerTest.swift in Sources */,
|
||||
|
||||
@@ -756,6 +756,28 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
operation: { try await ViewOnceMessages.expireIfNecessary() },
|
||||
)
|
||||
|
||||
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
|
||||
|
||||
let backupRefreshManager = BackupRefreshManager(
|
||||
accountKeyStore: DependenciesBridge.shared.accountKeyStore,
|
||||
backupRequestManager: DependenciesBridge.shared.backupRequestManager,
|
||||
backupSettingsStore: BackupSettingsStore(),
|
||||
db: DependenciesBridge.shared.db,
|
||||
networkManager: SSKEnvironment.shared.networkManagerRef,
|
||||
)
|
||||
cron.schedulePeriodically(
|
||||
uniqueKey: .refreshBackup,
|
||||
approximateInterval: BackupRefreshManager.backupRefreshTimeInterval,
|
||||
mustBeRegistered: true,
|
||||
mustBeConnected: true,
|
||||
operation: {
|
||||
guard let localIdentifiers = tsAccountManager.localIdentifiersWithMaybeSneakyTransaction else {
|
||||
throw OWSAssertionError("never registered")
|
||||
}
|
||||
try await backupRefreshManager.refreshBackup(localIdentifiers: localIdentifiers)
|
||||
},
|
||||
)
|
||||
|
||||
// Note that this does much more than set a flag; it will also run all deferred blocks.
|
||||
appReadiness.setAppIsReadyUIStillPending()
|
||||
|
||||
@@ -770,7 +792,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
appContext.appUserDefaults().removeObject(forKey: Constants.appLaunchesAttemptedKey)
|
||||
}
|
||||
|
||||
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
|
||||
let recipientDatabaseTable = DependenciesBridge.shared.recipientDatabaseTable
|
||||
let tsRegistrationState: TSRegistrationState = SSKEnvironment.shared.databaseStorageRef.read { tx in
|
||||
let registrationState = tsAccountManager.registrationState(tx: tx)
|
||||
|
||||
@@ -159,7 +159,6 @@ public class AppEnvironment: NSObject {
|
||||
let accountEntropyPoolManager = DependenciesBridge.shared.accountEntropyPoolManager
|
||||
let backupDisablingManager = DependenciesBridge.shared.backupDisablingManager
|
||||
let backupIdService = DependenciesBridge.shared.backupIdService
|
||||
let backupRefreshManager = DependenciesBridge.shared.backupRefreshManager
|
||||
let backupSubscriptionManager = DependenciesBridge.shared.backupSubscriptionManager
|
||||
let backupTestFlightEntitlementManager = DependenciesBridge.shared.backupTestFlightEntitlementManager
|
||||
let callRecordStore = DependenciesBridge.shared.callRecordStore
|
||||
@@ -195,12 +194,10 @@ public class AppEnvironment: NSObject {
|
||||
|
||||
let (
|
||||
isRegisteredPrimaryDevice,
|
||||
isRegistered,
|
||||
localIdentifiers
|
||||
) = db.read { tx in
|
||||
(
|
||||
tsAccountManager.registrationState(tx: tx).isRegisteredPrimaryDevice,
|
||||
tsAccountManager.registrationState(tx: tx).isRegistered,
|
||||
tsAccountManager.localIdentifiers(tx: tx),
|
||||
)
|
||||
}
|
||||
@@ -247,25 +244,6 @@ public class AppEnvironment: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Things that should run on only registered devices, both linked & primary.
|
||||
if isRegistered {
|
||||
Task {
|
||||
guard let localIdentifiers else {
|
||||
owsFailDebug("Registered but no local identifiers")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try await backupRefreshManager.refreshBackupIfNeeded(
|
||||
localIdentifiers: localIdentifiers,
|
||||
auth: .implicit()
|
||||
)
|
||||
} catch {
|
||||
owsFailDebug("Failed to refresh backup \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task {
|
||||
await db.awaitableWrite { tx in
|
||||
masterKeySyncManager.runStartupJobs(tx: tx)
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
//
|
||||
|
||||
import LibSignalClient
|
||||
import SignalServiceKit
|
||||
|
||||
// MARK: -
|
||||
|
||||
public final class BackupRefreshManager {
|
||||
final class BackupRefreshManager {
|
||||
private let accountKeyStore: AccountKeyStore
|
||||
private let api: NetworkAPI
|
||||
private let backupRequestManager: BackupRequestManager
|
||||
private let backupSettingsStore: BackupSettingsStore
|
||||
private let db: DB
|
||||
private let dateProvider: DateProvider
|
||||
|
||||
// Backups may expire after 30 days. Do a refresh every few days
|
||||
// to account for if the app isn't opened often.
|
||||
private let backupRefreshTimeInterval: TimeInterval = 3 * .day
|
||||
static let backupRefreshTimeInterval: TimeInterval = 3 * .day
|
||||
|
||||
init(
|
||||
accountKeyStore: AccountKeyStore,
|
||||
@@ -25,24 +25,12 @@ public final class BackupRefreshManager {
|
||||
backupSettingsStore: BackupSettingsStore,
|
||||
db: DB,
|
||||
networkManager: NetworkManagerProtocol,
|
||||
dateProvider: @escaping DateProvider
|
||||
) {
|
||||
self.accountKeyStore = accountKeyStore
|
||||
self.api = NetworkAPI(networkManager: networkManager)
|
||||
self.backupRequestManager = backupRequestManager
|
||||
self.backupSettingsStore = backupSettingsStore
|
||||
self.db = db
|
||||
self.dateProvider = dateProvider
|
||||
}
|
||||
|
||||
private func needsBackupRefresh() -> Bool {
|
||||
db.read { tx in
|
||||
let maxDaysAgo = dateProvider().addingTimeInterval(-1 * backupRefreshTimeInterval)
|
||||
let backupPlan = backupSettingsStore.backupPlan(tx: tx)
|
||||
let lastBackupRefreshDate = backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast
|
||||
|
||||
return backupPlan != .disabled && lastBackupRefreshDate < maxDaysAgo
|
||||
}
|
||||
}
|
||||
|
||||
private func rootBackupKeys(localIdentifiers: LocalIdentifiers) async throws -> (MessageRootBackupKey, MediaRootBackupKey) {
|
||||
@@ -59,32 +47,29 @@ public final class BackupRefreshManager {
|
||||
}
|
||||
}
|
||||
|
||||
public func refreshBackupIfNeeded(
|
||||
localIdentifiers: LocalIdentifiers,
|
||||
auth: ChatServiceAuth
|
||||
) async throws {
|
||||
guard needsBackupRefresh() else {
|
||||
func refreshBackup(localIdentifiers: LocalIdentifiers) async throws {
|
||||
let backupPlan = db.read(block: backupSettingsStore.backupPlan(tx:))
|
||||
switch backupPlan {
|
||||
case .disabled:
|
||||
return
|
||||
case .disabling, .free, .paid, .paidExpiringSoon, .paidAsTester:
|
||||
break
|
||||
}
|
||||
|
||||
let (messageBackupKey, mediaBackupKey) = try await rootBackupKeys(localIdentifiers: localIdentifiers)
|
||||
let messageBackupAuth = try await backupRequestManager.fetchBackupServiceAuth(
|
||||
for: messageBackupKey,
|
||||
localAci: localIdentifiers.aci,
|
||||
auth: auth
|
||||
auth: .implicit(),
|
||||
)
|
||||
try await api.refreshBackup(auth: messageBackupAuth)
|
||||
|
||||
let mediaBackupAuth = try await backupRequestManager.fetchBackupServiceAuth(
|
||||
for: mediaBackupKey,
|
||||
localAci: localIdentifiers.aci,
|
||||
auth: auth
|
||||
auth: .implicit(),
|
||||
)
|
||||
try await api.refreshBackup(auth: mediaBackupAuth)
|
||||
|
||||
await db.awaitableWrite { tx in
|
||||
backupSettingsStore.setLastBackupRefreshDate(dateProvider(), tx: tx)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
66
Signal/test/Backups/BackupRefreshManagerTest.swift
Normal file
66
Signal/test/Backups/BackupRefreshManagerTest.swift
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
@testable import Signal
|
||||
@testable import SignalServiceKit
|
||||
import Testing
|
||||
|
||||
@MainActor
|
||||
struct BackupRefreshManagerTest {
|
||||
private let backupSettingsStore: BackupSettingsStore
|
||||
private let db: InMemoryDB
|
||||
private let accountKeyStore: AccountKeyStore
|
||||
private let mockNetworkManager: MockNetworkManager
|
||||
private let mockBackupRefreshManager: BackupRefreshManager
|
||||
|
||||
init() {
|
||||
self.backupSettingsStore = BackupSettingsStore()
|
||||
self.db = InMemoryDB()
|
||||
self.accountKeyStore = AccountKeyStore(
|
||||
backupSettingsStore: backupSettingsStore,
|
||||
)
|
||||
self.mockNetworkManager = MockNetworkManager()
|
||||
self.mockBackupRefreshManager = BackupRefreshManager(
|
||||
accountKeyStore: accountKeyStore,
|
||||
backupRequestManager: BackupRequestManagerMock(),
|
||||
backupSettingsStore: backupSettingsStore,
|
||||
db: db,
|
||||
networkManager: mockNetworkManager,
|
||||
)
|
||||
}
|
||||
|
||||
var refreshSuccessResponse: (TSRequest, NetworkManager.RetryPolicy) async throws -> HTTPResponse = { request, _ in
|
||||
if request.url.absoluteString.hasSuffix("v1/archives") {
|
||||
return HTTPResponse(requestUrl: request.url, status: 204, headers: HttpHeaders(), bodyData: Data())
|
||||
}
|
||||
throw OWSAssertionError("")
|
||||
}
|
||||
|
||||
@Test
|
||||
func testRefresh() async throws {
|
||||
// Set up.
|
||||
db.write { tx in
|
||||
backupSettingsStore.setBackupPlan(.free, tx: tx)
|
||||
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
|
||||
}
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
|
||||
try await mockBackupRefreshManager.refreshBackup(localIdentifiers: LocalIdentifiers.forUnitTests)
|
||||
|
||||
#expect(mockNetworkManager.asyncRequestHandlers.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
func testDisabledBackupIgnoresRefresh() async throws {
|
||||
// Set up.
|
||||
db.write { tx in
|
||||
backupSettingsStore.setBackupPlan(.disabled, tx: tx)
|
||||
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
|
||||
}
|
||||
|
||||
try await mockBackupRefreshManager.refreshBackup(localIdentifiers: LocalIdentifiers.forUnitTests)
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public protocol BackupRequestManager {
|
||||
}
|
||||
|
||||
extension BackupRequestManager {
|
||||
func fetchBackupServiceAuth(
|
||||
public func fetchBackupServiceAuth(
|
||||
for key: BackupKeyMaterial,
|
||||
localAci: Aci,
|
||||
auth: ChatServiceAuth,
|
||||
|
||||
@@ -72,7 +72,6 @@ public struct BackupSettingsStore {
|
||||
static let shouldOptimizeLocalStorage = "shouldOptimizeLocalStorage"
|
||||
static let lastRecoveryKeyReminderDate = "lastBackupKeyReminderDate"
|
||||
static let haveSetBackupID = "haveSetBackupID"
|
||||
static let lastBackupRefreshDate = "lastBackupRefreshDate"
|
||||
static let lastBackupEnabledDetails = "lastBackupEnabledDetails"
|
||||
|
||||
static let backgroundBackupErrorCount = "backgroundBackupErrorCount"
|
||||
@@ -81,10 +80,12 @@ public struct BackupSettingsStore {
|
||||
|
||||
private let kvStore: KeyValueStore
|
||||
private let errorStateStore: KeyValueStore
|
||||
private let refreshBackupStore: CronStore
|
||||
|
||||
public init() {
|
||||
kvStore = KeyValueStore(collection: "BackupSettingsStore")
|
||||
errorStateStore = KeyValueStore(collection: "BackupSettingsErrorStateStore")
|
||||
refreshBackupStore = CronStore(uniqueKey: .refreshBackup)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
@@ -234,7 +235,7 @@ public struct BackupSettingsStore {
|
||||
setFirstBackupDate(date, tx: tx)
|
||||
}
|
||||
|
||||
setLastBackupRefreshDate(date, tx: tx)
|
||||
refreshBackupStore.setMostRecentDate(Date(), jitter: 0, tx: tx)
|
||||
|
||||
// We did a backup, so clear all error state.
|
||||
errorStateStore.removeAll(transaction: tx)
|
||||
@@ -248,7 +249,7 @@ public struct BackupSettingsStore {
|
||||
kvStore.removeValue(forKey: Keys.lastBackupDate, transaction: tx)
|
||||
kvStore.removeValue(forKey: Keys.lastBackupFileSizeBytes, transaction: tx)
|
||||
kvStore.removeValue(forKey: Keys.lastBackupSizeBytes, transaction: tx)
|
||||
setLastBackupRefreshDate(nil, tx: tx)
|
||||
refreshBackupStore.setMostRecentDate(.distantPast, jitter: 0, tx: tx)
|
||||
|
||||
tx.addSyncCompletion {
|
||||
NotificationCenter.default.postOnMainThread(name: .lastBackupDetailsDidChange, object: nil)
|
||||
@@ -419,20 +420,6 @@ public struct BackupSettingsStore {
|
||||
public func setHaveSetBackupID(haveSetBackupID: Bool, tx: DBWriteTransaction) {
|
||||
kvStore.setBool(haveSetBackupID, key: Keys.haveSetBackupID, transaction: tx)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public func lastBackupRefreshDate(tx: DBReadTransaction) -> Date? {
|
||||
return kvStore.getDate(Keys.lastBackupRefreshDate, transaction: tx)
|
||||
}
|
||||
|
||||
public func setLastBackupRefreshDate(_ lastBackupRefreshDate: Date?, tx: DBWriteTransaction) {
|
||||
if let lastBackupRefreshDate {
|
||||
kvStore.setDate(lastBackupRefreshDate, key: Keys.lastBackupRefreshDate, transaction: tx)
|
||||
} else {
|
||||
kvStore.removeValue(forKey: Keys.lastBackupRefreshDate, transaction: tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension BackupPlan {
|
||||
|
||||
@@ -1047,15 +1047,6 @@ extension AppSetup.GlobalsContinuation {
|
||||
storyRecipientStore: storyRecipientStore
|
||||
)
|
||||
|
||||
let backupRefreshManager = BackupRefreshManager(
|
||||
accountKeyStore: accountKeyStore,
|
||||
backupRequestManager: backupRequestManager,
|
||||
backupSettingsStore: backupSettingsStore,
|
||||
db: db,
|
||||
networkManager: networkManager,
|
||||
dateProvider: dateProvider
|
||||
)
|
||||
|
||||
let accountEntropyPoolManager = AccountEntropyPoolManagerImpl(
|
||||
accountAttributesUpdater: accountAttributesUpdater,
|
||||
accountKeyStore: accountKeyStore,
|
||||
@@ -1654,7 +1645,6 @@ extension AppSetup.GlobalsContinuation {
|
||||
backupKeyService: backupKeyService,
|
||||
backupListMediaManager: backupListMediaManager,
|
||||
backupListMediaStore: backupListMediaStore,
|
||||
backupRefreshManager: backupRefreshManager,
|
||||
backupRequestManager: backupRequestManager,
|
||||
backupPlanManager: backupPlanManager,
|
||||
backupSubscriptionManager: backupSubscriptionManager,
|
||||
|
||||
@@ -79,9 +79,8 @@ public class DependenciesBridge {
|
||||
public let backupKeyService: BackupKeyService
|
||||
public let backupListMediaManager: BackupListMediaManager
|
||||
public let backupListMediaStore: BackupListMediaStore
|
||||
public let backupRefreshManager: BackupRefreshManager
|
||||
public let backupRequestManager: BackupRequestManager
|
||||
public let backupPlanManager: BackupPlanManager
|
||||
public let backupRequestManager: BackupRequestManager
|
||||
public let backupSubscriptionManager: BackupSubscriptionManager
|
||||
public let backupTestFlightEntitlementManager: BackupTestFlightEntitlementManager
|
||||
public let badgeCountFetcher: BadgeCountFetcher
|
||||
@@ -218,7 +217,6 @@ public class DependenciesBridge {
|
||||
backupKeyService: BackupKeyService,
|
||||
backupListMediaManager: BackupListMediaManager,
|
||||
backupListMediaStore: BackupListMediaStore,
|
||||
backupRefreshManager: BackupRefreshManager,
|
||||
backupRequestManager: BackupRequestManager,
|
||||
backupPlanManager: BackupPlanManager,
|
||||
backupSubscriptionManager: BackupSubscriptionManager,
|
||||
@@ -356,7 +354,6 @@ public class DependenciesBridge {
|
||||
self.backupKeyService = backupKeyService
|
||||
self.backupListMediaManager = backupListMediaManager
|
||||
self.backupListMediaStore = backupListMediaStore
|
||||
self.backupRefreshManager = backupRefreshManager
|
||||
self.backupRequestManager = backupRequestManager
|
||||
self.backupPlanManager = backupPlanManager
|
||||
self.backupSubscriptionManager = backupSubscriptionManager
|
||||
|
||||
@@ -74,6 +74,7 @@ public class Cron {
|
||||
case fetchStaleGroup
|
||||
case fetchStaleProfiles
|
||||
case fetchSubscriptionConfig
|
||||
case refreshBackup
|
||||
case updateAttributes
|
||||
}
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
//
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Testing
|
||||
|
||||
@testable import SignalServiceKit
|
||||
|
||||
@MainActor
|
||||
struct BackupRefreshManagerTest {
|
||||
private let backupSettingsStore: BackupSettingsStore
|
||||
private let db: InMemoryDB
|
||||
private let accountKeyStore: AccountKeyStore
|
||||
private let mockNetworkManager: MockNetworkManager
|
||||
private let mockBackupRefreshManager: BackupRefreshManager
|
||||
|
||||
init() {
|
||||
self.backupSettingsStore = BackupSettingsStore()
|
||||
self.db = InMemoryDB()
|
||||
self.accountKeyStore = AccountKeyStore(
|
||||
backupSettingsStore: backupSettingsStore,
|
||||
)
|
||||
self.mockNetworkManager = MockNetworkManager()
|
||||
self.mockBackupRefreshManager = BackupRefreshManager(
|
||||
accountKeyStore: accountKeyStore,
|
||||
backupRequestManager: BackupRequestManagerMock(),
|
||||
backupSettingsStore: backupSettingsStore,
|
||||
db: db,
|
||||
networkManager: mockNetworkManager,
|
||||
dateProvider: { Date() }
|
||||
)
|
||||
}
|
||||
|
||||
var refreshSuccessResponse: (TSRequest, NetworkManager.RetryPolicy) async throws -> HTTPResponse = { request, _ in
|
||||
if request.url.absoluteString.hasSuffix("v1/archives") {
|
||||
return HTTPResponse(requestUrl: request.url, status: 204, headers: HttpHeaders(), bodyData: Data())
|
||||
}
|
||||
throw OWSAssertionError("")
|
||||
}
|
||||
|
||||
@Test
|
||||
func testMissingBackupTriggersRefresh() async throws {
|
||||
let testStart = Date()
|
||||
|
||||
// Set up.
|
||||
db.write { tx in
|
||||
backupSettingsStore.setBackupPlan(.free, tx: tx)
|
||||
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
|
||||
#expect(backupSettingsStore.lastBackupRefreshDate(tx: tx) == nil)
|
||||
}
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
|
||||
try await mockBackupRefreshManager.refreshBackupIfNeeded(localIdentifiers: LocalIdentifiers.forUnitTests, auth: .implicit())
|
||||
|
||||
db.read { tx in
|
||||
#expect(
|
||||
backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast > testStart,
|
||||
"Refresh date should update"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testOldBackupTriggersRefresh() async throws {
|
||||
let thirtyDaysAgo = Date(timeIntervalSinceNow: -5 * .day)
|
||||
let testStart = Date()
|
||||
|
||||
// Set up.
|
||||
db.write { tx in
|
||||
backupSettingsStore.setBackupPlan(.free, tx: tx)
|
||||
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
|
||||
backupSettingsStore.setLastBackupRefreshDate(thirtyDaysAgo, tx: tx)
|
||||
}
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
|
||||
try await mockBackupRefreshManager.refreshBackupIfNeeded(localIdentifiers: LocalIdentifiers.forUnitTests, auth: .implicit())
|
||||
|
||||
db.read { tx in
|
||||
#expect(
|
||||
backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast > testStart,
|
||||
"Refresh date should update"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testRecentBackupIgnoresRefresh() async throws {
|
||||
let oneDayAgo = Date(timeIntervalSinceNow: -1 * .day)
|
||||
|
||||
// Set up.
|
||||
db.write { tx in
|
||||
backupSettingsStore.setBackupPlan(.free, tx: tx)
|
||||
backupSettingsStore.setLastBackupRefreshDate(oneDayAgo, tx: tx)
|
||||
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
|
||||
}
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
|
||||
try await mockBackupRefreshManager.refreshBackupIfNeeded(localIdentifiers: LocalIdentifiers.forUnitTests, auth: .implicit())
|
||||
|
||||
db.read { tx in
|
||||
#expect(
|
||||
(backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast).timeIntervalSince1970 == oneDayAgo.timeIntervalSince1970,
|
||||
"Refresh date should not update since last backup was too recent"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testDisabledBackupIgnoresRefresh() async throws {
|
||||
let testStart = Date()
|
||||
|
||||
// Set up.
|
||||
db.write { tx in
|
||||
backupSettingsStore.setBackupPlan(.disabled, tx: tx)
|
||||
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
|
||||
#expect(backupSettingsStore.lastBackupRefreshDate(tx: tx) == nil)
|
||||
}
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
|
||||
|
||||
try await mockBackupRefreshManager.refreshBackupIfNeeded(localIdentifiers: LocalIdentifiers.forUnitTests, auth: .implicit())
|
||||
|
||||
db.read { tx in
|
||||
#expect(
|
||||
backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast < testStart,
|
||||
"Refresh date should not update since backups is disabled"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,18 +52,18 @@ class BackupSettingsStoreTests: XCTestCase {
|
||||
|
||||
func testBackupUpdatesRefreshDate() throws {
|
||||
var lastBackupRefresh = db.read { tx in
|
||||
backupSettingsStore.lastBackupRefreshDate(tx: tx)
|
||||
CronStore(uniqueKey: .refreshBackup).mostRecentDate(tx: tx)
|
||||
}
|
||||
XCTAssertNil(lastBackupRefresh, "Last backup should not be set")
|
||||
XCTAssertEqual(lastBackupRefresh, .distantPast, "Last backup should not be set")
|
||||
|
||||
db.write { tx in
|
||||
backupSettingsStore.setLastBackupDate(Date(), tx: tx)
|
||||
}
|
||||
|
||||
lastBackupRefresh = db.read { tx in
|
||||
backupSettingsStore.lastBackupRefreshDate(tx: tx)
|
||||
CronStore(uniqueKey: .refreshBackup).mostRecentDate(tx: tx)
|
||||
}
|
||||
XCTAssertNotNil(lastBackupRefresh, "Last backup should be set")
|
||||
XCTAssertNotEqual(lastBackupRefresh, .distantPast, "Last backup should be set")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user