From e9fc5b748e54f6512d62b2cd4e4de1883e7f4970 Mon Sep 17 00:00:00 2001
From: nielsandriesse <andriesseniels@gmail.com>
Date: Thu, 11 Jun 2020 12:12:42 +1000
Subject: [PATCH] Integrate transaction handling utility part 1

---
 .../src/Loki/Shelved/LokiRSSFeedPoller.swift  |  2 +-
 .../View Controllers/DeviceLinkingModal.swift |  9 ++-
 .../Loki/View Controllers/DeviceLinksVC.swift |  4 +-
 .../View Controllers/JoinPublicChatVC.swift   |  2 +-
 .../src/Loki/API/LokiAPI+SwarmAPI.swift       | 40 ++++---------
 SignalServiceKit/src/Loki/API/LokiAPI.swift   |  6 +-
 .../src/Loki/API/LokiDotNetAPI.swift          | 14 ++---
 .../src/Loki/API/LokiFileServerAPI.swift      | 34 ++---------
 .../API/Onion Requests/OnionRequestAPI.swift  | 18 ++----
 .../API/Open Groups/LokiPublicChatAPI.swift   | 12 ++--
 .../Open Groups/LokiPublicChatManager.swift   |  4 +-
 .../Open Groups/LokiPublicChatPoller.swift    |  7 +--
 .../Database/OWSPrimaryStorage+Loki.swift     | 56 ++++++-------------
 .../Multi Device/MultiDeviceProtocol.swift    | 20 +++----
 .../SessionManagementProtocol.swift           | 16 +++---
 15 files changed, 80 insertions(+), 164 deletions(-)

diff --git a/Signal/src/Loki/Shelved/LokiRSSFeedPoller.swift b/Signal/src/Loki/Shelved/LokiRSSFeedPoller.swift
index 62a0d0341..f29e3a884 100644
--- a/Signal/src/Loki/Shelved/LokiRSSFeedPoller.swift
+++ b/Signal/src/Loki/Shelved/LokiRSSFeedPoller.swift
@@ -63,7 +63,7 @@ public final class LokiRSSFeedPoller : NSObject {
                     envelope.setSource(NSLocalizedString("Loki", comment: ""))
                     envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
                     envelope.setContent(try! content.build().serializedData())
-                    OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
+                    try! Storage.syncWrite { transaction in
                         SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0)
                     }
                 }
diff --git a/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift b/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift
index b35c590a6..5aa389ca1 100644
--- a/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift	
+++ b/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift	
@@ -186,7 +186,7 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
             SSKEnvironment.shared.messageSender.send(linkingAuthorizationMessage, success: {
                 let storage = OWSPrimaryStorage.shared()
                 let slaveHexEncodedPublicKey = deviceLink.slave.hexEncodedPublicKey
-                storage.dbReadWriteConnection.readWrite { transaction in
+                try! Storage.syncWrite { transaction in
                     let thread = TSContactThread.getOrCreateThread(withContactId: slaveHexEncodedPublicKey, transaction: transaction)
                     thread.save(with: transaction)
                 }
@@ -196,7 +196,7 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
                     let _ = SSKEnvironment.shared.syncManager.syncAllContacts()
                 }
                 let _ = SSKEnvironment.shared.syncManager.syncAllOpenGroups()
-                storage.dbReadWriteConnection.readWrite { transaction in
+                try! Storage.syncWrite { transaction in
                     storage.setFriendRequestStatus(.friends, for: slaveHexEncodedPublicKey, transaction: transaction)
                 }
                 DispatchQueue.main.async {
@@ -251,9 +251,8 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
         session.markLinkingRequestAsProcessed() // Only relevant in master mode
         delegate?.handleDeviceLinkingModalDismissed() // Only relevant in slave mode
         if let deviceLink = deviceLink {
-            let storage = OWSPrimaryStorage.shared()
-            storage.dbReadWriteConnection.readWrite { transaction in
-                storage.removePreKeyBundle(forContact: deviceLink.slave.hexEncodedPublicKey, transaction: transaction)
+            try! Storage.syncWrite { transaction in
+                OWSPrimaryStorage.shared().removePreKeyBundle(forContact: deviceLink.slave.hexEncodedPublicKey, transaction: transaction)
             }
         }
         dismiss(animated: true, completion: nil)
diff --git a/Signal/src/Loki/View Controllers/DeviceLinksVC.swift b/Signal/src/Loki/View Controllers/DeviceLinksVC.swift
index 167a72303..85554b380 100644
--- a/Signal/src/Loki/View Controllers/DeviceLinksVC.swift	
+++ b/Signal/src/Loki/View Controllers/DeviceLinksVC.swift	
@@ -146,14 +146,14 @@ final class DeviceLinksVC : BaseVC, UITableViewDataSource, UITableViewDelegate,
             let unlinkDeviceMessage = UnlinkDeviceMessage(thread: thread)
             SSKEnvironment.shared.messageSender.send(unlinkDeviceMessage, success: {
                 let storage = OWSPrimaryStorage.shared()
-                storage.dbReadWriteConnection.readWrite { transaction in
+                try! Storage.syncWrite { transaction in
                     storage.removePreKeyBundle(forContact: linkedDeviceHexEncodedPublicKey, transaction: transaction)
                     storage.deleteAllSessions(forContact: linkedDeviceHexEncodedPublicKey, protocolContext: transaction)
                 }
             }, failure: { _ in
                 print("[Loki] Failed to send unlink device message.")
                 let storage = OWSPrimaryStorage.shared()
-                storage.dbReadWriteConnection.readWrite { transaction in
+                try! Storage.syncWrite { transaction in
                     storage.removePreKeyBundle(forContact: linkedDeviceHexEncodedPublicKey, transaction: transaction)
                     storage.deleteAllSessions(forContact: linkedDeviceHexEncodedPublicKey, protocolContext: transaction)
                 }
diff --git a/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift b/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift
index 149ca58c8..9b0ad0188 100644
--- a/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift	
+++ b/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift	
@@ -135,7 +135,7 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
         let urlAsString = url.absoluteString
         let displayName = OWSProfileManager.shared().localProfileName()
         // TODO: Profile picture & profile key
-        OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastMessageServerIDCollection)
             transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastDeletionServerIDCollection)
         }
diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift
index 4cf2d374d..800758a59 100644
--- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift
+++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift
@@ -53,12 +53,9 @@ public extension LokiAPI {
                 }
             }.done(on: DispatchQueue.global()) { snode in
                 seal.fulfill(snode)
-                // Dispatch async on the main queue to avoid nested write transactions
-                DispatchQueue.main.async {
-                    storage.dbReadWriteConnection.readWrite { transaction in
-                        print("[Loki] Persisting snode pool to database.")
-                        storage.setSnodePool(LokiAPI.snodePool, in: transaction)
-                    }
+                try! Storage.syncWrite { transaction in
+                    print("[Loki] Persisting snode pool to database.")
+                    storage.setSnodePool(LokiAPI.snodePool, in: transaction)
                 }
             }.catch(on: DispatchQueue.global()) { error in
                 print("[Loki] Failed to contact seed node at: \(target).")
@@ -90,12 +87,8 @@ public extension LokiAPI {
                 parseTargets(from: $0)
             }.get { swarm in
                 swarmCache[hexEncodedPublicKey] = swarm
-                // Dispatch async on the main queue to avoid nested write transactions
-                DispatchQueue.main.async {
-                    let storage = OWSPrimaryStorage.shared()
-                    storage.dbReadWriteConnection.readWrite { transaction in
-                        storage.setSwarm(swarm, for: hexEncodedPublicKey, in: transaction)
-                    }
+                try! Storage.syncWrite { transaction in
+                    storage.setSwarm(swarm, for: hexEncodedPublicKey, in: transaction)
                 }
             }
         }
@@ -108,12 +101,8 @@ public extension LokiAPI {
 
     internal static func dropSnodeFromSnodePool(_ target: LokiAPITarget) {
         LokiAPI.snodePool.remove(target)
-        // Dispatch async on the main queue to avoid nested write transactions
-        DispatchQueue.main.async {
-            let storage = OWSPrimaryStorage.shared()
-            storage.dbReadWriteConnection.readWrite { transaction in
-                storage.dropSnodeFromSnodePool(target, in: transaction)
-            }
+        try! Storage.syncWrite { transaction in
+            storage.dropSnodeFromSnodePool(target, in: transaction)
         }
     }
 
@@ -122,12 +111,8 @@ public extension LokiAPI {
         if var swarm = swarm, let index = swarm.firstIndex(of: target) {
             swarm.remove(at: index)
             LokiAPI.swarmCache[hexEncodedPublicKey] = swarm
-            // Dispatch async on the main queue to avoid nested write transactions
-            DispatchQueue.main.async {
-                let storage = OWSPrimaryStorage.shared()
-                storage.dbReadWriteConnection.readWrite { transaction in
-                    storage.setSwarm(swarm, for: hexEncodedPublicKey, in: transaction)
-                }
+            try! Storage.syncWrite { transaction in
+                storage.setSwarm(swarm, for: hexEncodedPublicKey, in: transaction)
             }
         }
     }
@@ -135,11 +120,8 @@ public extension LokiAPI {
     // MARK: Public API
     @objc public static func clearSnodePool() {
         snodePool.removeAll()
-        // Dispatch async on the main queue to avoid nested write transactions
-        DispatchQueue.main.async {
-            storage.dbReadWriteConnection.readWrite { transaction in
-                storage.clearSnodePool(in: transaction)
-            }
+        try! Storage.syncWrite { transaction in
+            storage.clearSnodePool(in: transaction)
         }
     }
 
diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift
index 4bd85fc5b..e8cdd58e9 100644
--- a/SignalServiceKit/src/Loki/API/LokiAPI.swift
+++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift
@@ -194,14 +194,14 @@ public final class LokiAPI : NSObject {
         var result: String? = nil
         // Uses a read/write connection because getting the last message hash value also removes expired messages as needed
         // TODO: This shouldn't be the case; a getter shouldn't have an unexpected side effect
-        storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             result = storage.getLastMessageHash(forSnode: target.address, transaction: transaction)
         }
         return result
     }
 
     private static func setLastMessageHashValue(for target: LokiAPITarget, hashValue: String, expirationDate: UInt64) {
-        storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             storage.setLastMessageHash(forSnode: target.address, hash: hashValue, expiresAt: expirationDate, transaction: transaction)
         }
     }
@@ -218,7 +218,7 @@ public final class LokiAPI : NSObject {
     }
 
     private static func setReceivedMessageHashValues(to receivedMessageHashValues: Set<String>) {
-        storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             transaction.setObject(receivedMessageHashValues, forKey: receivedMessageHashValuesKey, inCollection: receivedMessageHashValuesCollection)
         }
     }
diff --git a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift
index 08071be7f..22cc15947 100644
--- a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift
+++ b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift
@@ -38,11 +38,8 @@ public class LokiDotNetAPI : NSObject {
             return Promise.value(token)
         } else {
             return requestNewAuthToken(for: server).then(on: DispatchQueue.global()) { submitAuthToken($0, for: server) }.map(on: DispatchQueue.global()) { token in
-                // Dispatch async on the main queue to avoid nested write transactions
-                DispatchQueue.main.async {
-                    storage.dbReadWriteConnection.readWrite { transaction in
-                        setAuthToken(for: server, to: token, in: transaction)
-                    }
+                try! Storage.syncWrite { transaction in
+                    setAuthToken(for: server, to: token, in: transaction)
                 }
                 return token
             }
@@ -54,11 +51,8 @@ public class LokiDotNetAPI : NSObject {
     }
 
     public static func clearAuthToken(for server: String) {
-        // Dispatch async on the main queue to avoid nested write transactions
-        DispatchQueue.main.async {
-            storage.dbReadWriteConnection.readWrite { transaction in
-                transaction.removeObject(forKey: server, inCollection: authTokenCollection)
-            }
+        try! Storage.syncWrite { transaction in
+            transaction.removeObject(forKey: server, inCollection: authTokenCollection)
         }
     }
 
diff --git a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift
index f50b18dfc..d0eabc4dc 100644
--- a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift
+++ b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift
@@ -81,15 +81,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
                     }
                 })
             }.map(on: DispatchQueue.global()) { deviceLinks in
-                storage.cacheDeviceLinks(deviceLinks)
-                /*
-                // Dispatch async on the main queue to avoid nested write transactions
-                DispatchQueue.main.async {
-                    storage.dbReadWriteConnection.readWrite { transaction in
-                        storage.setDeviceLinks(deviceLinks, in: transaction)
-                    }
-                }
-                 */
+                storage.setDeviceLinks(deviceLinks)
                 return deviceLinks
             }
         }.handlingInvalidAuthTokenIfNeeded(for: server)
@@ -122,16 +114,8 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
             deviceLinks = storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction)
         }
         deviceLinks.insert(deviceLink)
-        return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise<Void> in
-            let (promise, seal) = Promise<Void>.pending()
-            // Dispatch async on the main queue to avoid nested write transactions
-            DispatchQueue.main.async {
-                storage.dbReadWriteConnection.readWrite { transaction in
-                    storage.addDeviceLink(deviceLink, in: transaction)
-                }
-                seal.fulfill(())
-            }
-            return promise
+        return setDeviceLinks(deviceLinks).map(on: LokiAPI.workQueue) { _ in
+            storage.addDeviceLink(deviceLink)
         }
     }
 
@@ -142,16 +126,8 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
             deviceLinks = storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction)
         }
         deviceLinks.remove(deviceLink)
-        return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise<Void> in
-            let (promise, seal) = Promise<Void>.pending()
-            // Dispatch async on the main queue to avoid nested write transactions
-            DispatchQueue.main.async {
-                storage.dbReadWriteConnection.readWrite { transaction in
-                    storage.removeDeviceLink(deviceLink, in: transaction)
-                }
-                seal.fulfill(())
-            }
-            return promise
+        return setDeviceLinks(deviceLinks).map(on: LokiAPI.workQueue) { _ in
+            storage.removeDeviceLink(deviceLink)
         }
     }
     
diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift
index 585a1b085..9ed5fb6f7 100644
--- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift	
+++ b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift	
@@ -123,13 +123,11 @@ public enum OnionRequestAPI {
                 }
             }.map(on: LokiAPI.workQueue) { paths in
                 OnionRequestAPI.paths = paths
-                // Dispatch async on the main queue to avoid nested write transactions
+                try! Storage.syncWrite { transaction in
+                    print("[Loki] Persisting onion request paths to database.")
+                    OWSPrimaryStorage.shared().setOnionRequestPaths(paths, in: transaction)
+                }
                 DispatchQueue.main.async {
-                    let storage = OWSPrimaryStorage.shared()
-                    storage.dbReadWriteConnection.readWrite { transaction in
-                        print("[Loki] Persisting onion request paths to database.")
-                        storage.setOnionRequestPaths(paths, in: transaction)
-                    }
                     NotificationCenter.default.post(name: .pathsBuilt, object: nil)
                 }
                 return paths
@@ -165,12 +163,8 @@ public enum OnionRequestAPI {
 
     private static func dropAllPaths() {
         paths.removeAll()
-        // Dispatch async on the main queue to avoid nested write transactions
-        DispatchQueue.main.async {
-            let storage = OWSPrimaryStorage.shared()
-            storage.dbReadWriteConnection.readWrite { transaction in
-                storage.clearOnionRequestPaths(in: transaction)
-            }
+        try! Storage.syncWrite { transaction in
+            OWSPrimaryStorage.shared().clearOnionRequestPaths(in: transaction)
         }
     }
 
diff --git a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift
index d58ce8356..815002664 100644
--- a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift	
+++ b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift	
@@ -37,13 +37,13 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
     }
     
     private static func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64) {
-        storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             transaction.setObject(newValue, forKey: "\(server).\(group)", inCollection: lastMessageServerIDCollection)
         }
     }
     
     private static func removeLastMessageServerID(for group: UInt64, on server: String) {
-        storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             transaction.removeObject(forKey: "\(server).\(group)", inCollection: lastMessageServerIDCollection)
         }
     }
@@ -57,13 +57,13 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
     }
     
     private static func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64) {
-        storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             transaction.setObject(newValue, forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection)
         }
     }
     
     private static func removeLastDeletionServerID(for group: UInt64, on server: String) {
-        storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             transaction.removeObject(forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection)
         }
     }
@@ -271,7 +271,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
                     print("[Loki] Couldn't parse display names for users: \(hexEncodedPublicKeys) from: \(rawResponse).")
                     throw LokiDotNetAPIError.parsingFailed
                 }
-                storage.dbReadWriteConnection.readWrite { transaction in
+                try! Storage.syncWrite { transaction in
                     data.forEach { data in
                         guard let user = data["user"] as? JSON, let hexEncodedPublicKey = user["username"] as? String, let rawDisplayName = user["name"] as? String else { return }
                         let endIndex = hexEncodedPublicKey.endIndex
@@ -355,7 +355,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
                         throw LokiDotNetAPIError.parsingFailed
                     }
                     let storage = OWSPrimaryStorage.shared()
-                    storage.dbReadWriteConnection.readWrite { transaction in
+                    try! Storage.syncWrite { transaction in
                         storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction)
                     }
                     // TODO: Use this to update open group names as needed
diff --git a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatManager.swift b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatManager.swift
index 1d1bf5e0e..a04e20975 100644
--- a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatManager.swift	
+++ b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatManager.swift	
@@ -71,7 +71,7 @@ public final class LokiPublicChatManager : NSObject {
         let model = TSGroupModel(title: chat.displayName, memberIds: [userHexEncodedPublicKey!, chat.server], image: nil, groupId: LKGroupUtilities.getEncodedOpenGroupIDAsData(chat.id), groupType: .openGroup, adminIds: [])
         
         // Store the group chat mapping
-        self.storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
            
             // Save the group chat
@@ -118,7 +118,7 @@ public final class LokiPublicChatManager : NSObject {
         }
         
         // Remove the chat from the db
-        storage.dbReadWriteConnection.readWrite { transaction in
+        try! Storage.syncWrite { transaction in
             LokiDatabaseUtilities.removePublicChat(for: threadId, in: transaction)
         }
 
diff --git a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatPoller.swift
index 974eb814b..9182ce78d 100644
--- a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatPoller.swift	
+++ b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatPoller.swift	
@@ -163,7 +163,7 @@ public final class LokiPublicChatPoller : NSObject {
                     envelope.setSource(senderHexEncodedPublicKey)
                     envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
                     envelope.setContent(try! content.build().serializedData())
-                    storage.dbReadWriteConnection.readWrite { transaction in
+                    try! Storage.syncWrite { transaction in
                         transaction.setObject(senderDisplayName, forKey: senderHexEncodedPublicKey, inCollection: publicChat.id)
                         let messageServerID = message.serverID
                         SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: messageServerID ?? 0)
@@ -213,9 +213,8 @@ public final class LokiPublicChatPoller : NSObject {
     private func pollForDeletedMessages() {
         let publicChat = self.publicChat
         let _ = LokiPublicChatAPI.getDeletedMessageServerIDs(for: publicChat.channel, on: publicChat.server).done(on: DispatchQueue.global()) { deletedMessageServerIDs in
-            let storage = OWSPrimaryStorage.shared()
-            storage.dbReadWriteConnection.readWrite { transaction in
-                let deletedMessageIDs = deletedMessageServerIDs.compactMap { storage.getIDForMessage(withServerID: UInt($0), in: transaction) }
+            try! Storage.syncWrite { transaction in
+                let deletedMessageIDs = deletedMessageServerIDs.compactMap { OWSPrimaryStorage.shared().getIDForMessage(withServerID: UInt($0), in: transaction) }
                 deletedMessageIDs.forEach { messageID in
                     TSMessage.fetch(uniqueId: messageID)?.remove(with: transaction)
                 }
diff --git a/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift b/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift
index c69a996a5..f4d07f4ab 100644
--- a/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift
+++ b/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift
@@ -4,22 +4,20 @@
 public extension OWSPrimaryStorage {
 
     // MARK: - Snode Pool
-    private static let snodePoolCollection = "LokiSnodePoolCollection"
-
     public func setSnodePool(_ snodePool: Set<LokiAPITarget>, in transaction: YapDatabaseReadWriteTransaction) {
         clearSnodePool(in: transaction)
         snodePool.forEach { snode in
-            transaction.setObject(snode, forKey: snode.description, inCollection: OWSPrimaryStorage.snodePoolCollection)
+            transaction.setObject(snode, forKey: snode.description, inCollection: Storage.snodePoolCollection)
         }
     }
 
     public func clearSnodePool(in transaction: YapDatabaseReadWriteTransaction) {
-        transaction.removeAllObjects(inCollection: OWSPrimaryStorage.snodePoolCollection)
+        transaction.removeAllObjects(inCollection: Storage.snodePoolCollection)
     }
 
     public func getSnodePool(in transaction: YapDatabaseReadTransaction) -> Set<LokiAPITarget> {
         var result: Set<LokiAPITarget> = []
-        transaction.enumerateKeysAndObjects(inCollection: OWSPrimaryStorage.snodePoolCollection) { _, object, _ in
+        transaction.enumerateKeysAndObjects(inCollection: Storage.snodePoolCollection) { _, object, _ in
             guard let snode = object as? LokiAPITarget else { return }
             result.insert(snode)
         }
@@ -27,33 +25,29 @@ public extension OWSPrimaryStorage {
     }
 
     public func dropSnodeFromSnodePool(_ snode: LokiAPITarget, in transaction: YapDatabaseReadWriteTransaction) {
-        transaction.removeObject(forKey: snode.description, inCollection: OWSPrimaryStorage.snodePoolCollection)
+        transaction.removeObject(forKey: snode.description, inCollection: Storage.snodePoolCollection)
     }
 
 
 
     // MARK: - Swarm
-    private func getSwarmCollection(for publicKey: String) -> String {
-        return "LokiSwarmCollection-\(publicKey)"
-    }
-
     public func setSwarm(_ swarm: [Snode], for publicKey: String, in transaction: YapDatabaseReadWriteTransaction) {
         print("[Loki] Caching swarm for: \(publicKey).")
         clearSwarm(for: publicKey, in: transaction)
-        let collection = getSwarmCollection(for: publicKey)
+        let collection = Storage.getSwarmCollection(for: publicKey)
         swarm.forEach { snode in
             transaction.setObject(snode, forKey: snode.description, inCollection: collection)
         }
     }
 
     public func clearSwarm(for publicKey: String, in transaction: YapDatabaseReadWriteTransaction) {
-        let collection = getSwarmCollection(for: publicKey)
+        let collection = Storage.getSwarmCollection(for: publicKey)
         transaction.removeAllObjects(inCollection: collection)
     }
 
     public func getSwarm(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> [Snode] {
         var result: [Snode] = []
-        let collection = getSwarmCollection(for: publicKey)
+        let collection = Storage.getSwarmCollection(for: publicKey)
         transaction.enumerateKeysAndObjects(inCollection: collection) { _, object, _ in
             guard let snode = object as? Snode else { return }
             result.append(snode)
@@ -64,15 +58,13 @@ public extension OWSPrimaryStorage {
 
 
     // MARK: - Onion Request Paths
-    private static let onionRequestPathCollection = "LokiOnionRequestPathCollection"
-
     public func setOnionRequestPaths(_ paths: [OnionRequestAPI.Path], in transaction: YapDatabaseReadWriteTransaction) {
         // FIXME: This is a bit of a dirty approach that assumes 2 paths of length 3 each. We should do better than this.
         guard paths.count == 2 else { return }
         let path0 = paths[0]
         let path1 = paths[1]
         guard path0.count == 3, path1.count == 3 else { return }
-        let collection = OWSPrimaryStorage.onionRequestPathCollection
+        let collection = Storage.onionRequestPathCollection
         transaction.setObject(path0[0], forKey: "0-0", inCollection: collection)
         transaction.setObject(path0[1], forKey: "0-1", inCollection: collection)
         transaction.setObject(path0[2], forKey: "0-2", inCollection: collection)
@@ -82,7 +74,7 @@ public extension OWSPrimaryStorage {
     }
 
     public func getOnionRequestPaths(in transaction: YapDatabaseReadTransaction) -> [OnionRequestAPI.Path] {
-        let collection = OWSPrimaryStorage.onionRequestPathCollection
+        let collection = Storage.onionRequestPathCollection
         guard
             let path0Snode0 = transaction.object(forKey: "0-0", inCollection: collection) as? LokiAPITarget,
             let path0Snode1 = transaction.object(forKey: "0-1", inCollection: collection) as? LokiAPITarget,
@@ -94,20 +86,18 @@ public extension OWSPrimaryStorage {
     }
 
     public func clearOnionRequestPaths(in transaction: YapDatabaseReadWriteTransaction) {
-        transaction.removeAllObjects(inCollection: OWSPrimaryStorage.onionRequestPathCollection)
+        transaction.removeAllObjects(inCollection: Storage.onionRequestPathCollection)
     }
 
 
 
     // MARK: - Session Requests
-    private static let sessionRequestTimestampCollection = "LokiSessionRequestTimestampCollection"
-
     public func setSessionRequestTimestamp(for publicKey: String, to timestamp: Date, in transaction: YapDatabaseReadWriteTransaction) {
-        transaction.setDate(timestamp, forKey: publicKey, inCollection: OWSPrimaryStorage.sessionRequestTimestampCollection)
+        transaction.setDate(timestamp, forKey: publicKey, inCollection: Storage.sessionRequestTimestampCollection)
     }
 
     public func getSessionRequestTimestamp(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> Date? {
-        transaction.date(forKey: publicKey, inCollection: OWSPrimaryStorage.sessionRequestTimestampCollection)
+        transaction.date(forKey: publicKey, inCollection: Storage.sessionRequestTimestampCollection)
     }
 
 
@@ -115,23 +105,15 @@ public extension OWSPrimaryStorage {
     // MARK: - Multi Device
     private static var deviceLinkCache: Set<DeviceLink> = []
 
-    private func getDeviceLinkCollection(for masterHexEncodedPublicKey: String) -> String {
-        return "LokiDeviceLinkCollection-\(masterHexEncodedPublicKey)"
-    }
-    
-    public func cacheDeviceLinks(_ deviceLinks: Set<DeviceLink>) {
-        OWSPrimaryStorage.deviceLinkCache.formUnion(deviceLinks)
+    public func setDeviceLinks(_ deviceLinks: Set<DeviceLink>) {
+        deviceLinks.forEach { addDeviceLink($0) }
     }
 
-    public func setDeviceLinks(_ deviceLinks: Set<DeviceLink>, in transaction: YapDatabaseReadWriteTransaction) {
-        deviceLinks.forEach { addDeviceLink($0, in: transaction) }
-    }
-
-    public func addDeviceLink(_ deviceLink: DeviceLink, in transaction: YapDatabaseReadWriteTransaction) {
+    public func addDeviceLink(_ deviceLink: DeviceLink) {
         OWSPrimaryStorage.deviceLinkCache.insert(deviceLink)
     }
 
-    public func removeDeviceLink(_ deviceLink: DeviceLink, in transaction: YapDatabaseReadWriteTransaction) {
+    public func removeDeviceLink(_ deviceLink: DeviceLink) {
         OWSPrimaryStorage.deviceLinkCache.remove(deviceLink)
     }
     
@@ -150,13 +132,11 @@ public extension OWSPrimaryStorage {
 
 
     // MARK: - Open Groups
-    private static let openGroupUserCountCollection = "LokiPublicChatUserCountCollection"
-
     public func getUserCount(for publicChat: LokiPublicChat, in transaction: YapDatabaseReadTransaction) -> Int? {
-        return transaction.object(forKey: publicChat.id, inCollection: OWSPrimaryStorage.openGroupUserCountCollection) as? Int
+        return transaction.object(forKey: publicChat.id, inCollection: Storage.openGroupUserCountCollection) as? Int
     }
     
     public func setUserCount(_ userCount: Int, forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadWriteTransaction) {
-        transaction.setObject(userCount, forKey: publicChatID, inCollection: OWSPrimaryStorage.openGroupUserCountCollection)
+        transaction.setObject(userCount, forKey: publicChatID, inCollection: Storage.openGroupUserCountCollection)
     }
 }
diff --git a/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift
index b1e740029..4a944c6b8 100644
--- a/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift	
+++ b/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift	
@@ -62,12 +62,9 @@ public final class MultiDeviceProtocol : NSObject {
         } else if let thread = TSContactThread.getWithContactId(destination.hexEncodedPublicKey, transaction: transaction) {
             threadPromiseSeal.fulfill(thread)
         } else {
-            // Dispatch async on the main queue to avoid nested write transactions
-            DispatchQueue.main.async {
-                storage.dbReadWriteConnection.readWrite { transaction in
-                    let thread = TSContactThread.getOrCreateThread(withContactId: destination.hexEncodedPublicKey, transaction: transaction)
-                    threadPromiseSeal.fulfill(thread)
-                }
+            Storage.write { transaction in
+                let thread = TSContactThread.getOrCreateThread(withContactId: destination.hexEncodedPublicKey, transaction: transaction)
+                threadPromiseSeal.fulfill(thread)
             }
         }
         return threadPromise.then(on: OWSDispatch.sendingQueue()) { thread -> Promise<Void> in
@@ -81,13 +78,10 @@ public final class MultiDeviceProtocol : NSObject {
                 let messageSendCopy = copy(messageSend, for: destination, with: seal)
                 messageSender.sendMessage(messageSendCopy)
             } else {
-                // Dispatch async on the main queue to avoid nested write transactions
-                DispatchQueue.main.async {
-                    storage.dbReadWriteConnection.readWrite { transaction in
-                        getAutoGeneratedMultiDeviceFRMessageSend(for: destination.hexEncodedPublicKey, in: transaction, seal: seal)
-                        .done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in
-                            messageSender.sendMessage(autoGeneratedFRMessageSend)
-                        }
+                Storage.write { transaction in
+                    getAutoGeneratedMultiDeviceFRMessageSend(for: destination.hexEncodedPublicKey, in: transaction, seal: seal)
+                    .done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in
+                        messageSender.sendMessage(autoGeneratedFRMessageSend)
                     }
                 }
             }
diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift
index d34af79aa..2c9afcc96 100644
--- a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift	
+++ b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift	
@@ -141,14 +141,12 @@ public final class SessionManagementProtocol : NSObject {
     @objc(repairSessionIfNeededForMessage:to:)
     public static func repairSessionIfNeeded(for message: TSOutgoingMessage, to hexEncodedPublicKey: String) {
         guard (message.thread as? TSGroupThread)?.groupModel.groupType == .closedGroup else { return }
-        DispatchQueue.main.async {
-            storage.dbReadWriteConnection.readWrite { transaction in
-                let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
-                let sessionRequestMessage = SessionRequestMessage(thread: thread)
-                storage.setSessionRequestTimestamp(for: hexEncodedPublicKey, to: Date(), in: transaction)
-                let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
-                messageSenderJobQueue.add(message: sessionRequestMessage, transaction: transaction)
-            }
+        Storage.write { transaction in
+            let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
+            let sessionRequestMessage = SessionRequestMessage(thread: thread)
+            storage.setSessionRequestTimestamp(for: hexEncodedPublicKey, to: Date(), in: transaction)
+            let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
+            messageSenderJobQueue.add(message: sessionRequestMessage, transaction: transaction)
         }
     }
 
@@ -205,7 +203,7 @@ public final class SessionManagementProtocol : NSObject {
             closedGroupMembers.formUnion(group.groupModel.groupMemberIds)
         }
         LokiFileServerAPI.getDeviceLinks(associatedWith: closedGroupMembers).ensure {
-            storage.dbReadWriteConnection.readWrite { transaction in
+            Storage.write { transaction in
                 let validHEPKs = closedGroupMembers.flatMap {
                     LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: $0, in: transaction)
                 }