Fixed a few issues caused by the PromiseKit refactor

Started cleaning up the TODOs
Fixed a couple of merge conflict issues
Fixed a bug with the state of attachments which failed to download

# Conflicts:
#	SessionMessagingKit/Database/Models/Attachment.swift
pull/856/head
Morgan Pretty 2 years ago
parent 6970ff22cc
commit 5033738994

@ -319,6 +319,8 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
.writePublisherFlatMap { db in .writePublisherFlatMap { db in
MessageSender.createClosedGroup(db, name: name, members: selectedContacts) MessageSender.createClosedGroup(db, name: name, members: selectedContacts)
} }
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {

@ -348,10 +348,7 @@ extension ConversationVC:
.attachmentPublisher .attachmentPublisher
.sinkUntilComplete( .sinkUntilComplete(
receiveValue: { [weak self] attachment in receiveValue: { [weak self] attachment in
guard guard !modalActivityIndicator.wasCancelled else { return }
!modalActivityIndicator.wasCancelled,
let attachment = attachment as? SignalAttachment
else { return }
modalActivityIndicator.dismiss { modalActivityIndicator.dismiss {
guard !attachment.hasError else { guard !attachment.hasError else {
@ -1680,12 +1677,10 @@ extension ConversationVC:
// Remote deletion logic // Remote deletion logic
func deleteRemotely(from viewController: UIViewController?, request: AnyPublisher<Void, Error>, onComplete: (() -> ())?) { func deleteRemotely(from viewController: UIViewController?, request: AnyPublisher<Void, Error>, onComplete: (() -> ())?) {
// TODO: Test that this works
// Show a loading indicator // Show a loading indicator
Future<Void, Error> { resolver in Future<Void, Error> { resolver in
ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in
// TODO: Remove the 'Swift.' resolver(Result.success(()))
resolver(Swift.Result.success(()))
} }
} }
.flatMap { _ in request } .flatMap { _ in request }

@ -291,7 +291,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
result.textAlignment = .center result.textAlignment = .center
result.numberOfLines = 0 result.numberOfLines = 0
result.isHidden = ( result.isHidden = (
!self.messageRequestView.isHidden || !self.messageRequestStackView.isHidden ||
self.viewModel.threadData.threadRequiresApproval == false self.viewModel.threadData.threadRequiresApproval == false
) )
@ -350,7 +350,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
// Message requests view & scroll to bottom // Message requests view & scroll to bottom
view.addSubview(scrollButton) view.addSubview(scrollButton)
view.addSubview(messageRequestView) view.addSubview(messageRequestBackgroundView)
view.addSubview(messageRequestStackView)
view.addSubview(pendingMessageRequestExplanationLabel) view.addSubview(pendingMessageRequestExplanationLabel)
messageRequestView.addSubview(messageRequestBlockButton) messageRequestView.addSubview(messageRequestBlockButton)
@ -364,7 +365,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
self.messageRequestsViewBotomConstraint = messageRequestView.pin(.bottom, to: .bottom, of: view, withInset: -16) self.messageRequestsViewBotomConstraint = messageRequestView.pin(.bottom, to: .bottom, of: view, withInset: -16)
self.scrollButtonBottomConstraint = scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -16) self.scrollButtonBottomConstraint = scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -16)
self.scrollButtonBottomConstraint?.isActive = false // Note: Need to disable this to avoid a conflict with the other bottom constraint self.scrollButtonBottomConstraint?.isActive = false // Note: Need to disable this to avoid a conflict with the other bottom constraint
self.scrollButtonMessageRequestsBottomConstraint = scrollButton.pin(.bottom, to: .top, of: messageRequestView, withInset: -16) self.scrollButtonMessageRequestsBottomConstraint = scrollButton.pin(.bottom, to: .top, of: messageRequestStackView)
self.scrollButtonPendingMessageRequestInfoBottomConstraint = scrollButton.pin(.bottom, to: .top, of: pendingMessageRequestExplanationLabel, withInset: -16) self.scrollButtonPendingMessageRequestInfoBottomConstraint = scrollButton.pin(.bottom, to: .top, of: pendingMessageRequestExplanationLabel, withInset: -16)
messageRequestBlockButton.pin(.top, to: .top, of: messageRequestView, withInset: 10) messageRequestBlockButton.pin(.top, to: .top, of: messageRequestView, withInset: 10)
@ -383,10 +384,14 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
messageRequestDeleteButton.pin(.right, to: .right, of: messageRequestView, withInset: -20) messageRequestDeleteButton.pin(.right, to: .right, of: messageRequestView, withInset: -20)
messageRequestDeleteButton.pin(.bottom, to: .bottom, of: messageRequestView) messageRequestDeleteButton.pin(.bottom, to: .bottom, of: messageRequestView)
messageRequestDeleteButton.set(.width, to: .width, of: messageRequestAcceptButton) messageRequestDeleteButton.set(.width, to: .width, of: messageRequestAcceptButton)
messageRequestBackgroundView.pin(.top, to: .top, of: messageRequestStackView)
messageRequestBackgroundView.pin(.leading, to: .leading, of: view)
messageRequestBackgroundView.pin(.trailing, to: .trailing, of: view)
messageRequestBackgroundView.pin(.bottom, to: .bottom, of: view)
pendingMessageRequestExplanationLabel.pin(.left, to: .left, of: messageRequestView, withInset: 40) pendingMessageRequestExplanationLabel.pin(.left, to: .left, of: messageRequestStackView, withInset: 40)
pendingMessageRequestExplanationLabel.pin(.right, to: .right, of: messageRequestView, withInset: -40) pendingMessageRequestExplanationLabel.pin(.right, to: .right, of: messageRequestStackView, withInset: -40)
pendingMessageRequestExplanationLabel.pin(.bottom, to: .bottom, of: messageRequestView, withInset: -16) pendingMessageRequestExplanationLabel.pin(.bottom, to: .bottom, of: messageRequestStackView, withInset: -16)
// Unread count view // Unread count view
view.addSubview(unreadCountView) view.addSubview(unreadCountView)
@ -621,7 +626,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
updateNavBarButtons(threadData: updatedThreadData, initialVariant: viewModel.initialThreadVariant) updateNavBarButtons(threadData: updatedThreadData, initialVariant: viewModel.initialThreadVariant)
let messageRequestsViewWasVisible: Bool = ( let messageRequestsViewWasVisible: Bool = (
messageRequestView.isHidden == false messageRequestStackView.isHidden == false
) )
let pendingMessageRequestInfoWasVisible: Bool = ( let pendingMessageRequestInfoWasVisible: Bool = (
pendingMessageRequestExplanationLabel.isHidden == false pendingMessageRequestExplanationLabel.isHidden == false
@ -632,13 +637,14 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
updatedThreadData.threadIsMessageRequest == false || updatedThreadData.threadIsMessageRequest == false ||
updatedThreadData.threadRequiresApproval == true updatedThreadData.threadRequiresApproval == true
) )
self?.messageRequestBackgroundView.isHidden = (self?.messageRequestStackView.isHidden == true)
self?.pendingMessageRequestExplanationLabel.isHidden = ( self?.pendingMessageRequestExplanationLabel.isHidden = (
self?.messageRequestView.isHidden == false || self?.messageRequestStackView.isHidden == false ||
updatedThreadData.threadRequiresApproval == false updatedThreadData.threadRequiresApproval == false
) )
self?.scrollButtonMessageRequestsBottomConstraint?.isActive = ( self?.scrollButtonMessageRequestsBottomConstraint?.isActive = (
self?.messageRequestView.isHidden == false self?.messageRequestStackView.isHidden == false
) )
self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive = ( self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive = (
self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive == false && self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive == false &&
@ -1162,7 +1168,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
} }
let keyboardTop = (UIScreen.main.bounds.height - keyboardRect.minY) let keyboardTop = (UIScreen.main.bounds.height - keyboardRect.minY)
let messageRequestsOffset: CGFloat = (messageRequestView.isHidden ? 0 : messageRequestView.bounds.height + 16) let messageRequestsOffset: CGFloat = (messageRequestStackView.isHidden ? 0 : messageRequestStackView.bounds.height + 16)
let pendingMessageRequestsOffset: CGFloat = (pendingMessageRequestExplanationLabel.isHidden ? 0 : (pendingMessageRequestExplanationLabel.bounds.height + (16 * 2))) let pendingMessageRequestsOffset: CGFloat = (pendingMessageRequestExplanationLabel.isHidden ? 0 : (pendingMessageRequestExplanationLabel.bounds.height + (16 * 2)))
let oldContentInset: UIEdgeInsets = tableView.contentInset let oldContentInset: UIEdgeInsets = tableView.contentInset
let newContentInset: UIEdgeInsets = UIEdgeInsets( let newContentInset: UIEdgeInsets = UIEdgeInsets(

@ -184,6 +184,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
.present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in .present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in
SnodeAPI SnodeAPI
.getSessionID(for: onsNameOrPublicKey) .getSessionID(for: onsNameOrPublicKey)
.receive(on: DispatchQueue.main)
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {

@ -195,7 +195,7 @@ class PhotoCaptureViewController: OWSViewController {
} }
photoCapture.switchCamera() photoCapture.switchCamera()
.receiveOnMain() .receive(on: DispatchQueue.main)
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { [weak self] result in receiveCompletion: { [weak self] result in
switch result { switch result {
@ -210,7 +210,7 @@ class PhotoCaptureViewController: OWSViewController {
func didTapFlashMode() { func didTapFlashMode() {
Logger.debug("") Logger.debug("")
photoCapture.switchFlashMode() photoCapture.switchFlashMode()
.receiveOnMain() .receive(on: DispatchQueue.main)
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { [weak self] _ in receiveCompletion: { [weak self] _ in
self?.updateFlashModeControl() self?.updateFlashModeControl()
@ -306,7 +306,7 @@ class PhotoCaptureViewController: OWSViewController {
previewView = CapturePreviewView(session: photoCapture.session) previewView = CapturePreviewView(session: photoCapture.session)
photoCapture.startCapture() photoCapture.startCapture()
.receiveOnMain() .receive(on: DispatchQueue.main)
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { [weak self] result in receiveCompletion: { [weak self] result in
switch result { switch result {

@ -78,10 +78,8 @@ public enum SyncPushTokensJob: JobExecutor {
pushToken: pushToken, pushToken: pushToken,
voipToken: voipToken, voipToken: voipToken,
isForcedUpdate: shouldUploadTokens, isForcedUpdate: shouldUploadTokens,
// TODO: Remove the 'Swift.' success: { resolver(Result.success(())) },
success: { resolver(Swift.Result.success(())) }, failure: { resolver(Result.failure($0)) }
// TODO: Remove the 'Swift.'
failure: { resolver(Swift.Result.failure($0)) }
) )
} }
.handleEvents( .handleEvents(

@ -168,7 +168,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
ModalActivityIndicatorViewController.present(fromViewController: navigationController, canCancel: false) { [weak self] _ in ModalActivityIndicatorViewController.present(fromViewController: navigationController, canCancel: false) { [weak self] _ in
Storage.shared Storage.shared
.writePublisher { db in .writePublisherFlatMap { db in
OpenGroupManager.shared.add( OpenGroupManager.shared.add(
db, db,
roomToken: roomToken, roomToken: roomToken,

@ -143,6 +143,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle
widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true
OpenGroupManager.getDefaultRoomsIfNeeded() OpenGroupManager.getDefaultRoomsIfNeeded()
.receive(on: DispatchQueue.main)
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { [weak self] _ in self?.update() }, receiveCompletion: { [weak self] _ in self?.update() },
receiveValue: { [weak self] rooms in self?.rooms = rooms } receiveValue: { [weak self] rooms in self?.rooms = rooms }
@ -316,7 +317,7 @@ extension OpenGroupSuggestionGrid {
return return
} }
imageView.image = nil // TODO: Test this imageView.image = nil
Publishers Publishers
.MergeMany( .MergeMany(
@ -331,15 +332,19 @@ extension OpenGroupSuggestionGrid {
// we can ignore this 'Just' call which is used to hide the image while loading // we can ignore this 'Just' call which is used to hide the image while loading
Just((Data(), false)) Just((Data(), false))
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
// .delay(for: .milliseconds(10), scheduler: DispatchQueue.main) .delay(for: .milliseconds(10), scheduler: DispatchQueue.main)
.eraseToAnyPublisher() .eraseToAnyPublisher()
) )
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.sinkUntilComplete( .sinkUntilComplete(
receiveValue: { [weak self] imageData, hasData in receiveValue: { [weak self] imageData, hasData in
// TODO: Test this behaviour
guard hasData else { guard hasData else {
self?.imageView.isHidden = true // This will emit twice (once with the data and once without it), if we
// have actually received the images then we don't want the second emission
// to hide the imageView anymore
if self?.imageView.image == nil {
self?.imageView.isHidden = true
}
return return
} }
@ -347,24 +352,6 @@ extension OpenGroupSuggestionGrid {
self?.imageView.isHidden = (self?.imageView.image == nil) self?.imageView.isHidden = (self?.imageView.image == nil)
} }
) )
// OpenGroupManager.roomImage(db, fileId: imageId, for: room.token, on: OpenGroupAPI.defaultServer)
// .values
//
// if let imageData: Data = promise.value {
// imageView.image = UIImage(data: imageData)
// imageView.isHidden = (imageView.image == nil)
// }
// else {
// imageView.isHidden = true
//
// _ = promise.done { [weak self] imageData in
// DispatchQueue.main.async {
// self?.imageView.image = UIImage(data: imageData)
// self?.imageView.isHidden = (self?.imageView.image == nil)
// }
// }
// }
} }
} }
} }

@ -575,66 +575,3 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
self.transitionToScreen(shareVC, transitionType: .present) self.transitionToScreen(shareVC, transitionType: .present)
} }
} }
// MARK: - ImagePickerHandler
class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigationControllerDelegate {
private let viewModel: SettingsViewModel
// MARK: - Initialization
init(viewModel: SettingsViewModel) {
self.viewModel = viewModel
}
// MARK: - UIImagePickerControllerDelegate
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard
let imageUrl: URL = info[.imageURL] as? URL,
let rawAvatar: UIImage = info[.originalImage] as? UIImage
else {
picker.presentingViewController?.dismiss(animated: true)
return
}
let name: String = self.viewModel.oldDisplayName
picker.presentingViewController?.dismiss(animated: true) { [weak self] in
// Check if the user selected an animated image (if so then don't crop, just
// set the avatar directly
guard
let resourceValues: URLResourceValues = (try? imageUrl.resourceValues(forKeys: [.typeIdentifierKey])),
let type: Any = resourceValues.allValues.first?.value,
let typeString: String = type as? String,
MIMETypeUtil.supportedAnimatedImageUTITypes().contains(typeString)
else {
let viewController: CropScaleImageViewController = CropScaleImageViewController(
srcImage: rawAvatar,
successCompletion: { resultImage in
self?.viewModel.updateProfile(
name: name,
profilePicture: resultImage,
profilePictureFilePath: nil,
isUpdatingDisplayName: false,
isUpdatingProfilePicture: true
)
}
)
self?.viewModel.transitionToScreen(viewController, transitionType: .present)
return
}
self?.viewModel.updateProfile(
name: name,
profilePicture: nil,
profilePictureFilePath: imageUrl.path,
isUpdatingDisplayName: false,
isUpdatingProfilePicture: true
)
}
}
}

@ -12,7 +12,6 @@ public final class BackgroundPoller {
public static var isValid: Bool = false public static var isValid: Bool = false
public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// TODO: Test this works
Publishers Publishers
.MergeMany( .MergeMany(
[pollForMessages()] [pollForMessages()]

@ -972,6 +972,18 @@ extension Attachment {
// MARK: - Upload // MARK: - Upload
extension Attachment { extension Attachment {
public enum Destination {
case fileServer
case openGroup(OpenGroup)
var shouldEncrypt: Bool {
switch self {
case .fileServer: return true
case .openGroup: return false
}
}
}
public static func prepare(_ db: Database, attachments: [SignalAttachment], for interactionId: Int64) throws { public static func prepare(_ db: Database, attachments: [SignalAttachment], for interactionId: Int64) throws {
// Prepare any attachments // Prepare any attachments
try attachments.enumerated() try attachments.enumerated()
@ -979,7 +991,7 @@ extension Attachment {
let maybeAttachment: Attachment? = Attachment( let maybeAttachment: Attachment? = Attachment(
variant: (signalAttachment.isVoiceMessage ? variant: (signalAttachment.isVoiceMessage ?
.voiceMessage : .voiceMessage :
.standard .standard
), ),
contentType: signalAttachment.mimeType, contentType: signalAttachment.mimeType,
dataSource: signalAttachment.dataSource, dataSource: signalAttachment.dataSource,
@ -1001,176 +1013,160 @@ extension Attachment {
} }
internal func upload( internal func upload(
_ db: Database? = nil, to destination: Attachment.Destination,
queue: DispatchQueue, queue: DispatchQueue
using upload: @escaping (Database, Data) -> AnyPublisher<String, Error>, ) -> AnyPublisher<String?, Error> {
encrypt: Bool,
success: ((String?) -> Void)?,
failure: ((Error) -> Void)?
) {
// This can occur if an AttachmnetUploadJob was explicitly created for a message // This can occur if an AttachmnetUploadJob was explicitly created for a message
// dependant on the attachment being uploaded (in this case the attachment has // dependant on the attachment being uploaded (in this case the attachment has
// already been uploaded so just succeed) // already been uploaded so just succeed)
guard state != .uploaded else { guard state != .uploaded else {
success?(Attachment.fileId(for: self.downloadUrl)) return Just(Attachment.fileId(for: self.downloadUrl))
return .setFailureType(to: Error.self)
.eraseToAnyPublisher()
} }
// Get the attachment // Get the attachment
guard var data = try? readDataFromFile() else { guard var data = try? readDataFromFile() else {
SNLog("Couldn't read attachment from disk.") SNLog("Couldn't read attachment from disk.")
failure?(AttachmentError.noAttachment) return Fail(error: AttachmentError.noAttachment)
return .eraseToAnyPublisher()
} }
let attachmentId: String = self.id let attachmentId: String = self.id
// If the attachment is a downloaded attachment, check if it came from the server return Storage.shared
// and if so just succeed immediately (no use re-uploading an attachment that is .writePublisherFlatMap { db -> AnyPublisher<(String?, Data?, Data?), Error> in
// already present on the server) - or if we want it to be encrypted and it's not // If the attachment is a downloaded attachment, check if it came from
// then encrypt it // the server and if so just succeed immediately (no use re-uploading
// // an attachment that is already present on the server) - or if we want
// Note: The most common cases for this will be for LinkPreviews or Quotes // it to be encrypted and it's not then encrypt it
guard //
state != .downloaded || // Note: The most common cases for this will be for LinkPreviews or Quotes
serverId == nil || guard
downloadUrl == nil || state != .downloaded ||
!encrypt || serverId == nil ||
encryptionKey == nil || downloadUrl == nil ||
digest == nil !destination.shouldEncrypt ||
else { encryptionKey == nil ||
// Save the final upload info digest == nil
let uploadedAttachment: Attachment? = { else {
guard let db: Database = db else { // Save the final upload info
Storage.shared.write { db in _ = try? Attachment
try? Attachment .filter(id: attachmentId)
.filter(id: attachmentId) .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploaded))
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploaded))
return Just((Attachment.fileId(for: self.downloadUrl), nil, nil))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
var encryptionKey: NSData = NSData()
var digest: NSData = NSData()
// Encrypt the attachment if needed
if destination.shouldEncrypt {
guard let ciphertext = Cryptography.encryptAttachmentData(data, shouldPad: true, outKey: &encryptionKey, outDigest: &digest) else {
SNLog("Couldn't encrypt attachment.")
return Fail(error: AttachmentError.encryptionFailed)
.eraseToAnyPublisher()
} }
return self.with(state: .uploaded) data = ciphertext
}
// Check the file size
SNLog("File size: \(data.count) bytes.")
if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier {
return Fail(error: HTTPError.maxFileSizeExceeded)
.eraseToAnyPublisher()
} }
// Update the attachment to the 'uploading' state
_ = try? Attachment _ = try? Attachment
.filter(id: attachmentId) .filter(id: attachmentId)
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploaded)) .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploading))
return self.with(state: .uploaded) switch destination {
}() case .openGroup(let openGroup):
return OpenGroupAPI
guard uploadedAttachment != nil else { .uploadFile(
SNLog("Couldn't update attachmentUpload job.") db,
failure?(StorageError.failedToSave) bytes: data.bytes,
return to: openGroup.roomToken,
} on: openGroup.server
)
success?(Attachment.fileId(for: self.downloadUrl)) .map { _, response -> (String, Data?, Data?) in
return (
} response.id,
(destination.shouldEncrypt ? encryptionKey as Data : nil),
var processedAttachment: Attachment = self (destination.shouldEncrypt ? digest as Data : nil)
)
// Encrypt the attachment if needed }
if encrypt { .eraseToAnyPublisher()
var encryptionKey: NSData = NSData()
var digest: NSData = NSData() case .fileServer:
/// **Note:** FileServer uploads don't need database access so
guard let ciphertext = Cryptography.encryptAttachmentData(data, shouldPad: true, outKey: &encryptionKey, outDigest: &digest) else { return Just((
SNLog("Couldn't encrypt attachment.") nil,
failure?(AttachmentError.encryptionFailed) (destination.shouldEncrypt ? encryptionKey as Data : nil),
return (destination.shouldEncrypt ? digest as Data : nil)
))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
} }
.flatMap { maybeFileId, encryptionKey, digest -> AnyPublisher<(String?, Data?, Data?), Error> in
processedAttachment = processedAttachment.with( switch destination {
encryptionKey: encryptionKey as Data, case .openGroup:
digest: digest as Data /// **Note:** OpenGroup uploads need database access so this should
) /// have already been uploaded
data = ciphertext return Just((maybeFileId, encryptionKey, digest))
} .setFailureType(to: Error.self)
.eraseToAnyPublisher()
// Check the file size
SNLog("File size: \(data.count) bytes.") case .fileServer:
if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier { return FileServerAPI.upload(data)
failure?(HTTP.Error.maxFileSizeExceeded) .map { response -> (String, Data?, Data?) in (response.id, encryptionKey, digest) }
return .eraseToAnyPublisher()
}
// Update the attachment to the 'uploading' state
let updatedAttachment: Attachment? = {
guard let db: Database = db else {
Storage.shared.write { db in
try? Attachment
.filter(id: attachmentId)
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploading))
} }
return processedAttachment.with(state: .uploading)
} }
.flatMap { fileId, encryptionKey, digest -> AnyPublisher<String?, Error> in
_ = try? Attachment /// Save the final upload info
.filter(id: attachmentId) ///
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploading)) /// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is
/// updated correctly
return processedAttachment.with(state: .uploading) Storage.shared
}() .writePublisher { db in
try self
guard updatedAttachment != nil else { .with(
SNLog("Couldn't update attachmentUpload job.") serverId: fileId,
failure?(StorageError.failedToSave) state: .uploaded,
return creationTimestamp: (
} self.creationTimestamp ??
Date().timeIntervalSince1970
// Perform the upload ),
let uploadPublisher: AnyPublisher<String, Error> = { downloadUrl: fileId.map { "\(FileServerAPI.server)/file/\($0)" },
guard let db: Database = db else { encryptionKey: encryptionKey,
return Storage.shared.readPublisherFlatMap { db in upload(db, data) } digest: digest
)
.saved(db)
}
.map { _ in fileId }
.eraseToAnyPublisher()
} }
.handleEvents(
return upload(db, data)
}()
uploadPublisher
.sinkUntilComplete(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
case .finished: break case .finished: break
case .failure(let error): case .failure:
Storage.shared.write { db in Storage.shared.write { db in
try Attachment try Attachment
.filter(id: attachmentId) .filter(id: attachmentId)
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedUpload)) .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedUpload))
} }
failure?(error)
} }
},
receiveValue: { fileId in
/// Save the final upload info
///
/// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is
/// updated correctly
let uploadedAttachment: Attachment? = Storage.shared.write { db in
try updatedAttachment?
.with(
serverId: "\(fileId)",
state: .uploaded,
creationTimestamp: (
updatedAttachment?.creationTimestamp ??
Date().timeIntervalSince1970
),
downloadUrl: "\(FileServerAPI.server)/files/\(fileId)"
)
.saved(db)
}
guard uploadedAttachment != nil else {
SNLog("Couldn't update attachmentUpload job.")
failure?(StorageError.failedToSave)
return
}
success?(fileId)
} }
) )
.eraseToAnyPublisher()
} }
} }

@ -100,22 +100,3 @@ public extension Contact {
return ((try? fetchOne(db, id: id)) ?? Contact(id: id)) return ((try? fetchOne(db, id: id)) ?? Contact(id: id))
} }
} }
// MARK: - Objective-C Support
// TODO: Remove this when possible
@objc(SMKContact)
public class SMKContact: NSObject {
@objc(isBlockedFor:)
public static func isBlocked(id: String) -> Bool {
return Storage.shared
.read { db in
try Contact
.filter(id: id)
.select(.isBlocked)
.asRequest(of: Bool.self)
.fetchOne(db)
}
.defaulting(to: false)
}
}

@ -52,29 +52,16 @@ public enum AttachmentUploadJob: JobExecutor {
// reentrancy issues when the success/failure closures get called before the upload as the JobRunner // reentrancy issues when the success/failure closures get called before the upload as the JobRunner
// will attempt to update the state of the job immediately // will attempt to update the state of the job immediately
attachment.upload( attachment.upload(
queue: queue, to: (openGroup.map { .openGroup($0) } ?? .fileServer),
using: { db, data in queue: queue
SNLog("[AttachmentUpload] Started for message \(interactionId) (\(attachment.byteCount) bytes)") )
.sinkUntilComplete(
if let openGroup: OpenGroup = openGroup { receiveCompletion: { result in
return OpenGroupAPI switch result {
.uploadFile( case .failure(let error): failure(job, error, false)
db, case .finished: success(job, false)
bytes: data.bytes,
to: openGroup.roomToken,
on: openGroup.server
)
.map { _, response -> String in response.id }
.eraseToAnyPublisher()
} }
}
return FileServerAPI.upload(data)
.map { response -> String in response.id }
.eraseToAnyPublisher()
},
encrypt: (openGroup == nil),
success: { _ in success(job, false) },
failure: { error in failure(job, error, false) }
) )
} }
} }

@ -169,12 +169,12 @@ public enum MessageSendJob: JobExecutor {
try MessageSender.preparedSendData( try MessageSender.preparedSendData(
db, db,
message: details.message, message: details.message,
to: details.destination to: details.destination,
.with(fileIds: messageFileIds),
interactionId: job.interactionId interactionId: job.interactionId
) )
} }
.subscribe(on: queue) .subscribe(on: queue)
.map { sendData in sendData.with(fileIds: messageFileIds) }
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { result in receiveCompletion: { result in

@ -158,13 +158,7 @@ public final class VisibleMessage: Message {
// Attachments // Attachments
let attachments: [Attachment]? = try? Attachment.fetchAll(db, ids: self.attachmentIds) let attachments: [Attachment]? = try? Attachment.fetchAll(db, ids: self.attachmentIds)
if !(attachments ?? []).allSatisfy({ $0.state == .uploaded }) {
#if DEBUG
preconditionFailure("Sending a message before all associated attachments have been uploaded.")
#endif
}
let attachmentProtos = (attachments ?? []).compactMap { $0.buildProto() } let attachmentProtos = (attachments ?? []).compactMap { $0.buildProto() }
dataMessage.setAttachments(attachmentProtos) dataMessage.setAttachments(attachmentProtos)

@ -1434,9 +1434,12 @@ public enum OpenGroupAPI {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
return dependencies.onionApi // We was to avoid blocking the db write thread so we dispatch the API call to a different thread
.sendOnionRequest(signedRequest, to: request.server, with: publicKey) return Just(())
.setFailureType(to: Error.self)
.subscribe(on: OpenGroupAPI.workQueue) .subscribe(on: OpenGroupAPI.workQueue)
.receive(on: OpenGroupAPI.workQueue)
.flatMap { dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }

@ -246,11 +246,13 @@ public final class OpenGroupManager: NSObject {
OpenGroup.Columns.sequenceNumber.set(to: 0) OpenGroup.Columns.sequenceNumber.set(to: 0)
) )
// We was to avoid blocking the db write thread so we dispatch the API call to a different thread
//
// Note: We don't do this after the db commit as it can fail (resulting in endless loading) // Note: We don't do this after the db commit as it can fail (resulting in endless loading)
return Future<Void, Error> { resolver in return Just(())
OpenGroupAPI.workQueue.async { resolver(Result.success(())) } .setFailureType(to: Error.self)
}
.subscribe(on: OpenGroupAPI.workQueue) .subscribe(on: OpenGroupAPI.workQueue)
.receive(on: OpenGroupAPI.workQueue)
.flatMap { _ in .flatMap { _ in
dependencies.storage dependencies.storage
.readPublisherFlatMap { db in .readPublisherFlatMap { db in
@ -286,8 +288,7 @@ public final class OpenGroupManager: NSObject {
on: targetServer, on: targetServer,
dependencies: dependencies dependencies: dependencies
) { ) {
// TODO: Remove the 'Swift.' resolver(Result.success(()))
resolver(Swift.Result.success(()))
} }
} }
} }
@ -943,10 +944,6 @@ public final class OpenGroupManager: NSObject {
@discardableResult public static func getDefaultRoomsIfNeeded( @discardableResult public static func getDefaultRoomsIfNeeded(
using dependencies: OGMDependencies = OGMDependencies() using dependencies: OGMDependencies = OGMDependencies()
) -> AnyPublisher<[OpenGroupAPI.Room], Error> { ) -> AnyPublisher<[OpenGroupAPI.Room], Error> {
return Just([]) // TODO: Remove this
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
// Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again // Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again
if let existingPublisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.cache.defaultRoomsPublisher { if let existingPublisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.cache.defaultRoomsPublisher {
return existingPublisher return existingPublisher

@ -10,6 +10,7 @@ public enum MessageSenderError: LocalizedError {
case signingFailed case signingFailed
case encryptionFailed case encryptionFailed
case noUsername case noUsername
case attachmentsNotUploaded
// Closed groups // Closed groups
case noThread case noThread
@ -34,6 +35,7 @@ public enum MessageSenderError: LocalizedError {
case .signingFailed: return "Couldn't sign message." case .signingFailed: return "Couldn't sign message."
case .encryptionFailed: return "Couldn't encrypt message." case .encryptionFailed: return "Couldn't encrypt message."
case .noUsername: return "Missing username." case .noUsername: return "Missing username."
case .attachmentsNotUploaded: return "Attachments for this message have not been uploaded."
// Closed groups // Closed groups
case .noThread: return "Couldn't find a thread associated with the given group public key." case .noThread: return "Couldn't find a thread associated with the given group public key."

@ -99,7 +99,7 @@ extension MessageSender {
// the 'ClosedGroup' object we created // the 'ClosedGroup' object we created
sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) sentTimestampMs: UInt64(floor(formationTimestamp * 1000))
), ),
to: try Message.Destination.from(db, thread: thread), to: .contact(publicKey: memberId),
interactionId: nil interactionId: nil
) )
} }

@ -93,66 +93,57 @@ extension MessageSender {
case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey
} }
}() }()
let fileIdPublisher: AnyPublisher<[String?], Error> = Storage.shared
.write { db -> AnyPublisher<[String?], Error>? in return Storage.shared
.readPublisherFlatMap { db -> AnyPublisher<(attachments: [Attachment], openGroup: OpenGroup?), Error> in
let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment
.stateInfo(interactionId: interactionId, state: .uploading) .stateInfo(interactionId: interactionId, state: .uploading)
.fetchAll(db)) .fetchAll(db))
.defaulting(to: []) .defaulting(to: [])
// If there is no attachment data then just return early // If there is no attachment data then just return early
guard !attachmentStateInfo.isEmpty else { return nil } guard !attachmentStateInfo.isEmpty else {
// TODO: Just run an AttachmentUploadJob directly??? return Just(([], nil))
// Otherwise we need to generate the upload requests .setFailureType(to: Error.self)
let openGroup: OpenGroup? = try? OpenGroup.fetchOne(db, id: threadId) .eraseToAnyPublisher()
}
// Otherwise fetch the open group (if there is one)
return Just((
(try? Attachment
.filter(ids: attachmentStateInfo.map { $0.attachmentId })
.fetchAll(db))
.defaulting(to: []),
try? OpenGroup.fetchOne(db, id: threadId)
))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
.flatMap { attachments, openGroup -> AnyPublisher<[String?], Error> in
guard !attachments.isEmpty else {
return Just<[String?]>([])
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
return Publishers return Publishers
.MergeMany( .MergeMany(
(try? Attachment attachments
.filter(ids: attachmentStateInfo.map { $0.attachmentId })
.fetchAll(db))
.defaulting(to: [])
.map { attachment -> AnyPublisher<String?, Error> in .map { attachment -> AnyPublisher<String?, Error> in
Future { resolver in attachment
attachment.upload( .upload(
db, to: (
queue: DispatchQueue.global(qos: .userInitiated), openGroup.map { Attachment.Destination.openGroup($0) } ??
using: { db, data in .fileServer
if let openGroup: OpenGroup = openGroup { ),
return OpenGroupAPI queue: DispatchQueue.global(qos: .userInitiated)
.uploadFile(
db,
bytes: data.bytes,
to: openGroup.roomToken,
on: openGroup.server
)
.map { _, response -> String in response.id }
.eraseToAnyPublisher()
}
return FileServerAPI.upload(data)
.map { response -> String in response.id }
.eraseToAnyPublisher()
},
encrypt: (openGroup == nil),
success: { fileId in resolver(Swift.Result.success(fileId)) },
failure: { resolver(Swift.Result.failure($0)) }
) )
}
.eraseToAnyPublisher()
} }
) )
.collect() .collect()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
.defaulting( .map { results -> PreparedSendData in
to: Just<[String?]>([])
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
)
return fileIdPublisher
.map { results in
// Once the attachments are processed then update the PreparedSendData with // Once the attachments are processed then update the PreparedSendData with
// the fileIds associated to the message // the fileIds associated to the message
let fileIds: [String] = results.compactMap { result -> String? in result } let fileIds: [String] = results.compactMap { result -> String? in result }
@ -244,15 +235,11 @@ extension MessageSender {
.catch { _ in seal.reject(StorageError.generic) } .catch { _ in seal.reject(StorageError.generic) }
.retainUntilComplete() .retainUntilComplete()
// TODO: Test this (does it break anything? want to stop the db write asap) /// We want to avoid blocking the db write thread so we dispatch the API call to a different thread
/// We don't want to block the db write thread so we trigger the actual message sending after the query has return Just(())
/// finished .setFailureType(to: Error.self)
return Future<Void, Error> { resolver in .receive(on: DispatchQueue.global(qos: .userInitiated))
db.afterNextTransaction { _ in .flatMap { _ in MessageSender.sendImmediate(preparedSendData: sendData) }
resolver(Result.success(())) .eraseToAnyPublisher()
}
}
.flatMap { _ in MessageSender.sendImmediate(preparedSendData: sendData) }
.eraseToAnyPublisher()
} }
} }

@ -17,6 +17,7 @@ public final class MessageSender {
let destination: Message.Destination? let destination: Message.Destination?
let interactionId: Int64? let interactionId: Int64?
let isSyncMessage: Bool? let isSyncMessage: Bool?
let totalAttachmentsUploaded: Int
let snodeMessage: SnodeMessage? let snodeMessage: SnodeMessage?
let plaintext: Data? let plaintext: Data?
@ -32,6 +33,7 @@ public final class MessageSender {
destination: Message.Destination?, destination: Message.Destination?,
interactionId: Int64?, interactionId: Int64?,
isSyncMessage: Bool?, isSyncMessage: Bool?,
totalAttachmentsUploaded: Int = 0,
snodeMessage: SnodeMessage?, snodeMessage: SnodeMessage?,
plaintext: Data?, plaintext: Data?,
ciphertext: Data?, ciphertext: Data?,
@ -44,6 +46,7 @@ public final class MessageSender {
self.destination = destination self.destination = destination
self.interactionId = interactionId self.interactionId = interactionId
self.isSyncMessage = isSyncMessage self.isSyncMessage = isSyncMessage
self.totalAttachmentsUploaded = totalAttachmentsUploaded
self.snodeMessage = snodeMessage self.snodeMessage = snodeMessage
self.plaintext = plaintext self.plaintext = plaintext
@ -60,6 +63,7 @@ public final class MessageSender {
self.destination = nil self.destination = nil
self.interactionId = nil self.interactionId = nil
self.isSyncMessage = nil self.isSyncMessage = nil
self.totalAttachmentsUploaded = 0
self.snodeMessage = nil self.snodeMessage = nil
self.plaintext = nil self.plaintext = nil
@ -84,6 +88,7 @@ public final class MessageSender {
self.destination = destination self.destination = destination
self.interactionId = interactionId self.interactionId = interactionId
self.isSyncMessage = isSyncMessage self.isSyncMessage = isSyncMessage
self.totalAttachmentsUploaded = 0
self.snodeMessage = snodeMessage self.snodeMessage = snodeMessage
self.plaintext = nil self.plaintext = nil
@ -105,6 +110,7 @@ public final class MessageSender {
self.destination = destination self.destination = destination
self.interactionId = interactionId self.interactionId = interactionId
self.isSyncMessage = false self.isSyncMessage = false
self.totalAttachmentsUploaded = 0
self.snodeMessage = nil self.snodeMessage = nil
self.plaintext = plaintext self.plaintext = plaintext
@ -126,6 +132,7 @@ public final class MessageSender {
self.destination = destination self.destination = destination
self.interactionId = interactionId self.interactionId = interactionId
self.isSyncMessage = false self.isSyncMessage = false
self.totalAttachmentsUploaded = 0
self.snodeMessage = nil self.snodeMessage = nil
self.plaintext = nil self.plaintext = nil
@ -143,6 +150,7 @@ public final class MessageSender {
destination: destination?.with(fileIds: fileIds), destination: destination?.with(fileIds: fileIds),
interactionId: interactionId, interactionId: interactionId,
isSyncMessage: isSyncMessage, isSyncMessage: isSyncMessage,
totalAttachmentsUploaded: fileIds.count,
snodeMessage: snodeMessage, snodeMessage: snodeMessage,
plaintext: plaintext, plaintext: plaintext,
ciphertext: ciphertext, ciphertext: ciphertext,
@ -607,6 +615,24 @@ public final class MessageSender {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
// We now allow the creation of message data without validating it's attachments have finished
// uploading first, this is here to ensure we don't send a message which should have uploaded
// files
//
// If you see this error then you need to call `MessageSender.performUploadsIfNeeded(preparedSendData:)`
// before calling this function
switch preparedSendData.message {
case let visibleMessage as VisibleMessage:
guard visibleMessage.attachmentIds.count == preparedSendData.totalAttachmentsUploaded else {
return Fail(error: MessageSenderError.attachmentsNotUploaded)
.eraseToAnyPublisher()
}
break
default: break
}
switch preparedSendData.destination { switch preparedSendData.destination {
case .contact, .closedGroup: return sendToSnodeDestination(data: preparedSendData, using: dependencies) case .contact, .closedGroup: return sendToSnodeDestination(data: preparedSendData, using: dependencies)
case .openGroup: return sendToOpenGroupDestination(data: preparedSendData, using: dependencies) case .openGroup: return sendToOpenGroupDestination(data: preparedSendData, using: dependencies)

@ -28,7 +28,6 @@ extension OpenGroupAPI {
} }
public func startIfNeeded(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) { public func startIfNeeded(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) {
return// TODO: Remove this (reentrancy issues - looks like it could be resolved by splitting out the OpenGroupAPI request signing into it's own step)
guard !hasStarted else { return } guard !hasStarted else { return }
hasStarted = true hasStarted = true
@ -113,14 +112,7 @@ extension OpenGroupAPI {
.map { response in (failureCount, response) } .map { response in (failureCount, response) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
.subscribe( .subscribe(on: Threading.pollerQueue)
// If this was run via the background poller then don't run on the pollerQueue
// TODO: Need to test if this dispatches to the next run loop or blocks (want it to block)
on: (calledFromBackgroundPoller ?
DispatchQueue.main :
Threading.pollerQueue
)
)
.handleEvents( .handleEvents(
receiveOutput: { [weak self] failureCount, response in receiveOutput: { [weak self] failureCount, response in
guard !calledFromBackgroundPoller || isBackgroundPollerValid() else { guard !calledFromBackgroundPoller || isBackgroundPollerValid() else {

@ -472,7 +472,7 @@ public struct ProfileManager {
} }
}, },
receiveValue: { fileUploadResponse in receiveValue: { fileUploadResponse in
let downloadUrl: String = "\(FileServerAPI.server)/files/\(fileUploadResponse.id)" let downloadUrl: String = "\(FileServerAPI.server)/file/\(fileUploadResponse.id)"
// Update the cached avatar image value // Update the cached avatar image value
profileAvatarCache.mutate { $0[fileName] = data } profileAvatarCache.mutate { $0[fileName] = data }

@ -155,7 +155,6 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
receiveValue: { [weak self] attachments in receiveValue: { [weak self] attachments in
guard let strongSelf = self else { return } guard let strongSelf = self else { return }
// TODO: Test this
let approvalVC: UINavigationController = AttachmentApprovalViewController.wrappedInNavController( let approvalVC: UINavigationController = AttachmentApprovalViewController.wrappedInNavController(
threadId: strongSelf.viewModel.viewData[indexPath.row].threadId, threadId: strongSelf.viewModel.viewData[indexPath.row].threadId,
attachments: attachments, attachments: attachments,
@ -190,7 +189,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
NotificationCenter.default.post(name: Database.resumeNotification, object: self) NotificationCenter.default.post(name: Database.resumeNotification, object: self)
Storage.shared Storage.shared
.writePublisher { [weak self] db -> MessageSender.PreparedSendData in .writePublisher { db -> MessageSender.PreparedSendData in
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else {
throw MessageSenderError.noThread throw MessageSenderError.noThread
} }
@ -251,6 +250,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
} }
.flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) }
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
.receive(on: DispatchQueue.main)
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { [weak self] result in receiveCompletion: { [weak self] result in
// Suspend the database // Suspend the database

@ -88,9 +88,6 @@ public final class SnodeAPI {
} }
private static func dropSnodeFromSnodePool(_ snode: Snode) { private static func dropSnodeFromSnodePool(_ snode: Snode) {
#if DEBUG
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
#endif
var snodePool = SnodeAPI.snodePool.wrappedValue var snodePool = SnodeAPI.snodePool.wrappedValue
snodePool.remove(snode) snodePool.remove(snode)
setSnodePool(to: snodePool) setSnodePool(to: snodePool)

@ -29,7 +29,6 @@ internal extension OnionRequestAPI {
_ payload: Data, _ payload: Data,
for destination: OnionRequestAPIDestination for destination: OnionRequestAPIDestination
) -> AnyPublisher<AESGCM.EncryptionResult, Error> { ) -> AnyPublisher<AESGCM.EncryptionResult, Error> {
// TODO: Test performance
switch destination { switch destination {
case .snode(let snode): case .snode(let snode):
// Need to wrap the payload for snode requests // Need to wrap the payload for snode requests
@ -66,7 +65,6 @@ internal extension OnionRequestAPI {
to rhs: OnionRequestAPIDestination, to rhs: OnionRequestAPIDestination,
using previousEncryptionResult: AESGCM.EncryptionResult using previousEncryptionResult: AESGCM.EncryptionResult
) -> AnyPublisher<AESGCM.EncryptionResult, Error> { ) -> AnyPublisher<AESGCM.EncryptionResult, Error> {
// TODO: Test performance
var parameters: JSON var parameters: JSON
switch rhs { switch rhs {

@ -89,7 +89,7 @@ public enum HTTP {
public static func execute( public static func execute(
_ method: HTTPMethod, _ method: HTTPMethod,
_ url: String, _ url: String,
body: Data?, // TODO: Default Value? body: Data?,
timeout: TimeInterval = HTTP.defaultTimeout, timeout: TimeInterval = HTTP.defaultTimeout,
useSeedNodeURLSession: Bool = false useSeedNodeURLSession: Bool = false
) -> AnyPublisher<Data, Error> { ) -> AnyPublisher<Data, Error> {

Loading…
Cancel
Save