Update AttachmentDownloadJob & AttachmentUploadJob for V2 open groups

pull/370/head
Niels Andriesse 3 years ago
parent fd7081ee94
commit e50f4da63e

@ -5,16 +5,19 @@ import SignalCoreKit
public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility
public let attachmentID: String
public let tsMessageID: String
public let openGroupID: String?
public var delegate: JobDelegate?
public var id: String?
public var failureCount: UInt = 0
public enum Error : LocalizedError {
case noAttachment
case invalidURL
public var errorDescription: String? {
switch self {
case .noAttachment: return "No such attachment."
case .invalidURL: return "Invalid file URL."
}
}
}
@ -24,9 +27,10 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
public static let maxFailureCount: UInt = 20
// MARK: Initialization
public init(attachmentID: String, tsMessageID: String) {
public init(attachmentID: String, tsMessageID: String, openGroupID: String?) {
self.attachmentID = attachmentID
self.tsMessageID = tsMessageID
self.openGroupID = openGroupID
}
// MARK: Coding
@ -36,6 +40,7 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
let id = coder.decodeObject(forKey: "id") as! String? else { return nil }
self.attachmentID = attachmentID
self.tsMessageID = tsMessageID
self.openGroupID = coder.decodeObject(forKey: "openGroupID") as! String?
self.id = id
self.failureCount = coder.decodeObject(forKey: "failureCount") as! UInt? ?? 0
}
@ -43,6 +48,7 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
public func encode(with coder: NSCoder) {
coder.encode(attachmentID, forKey: "attachmentID")
coder.encode(tsMessageID, forKey: "tsIncomingMessageID")
coder.encode(openGroupID, forKey: "openGroupID")
coder.encode(id, forKey: "id")
coder.encode(failureCount, forKey: "failureCount")
}
@ -80,34 +86,59 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
self.handleFailure(error: error)
}
}
FileServerAPI.downloadAttachment(from: pointer.downloadURL).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
do {
try data.write(to: temporaryFilePath, options: .atomic)
} catch {
return handleFailure(error)
if let openGroupID = openGroupID, let v2OpenGroup = storage.getV2OpenGroup(for: LKGroupUtilities.getEncodedOpenGroupID(openGroupID)) {
guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let file = UInt64(fileAsString) else {
return handleFailure(Error.invalidURL)
}
let plaintext: Data
if let key = pointer.encryptionKey, let digest = pointer.digest {
OpenGroupAPIV2.download(file, from: v2OpenGroup.room, on: v2OpenGroup.server).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
do {
plaintext = try Cryptography.decryptAttachment(data, withKey: key, digest: digest, unpaddedSize: pointer.byteCount)
try data.write(to: temporaryFilePath, options: .atomic)
} catch {
return handleFailure(error)
}
} else {
plaintext = data // Open group attachments are unencrypted
}
let stream = TSAttachmentStream(pointer: pointer)
do {
try stream.write(plaintext)
} catch {
return handleFailure(error)
let stream = TSAttachmentStream(pointer: pointer)
do {
try stream.write(data)
} catch {
return handleFailure(error)
}
OWSFileSystem.deleteFile(temporaryFilePath.absoluteString)
storage.write { transaction in
storage.persist(stream, associatedWith: self.tsMessageID, using: transaction)
}
}.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
OWSFileSystem.deleteFile(temporaryFilePath.absoluteString)
storage.write { transaction in
storage.persist(stream, associatedWith: self.tsMessageID, using: transaction)
} else {
FileServerAPI.downloadAttachment(from: pointer.downloadURL).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
do {
try data.write(to: temporaryFilePath, options: .atomic)
} catch {
return handleFailure(error)
}
let plaintext: Data
if let key = pointer.encryptionKey, let digest = pointer.digest {
do {
plaintext = try Cryptography.decryptAttachment(data, withKey: key, digest: digest, unpaddedSize: pointer.byteCount)
} catch {
return handleFailure(error)
}
} else {
plaintext = data // Open group attachments are unencrypted
}
let stream = TSAttachmentStream(pointer: pointer)
do {
try stream.write(plaintext)
} catch {
return handleFailure(error)
}
OWSFileSystem.deleteFile(temporaryFilePath.absoluteString)
storage.write { transaction in
storage.persist(stream, associatedWith: self.tsMessageID, using: transaction)
}
}.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
}.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
}

@ -61,19 +61,46 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
return handleFailure(error: Error.noAttachment)
}
guard !stream.isUploaded else { return handleSuccess() } // Should never occur
let openGroup = SNMessagingKitConfiguration.shared.storage.getOpenGroup(for: threadID)
let server = openGroup?.server ?? FileServerAPI.server
// FIXME: A lot of what's currently happening in FileServerAPI should really be happening here
FileServerAPI.uploadAttachment(stream, with: attachmentID, to: server).done(on: DispatchQueue.global(qos: .userInitiated)) { // Intentionally capture self
self.handleSuccess()
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
if let error = error as? Error, case .noAttachment = error {
self.handlePermanentFailure(error: error)
} else if let error = error as? DotNetAPI.Error, !error.isRetryable {
self.handlePermanentFailure(error: error)
} else {
let storage = SNMessagingKitConfiguration.shared.storage
if let v2OpenGroup = storage.getV2OpenGroup(for: threadID) {
// Get the attachment
guard let data = try? stream.readDataFromFile() else {
SNLog("Couldn't read attachment from disk.")
return handleFailure(error: Error.noAttachment)
}
// Check the file size
SNLog("File size: \(data.count) bytes.")
if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier {
return handleFailure(error: FileServerAPI.Error.maxFileSizeExceeded)
}
// Send the request
stream.isUploaded = false
stream.save()
OpenGroupAPIV2.upload(data, to: v2OpenGroup.room, on: v2OpenGroup.server).done(on: DispatchQueue.global(qos: .userInitiated)) { fileID in
let downloadURL = "\(v2OpenGroup.server)/files/\(fileID)"
stream.serverId = fileID
stream.isUploaded = true
stream.downloadURL = downloadURL
stream.save()
self.handleSuccess()
}.catch { error in
self.handleFailure(error: error)
}
} else {
let openGroup = storage.getOpenGroup(for: threadID)
let server = openGroup?.server ?? FileServerAPI.server
// FIXME: A lot of what's currently happening in FileServerAPI should really be happening here
FileServerAPI.uploadAttachment(stream, with: attachmentID, to: server).done(on: DispatchQueue.global(qos: .userInitiated)) { // Intentionally capture self
self.handleSuccess()
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
if let error = error as? Error, case .noAttachment = error {
self.handlePermanentFailure(error: error)
} else if let error = error as? DotNetAPI.Error, !error.isRetryable {
self.handlePermanentFailure(error: error)
} else {
self.handleFailure(error: error)
}
}
}
}

@ -1,7 +1,6 @@
import PromiseKit
import SessionSnodeKit
// TODO: Update AttachmentDownloadJob & AttachmentUploadJob for the new API
// TODO: Show images w/ room suggestions
// TODO: Distinguish between V1 and V2 open groups in the join open group screen
@ -177,17 +176,17 @@ public final class OpenGroupAPIV2 : NSObject {
}
// MARK: File Storage
public static func upload(_ file: Data, to room: String, on server: String) -> Promise<String> {
public static func upload(_ file: Data, to room: String, on server: String) -> Promise<UInt64> {
let base64EncodedFile = file.base64EncodedString()
let parameters = [ "file" : base64EncodedFile ]
let request = Request(verb: .post, room: room, server: server, endpoint: "files", parameters: parameters)
return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in
guard let fileID = json["result"] as? String else { throw Error.parsingFailed }
guard let fileID = json["result"] as? UInt64 else { throw Error.parsingFailed }
return fileID
}
}
public static func download(_ file: String, from room: String, on server: String) -> Promise<Data> {
public static func download(_ file: UInt64, from room: String, on server: String) -> Promise<Data> {
let request = Request(verb: .get, room: room, server: server, endpoint: "files/\(file)")
return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in
guard let base64EncodedFile = json["result"] as? String, let file = Data(base64Encoded: base64EncodedFile) else { throw Error.parsingFailed }

@ -277,7 +277,7 @@ extension MessageReceiver {
message.threadID = threadID
// Start attachment downloads if needed
attachmentsToDownload.forEach { attachmentID in
let downloadJob = AttachmentDownloadJob(attachmentID: attachmentID, tsMessageID: tsMessageID)
let downloadJob = AttachmentDownloadJob(attachmentID: attachmentID, tsMessageID: tsMessageID, openGroupID: openGroupID)
if isMainAppAndActive {
JobQueue.shared.add(downloadJob, using: transaction)
} else {

Loading…
Cancel
Save