diff --git a/SignalMessaging/Storage Service/StorageServiceManager.swift b/SignalMessaging/Storage Service/StorageServiceManager.swift index a86b03f7db..7e069f74d2 100644 --- a/SignalMessaging/Storage Service/StorageServiceManager.swift +++ b/SignalMessaging/Storage Service/StorageServiceManager.swift @@ -547,20 +547,18 @@ class StorageServiceOperation: OWSOperation { manifestVersion = StorageServiceOperation.manifestVersion(transaction: transaction) } - StorageService.fetchManifest().done(on: .global()) { manifest in - guard let manifest = manifest else { - // There is no existing manifest, lets create one with all our contacts. + StorageService.fetchLatestManifest(greaterThanVersion: manifestVersion).done(on: .global()) { response in + switch response { + case .noExistingManifest: + // There is no existing manifest, lets create one. return self.createNewManifest(version: 1) - } - - guard manifest.version != manifestVersion else { + case .noNewerManifest: // Our manifest version matches the server version, nothing to do here. return self.reportSuccess() + case .latestManifest(let manifest): + // Our manifest is not the latest, merge in the latest copy. + self.mergeLocalManifest(withRemoteManifest: manifest, backupAfterSuccess: false) } - - // Our manifest is not the latest, merge in the latest copy. - self.mergeLocalManifest(withRemoteManifest: manifest, backupAfterSuccess: false) - }.catch { error in if let storageError = error as? StorageService.StorageError { diff --git a/SignalServiceKit/src/Util/StorageService.swift b/SignalServiceKit/src/Util/StorageService.swift index 85cc35d1bc..d61d3accc9 100644 --- a/SignalServiceKit/src/Util/StorageService.swift +++ b/SignalServiceKit/src/Util/StorageService.swift @@ -119,13 +119,27 @@ public struct StorageService { } } - /// Fetch the latest manifest from the storage service + public enum FetchLatestManifestResponse { + case latestManifest(StorageServiceProtoManifestRecord) + case noNewerManifest + case noExistingManifest + } + + /// Fetch the latest manifest from the storage service. + /// If the greater than version is provided, only returns a manifest + /// if a newer one exists on the service, otherwise indicates + /// that there is no new content. /// /// Returns nil if a manifest has never been stored. - public static func fetchManifest() -> Promise { + public static func fetchLatestManifest(greaterThanVersion: UInt64? = nil) -> Promise { Logger.info("") - return storageRequest(withMethod: "GET", endpoint: "v1/storage/manifest").map(on: .global()) { response in + var endpoint = "v1/storage/manifest" + if let greaterThanVersion = greaterThanVersion { + endpoint += "/version/\(greaterThanVersion)" + } + + return storageRequest(withMethod: "GET", endpoint: endpoint).map(on: .global()) { response in switch response.status { case .success: let encryptedManifestContainer = try StorageServiceProtoStorageManifest.parseData(response.data) @@ -138,9 +152,11 @@ public struct StorageService { } catch { throw StorageError.manifestDecryptionFailed(version: encryptedManifestContainer.version) } - return try StorageServiceProtoManifestRecord.parseData(manifestData) + return .latestManifest(try StorageServiceProtoManifestRecord.parseData(manifestData)) case .notFound: - return nil + return .noExistingManifest + case .noContent: + return .noNewerManifest default: owsFailDebug("unexpected response \(response.status)") throw StorageError.retryableAssertion @@ -285,6 +301,7 @@ public struct StorageService { case success case conflict case notFound + case noContent } let status: Status let data: Data @@ -362,6 +379,8 @@ public struct StorageService { switch response.statusCode { case 200: status = .success + case 204: + status = .noContent case 409: status = .conflict case 404: @@ -414,42 +433,56 @@ public extension StorageService { var ourManifestVersion: UInt64 = 0 // Fetch Existing - fetchManifest().map { manifest in - let previousVersion = manifest?.version ?? ourManifestVersion - ourManifestVersion = previousVersion + 1 + fetchLatestManifest().map { response in + let previousVersion: UInt64 + var existingKeys: [Data]? + switch response { + case .latestManifest(let latestManifest): + previousVersion = latestManifest.version + existingKeys = latestManifest.keys + case .noNewerManifest, .noExistingManifest: + previousVersion = ourManifestVersion + } // set keys let newManifestBuilder = StorageServiceProtoManifestRecord.builder(version: ourManifestVersion) newManifestBuilder.setKeys(recordsInManifest.map { $0.identifier.data }) - return (try! newManifestBuilder.build(), manifest?.keys.map { StorageIdentifier(data: $0) } ?? []) + return (try! newManifestBuilder.build(), existingKeys?.map { StorageIdentifier(data: $0) } ?? []) // Update or create initial manifest with test data - }.then { manifest, deletedKeys in - updateManifest(manifest, newItems: recordsInManifest, deletedIdentifiers: deletedKeys) - }.map { manifest in - guard manifest == nil else { + }.then { latestManifest, deletedKeys in + updateManifest(latestManifest, newItems: recordsInManifest, deletedIdentifiers: deletedKeys) + }.map { latestManifest in + guard latestManifest == nil else { owsFailDebug("Manifest conflicted unexpectedly, should be nil") throw StorageError.assertion } // Fetch the manifest we just created - }.then { fetchManifest() }.map { manifest in - guard let manifest = manifest else { + }.then { fetchLatestManifest() }.map { response in + guard case .latestManifest(let latestManifest) = response else { owsFailDebug("manifest should exist, we just created it") throw StorageError.assertion } - guard Set(manifest.keys) == Set(identifiersInManfest.map { $0.data }) else { + guard Set(latestManifest.keys) == Set(identifiersInManfest.map { $0.data }) else { owsFailDebug("manifest should only contain our test keys") throw StorageError.assertion } - guard manifest.version == ourManifestVersion else { + guard latestManifest.version == ourManifestVersion else { owsFailDebug("manifest version should be the version we set") throw StorageError.assertion } + // Fetch the manifest we just created specifying the local version + }.then { fetchLatestManifest(greaterThanVersion: ourManifestVersion) }.map { response in + guard case .noNewerManifest = response else { + owsFailDebug("no new manifest should exist, we just created it") + throw StorageError.assertion + } + // Fetch the first contact we just stored }.then { fetchItem(for: identifiersInManfest.first!) @@ -500,27 +533,27 @@ public extension StorageService { ourManifestVersion += 1 let newManifestBuilder = StorageServiceProtoManifestRecord.builder(version: ourManifestVersion) return try! newManifestBuilder.build() - }.then { manifest in - updateManifest(manifest, newItems: [], deletedIdentifiers: identifiersInManfest) - }.map { manifest in - guard manifest == nil else { + }.then { latestManifest in + updateManifest(latestManifest, newItems: [], deletedIdentifiers: identifiersInManfest) + }.map { latestManifest in + guard latestManifest == nil else { owsFailDebug("Manifest conflicted unexpectedly, should be nil") throw StorageError.assertion } // Fetch the manifest we just stored - }.then { fetchManifest() }.map { manifest in - guard let manifest = manifest else { + }.then { fetchLatestManifest() }.map { latestManifest in + guard case .latestManifest(let latestManifest) = latestManifest else { owsFailDebug("manifest should exist, we just created it") throw StorageError.assertion } - guard manifest.keys.isEmpty else { + guard latestManifest.keys.isEmpty else { owsFailDebug("manifest should have no keys") throw StorageError.assertion } - guard manifest.version == ourManifestVersion else { + guard latestManifest.version == ourManifestVersion else { owsFailDebug("manifest version should be the version we set") throw StorageError.assertion } @@ -539,18 +572,18 @@ public extension StorageService { return (try! oldManifestBuilder.build(), try! StorageItem(identifier: identifier, contact: try! recordBuilder.build())) }.then { oldManifest, item in updateManifest(oldManifest, newItems: [item], deletedIdentifiers: []) - }.done { manifest in - guard let manifest = manifest else { + }.done { latestManifest in + guard let latestManifest = latestManifest else { owsFailDebug("manifest should exist, because there was a conflict") throw StorageError.assertion } - guard manifest.keys.isEmpty else { + guard latestManifest.keys.isEmpty else { owsFailDebug("manifest should still have no keys") throw StorageError.assertion } - guard manifest.version == ourManifestVersion else { + guard latestManifest.version == ourManifestVersion else { owsFailDebug("manifest version should be the version we set") throw StorageError.assertion }