Renamed FileServerAPIV2 to FileServerAPI

Updated the direct file upload/download to use the non-base64 approaches as well
Updated the attachment 'serverId' value to be a string instead of a UInt64 (future-proofing)
Updated the OnionRequest V4 response handling to avoid converting the "response body" part to a string and processing that, instead just slice the byte array (ie. stopped it from being broken from multiple conversions)
Removed the base64-based file upload/download endpoints (no use including them when they are inefficient and we don't want to use them)
This commit is contained in:
Morgan Pretty 2022-03-04 16:17:03 +11:00
parent 1c474955de
commit 81f563229f
28 changed files with 154 additions and 265 deletions

View File

@ -203,7 +203,7 @@
B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */; };
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; };
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B879D448247E1BE300DB3608 /* PathVC.swift */; };
B87EF17126367CF800124B3C /* FileServerAPIV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87EF17026367CF800124B3C /* FileServerAPIV2.swift */; };
B87EF17126367CF800124B3C /* FileServerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87EF17026367CF800124B3C /* FileServerAPI.swift */; };
B87EF18126377A1D00124B3C /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87EF18026377A1D00124B3C /* Features.swift */; };
B8856CA8256F0F42001CE70E /* OWSBackupFragment.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB07255A580700E217F9 /* OWSBackupFragment.m */; };
B8856CB1256F0F47001CE70E /* OWSBackupFragment.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAEA255A580500E217F9 /* OWSBackupFragment.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -827,12 +827,8 @@
FDC4384C27B47F7700C60D73 /* OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384B27B47F7700C60D73 /* OpenGroup.swift */; };
FDC4384F27B4804F00C60D73 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384E27B4804F00C60D73 /* Header.swift */; };
FDC4385127B4807400C60D73 /* QueryParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385027B4807400C60D73 /* QueryParam.swift */; };
FDC4385727B484B700C60D73 /* LegacyFileUploadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385627B484B700C60D73 /* LegacyFileUploadResponse.swift */; };
FDC4385927B484E800C60D73 /* FileUploadBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385827B484E800C60D73 /* FileUploadBody.swift */; };
FDC4385B27B485DE00C60D73 /* LegacyFileDownloadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385A27B485DE00C60D73 /* LegacyFileDownloadResponse.swift */; };
FDC4385D27B4C18900C60D73 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385C27B4C18900C60D73 /* Room.swift */; };
FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */; };
FDC4386127B4CDDF00C60D73 /* FileResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386027B4CDDF00C60D73 /* FileResponse.swift */; };
FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */; };
FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */; };
FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386627B4E10E00C60D73 /* Capabilities.swift */; };
@ -841,7 +837,6 @@
FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
FDC4387227B5BB3B00C60D73 /* FileUploadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */; };
FDC4387427B5BB9B00C60D73 /* Promise+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */; };
FDC4387627B5BEF300C60D73 /* FileDownloadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387527B5BEF300C60D73 /* FileDownloadResponse.swift */; };
FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */; };
FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; platformFilter = ios; };
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */; };
@ -1341,7 +1336,7 @@
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = "<group>"; };
B879D448247E1BE300DB3608 /* PathVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathVC.swift; sourceTree = "<group>"; };
B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = "<group>"; };
B87EF17026367CF800124B3C /* FileServerAPIV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerAPIV2.swift; sourceTree = "<group>"; };
B87EF17026367CF800124B3C /* FileServerAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerAPI.swift; sourceTree = "<group>"; };
B87EF18026377A1D00124B3C /* Features.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features.swift; sourceTree = "<group>"; };
B883F8C5D6D90C5077B2FD14 /* Pods_GlobalDependencies_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B8856D5F256F129B001CE70E /* OWSAlerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAlerts.swift; sourceTree = "<group>"; };
@ -1985,12 +1980,8 @@
FDC4384B27B47F7700C60D73 /* OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroup.swift; sourceTree = "<group>"; };
FDC4384E27B4804F00C60D73 /* Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = "<group>"; };
FDC4385027B4807400C60D73 /* QueryParam.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryParam.swift; sourceTree = "<group>"; };
FDC4385627B484B700C60D73 /* LegacyFileUploadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyFileUploadResponse.swift; sourceTree = "<group>"; };
FDC4385827B484E800C60D73 /* FileUploadBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadBody.swift; sourceTree = "<group>"; };
FDC4385A27B485DE00C60D73 /* LegacyFileDownloadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyFileDownloadResponse.swift; sourceTree = "<group>"; };
FDC4385C27B4C18900C60D73 /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = "<group>"; };
FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessage.swift; sourceTree = "<group>"; };
FDC4386027B4CDDF00C60D73 /* FileResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileResponse.swift; sourceTree = "<group>"; };
FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSMessage.swift; sourceTree = "<group>"; };
FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfo.swift; sourceTree = "<group>"; };
FDC4386627B4E10E00C60D73 /* Capabilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capabilities.swift; sourceTree = "<group>"; };
@ -1998,7 +1989,6 @@
FDC4386A27B4E88F00C60D73 /* BatchRequestInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfo.swift; sourceTree = "<group>"; };
FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponse.swift; sourceTree = "<group>"; };
FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Utilities.swift"; sourceTree = "<group>"; };
FDC4387527B5BEF300C60D73 /* FileDownloadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloadResponse.swift; sourceTree = "<group>"; };
FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = "<group>"; };
FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionMessagingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPISpec.swift; sourceTree = "<group>"; };
@ -3423,7 +3413,7 @@
children = (
FDC4383227B385B200C60D73 /* Models */,
FD83B9CA27D179AF005E1583 /* Types */,
B87EF17026367CF800124B3C /* FileServerAPIV2.swift */,
B87EF17026367CF800124B3C /* FileServerAPI.swift */,
);
path = "File Server";
sourceTree = "<group>";
@ -3947,7 +3937,6 @@
FDC4385C27B4C18900C60D73 /* Room.swift */,
FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */,
FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */,
FDC4386027B4CDDF00C60D73 /* FileResponse.swift */,
FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */,
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */,
FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */,
@ -4006,11 +3995,7 @@
FDC4385527B484AE00C60D73 /* Models */ = {
isa = PBXGroup;
children = (
FDC4385827B484E800C60D73 /* FileUploadBody.swift */,
FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */,
FDC4387527B5BEF300C60D73 /* FileDownloadResponse.swift */,
FDC4385627B484B700C60D73 /* LegacyFileUploadResponse.swift */,
FDC4385A27B485DE00C60D73 /* LegacyFileDownloadResponse.swift */,
);
path = Models;
sourceTree = "<group>";
@ -5333,7 +5318,6 @@
C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */,
C32C5DC0256DD743003C73A2 /* Poller.swift in Sources */,
C3C2A7682553A3D900C340D1 /* VisibleMessage+Contact.swift in Sources */,
FDC4386127B4CDDF00C60D73 /* FileResponse.swift in Sources */,
C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */,
C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */,
B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */,
@ -5354,7 +5338,6 @@
C300A5BD2554B00D00555489 /* ReadReceipt.swift in Sources */,
FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */,
C32C5AB5256DBE8F003C73A2 /* TSOutgoingMessage+Conversion.swift in Sources */,
FDC4385927B484E800C60D73 /* FileUploadBody.swift in Sources */,
C32C5E5B256DDF45003C73A2 /* OWSStorage.m in Sources */,
C32C5E15256DDC78003C73A2 /* SSKPreferences.swift in Sources */,
FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */,
@ -5371,7 +5354,6 @@
C32C5EDC256DF501003C73A2 /* YapDatabaseConnection+OWS.m in Sources */,
FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */,
C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */,
FDC4387627B5BEF300C60D73 /* FileDownloadResponse.swift in Sources */,
C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */,
FDC4382C27B380E300C60D73 /* LegacyMemberCountResponse.swift in Sources */,
B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */,
@ -5427,7 +5409,7 @@
C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */,
B8856CA8256F0F42001CE70E /* OWSBackupFragment.m in Sources */,
C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */,
B87EF17126367CF800124B3C /* FileServerAPIV2.swift in Sources */,
B87EF17126367CF800124B3C /* FileServerAPI.swift in Sources */,
C3A3A18A256E2092004D228D /* SignalRecipient.m in Sources */,
C3C2A74425539EB700C340D1 /* Message.swift in Sources */,
C32C5F11256DF79A003C73A2 /* SSKIncrementingIdFinder.swift in Sources */,
@ -5452,8 +5434,6 @@
C32C5B62256DC333003C73A2 /* OWSDisappearingConfigurationUpdateInfoMessage.m in Sources */,
FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */,
C352A2F525574B4700338F3E /* Job.swift in Sources */,
FDC4385727B484B700C60D73 /* LegacyFileUploadResponse.swift in Sources */,
FDC4385B27B485DE00C60D73 /* LegacyFileDownloadResponse.swift in Sources */,
C32C5C01256DC9A0003C73A2 /* OWSIdentityManager.m in Sources */,
FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */,
C32C59C4256DB41F003C73A2 /* TSContactThread.m in Sources */,

View File

@ -1,50 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
// TODO: Update this (looks like it's getting changed to just be the data, the properties are send through as headers)
public struct FileDownloadResponse: Codable {
enum CodingKeys: String, CodingKey {
case fileName = "filename"
case size
case uploaded
case expires
case base64EncodedData = "result" // TODO: Confirm the name of this value
}
public let fileName: String
public let size: Int64
public let uploaded: TimeInterval
public let expires: TimeInterval?
public let data: Data
public func encode(to encoder: Encoder) throws {
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
try container.encode(fileName, forKey: .fileName)
try container.encode(size, forKey: .size)
try container.encode(uploaded, forKey: .uploaded)
try container.encodeIfPresent(expires, forKey: .expires)
try container.encode(data.base64EncodedString(), forKey: .base64EncodedData)
}
}
// MARK: - Decoder
extension FileDownloadResponse {
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
let base64EncodedData: String = try container.decode(String.self, forKey: .base64EncodedData)
guard let data = Data(base64Encoded: base64EncodedData) else { throw HTTP.Error.parsingFailed }
self = FileDownloadResponse(
fileName: try container.decode(String.self, forKey: .fileName),
size: try container.decode(Int64.self, forKey: .size),
uploaded: try container.decode(TimeInterval.self, forKey: .uploaded),
expires: try? container.decode(TimeInterval.self, forKey: .expires),
data: data
)
}
}

View File

@ -1,7 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
struct FileUploadBody: Codable {
let file: String
}

View File

@ -1,5 +1,25 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
public struct FileUploadResponse: Codable {
public let id: UInt64
public let id: String
}
// MARK: - Codable
extension FileUploadResponse {
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
// Note: SOGS returns an 'int' value but we want to avoid handling both cases so parse
// that and convert the value to a string so we can be consistent (SOGS is able to handle
// an array of Strings for the `files` param when posting a message just fine)
if let intValue: Int64 = try? container.decode(Int64.self, forKey: .id) {
self = FileUploadResponse(id: "\(intValue)")
return
}
self = FileUploadResponse(
id: try container.decode(String.self, forKey: .id)
)
}
}

View File

@ -1,33 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
struct LegacyFileDownloadResponse: Codable {
enum CodingKeys: String, CodingKey {
case base64EncodedData = "result"
}
let data: Data
public func encode(to encoder: Encoder) throws {
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
try container.encode(data.base64EncodedString(), forKey: .base64EncodedData)
}
}
// MARK: - Decoder
extension LegacyFileDownloadResponse {
init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
let base64EncodedData: String = try container.decode(String.self, forKey: .base64EncodedData)
guard let data = Data(base64Encoded: base64EncodedData) else { throw HTTP.Error.parsingFailed }
self = LegacyFileDownloadResponse(
data: data
)
}
}

View File

@ -1,11 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
struct LegacyFileUploadResponse: Codable {
enum CodingKeys: String, CodingKey {
case fileId = "result"
}
public let fileId: UInt64
}

View File

@ -1,8 +1,8 @@
import PromiseKit
import SessionSnodeKit
@objc(SNFileServerAPIV2)
public final class FileServerAPIV2 : NSObject {
@objc(SNFileServerAPI)
public final class FileServerAPI: NSObject {
// MARK: - Settings
@ -19,30 +19,27 @@ public final class FileServerAPIV2 : NSObject {
/// possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds.
public static let fileSizeORMultiplier: Double = 2
// MARK: - Initialization
private override init() { }
// MARK: - File Storage
@objc(upload:)
public static func objc_upload(file: Data) -> AnyPromise {
return AnyPromise.from(upload(file).map { String($0) })
return AnyPromise.from(upload(file).map { String($0.id) })
}
public static func upload(_ file: Data) -> Promise<UInt64> {
let requestBody: FileUploadBody = FileUploadBody(file: file.base64EncodedString())
public static func upload(_ file: Data) -> Promise<FileUploadResponse> {
let request = Request(
method: .post,
server: server,
endpoint: Endpoint.files,
body: requestBody
endpoint: Endpoint.file,
headers: [
.contentDisposition: "attachment",
.contentType: "application/octet-stream"
],
body: Array(file)
)
return send(request, serverPublicKey: serverPublicKey)
.decoded(as: LegacyFileUploadResponse.self, on: .global(qos: .userInitiated), error: HTTP.Error.parsingFailed)
.map { response in response.fileId }
.decoded(as: FileUploadResponse.self, on: .global(qos: .userInitiated))
}
@objc(download:useOldServer:)
@ -55,12 +52,10 @@ public final class FileServerAPIV2 : NSObject {
let serverPublicKey: String = (useOldServer ? oldServerPublicKey : serverPublicKey)
let request = Request<NoBody, Endpoint>(
server: (useOldServer ? oldServer : server),
endpoint: .file(fileId: file)
endpoint: .fileIndividual(fileId: file)
)
return send(request, serverPublicKey: serverPublicKey)
.decoded(as: LegacyFileDownloadResponse.self, on: .global(qos: .userInitiated), error: HTTP.Error.parsingFailed)
.map { response in response.data }
}
public static func getVersion(_ platform: String) -> Promise<String> {
@ -73,7 +68,7 @@ public final class FileServerAPIV2 : NSObject {
)
return send(request, serverPublicKey: serverPublicKey)
.decoded(as: VersionResponse.self, on: .global(qos: .userInitiated), error: HTTP.Error.parsingFailed)
.decoded(as: VersionResponse.self, on: .global(qos: .userInitiated))
.map { response in response.version }
}
@ -93,7 +88,6 @@ public final class FileServerAPIV2 : NSObject {
return Promise(error: error)
}
// TODO: Rename file to be 'FileServerAPI' (drop the 'V2')
return OnionRequestAPI.sendOnionRequest(urlRequest, to: request.server, with: serverPublicKey)
.map2 { _, response in
guard let response: Data = response else { throw HTTP.Error.parsingFailed }

View File

@ -2,7 +2,7 @@
import Foundation
extension FileServerAPIV2 {
extension FileServerAPI {
struct VersionResponse: Codable {
enum CodingKeys: String, CodingKey {
case version = "version"

View File

@ -2,16 +2,16 @@
import Foundation
extension FileServerAPIV2 {
extension FileServerAPI {
public enum Endpoint: EndpointType {
case files
case file(fileId: UInt64)
case file
case fileIndividual(fileId: UInt64)
case sessionVersion
var path: String {
switch self {
case .files: return "files"
case .file(let fileId): return "files/\(fileId)"
case .file: return "file"
case .fileIndividual(let fileId): return "file/\(fileId)"
case .sessionVersion: return "session_version"
}
}

View File

@ -63,60 +63,78 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
if let id = id {
JobQueue.currentlyExecutingJobs.insert(id)
}
guard !isDeferred else { return }
if TSAttachment.fetch(uniqueId: attachmentID) is TSAttachmentStream {
// FIXME: It's not clear * how * this happens, but apparently we can get to this point
// from time to time with an already downloaded attachment.
return handleSuccess()
}
guard let pointer = TSAttachment.fetch(uniqueId: attachmentID) as? TSAttachmentPointer else {
return handleFailure(error: Error.noAttachment)
}
let storage = SNMessagingKitConfiguration.shared.storage
storage.write(with: { transaction in
storage.setAttachmentState(to: .downloading, for: pointer, associatedWith: self.tsMessageID, using: transaction)
}, completion: { })
let temporaryFilePath = URL(fileURLWithPath: OWSTemporaryDirectoryAccessibleAfterFirstAuth() + UUID().uuidString)
let handleFailure: (Swift.Error) -> Void = { error in // Intentionally capture self
OWSFileSystem.deleteFile(temporaryFilePath.absoluteString)
if let error = error as? Error, case .noAttachment = error {
storage.write(with: { transaction in
storage.write { transaction in
storage.setAttachmentState(to: .failed, for: pointer, associatedWith: self.tsMessageID, using: transaction)
}, completion: { })
}
self.handlePermanentFailure(error: error)
} else if let error = error as? OnionRequestAPI.Error, case .httpRequestFailedAtDestination(let statusCode, _, _) = error,
statusCode == 400 {
}
else if let error = error as? OnionRequestAPI.Error, case .httpRequestFailedAtDestination(let statusCode, _, _) = error, statusCode == 400 {
// Otherwise, the attachment will show a state of downloading forever,
// and the message won't be able to be marked as read.
storage.write(with: { transaction in
storage.write { transaction in
storage.setAttachmentState(to: .failed, for: pointer, associatedWith: self.tsMessageID, using: transaction)
}, completion: { })
}
// This usually indicates a file that has expired on the server, so there's no need to retry.
self.handlePermanentFailure(error: error)
} else {
}
else {
self.handleFailure(error: error)
}
}
if let tsMessage = TSMessage.fetch(uniqueId: tsMessageID), let openGroup = storage.getOpenGroup(for: tsMessage.uniqueThreadId) {
guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let fileId = UInt64(fileAsString) else {
return handleFailure(Error.invalidURL)
}
// TODO: Upgrade this to use the non-legacy version
OpenGroupAPI.legacyDownload(file, from: openGroup.room, on: openGroup.server).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
self.handleDownloadedAttachment(data: data, temporaryFilePath: temporaryFilePath, pointer: pointer, failureHandler: handleFailure)
}.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
} else {
OpenGroupAPI
.downloadFile(fileId, from: openGroup.room, on: openGroup.server)
.done(on: DispatchQueue.global(qos: .userInitiated)) { [weak self] _, data in
self?.handleDownloadedAttachment(data: data, temporaryFilePath: temporaryFilePath, pointer: pointer, failureHandler: handleFailure)
}
.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
}
else {
guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let file = UInt64(fileAsString) else {
return handleFailure(Error.invalidURL)
}
let useOldServer = pointer.downloadURL.contains(FileServerAPIV2.oldServer)
FileServerAPIV2.download(file, useOldServer: useOldServer).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
self.handleDownloadedAttachment(data: data, temporaryFilePath: temporaryFilePath, pointer: pointer, failureHandler: handleFailure)
}.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
let useOldServer = pointer.downloadURL.contains(FileServerAPI.oldServer)
FileServerAPI
.download(file, useOldServer: useOldServer)
.done(on: DispatchQueue.global(qos: .userInitiated)) { [weak self] data in
self?.handleDownloadedAttachment(data: data, temporaryFilePath: temporaryFilePath, pointer: pointer, failureHandler: handleFailure)
}
.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
}
}

View File

@ -74,7 +74,7 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
stream,
using: { data in
OpenGroupAPI.uploadFile(data.bytes, to: openGroup.room, on: openGroup.server)
.map { _, response -> UInt64 in response.id }
.map { _, response -> String in response.id }
},
encrypt: false,
onSuccess: { [weak self] fileId in self?.handleSuccess(fileId) },
@ -84,7 +84,10 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
else {
AttachmentUploadJob.upload(
stream,
using: FileServerAPIV2.upload,
using: { data in
FileServerAPI.upload(data)
.map { response -> String in response.id }
},
encrypt: true,
onSuccess: { [weak self] fileId in self?.handleSuccess(fileId) },
onFailure: handleFailure
@ -92,7 +95,7 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
}
}
public static func upload(_ stream: TSAttachmentStream, using upload: (Data) -> Promise<UInt64>, encrypt: Bool, onSuccess: ((UInt64) -> Void)?, onFailure: ((Swift.Error) -> Void)?) {
public static func upload(_ stream: TSAttachmentStream, using upload: (Data) -> Promise<String>, encrypt: Bool, onSuccess: ((String) -> Void)?, onFailure: ((Swift.Error) -> Void)?) {
// Get the attachment
guard var data = try? stream.readDataFromFile() else {
SNLog("Couldn't read attachment from disk.")
@ -112,7 +115,7 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
}
// Check the file size
SNLog("File size: \(data.count) bytes.")
if Double(data.count) > Double(FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier {
if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier {
onFailure?(HTTP.Error.maxFileSizeExceeded)
return
}
@ -121,7 +124,7 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
stream.isUploaded = false
stream.save()
upload(data).done(on: DispatchQueue.global(qos: .userInitiated)) { fileId in
let downloadURL = "\(FileServerAPIV2.server)/files/\(fileId)"
let downloadURL = "\(FileServerAPI.server)/files/\(fileId)"
stream.serverId = fileId
stream.isUploaded = true
stream.downloadURL = downloadURL
@ -132,7 +135,7 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
}
}
private func handleSuccess(_ fileId: UInt64) {
private func handleSuccess(_ fileId: String) {
SNLog("Attachment uploaded successfully.")
delegate?.handleJobSucceeded(self)

View File

@ -100,7 +100,7 @@ public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCodi
.replacingOccurrences(of: "]", with: "")
.split(separator: "|")
.map { String($0) }
let fileIds: [UInt64]? = (fileIdStrings.isEmpty ? nil : fileIdStrings.compactMap { UInt64($0) })
let fileIds: [String]? = (fileIdStrings.isEmpty ? nil : fileIdStrings)
destination = .openGroup(
roomToken: roomToken,

View File

@ -10,11 +10,11 @@ public extension Message {
server: String,
whisperTo: String? = nil,
whisperMods: Bool = false,
fileIds: [UInt64]? = nil
fileIds: [String]? = nil
)
case openGroupInbox(server: String, openGroupPublicKey: String, blindedPublicKey: String)
static func from(_ thread: TSThread, fileIds: [UInt64]? = nil) -> Message.Destination {
static func from(_ thread: TSThread, fileIds: [String]? = nil) -> Message.Destination {
if let thread = thread as? TSContactThread {
if SessionId.Prefix(from: thread.contactSessionID()) == .blinded {
guard let server: String = thread.originalOpenGroupServer, let publicKey: String = thread.originalOpenGroupPublicKey else {

View File

@ -1,19 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
extension OpenGroupAPI {
public struct FileResponse: Codable {
enum CodingKeys: String, CodingKey {
case fileName = "filename"
case size
case uploaded
case expires
}
let fileName: String?
let size: Int64
let uploaded: TimeInterval
let expires: TimeInterval?
}
}

View File

@ -40,7 +40,12 @@ extension OpenGroupAPI {
///
/// When submitting a message edit this field must contain the IDs of any newly uploaded files that are part of the edit; existing
/// attachment IDs may also be included, but are not required
let fileIds: [UInt64]?
///
/// **Note:** The SOGS API actually expects an array of Int64 (ie. what is returned when uploading a file to SOGS) but
/// when uploading direct to the FileServer we get a string id back. In order to avoid supporting both cases we convert
/// the id returned by SOGS to a string and send those through - luckily SOGS converts the values to ints so supports
/// receipving an array of String values
let fileIds: [String]?
// MARK: - Initialization
@ -49,7 +54,7 @@ extension OpenGroupAPI {
signature: Data,
whisperTo: String? = nil,
whisperMods: Bool? = nil,
fileIds: [UInt64]? = nil
fileIds: [String]? = nil
) {
self.data = data
self.signature = signature

View File

@ -331,7 +331,7 @@ public final class OpenGroupAPI: NSObject {
on server: String,
whisperTo: String?,
whisperMods: Bool,
fileIds: [UInt64]?,
fileIds: [String]?,
using dependencies: Dependencies = Dependencies()
) -> Promise<(OnionRequestResponseInfoType, Message)> {
guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, using: dependencies) else {
@ -558,16 +558,6 @@ public final class OpenGroupAPI: NSObject {
}
}
public static func downloadFileJson(_ fileId: UInt64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileDownloadResponse)> {
let request: Request = Request<NoBody, Endpoint>(
server: server,
endpoint: .roomFileIndividualJson(roomToken, fileId)
)
// TODO: This endpoint is getting rewritten to return just data (properties would come through as headers).
return send(request, using: dependencies)
.decoded(as: FileDownloadResponse.self, on: OpenGroupAPI.workQueue, using: dependencies)
}
// MARK: - Inbox/Outbox (Message Requests)
/// Retrieves all of the user's current DMs (up to limit)

View File

@ -35,7 +35,6 @@ extension OpenGroupAPI {
case roomFile(String)
case roomFileIndividual(String, UInt64)
case roomFileIndividualJson(String, UInt64)
// Inbox/Outbox (Message Requests)
@ -125,14 +124,7 @@ extension OpenGroupAPI {
// Files
case .roomFile(let roomToken): return "room/\(roomToken)/file"
case .roomFileIndividual(let roomToken, let fileId):
// Note: The 'fileName' value is ignored by the server and is only used to distinguish
// this from the 'Json' variant
let fileName: String = ""
return "room/\(roomToken)/file/\(fileId)/\(fileName)"
case .roomFileIndividualJson(let roomToken, let fileId):
return "room/\(roomToken)/file/\(fileId)"
case .roomFileIndividual(let roomToken, let fileId): return "room/\(roomToken)/file/\(fileId)"
// Inbox/Outbox (Message Requests)

View File

@ -23,7 +23,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
// The attachmentSchemaVersion and serverId properties only apply to
// TSAttachmentPointer, which can be distinguished by the isDownloaded
// property.
@property (atomic, readwrite) UInt64 serverId;
@property (atomic, readwrite) NSString *serverId;
@property (atomic, readwrite, nullable) NSData *encryptionKey;
@property (nonatomic, readonly) NSString *contentType;
@property (atomic, readwrite) BOOL isDownloaded;

View File

@ -27,7 +27,7 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
// This constructor is used for new instances of TSAttachmentPointer,
// i.e. undownloaded incoming attachments.
- (instancetype)initWithServerId:(UInt64)serverId
- (instancetype)initWithServerId:(NSString *)serverId
encryptionKey:(nullable NSData *)encryptionKey
byteCount:(UInt32)byteCount
contentType:(NSString *)contentType

View File

@ -169,10 +169,8 @@ NS_ASSUME_NONNULL_BEGIN
// Legacy instances of TSAttachmentPointer apparently used the serverId as their
// uniqueId.
if (attachmentSchemaVersion < 2 && self.serverId == 0) {
if ([self isDecimalNumberText:self.uniqueId]) {
// For legacy instances, try to parse the serverId from the uniqueId.
self.serverId = (UInt64)[self.uniqueId integerValue];
}
// For legacy instances, try to parse the serverId from the uniqueId.
self.serverId = self.uniqueId;
}
}

View File

@ -9,15 +9,15 @@ extension OpenGroupAPI.Dependencies {
}
public extension Data {
func decoded<T: Decodable>(as type: T.Type, customError: Error? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> T {
func decoded<T: Decodable>(as type: T.Type, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> T {
do {
let decoder: JSONDecoder = JSONDecoder()
decoder.userInfo = [ OpenGroupAPI.Dependencies.userInfoKey: dependencies ]
return try decoder.decode(type, from: self)
}
catch let error {
throw (customError ?? error)
catch {
throw HTTP.Error.parsingFailed
}
}
}

View File

@ -5,9 +5,9 @@ import PromiseKit
import SessionSnodeKit
extension Promise where T == Data {
func decoded<R: Decodable>(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<R> {
func decoded<R: Decodable>(as type: R.Type, on queue: DispatchQueue? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<R> {
self.map(on: queue) { data -> R in
try data.decoded(as: type, customError: error, using: dependencies)
try data.decoded(as: type, using: dependencies)
}
}
}

View File

@ -148,6 +148,7 @@ class OpenGroupAPISpec: QuickSpec {
testStorage = nil
response = nil
pollResponse = nil
error = nil
}
// MARK: - Batching & Polling
@ -424,10 +425,10 @@ class OpenGroupAPISpec: QuickSpec {
// MARK: - Files
context("when uploading files") {
it("doesn't add a fileName header when not provided") {
it("doesn't add a fileName to the content-disposition header when not provided") {
class LocalTestApi: TestApi {
override class var mockResponse: Data? {
return try! JSONEncoder().encode(FileUploadResponse(id: 1))
return try! JSONEncoder().encode(FileUploadResponse(id: "1"))
}
}
dependencies = dependencies.with(api: LocalTestApi.self)
@ -448,15 +449,15 @@ class OpenGroupAPISpec: QuickSpec {
let requestData: TestApi.RequestData? = (response?.0 as? TestResponseInfo)?.requestData
expect(requestData?.urlString).to(equal("testServer/room/testRoom/file"))
expect(requestData?.httpMethod).to(equal("POST"))
expect(requestData?.headers).to(haveCount(4))
expect(requestData?.headers).to(haveCount(6))
expect(requestData?.headers[Header.contentDisposition.rawValue])
.toNot(contain("filename"))
}
it("adds a fileName header when provided") {
it("adds the fileName to the content-disposition header when provided") {
class LocalTestApi: TestApi {
override class var mockResponse: Data? {
return try! JSONEncoder().encode(FileUploadResponse(id: 1))
return try! JSONEncoder().encode(FileUploadResponse(id: "1"))
}
}
dependencies = dependencies.with(api: LocalTestApi.self)
@ -477,7 +478,7 @@ class OpenGroupAPISpec: QuickSpec {
let requestData: TestApi.RequestData? = (response?.0 as? TestResponseInfo)?.requestData
expect(requestData?.urlString).to(equal("testServer/room/testRoom/file"))
expect(requestData?.httpMethod).to(equal("POST"))
expect(requestData?.headers).to(haveCount(5))
expect(requestData?.headers).to(haveCount(6))
expect(requestData?.headers[Header.contentDisposition.rawValue]).to(contain("TestFileName"))
}
}

View File

@ -80,6 +80,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
func getAllPendingJobs(of type: Job.Type) -> [Job] { return [] }
func getAttachmentUploadJob(for attachmentID: String) -> AttachmentUploadJob? { return nil }
func getMessageSendJob(for messageSendJobID: String) -> MessageSendJob? { return nil }
func getMessageSendJob(for messageSendJobID: String, using transaction: Any) -> MessageSendJob? { return nil }
func resumeMessageSendJobIfNeeded(_ messageSendJobID: String) {}
func isJobCanceled(_ job: Job) -> Bool { return true }

View File

@ -644,17 +644,15 @@ public enum OnionRequestAPI: OnionRequestAPIType {
let dataString: String = String(responseString.suffix(from: infoStringEndIndex))
let dataStringParts: [String.SubSequence] = dataString.split(separator: ":")
guard dataStringParts.count > 1, let finalDataLength: Int = Int(dataStringParts[0]) else {
guard dataStringParts.count > 1, let finalDataLength: Int = Int(dataStringParts[0]), let suffixData: Data = "e".data(using: .utf8) else {
return seal.reject(HTTP.Error.invalidResponse)
}
let finalDataStringStartIndex: String.Index = responseString.index(infoStringEndIndex, offsetBy: "\(finalDataLength):".count)
let finalDataStringEndIndex: String.Index = responseString.index(finalDataStringStartIndex, offsetBy: finalDataLength)
let finalDataString: String = String(responseString[finalDataStringStartIndex..<finalDataStringEndIndex])
guard let finalData: Data = finalDataString.data(using: .ascii) else {
return seal.reject(HTTP.Error.invalidResponse)
}
let dataBytes: Array<UInt8> = Array(data)
let dataEndIndex: Int = (dataBytes.count - suffixData.count)
let dataStartIndex: Int = (dataEndIndex - finalDataLength)
let finalDataBytes: ArraySlice<UInt8> = dataBytes[dataStartIndex..<dataEndIndex]
let finalData: Data = Data(finalDataBytes)
return seal.fulfill((responseInfo, finalData))
}

View File

@ -9,6 +9,9 @@ public final class Configuration : NSObject {
@objc public static func performMainSetup() {
SNMessagingKit.configure(storage: Storage.shared)
SNSnodeKit.configure(storage: Storage.shared)
SNUtilitiesKit.configure(owsPrimaryStorage: OWSPrimaryStorage.shared(), maxFileSize: UInt(Double(FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier))
SNUtilitiesKit.configure(
owsPrimaryStorage: OWSPrimaryStorage.shared(),
maxFileSize: UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier)
)
}
}

View File

@ -43,11 +43,11 @@ extension MessageSender {
TSAttachment.fetch(uniqueId: $0, transaction: transaction) as? TSAttachmentStream
}
let attachmentsToUpload = attachments.filter { !$0.isUploaded }
let attachmentUploadPromises: [Promise<UInt64>] = attachmentsToUpload.map { stream in
let attachmentUploadPromises: [Promise<String>] = attachmentsToUpload.map { stream in
let storage = SNMessagingKitConfiguration.shared.storage
if let threadId: String = thread.uniqueId, let openGroup = storage.getOpenGroup(for: threadId) {
let (promise, seal) = Promise<UInt64>.pending()
let (promise, seal) = Promise<String>.pending()
AttachmentUploadJob.upload(
stream,
using: { data in
@ -57,7 +57,7 @@ extension MessageSender {
to: openGroup.room,
on: openGroup.server
)
.map { _, response -> UInt64 in response.id }
.map { _, response -> String in response.id }
},
encrypt: false,
onSuccess: { fileId in seal.fulfill(fileId) },
@ -67,10 +67,13 @@ extension MessageSender {
return promise
}
let (promise, seal) = Promise<UInt64>.pending()
let (promise, seal) = Promise<String>.pending()
AttachmentUploadJob.upload(
stream,
using: FileServerAPIV2.upload,
using: { data in
FileServerAPI.upload(data)
.map { response -> String in response.id }
},
encrypt: true,
onSuccess: { fileId in seal.fulfill(fileId) },
onFailure: { seal.reject($0) }
@ -84,7 +87,7 @@ extension MessageSender {
if case .rejected(let error) = result { return error } else { return nil }
}
if let error = errors.first { return Promise(error: error) }
let fileIds: [UInt64] = results.compactMap { result -> UInt64? in
let fileIds: [String] = results.compactMap { result -> String? in
switch result {
case .fulfilled(let fileId): return fileId
default: return nil
@ -95,7 +98,7 @@ extension MessageSender {
}
}
public static func sendNonDurably(_ message: Message, in thread: TSThread, with fileIds: [UInt64]? = nil, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
public static func sendNonDurably(_ message: Message, in thread: TSThread, with fileIds: [String]? = nil, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
message.threadID = thread.uniqueId!
let destination = Message.Destination.from(thread, fileIds: fileIds)
return MessageSender.send(message, to: destination, using: transaction)
@ -107,11 +110,11 @@ extension MessageSender {
}
let attachments = message.attachmentIDs.compactMap { TSAttachment.fetch(uniqueId: $0) as? TSAttachmentStream }
let attachmentsToUpload = attachments.filter { !$0.isUploaded }
let attachmentUploadPromises: [Promise<UInt64>] = attachmentsToUpload.map { stream in
let attachmentUploadPromises: [Promise<String>] = attachmentsToUpload.map { stream in
let storage = SNMessagingKitConfiguration.shared.storage
if let openGroup = storage.getOpenGroup(for: thread.uniqueId!) {
let (promise, seal) = Promise<UInt64>.pending()
let (promise, seal) = Promise<String>.pending()
AttachmentUploadJob.upload(
stream,
@ -122,7 +125,7 @@ extension MessageSender {
to: openGroup.room,
on: openGroup.server
)
.map { _, response in response.id }
.map { _, response -> String in response.id }
},
encrypt: false,
onSuccess: { fileId in seal.fulfill(fileId) },
@ -131,10 +134,13 @@ extension MessageSender {
return promise
}
let (promise, seal) = Promise<UInt64>.pending()
let (promise, seal) = Promise<String>.pending()
AttachmentUploadJob.upload(
stream,
using: FileServerAPIV2.upload,
using: { data in
FileServerAPI.upload(data)
.map { response -> String in response.id }
},
encrypt: true,
onSuccess: { fileId in seal.fulfill(fileId) },
onFailure: { seal.reject($0) }
@ -148,7 +154,7 @@ extension MessageSender {
if case .rejected(let error) = result { return error } else { return nil }
}
if let error = errors.first { seal.reject(error) }
let fileIds: [UInt64] = results.compactMap { result -> UInt64? in
let fileIds: [String] = results.compactMap { result -> String? in
switch result {
case .fulfilled(let fileId): return fileId
default: return nil

View File

@ -268,10 +268,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
NSData *encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey];
OWSAssertDebug(encryptedAvatarData.length > 0);
AnyPromise *promise = [SNFileServerAPIV2 upload:encryptedAvatarData];
AnyPromise *promise = [SNFileServerAPI upload:encryptedAvatarData];
[promise.thenOn(dispatch_get_main_queue(), ^(NSString *fileID) {
NSString *downloadURL = [NSString stringWithFormat:@"%@/files/%@", SNFileServerAPIV2.server, fileID];
NSString *downloadURL = [NSString stringWithFormat:@"%@/files/%@", SNFileServerAPI.server, fileID];
[NSUserDefaults.standardUserDefaults setObject:[NSDate new] forKey:@"lastProfilePictureUpload"];
SNContact *user = [LKStorage.shared getUser];
@ -502,8 +502,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
NSString *profilePictureURL = contact.profilePictureURL;
NSString *file = [profilePictureURL lastPathComponent];
BOOL useOldServer = [profilePictureURL containsString:SNFileServerAPIV2.oldServer];
AnyPromise *promise = [SNFileServerAPIV2 download:file useOldServer:useOldServer];
BOOL useOldServer = [profilePictureURL containsString:SNFileServerAPI.oldServer];
AnyPromise *promise = [SNFileServerAPI download:file useOldServer:useOldServer];
[promise.then(^(NSData *data) {
@synchronized(self.currentAvatarDownloads)