@@ -36,8 +36,10 @@ public class NotificationStore: Store {
3636 synchronizeNotification ( with: noteId, onCompletion: onCompletion)
3737 case . updateLastSeen( let timestamp, let onCompletion) :
3838 updateLastSeen ( timestamp: timestamp, onCompletion: onCompletion)
39- case . updateReadStatus( let noteID, let read, let onCompletion) :
40- updateReadStatus ( noteID: noteID, read: read, onCompletion: onCompletion)
39+ case . updateReadStatus( let noteId, let read, let onCompletion) :
40+ updateReadStatus ( for: [ noteId] , read: read, onCompletion: onCompletion)
41+ case . updateMultipleReadStatus( let noteIds, let read, let onCompletion) :
42+ updateReadStatus ( for: noteIds, read: read, onCompletion: onCompletion)
4143 }
4244 }
4345}
@@ -116,18 +118,34 @@ private extension NotificationStore {
116118 }
117119
118120
119- /// Updates the read status for the given notification ID
121+ /// Updates the read status for the given notification ID(s)
120122 ///
121- func updateReadStatus( noteID: Int64 , read: Bool , onCompletion: @escaping ( Error ? ) -> Void ) {
123+ func updateReadStatus( for noteIds: [ Int64 ] , read: Bool , onCompletion: @escaping ( Error ? ) -> Void ) {
124+ /// Optimistically Update
125+ ///
126+ updateLocalNoteReadStatus ( for: noteIds, read: read)
127+
128+ /// On error we'll just mark the Note for Refresh
129+ ///
122130 let remote = NotificationsRemote ( network: network)
123- remote. updateReadStatus ( String ( noteID) , read: read) { [ weak self] ( error) in
124- guard let `self` = self , error == nil else {
125- onCompletion ( error)
131+ remote. updateReadStatus ( noteIds: noteIds, read: read) { error in
132+ guard let error = error else {
133+
134+ /// What is this about:
135+ /// Notice that there are few conditions in which the Network Request's callback may run *before*
136+ /// the Optimisitc Update, such as in Unit Tests.
137+ /// This may cause the Callback to run before the Read Flag has been toggled, which isn't cool.
138+ /// *FORGIVE ME*, this is a workaround: the onCompletion closure must run after a No-OP in the derived
139+ /// storage.
140+ ///
141+ self . performSharedDerivedStorageNoOp {
142+ onCompletion ( nil )
143+ }
126144 return
127145 }
128146
129- self . updateLocalNoteReadStatus ( for: noteID , read : read ) {
130- onCompletion ( nil )
147+ self . invalidateCache ( for: noteIds ) {
148+ onCompletion ( error )
131149 }
132150 }
133151 }
@@ -169,7 +187,7 @@ extension NotificationStore {
169187 /// - remoteNotes: Collection of Notes
170188 /// - completion: Callback to be executed on completion
171189 ///
172- func updateLocalNotes( with remoteNotes: [ Note ] , completion : ( ( ) -> Void ) ? = nil ) {
190+ func updateLocalNotes( with remoteNotes: [ Note ] , onCompletion : ( ( ) -> Void ) ? = nil ) {
173191 let derivedStorage = type ( of: self ) . sharedDerivedStorage ( with: storageManager)
174192
175193 derivedStorage. perform {
@@ -180,26 +198,33 @@ extension NotificationStore {
180198 }
181199
182200 storageManager. saveDerivedType ( derivedStorage: derivedStorage) {
183- DispatchQueue . main . async {
184- completion ? ( )
201+ guard let onCompletion = onCompletion else {
202+ return
185203 }
204+
205+ DispatchQueue . main. async ( execute: onCompletion)
186206 }
187207 }
188208
189- /// Updates the read status for the given noteID in the Storage layer. If the local note to update cannot be found,
190- /// nothing is updated/created in storage.
209+ /// Updates the read status for the specified Notifications. The callback happens on the Main Thread.
191210 ///
192- func updateLocalNoteReadStatus( for noteID: Int64 , read: Bool , completion: ( ( ) -> Void ) ? = nil ) {
193- assert ( Thread . isMainThread)
194- let storage = storageManager. viewStorage
195- guard let storageNote = storage. loadNotification ( noteID: noteID) else {
196- completion ? ( )
197- return
211+ func updateLocalNoteReadStatus( for noteIDs: [ Int64 ] , read: Bool , onCompletion: ( ( ) -> Void ) ? = nil ) {
212+ let derivedStorage = type ( of: self ) . sharedDerivedStorage ( with: storageManager)
213+
214+ derivedStorage. perform {
215+ let notifications = noteIDs. compactMap { derivedStorage. loadNotification ( noteID: $0) }
216+ for note in notifications {
217+ note. read = read
218+ }
198219 }
199220
200- storageNote. read = read
201- storage. saveIfNeeded ( )
202- completion ? ( )
221+ storageManager. saveDerivedType ( derivedStorage: derivedStorage) {
222+ guard let onCompletion = onCompletion else {
223+ return
224+ }
225+
226+ DispatchQueue . main. async ( execute: onCompletion)
227+ }
203228 }
204229
205230 /// Given a collection of NoteHash Entities, this method will determine the `.noteID`'s of those entities that
@@ -229,6 +254,37 @@ extension NotificationStore {
229254 }
230255 }
231256 }
257+
258+ /// Invalidates the Hash for the specified Notifications.
259+ ///
260+ func invalidateCache( for noteIds: [ Int64 ] , onCompletion: ( ( ) -> Void ) ? = nil ) {
261+ let derivedStorage = type ( of: self ) . sharedDerivedStorage ( with: storageManager)
262+
263+ derivedStorage. perform {
264+ let notifications = noteIds. compactMap { derivedStorage. loadNotification ( noteID: $0) }
265+ for note in notifications {
266+ note. noteHash = Int64 . min
267+ }
268+ }
269+
270+ storageManager. saveDerivedType ( derivedStorage: derivedStorage) {
271+ guard let onCompletion = onCompletion else {
272+ return
273+ }
274+
275+ DispatchQueue . main. async ( execute: onCompletion)
276+ }
277+ }
278+
279+ /// Runs a No-OP in the Shared Derived Storage. On completion, the callback will be executed on the main thread.
280+ ///
281+ func performSharedDerivedStorageNoOp( onCompletion: @escaping ( ) -> Void ) {
282+ let derivedStorage = type ( of: self ) . sharedDerivedStorage ( with: storageManager)
283+
284+ derivedStorage. perform {
285+ DispatchQueue . main. async ( execute: onCompletion)
286+ }
287+ }
232288}
233289
234290
0 commit comments