Merge branch 'dev' into feature/remove-OWSBlockingManager

# Conflicts:
#	Session/Conversations/ConversationVC+Interaction.swift
#	Session/Meta/AppDelegate.swift
#	SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift
#	SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift
pull/602/head
Morgan Pretty 3 years ago
commit 7165b9e4f6

@ -778,6 +778,7 @@
FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; }; FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; };
FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; }; FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; };
FDC4389E27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; FDC4389E27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
FDFD645827EC1F4000808CA1 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645727EC1F4000808CA1 /* Atomic.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -1811,6 +1812,7 @@
FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; }; FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; }; FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; };
FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; }; FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
FDFD645727EC1F4000808CA1 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; }; FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; }; FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -2321,6 +2323,7 @@
C33FDB8A255A581200E217F9 /* AppContext.h */, C33FDB8A255A581200E217F9 /* AppContext.h */,
C33FDB85255A581100E217F9 /* AppContext.m */, C33FDB85255A581100E217F9 /* AppContext.m */,
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */, C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
FDFD645727EC1F4000808CA1 /* Atomic.swift */,
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */, C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */, B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */,
B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */, B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */,
@ -4629,6 +4632,7 @@
C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */, C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */,
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */, B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */,
FDFD645827EC1F4000808CA1 /* Atomic.swift in Sources */,
C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */, C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */,
B8BC00C0257D90E30032E807 /* General.swift in Sources */, B8BC00C0257D90E30032E807 /* General.swift in Sources */,
C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */, C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */,

@ -48,14 +48,17 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
self.blockedBanner.alpha = 0 self.blockedBanner.alpha = 0
}, completion: { _ in }, completion: { _ in
if let contact: Contact = Storage.shared.getContact(with: publicKey) { if let contact: Contact = Storage.shared.getContact(with: publicKey) {
Storage.shared.write { transaction in Storage.shared.write(
guard let transaction = transaction as? YapDatabaseReadWriteTransaction else { return } with: { transaction in
guard let transaction = transaction as? YapDatabaseReadWriteTransaction else { return }
contact.isBlocked = false
Storage.shared.setContact(contact, using: transaction)
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete() contact.isBlocked = false
} Storage.shared.setContact(contact, using: transaction)
},
completion: {
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
)
} }
}) })
} }
@ -264,49 +267,46 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
let linkPreviewDraft = snInputView.linkPreviewInfo?.draft let linkPreviewDraft = snInputView.linkPreviewInfo?.draft
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
Storage.write(with: { transaction in let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
let promise: Promise<Void> = self.approveMessageRequestIfNeeded( for: self.thread,
for: self.thread, isNewThread: !oldThreadShouldBeVisible,
with: transaction, timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
isNewThread: !oldThreadShouldBeVisible, )
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting .map { [weak self] _ in
) self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage)
.map { [weak self] _ in
self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage) Storage.write(with: { transaction in
message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction)
Storage.write(with: { transaction in }, completion: { [weak self] in
message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction) tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
}, completion: { [weak self] in
tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
Storage.shared.write(
with: { transaction in
tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction)
},
completion: { [weak self] in
// At this point the TSOutgoingMessage should have its link preview set, so we can scroll to the bottom knowing
// the height of the new message cell
self?.scrollToBottom(isAnimated: false)
}
)
Storage.shared.write { transaction in Storage.shared.write(
MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction) with: { transaction in
tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction)
},
completion: { [weak self] in
// At this point the TSOutgoingMessage should have its link preview set, so we can scroll to the bottom knowing
// the height of the new message cell
self?.scrollToBottom(isAnimated: false)
} }
)
self?.handleMessageSent() Storage.shared.write { transaction in
}) MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction)
} }
// Show an error indicating that approving the thread failed self?.handleMessageSent()
promise.catch(on: DispatchQueue.main) { [weak self] _ in })
let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert) }
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
}
promise.retainUntilComplete() // Show an error indicating that approving the thread failed
}) promise.catch(on: DispatchQueue.main) { [weak self] _ in
let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
}
promise.retainUntilComplete()
} }
func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) { func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) {
@ -330,44 +330,41 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
let oldThreadShouldBeVisible: Bool = thread.shouldBeVisible let oldThreadShouldBeVisible: Bool = thread.shouldBeVisible
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
Storage.write(with: { transaction in let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
let promise: Promise<Void> = self.approveMessageRequestIfNeeded( for: self.thread,
for: self.thread, isNewThread: !oldThreadShouldBeVisible,
with: transaction, timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
isNewThread: !oldThreadShouldBeVisible, )
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting .map { [weak self] _ in
Storage.write(
with: { transaction in
tsMessage.save(with: transaction)
// The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet
},
completion: { [weak self] in
Storage.write(with: { transaction in
MessageSender.send(message, with: attachments, in: thread, using: transaction)
}, completion: { [weak self] in
// At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing
// the height of the new message cell
self?.scrollToBottom(isAnimated: false)
})
self?.handleMessageSent()
// Attachment successfully sent - dismiss the screen
onComplete?()
}
) )
.map { [weak self] _ in }
Storage.write(
with: { transaction in
tsMessage.save(with: transaction)
// The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet
},
completion: { [weak self] in
Storage.write(with: { transaction in
MessageSender.send(message, with: attachments, in: thread, using: transaction)
}, completion: { [weak self] in
// At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing
// the height of the new message cell
self?.scrollToBottom(isAnimated: false)
})
self?.handleMessageSent()
// Attachment successfully sent - dismiss the screen
onComplete?()
}
)
}
// Show an error indicating that approving the thread failed // Show an error indicating that approving the thread failed
promise.catch(on: DispatchQueue.main) { [weak self] _ in promise.catch(on: DispatchQueue.main) { [weak self] _ in
let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert) let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil) self?.present(alert, animated: true, completion: nil)
} }
promise.retainUntilComplete() promise.retainUntilComplete()
})
} }
func handleMessageSent() { func handleMessageSent() {
@ -1104,7 +1101,7 @@ extension ConversationVC: UIDocumentInteractionControllerDelegate {
extension ConversationVC { extension ConversationVC {
fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, with transaction: YapDatabaseReadWriteTransaction, isNewThread: Bool, timestamp: UInt64) -> Promise<Void> { fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, isNewThread: Bool, timestamp: UInt64) -> Promise<Void> {
guard let contactThread: TSContactThread = thread as? TSContactThread else { return Promise.value(()) } guard let contactThread: TSContactThread = thread as? TSContactThread else { return Promise.value(()) }
// If the contact doesn't exist then we should create it so we can store the 'isApproved' state // If the contact doesn't exist then we should create it so we can store the 'isApproved' state
@ -1135,7 +1132,17 @@ extension ConversationVC {
} }
return promise return promise
.then { MessageSender.sendNonDurably(messageRequestResponse, in: contactThread, using: transaction) } .then { _ -> Promise<Void> in
let (promise, seal) = Promise<Void>.pending()
Storage.writeSync { transaction in
MessageSender.sendNonDurably(messageRequestResponse, in: contactThread, using: transaction)
.done { seal.fulfill(()) }
.catch { _ in seal.fulfill(()) } // Fulfill even if this failed; the configuration in the swarm should be at most 2 days old
.retainUntilComplete()
}
return promise
}
.map { _ in .map { _ in
if self?.presentedViewController is ModalActivityIndicatorViewController { if self?.presentedViewController is ModalActivityIndicatorViewController {
self?.dismiss(animated: true, completion: nil) // Dismiss the loader self?.dismiss(animated: true, completion: nil) // Dismiss the loader
@ -1144,9 +1151,11 @@ extension ConversationVC {
} }
.map { _ in .map { _ in
// Default 'didApproveMe' to true for the person approving the message request // Default 'didApproveMe' to true for the person approving the message request
contact.isApproved = true Storage.write { transaction in
contact.didApproveMe = (contact.didApproveMe || !isNewThread) contact.isApproved = true
Storage.shared.setContact(contact, using: transaction) contact.didApproveMe = (contact.didApproveMe || !isNewThread)
Storage.shared.setContact(contact, using: transaction)
}
// Send a sync message with the details of the contact // Send a sync message with the details of the contact
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
@ -1189,23 +1198,20 @@ extension ConversationVC {
} }
@objc func acceptMessageRequest() { @objc func acceptMessageRequest() {
Storage.write { transaction in let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
let promise: Promise<Void> = self.approveMessageRequestIfNeeded( for: self.thread,
for: self.thread, isNewThread: false,
with: transaction, timestamp: NSDate.millisecondTimestamp()
isNewThread: false, )
timestamp: NSDate.millisecondTimestamp()
) // Show an error indicating that approving the thread failed
promise.catch(on: DispatchQueue.main) { [weak self] _ in
// Show an error indicating that approving the thread failed let alert = UIAlertController(title: "Session", message: NSLocalizedString("MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE", comment: ""), preferredStyle: .alert)
promise.catch(on: DispatchQueue.main) { [weak self] _ in alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
let alert = UIAlertController(title: "Session", message: NSLocalizedString("MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE", comment: ""), preferredStyle: .alert) self?.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
}
promise.retainUntilComplete()
} }
promise.retainUntilComplete()
} }
@objc func deleteMessageRequest() { @objc func deleteMessageRequest() {
@ -1244,11 +1250,11 @@ extension ConversationVC {
// Delete all thread content // Delete all thread content
self?.thread.removeAllThreadInteractions(with: transaction) self?.thread.removeAllThreadInteractions(with: transaction)
self?.thread.remove(with: transaction) self?.thread.remove(with: transaction)
// Force a config sync and pop to the previous screen (both must run on the main thread)
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete()
}, },
completion: { [weak self] in completion: { [weak self] in
// Force a config sync and pop to the previous screen
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
DispatchQueue.main.async { DispatchQueue.main.async {
self?.navigationController?.popViewController(animated: true) self?.navigationController?.popViewController(animated: true)
} }

@ -9,7 +9,7 @@ final class LinkPreviewView : UIView {
private lazy var imageViewContainerHeightConstraint = imageView.set(.height, to: 100) private lazy var imageViewContainerHeightConstraint = imageView.set(.height, to: 100)
private lazy var sentLinkPreviewTextColor: UIColor = { private lazy var sentLinkPreviewTextColor: UIColor = {
let isOutgoing = (viewItem!.interaction.interactionType() == .outgoingMessage) let isOutgoing = (viewItem?.interaction.interactionType() == .outgoingMessage)
switch (isOutgoing, AppModeManager.shared.currentAppMode) { switch (isOutgoing, AppModeManager.shared.currentAppMode) {
case (true, .dark), (false, .light): return .black case (true, .dark), (false, .light): return .black
case (true, .light): return Colors.grey case (true, .light): return Colors.grey

@ -66,16 +66,19 @@ final class BlockedModal: Modal {
@objc private func unblock() { @objc private func unblock() {
let publicKey: String = self.publicKey let publicKey: String = self.publicKey
Storage.shared.write { transaction in Storage.shared.write(
guard let transaction = transaction as? YapDatabaseReadWriteTransaction, let contact: Contact = Storage.shared.getContact(with: publicKey, using: transaction) else { with: { transaction in
return guard let transaction = transaction as? YapDatabaseReadWriteTransaction, let contact: Contact = Storage.shared.getContact(with: publicKey, using: transaction) else {
} return
}
contact.isBlocked = false
Storage.shared.setContact(contact, using: transaction as Any)
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete() contact.isBlocked = false
} Storage.shared.setContact(contact, using: transaction as Any)
},
completion: {
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
)
presentingViewController?.dismiss(animated: true, completion: nil) presentingViewController?.dismiss(animated: true, completion: nil)
} }

@ -84,6 +84,12 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
// MARK: Lifecycle // MARK: Lifecycle
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Note: This is a hack to ensure `isRTL` is initially gets run on the main thread so the value is cached (it gets
// called on background threads and if it hasn't cached the value then it can cause odd performance issues since
// it accesses UIKit)
_ = CurrentAppContext().isRTL
// Threads (part 1) // Threads (part 1)
dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to) dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to)
// Preparation // Preparation
@ -517,10 +523,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
contact.isBlocked = true contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction as Any) Storage.shared.setContact(contact, using: transaction as Any)
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete()
}, },
completion: { completion: {
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
DispatchQueue.main.async { DispatchQueue.main.async {
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade) tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
} }
@ -537,10 +543,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
contact.isBlocked = false contact.isBlocked = false
Storage.shared.setContact(contact, using: transaction as Any) Storage.shared.setContact(contact, using: transaction as Any)
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete()
}, },
completion: { completion: {
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
DispatchQueue.main.async { DispatchQueue.main.async {
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade) tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
} }

@ -336,35 +336,38 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE", comment: ""), message: nil, preferredStyle: .actionSheet) let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE", comment: ""), message: nil, preferredStyle: .actionSheet)
alertVC.addAction(UIAlertAction(title: NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON", comment: ""), style: .destructive) { _ in alertVC.addAction(UIAlertAction(title: NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON", comment: ""), style: .destructive) { _ in
// Clear the requests // Clear the requests
Storage.write { [weak self] transaction in Storage.write(
threads.forEach { thread in with: { [weak self] transaction in
if let uniqueId: String = thread.uniqueId { threads.forEach { thread in
Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction) if let uniqueId: String = thread.uniqueId {
} Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction)
}
self?.updateContactAndThread(thread: thread, with: transaction) { threadNeedsSync in
if threadNeedsSync {
needsSync = true
}
}
self?.updateContactAndThread(thread: thread, with: transaction) { threadNeedsSync in // Block the contact
if threadNeedsSync { if
let sessionId: String = (thread as? TSContactThread)?.contactSessionID(),
!thread.isBlocked(),
let contact: Contact = Storage.shared.getContact(with: sessionId, using: transaction)
{
contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction)
needsSync = true needsSync = true
} }
} }
},
// Block the contact completion: {
if // Force a config sync
let sessionId: String = (thread as? TSContactThread)?.contactSessionID(), if needsSync {
!thread.isBlocked(), MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
let contact: Contact = Storage.shared.getContact(with: sessionId, using: transaction)
{
contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction)
needsSync = true
} }
} }
)
// Force a config sync
if needsSync {
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete()
}
}
}) })
alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel, handler: nil)) alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel, handler: nil))
self.present(alertVC, animated: true, completion: nil) self.present(alertVC, animated: true, completion: nil)
@ -375,23 +378,26 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON", comment: ""), message: nil, preferredStyle: .actionSheet) let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON", comment: ""), message: nil, preferredStyle: .actionSheet)
alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in
Storage.write { [weak self] transaction in Storage.write(
Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction) with: { [weak self] transaction in
self?.updateContactAndThread(thread: thread, with: transaction) Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction)
self?.updateContactAndThread(thread: thread, with: transaction)
// Block the contact
if
let sessionId: String = (thread as? TSContactThread)?.contactSessionID(),
!thread.isBlocked(),
let contact: Contact = Storage.shared.getContact(with: sessionId, using: transaction)
{
contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction)
}
// Force a config sync // Block the contact
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete() if
} let sessionId: String = (thread as? TSContactThread)?.contactSessionID(),
!thread.isBlocked(),
let contact: Contact = Storage.shared.getContact(with: sessionId, using: transaction)
{
contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction)
}
},
completion: {
// Force a config sync
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
)
}) })
alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel, handler: nil)) alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel, handler: nil))
self.present(alertVC, animated: true, completion: nil) self.present(alertVC, animated: true, completion: nil)

@ -166,6 +166,7 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
- (BOOL)isRTL - (BOOL)isRTL
{ {
// FIXME: We should try to remove this as we've had to add a hack to ensure the first call to this runs on the main thread
static BOOL isRTL = NO; static BOOL isRTL = NO;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{

@ -127,7 +127,7 @@ final class NukeDataModal : Modal {
MessageSender.syncConfiguration(forceSyncNow: true).ensure(on: DispatchQueue.main) { MessageSender.syncConfiguration(forceSyncNow: true).ensure(on: DispatchQueue.main) {
self?.dismiss(animated: true, completion: nil) // Dismiss the loader self?.dismiss(animated: true, completion: nil) // Dismiss the loader
UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
General.Cache.cachedEncodedPublicKey = nil // Remove the cached key so it gets re-cached on next access General.Cache.cachedEncodedPublicKey.mutate { $0 = nil } // Remove the cached key so it gets re-cached on next access
NotificationCenter.default.post(name: .dataNukeRequested, object: nil) NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
}.retainUntilComplete() }.retainUntilComplete()
} }
@ -139,7 +139,7 @@ final class NukeDataModal : Modal {
self?.dismiss(animated: true, completion: nil) // Dismiss the loader self?.dismiss(animated: true, completion: nil) // Dismiss the loader
let potentiallyMaliciousSnodes = confirmations.compactMap { $0.value == false ? $0.key : nil } let potentiallyMaliciousSnodes = confirmations.compactMap { $0.value == false ? $0.key : nil }
if potentiallyMaliciousSnodes.isEmpty { if potentiallyMaliciousSnodes.isEmpty {
General.Cache.cachedEncodedPublicKey = nil // Remove the cached key so it gets re-cached on next access General.Cache.cachedEncodedPublicKey.mutate { $0 = nil } // Remove the cached key so it gets re-cached on next access
UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
NotificationCenter.default.post(name: .dataNukeRequested, object: nil) NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
} else { } else {

@ -10,13 +10,19 @@ extension Storage {
private static let closedGroupZombieMembersCollection = "SNClosedGroupZombieMembersCollection" private static let closedGroupZombieMembersCollection = "SNClosedGroupZombieMembersCollection"
public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [ECKeyPair] { public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [ECKeyPair] {
var result: [ECKeyPair] = []
Storage.read { transaction in
result = self.getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction)
}
return result
}
public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String, using transaction: YapDatabaseReadTransaction) -> [ECKeyPair] {
let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey) let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey)
var timestampsAndKeyPairs: [(timestamp: Double, keyPair: ECKeyPair)] = [] var timestampsAndKeyPairs: [(timestamp: Double, keyPair: ECKeyPair)] = []
Storage.read { transaction in transaction.enumerateKeysAndObjects(inCollection: collection) { key, object, _ in
transaction.enumerateKeysAndObjects(inCollection: collection) { key, object, _ in guard let timestamp = Double(key), let keyPair = object as? ECKeyPair else { return }
guard let timestamp = Double(key), let keyPair = object as? ECKeyPair else { return } timestampsAndKeyPairs.append((timestamp, keyPair))
timestampsAndKeyPairs.append((timestamp, keyPair))
}
} }
return timestampsAndKeyPairs.sorted { $0.timestamp < $1.timestamp }.map { $0.keyPair } return timestampsAndKeyPairs.sorted { $0.timestamp < $1.timestamp }.map { $0.keyPair }
} }
@ -25,6 +31,10 @@ extension Storage {
return getClosedGroupEncryptionKeyPairs(for: groupPublicKey).last return getClosedGroupEncryptionKeyPairs(for: groupPublicKey).last
} }
public func getLatestClosedGroupEncryptionKeyPair(for groupPublicKey: String, using transaction: YapDatabaseReadTransaction) -> ECKeyPair? {
return getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction).last
}
public func addClosedGroupEncryptionKeyPair(_ keyPair: ECKeyPair, for groupPublicKey: String, using transaction: Any) { public func addClosedGroupEncryptionKeyPair(_ keyPair: ECKeyPair, for groupPublicKey: String, using transaction: Any) {
let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey) let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey)
let timestamp = String(Date().timeIntervalSince1970) let timestamp = String(Date().timeIntervalSince1970)
@ -39,11 +49,15 @@ extension Storage {
public func getUserClosedGroupPublicKeys() -> Set<String> { public func getUserClosedGroupPublicKeys() -> Set<String> {
var result: Set<String> = [] var result: Set<String> = []
Storage.read { transaction in Storage.read { transaction in
result = Set(transaction.allKeys(inCollection: Storage.closedGroupPublicKeyCollection)) result = self.getUserClosedGroupPublicKeys(using: transaction)
} }
return result return result
} }
public func getUserClosedGroupPublicKeys(using transaction: YapDatabaseReadTransaction) -> Set<String> {
return Set(transaction.allKeys(inCollection: Storage.closedGroupPublicKeyCollection))
}
public func addClosedGroupPublicKey(_ groupPublicKey: String, using transaction: Any) { public func addClosedGroupPublicKey(_ groupPublicKey: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(groupPublicKey, forKey: groupPublicKey, inCollection: Storage.closedGroupPublicKeyCollection) (transaction as! YapDatabaseReadWriteTransaction).setObject(groupPublicKey, forKey: groupPublicKey, inCollection: Storage.closedGroupPublicKeyCollection)
} }
@ -81,4 +95,8 @@ extension Storage {
public func isClosedGroup(_ publicKey: String) -> Bool { public func isClosedGroup(_ publicKey: String) -> Bool {
getUserClosedGroupPublicKeys().contains(publicKey) getUserClosedGroupPublicKeys().contains(publicKey)
} }
public func isClosedGroup(_ publicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool {
getUserClosedGroupPublicKeys(using: transaction).contains(publicKey)
}
} }

@ -36,11 +36,21 @@ extension Storage {
} }
@objc public func getUser() -> Contact? { @objc public func getUser() -> Contact? {
guard let userPublicKey = getUserPublicKey() else { return nil } return getUser(using: nil)
}
public func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact? {
let userPublicKey = getUserHexEncodedPublicKey()
var result: Contact? var result: Contact?
Storage.read { transaction in
if let transaction = transaction {
result = Storage.shared.getContact(with: userPublicKey, using: transaction) result = Storage.shared.getContact(with: userPublicKey, using: transaction)
} }
else {
Storage.read { transaction in
result = Storage.shared.getContact(with: userPublicKey, using: transaction)
}
}
return result return result
} }
} }

@ -2,9 +2,9 @@ import SessionUtilitiesKit
extension ConfigurationMessage { extension ConfigurationMessage {
public static func getCurrent(with transaction: YapDatabaseReadWriteTransaction? = nil) -> ConfigurationMessage? { public static func getCurrent(with transaction: YapDatabaseReadTransaction) -> ConfigurationMessage? {
let storage = Storage.shared let storage = Storage.shared
guard let user = storage.getUser() else { return nil } guard let user = storage.getUser(using: transaction) else { return nil }
let displayName = user.name let displayName = user.name
let profilePictureURL = user.profilePictureURL let profilePictureURL = user.profilePictureURL
@ -13,92 +13,85 @@ extension ConfigurationMessage {
var openGroups: Set<String> = [] var openGroups: Set<String> = []
var contacts: Set<ConfigurationMessage.Contact> = [] var contacts: Set<ConfigurationMessage.Contact> = []
let populateDataClosure: (YapDatabaseReadTransaction) -> () = { transaction in TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in
TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in guard let thread = object as? TSGroupThread else { return }
guard let thread = object as? TSGroupThread else { return }
switch thread.groupModel.groupType {
switch thread.groupModel.groupType { case .closedGroup:
case .closedGroup: guard thread.isCurrentUserMemberInGroup() else { return }
guard thread.isCurrentUserMemberInGroup() else { return }
let groupID = thread.groupModel.groupId
let groupID = thread.groupModel.groupId let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
guard
guard storage.isClosedGroup(groupPublicKey), let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { storage.isClosedGroup(groupPublicKey, using: transaction),
return let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey, using: transaction)
} else {
return
let closedGroup = ClosedGroup( }
publicKey: groupPublicKey,
name: thread.groupModel.groupName!, let closedGroup = ClosedGroup(
encryptionKeyPair: encryptionKeyPair, publicKey: groupPublicKey,
members: Set(thread.groupModel.groupMemberIds), name: (thread.groupModel.groupName ?? ""),
admins: Set(thread.groupModel.groupAdminIds), encryptionKeyPair: encryptionKeyPair,
expirationTimer: thread.disappearingMessagesDuration(with: transaction) members: Set(thread.groupModel.groupMemberIds),
) admins: Set(thread.groupModel.groupAdminIds),
closedGroups.insert(closedGroup) expirationTimer: thread.disappearingMessagesDuration(with: transaction)
)
case .openGroup: closedGroups.insert(closedGroup)
if let v2OpenGroup = storage.getV2OpenGroup(for: thread.uniqueId!) {
openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)") case .openGroup:
} if let threadId: String = thread.uniqueId, let v2OpenGroup = storage.getV2OpenGroup(for: threadId) {
openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)")
default: break }
}
default: break
} }
}
let currentUserPublicKey: String = getUserHexEncodedPublicKey() let currentUserPublicKey: String = getUserHexEncodedPublicKey()
contacts = storage.getAllContacts(with: transaction) contacts = storage.getAllContacts(with: transaction)
.filter { contact -> Bool in .compactMap { contact -> ConfigurationMessage.Contact? in
let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID) let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID)
return ( guard
// Skip the current user // Skip the current user
contact.sessionID != currentUserPublicKey && contact.sessionID != currentUserPublicKey &&
// Contacts which have visible threads // Contacts which have visible threads
TSContactThread.fetch(uniqueId: threadID, transaction: transaction)?.shouldBeVisible == true && ( TSContactThread.fetch(uniqueId: threadID, transaction: transaction)?.shouldBeVisible == true && (
// Include already approved contacts // Include already approved contacts
contact.isApproved || contact.isApproved ||
contact.didApproveMe || contact.didApproveMe ||
// Sync blocked contacts // Sync blocked contacts
contact.isBlocked || contact.isBlocked ||
contact.hasBeenBlocked contact.hasBeenBlocked
)
) )
else {
return nil
} }
.map { contact -> ConfigurationMessage.Contact in
// Can just default the 'hasX' values to true as they will be set to this
// when converting to proto anyway
let profilePictureURL = contact.profilePictureURL
let profileKey = contact.profileEncryptionKey?.keyData
return ConfigurationMessage.Contact(
publicKey: contact.sessionID,
displayName: (contact.name ?? contact.sessionID),
profilePictureURL: profilePictureURL,
profileKey: profileKey,
hasIsApproved: true,
isApproved: contact.isApproved,
hasIsBlocked: true,
isBlocked: contact.isBlocked,
hasDidApproveMe: true,
didApproveMe: contact.didApproveMe
)
}
.asSet()
}
// If we are provided with a transaction then read the data based on the state of the database // Can just default the 'hasX' values to true as they will be set to this
// from within the transaction rather than the state in disk // when converting to proto anyway
if let transaction: YapDatabaseReadWriteTransaction = transaction { let profilePictureURL = contact.profilePictureURL
populateDataClosure(transaction) let profileKey = contact.profileEncryptionKey?.keyData
}
else { return ConfigurationMessage.Contact(
Storage.read { transaction in populateDataClosure(transaction) } publicKey: contact.sessionID,
} displayName: (contact.name ?? contact.sessionID),
profilePictureURL: profilePictureURL,
profileKey: profileKey,
hasIsApproved: true,
isApproved: contact.isApproved,
hasIsBlocked: true,
isBlocked: contact.isBlocked,
hasDidApproveMe: true,
didApproveMe: contact.didApproveMe
)
}
.asSet()
return ConfigurationMessage( return ConfigurationMessage(
displayName: displayName, displayName: displayName,

@ -803,7 +803,7 @@ extension MessageReceiver {
// Force a config sync to ensure all devices know the contact approval state if desired // Force a config sync to ensure all devices know the contact approval state if desired
guard forceConfigSync else { return } guard forceConfigSync else { return }
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete() MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
} }
public static func handleMessageRequestResponse(_ message: MessageRequestResponse, using transaction: Any) { public static func handleMessageRequestResponse(_ message: MessageRequestResponse, using transaction: Any) {

@ -17,15 +17,18 @@ public protocol SessionMessagingKitStorageProtocol {
func getUserKeyPair() -> ECKeyPair? func getUserKeyPair() -> ECKeyPair?
func getUserED25519KeyPair() -> Box.KeyPair? func getUserED25519KeyPair() -> Box.KeyPair?
func getUser() -> Contact? func getUser() -> Contact?
func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact?
func getAllContacts() -> Set<Contact> func getAllContacts() -> Set<Contact>
func getAllContacts(with transaction: YapDatabaseReadTransaction) -> Set<Contact> func getAllContacts(with transaction: YapDatabaseReadTransaction) -> Set<Contact>
// MARK: - Closed Groups // MARK: - Closed Groups
func getUserClosedGroupPublicKeys() -> Set<String> func getUserClosedGroupPublicKeys() -> Set<String>
func getUserClosedGroupPublicKeys(using transaction: YapDatabaseReadTransaction) -> Set<String>
func getZombieMembers(for groupPublicKey: String) -> Set<String> func getZombieMembers(for groupPublicKey: String) -> Set<String>
func setZombieMembers(for groupPublicKey: String, to zombies: Set<String>, using transaction: Any) func setZombieMembers(for groupPublicKey: String, to zombies: Set<String>, using transaction: Any)
func isClosedGroup(_ publicKey: String) -> Bool func isClosedGroup(_ publicKey: String) -> Bool
func isClosedGroup(_ publicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool
// MARK: - Jobs // MARK: - Jobs

@ -2,7 +2,7 @@ import Foundation
public enum General { public enum General {
public enum Cache { public enum Cache {
public static var cachedEncodedPublicKey: String? = nil public static var cachedEncodedPublicKey: Atomic<String?> = Atomic(nil)
} }
} }
@ -14,10 +14,10 @@ public class GeneralUtilities: NSObject {
} }
public func getUserHexEncodedPublicKey() -> String { public func getUserHexEncodedPublicKey() -> String {
if let cachedKey: String = General.Cache.cachedEncodedPublicKey { return cachedKey } if let cachedKey: String = General.Cache.cachedEncodedPublicKey.wrappedValue { return cachedKey }
if let keyPair = OWSIdentityManager.shared().identityKeyPair() { // Can be nil under some circumstances if let keyPair = OWSIdentityManager.shared().identityKeyPair() { // Can be nil under some circumstances
General.Cache.cachedEncodedPublicKey = keyPair.hexEncodedPublicKey General.Cache.cachedEncodedPublicKey.mutate { $0 = keyPair.hexEncodedPublicKey }
return keyPair.hexEncodedPublicKey return keyPair.hexEncodedPublicKey
} }

@ -162,7 +162,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
message.sentTimestamp = NSDate.millisecondTimestamp() message.sentTimestamp = NSDate.millisecondTimestamp()
message.text = (isSharingUrl && (messageText?.isEmpty == true || attachments[0].linkPreviewDraft == nil) ? message.text = (isSharingUrl && (messageText?.isEmpty == true || attachments[0].linkPreviewDraft == nil) ?
( (
(messageText?.isEmpty == true ? (messageText?.isEmpty == true || (attachments[0].text() == messageText) ?
attachments[0].text() : attachments[0].text() :
"\(attachments[0].text() ?? "")\n\n\(messageText ?? "")" "\(attachments[0].text() ?? "")\n\n\(messageText ?? "")"
) )

@ -0,0 +1,44 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
// MARK: - Atomic<Value>
/// The `Atomic<Value>` wrapper is a generic wrapper providing a thread-safe way to get and set a value
///
/// A write-up on the need for this class and it's approach can be found here:
/// https://www.vadimbulavin.com/swift-atomic-properties-with-property-wrappers/
/// there is also another approach which can be taken but it requires separate types for collections and results in
/// a somewhat inconsistent interface between different `Atomic` wrappers
@propertyWrapper
public class Atomic<Value> {
private let queue: DispatchQueue = DispatchQueue(label: "io.oxen.\(UUID().uuidString)")
private var value: Value
/// In order to change the value you **must** use the `mutate` function
public var wrappedValue: Value {
return queue.sync { return value }
}
/// For more information see https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#projections
public var projectedValue: Atomic<Value> {
return self
}
// MARK: - Initialization
public init(_ initialValue: Value) {
self.value = initialValue
}
// MARK: - Functions
public func mutate(_ mutation: (inout Value) -> Void) {
return queue.sync {
mutation(&value)
}
}
}
extension Atomic where Value: CustomDebugStringConvertible {
var debugDescription: String {
return value.debugDescription
}
}

@ -1,4 +1,5 @@
import PromiseKit import PromiseKit
import SessionUtilitiesKit
extension MessageSender { extension MessageSender {
@ -105,14 +106,16 @@ extension MessageSender {
return promise return promise
} }
public static func syncConfiguration(forceSyncNow: Bool = true, with transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise<Void> { public static func syncConfiguration(forceSyncNow: Bool = true) -> Promise<Void> {
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
return Promise.value(())
}
let (promise, seal) = Promise<Void>.pending() let (promise, seal) = Promise<Void>.pending()
let sendMessage: (YapDatabaseReadTransaction) -> () = { transaction in let destination: Message.Destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
let destination: Message.Destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
// Note: SQLite only supports a single write thread so we can be sure this will retrieve the most up-to-date data
Storage.writeSync { transaction in
guard Storage.shared.getUser(using: transaction)?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
seal.fulfill(())
return
}
if forceSyncNow { if forceSyncNow {
MessageSender.send(configurationMessage, to: destination, using: transaction).done { MessageSender.send(configurationMessage, to: destination, using: transaction).done {
@ -128,15 +131,6 @@ extension MessageSender {
} }
} }
// If we are provided with a transaction then read the data based on the state of the database
// from within the transaction rather than the state in disk
if let transaction: YapDatabaseReadWriteTransaction = transaction {
sendMessage(transaction)
}
else {
Storage.writeSync { transaction in sendMessage(transaction) }
}
return promise return promise
} }
} }
@ -144,6 +138,6 @@ extension MessageSender {
extension MessageSender { extension MessageSender {
@objc(forceSyncConfigurationNow) @objc(forceSyncConfigurationNow)
public static func objc_forceSyncConfigurationNow() { public static func objc_forceSyncConfigurationNow() {
return syncConfiguration(forceSyncNow: true, with: nil).retainUntilComplete() return syncConfiguration(forceSyncNow: true).retainUntilComplete()
} }
} }

Loading…
Cancel
Save