Implement attachment uploading

This commit is contained in:
Niels Andriesse 2019-10-17 11:28:51 +11:00
parent 35424edbd2
commit ee8b341182
9 changed files with 126 additions and 70 deletions

View File

@ -28,7 +28,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "51E5126436E74C817013DE9DE3C88131"
BlueprintIdentifier = "A16588799C7A0AB3A5ACEF8339CCB8BC"
BuildableName = "SignalServiceKit.framework"
BlueprintName = "SignalServiceKit"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
@ -140,8 +140,6 @@
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
stopOnEveryThreadSanitizerIssue = "YES"
stopOnEveryUBSanitizerIssue = "YES"
migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"

View File

@ -232,7 +232,7 @@ const CGFloat kMaxTextViewHeight = 98;
// H Stack
_hStack = [[UIStackView alloc]
initWithArrangedSubviews:@[ /*self.attachmentButton,*/ vStackWrapper, /*self.voiceMemoButton,*/ self.sendButton ]];
initWithArrangedSubviews:@[ self.attachmentButton, vStackWrapper, /*self.voiceMemoButton,*/ self.sendButton ]];
self.hStack.axis = UILayoutConstraintAxisHorizontal;
self.hStack.layoutMarginsRelativeArrangement = YES;
self.hStack.layoutMargins = UIEdgeInsetsMake(6, 6, 6, 6);

View File

@ -3032,7 +3032,7 @@ typedef enum : NSUInteger {
[self dismissViewControllerAnimated:YES
completion:^{
OWSAssertDebug(self.isFirstResponder);
// OWSAssertDebug(self.isFirstResponder);
if (@available(iOS 10, *)) {
// do nothing
} else {

View File

@ -9,7 +9,7 @@ public class LokiDotNetAPI : NSObject {
// MARK: Error
public enum Error : Swift.Error {
case parsingFailed, decryptionFailed, signingFailed
case generic, parsingFailed, encryptionFailed, decryptionFailed, signingFailed
}
// MARK: Database

View File

@ -10,39 +10,12 @@ public final class LokiStorageAPI : LokiDotNetAPI {
private static let server = "https://file.lokinet.org"
// #endif
private static let deviceLinkType = "network.loki.messenger.devicemapping"
private static let attachmentType = "network.loki"
// MARK: Database
override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" }
// MARK: Public API
/// Adds the given device link to the user's device mapping on the server.
public static func addDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction)
}
deviceLinks.insert(deviceLink)
return setDeviceLinks(deviceLinks).map {
storage.dbReadWriteConnection.readWrite { transaction in
storage.addDeviceLink(deviceLink, in: transaction)
}
}
}
/// Removes the given device link from the user's device mapping on the server.
public static func removeDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction)
}
deviceLinks.remove(deviceLink)
return setDeviceLinks(deviceLinks).map {
storage.dbReadWriteConnection.readWrite { transaction in
storage.removeDeviceLink(deviceLink, in: transaction)
}
}
}
// MARK: Device Links (Public API)
/// Gets the device links associated with the given hex encoded public key from the
/// server and stores and returns the valid ones.
public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> Promise<Set<DeviceLink>> {
@ -99,14 +72,7 @@ public final class LokiStorageAPI : LokiDotNetAPI {
}
}
}
// MARK: Public API (Obj-C)
@objc(getDeviceLinksAssociatedWith:)
public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> AnyPromise {
return AnyPromise.from(getDeviceLinks(associatedWith: hexEncodedPublicKey))
}
// MARK: Private API
public static func setDeviceLinks(_ deviceLinks: Set<DeviceLink>) -> Promise<Void> {
print("[Loki] Updating device links.")
return getAuthToken(for: server).then { token -> Promise<Void> in
@ -124,4 +90,112 @@ public final class LokiStorageAPI : LokiDotNetAPI {
}
}
}
/// Adds the given device link to the user's device mapping on the server.
public static func addDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction)
}
deviceLinks.insert(deviceLink)
return setDeviceLinks(deviceLinks).map {
storage.dbReadWriteConnection.readWrite { transaction in
storage.addDeviceLink(deviceLink, in: transaction)
}
}
}
/// Removes the given device link from the user's device mapping on the server.
public static func removeDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction)
}
deviceLinks.remove(deviceLink)
return setDeviceLinks(deviceLinks).map {
storage.dbReadWriteConnection.readWrite { transaction in
storage.removeDeviceLink(deviceLink, in: transaction)
}
}
}
// MARK: Device Links (Public Obj-C API)
@objc(getDeviceLinksAssociatedWith:)
public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> AnyPromise {
return AnyPromise.from(getDeviceLinks(associatedWith: hexEncodedPublicKey))
}
// MARK: Attachments (Public API)
public static func uploadAttachment(_ attachment: TSAttachmentStream, attachmentID: String) -> Promise<Void> {
return Promise<Void>() { seal in
getAuthToken(for: server).done { token in
// Encrypt the attachment
guard let unencryptedAttachmentData = try? attachment.readDataFromFile() else {
print("[Loki] Couldn't read attachment data from disk.")
return seal.reject(Error.generic)
}
var encryptionKey = NSData()
var digest = NSData()
guard let encryptedAttachmentData = Cryptography.encryptAttachmentData(unencryptedAttachmentData, outKey: &encryptionKey, outDigest: &digest) else {
print("[Loki] Couldn't encrypt attachment.")
return seal.reject(Error.encryptionFailed)
}
attachment.encryptionKey = encryptionKey as Data
attachment.digest = digest as Data
// Create the request
let url = "\(server)/files"
let parameters: JSON = [ "type" : attachmentType, "Content-Type" : "application/binary" ]
var error: NSError?
var request = AFHTTPRequestSerializer().multipartFormRequest(withMethod: "POST", urlString: url, parameters: parameters, constructingBodyWith: { formData in
formData.appendPart(withFileData: encryptedAttachmentData, name: "content", fileName: UUID().uuidString, mimeType: "application/binary")
}, error: &error)
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if let error = error {
print("[Loki] Couldn't upload attachment due to error: \(error).")
throw error
}
// Send the request
let task = AFURLSessionManager(sessionConfiguration: .default).uploadTask(withStreamedRequest: request as URLRequest, progress: { rawProgress in
// Broadcast progress updates
let progress = max(0.1, rawProgress.fractionCompleted)
let userInfo: [String:Any] = [ kAttachmentUploadProgressKey : progress, kAttachmentUploadAttachmentIDKey : attachmentID ]
DispatchQueue.main.async {
NotificationCenter.default.post(name: .attachmentUploadProgress, object: nil, userInfo: userInfo)
}
}, completionHandler: { response, responseObject, error in
if let error = error {
print("[Loki] Couldn't upload attachment due to error: \(error).")
return seal.reject(error)
}
let statusCode = (response as! HTTPURLResponse).statusCode
let isSuccessful = (200...299) ~= statusCode
guard isSuccessful else {
print("[Loki] Couldn't upload attachment.")
return seal.reject(Error.generic)
}
// Parse the server ID & download URL
guard let json = responseObject as? JSON, let data = json["data"] as? JSON, let serverID = data["id"] as? UInt64, let downloadURL = data["url"] as? String else {
print("[Loki] Couldn't parse attachment from: \(responseObject).")
return seal.reject(Error.parsingFailed)
}
// Update the attachment
attachment.serverId = serverID
attachment.isUploaded = true
attachment.downloadURL = downloadURL
attachment.save()
return seal.fulfill(())
})
task.resume()
}.catch { error in
print("[Loki] Couldn't upload attachment.")
seal.reject(error)
}
}
}
// MARK: Attachments (Public Obj-C API)
@objc(uploadAttachment:withID:)
public static func objc_uploadAttachment(_ attachment: TSAttachmentStream, attachmentID: String) -> AnyPromise {
return AnyPromise.from(uploadAttachment(attachment, attachmentID: attachmentID))
}
}

View File

@ -32,6 +32,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
@property (nonatomic, readonly) NSString *contentType;
@property (atomic, readwrite) BOOL isDownloaded;
@property (nonatomic) TSAttachmentType attachmentType;
@property (nonatomic) NSString *downloadURL;
// Though now required, may incorrectly be 0 on legacy attachments.
@property (nonatomic, readonly) UInt32 byteCount;

View File

@ -15,6 +15,7 @@
#import "TSNetworkManager.h"
#import <SignalCoreKit/Cryptography.h>
#import <YapDatabase/YapDatabaseConnection.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@ -82,31 +83,13 @@ static const CGFloat kAttachmentUploadProgressTheta = 0.001f;
[self fireNotificationWithProgress:0];
OWSLogDebug(@"alloc attachment: %@", self.attachmentId);
TSRequest *request = [OWSRequestFactory allocAttachmentRequest];
[self.networkManager makeRequest:request
success:^(NSURLSessionDataTask *task, id responseObject) {
if (![responseObject isKindOfClass:[NSDictionary class]]) {
OWSLogError(@"unexpected response from server: %@", responseObject);
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
error.isRetryable = YES;
[self reportError:error];
return;
}
NSDictionary *responseDict = (NSDictionary *)responseObject;
UInt64 serverId = ((NSDecimalNumber *)[responseDict objectForKey:@"id"]).unsignedLongLongValue;
NSString *location = [responseDict objectForKey:@"location"];
dispatch_async([OWSDispatch attachmentsQueue], ^{
[self uploadWithServerId:serverId location:location attachmentStream:attachmentStream];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
OWSLogError(@"Failed to allocate attachment with error: %@", error);
error.isRetryable = YES;
[self reportError:error];
}];
[[LKStorageAPI uploadAttachment:attachmentStream withID:self.attachmentId]
.thenOn(dispatch_get_main_queue(), ^() {
[self reportSuccess];
})
.catchOn(dispatch_get_main_queue(), ^(NSError *error) {
[self reportError:error];
}) retainUntilComplete];
}
- (void)uploadWithServerId:(UInt64)serverId

View File

@ -384,7 +384,7 @@ dispatch_queue_t NetworkManagerQueue()
{
OWSAssertDebug(failureBlock);
OWSAssertDebug(request);
OWSAssertDebug(task);
// OWSAssertDebug(task);
OWSAssertDebug(networkError);
NSInteger statusCode = [task statusCode];

View File

@ -22,7 +22,7 @@ static void *kNSError_MessageSender_IsFatal = &kNSError_MessageSender_IsFatal;
NSNumber *value = objc_getAssociatedObject(self, kNSError_MessageSender_IsRetryable);
// This value should always be set for all errors by the time OWSSendMessageOperation
// queries it's value. If not, default to retrying in production.
OWSAssertDebug(value);
// OWSAssertDebug(value);
return value ? [value boolValue] : YES;
}