1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.paging
18 
19 import androidx.annotation.VisibleForTesting
20 import androidx.paging.internal.ReentrantLock
21 import androidx.paging.internal.withLock
22 
23 /** Helper class for thread-safe invalidation callback tracking + triggering on registration. */
24 internal class InvalidateCallbackTracker<T>(
25     private val callbackInvoker: (T) -> Unit,
26     /** User-provided override of DataSource.isInvalid */
27     private val invalidGetter: (() -> Boolean)? = null,
28 ) {
29     private val lock = ReentrantLock()
30     private val callbacks = mutableListOf<T>()
31     internal var invalid = false
32         private set
33 
callbackCountnull34     @VisibleForTesting internal fun callbackCount() = callbacks.size
35 
36     internal fun registerInvalidatedCallback(callback: T) {
37         // This isn't sufficient, but is the best we can do in cases where DataSource.isInvalid
38         // is overridden, since we have no way of knowing when the result gets flipped if user
39         // never calls .invalidate().
40         if (invalidGetter?.invoke() == true) {
41             invalidate()
42         }
43 
44         if (invalid) {
45             callbackInvoker(callback)
46             return
47         }
48 
49         val callImmediately =
50             lock.withLock {
51                 if (invalid) {
52                     true // call immediately
53                 } else {
54                     callbacks.add(callback)
55                     false // don't call, not invalid yet.
56                 }
57             }
58 
59         if (callImmediately) {
60             callbackInvoker(callback)
61         }
62     }
63 
unregisterInvalidatedCallbacknull64     internal fun unregisterInvalidatedCallback(callback: T) {
65         lock.withLock { callbacks.remove(callback) }
66     }
67 
invalidatenull68     internal fun invalidate(): Boolean {
69         if (invalid) return false
70 
71         val callbacksToInvoke =
72             lock.withLock {
73                 if (invalid) return false
74 
75                 invalid = true
76                 callbacks.toList().also { callbacks.clear() }
77             }
78 
79         callbacksToInvoke.forEach(callbackInvoker)
80         return true
81     }
82 }
83