Implement attachment uploading
This commit is contained in:
parent
35424edbd2
commit
ee8b341182
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3032,7 +3032,7 @@ typedef enum : NSUInteger {
|
|||
|
||||
[self dismissViewControllerAnimated:YES
|
||||
completion:^{
|
||||
OWSAssertDebug(self.isFirstResponder);
|
||||
// OWSAssertDebug(self.isFirstResponder);
|
||||
if (@available(iOS 10, *)) {
|
||||
// do nothing
|
||||
} else {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>> {
|
||||
|
@ -100,13 +73,6 @@ 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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
[[LKStorageAPI uploadAttachment:attachmentStream withID:self.attachmentId]
|
||||
.thenOn(dispatch_get_main_queue(), ^() {
|
||||
[self reportSuccess];
|
||||
})
|
||||
.catchOn(dispatch_get_main_queue(), ^(NSError *error) {
|
||||
[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];
|
||||
}];
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
|
||||
- (void)uploadWithServerId:(UInt64)serverId
|
||||
|
|
|
@ -384,7 +384,7 @@ dispatch_queue_t NetworkManagerQueue()
|
|||
{
|
||||
OWSAssertDebug(failureBlock);
|
||||
OWSAssertDebug(request);
|
||||
OWSAssertDebug(task);
|
||||
// OWSAssertDebug(task);
|
||||
OWSAssertDebug(networkError);
|
||||
|
||||
NSInteger statusCode = [task statusCode];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue