diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index f396085e4..9053587ac 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -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) } } diff --git a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift index b23cfec6a..e433330b9 100644 --- a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift @@ -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) + } + } } } diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift b/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift index 7944a35c9..f135e9642 100644 --- a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift +++ b/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift @@ -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 { + public static func upload(_ file: Data, to room: String, on server: String) -> Promise { 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 { + public static func download(_ file: UInt64, from room: String, on server: String) -> Promise { 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 } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index ee30b81b5..b90402146 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -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 {