Skip to content

Commit e56dcc8

Browse files
authored
Merge pull request #20939 from wordpress-mobile/issue/20905-fix-excessive-media-requests
Post List: Fix excessive media requests
2 parents 8f259b9 + 0637eec commit e56dcc8

File tree

3 files changed

+137
-4
lines changed

3 files changed

+137
-4
lines changed

WordPress/src/main/java/org/wordpress/android/ui/posts/PostListEventListener.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ class PostListEventListener(
174174
@Suppress("unused", "SpreadOperator")
175175
@Subscribe(threadMode = BACKGROUND)
176176
fun onMediaChanged(event: OnMediaChanged) {
177+
featuredMediaChanged(*event.mediaList.map { it.mediaId }.toLongArray())
177178
if (!event.isError) {
178-
featuredMediaChanged(*event.mediaList.map { it.mediaId }.toLongArray())
179179
uploadStatusChanged(*event.mediaList.map { it.localPostId }.toIntArray())
180180
}
181181
}

WordPress/src/main/java/org/wordpress/android/ui/posts/PostListFeaturedImageTracker.kt

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.wordpress.android.ui.posts
22

33
import android.annotation.SuppressLint
4+
import androidx.annotation.VisibleForTesting
45
import org.wordpress.android.fluxc.Dispatcher
56
import org.wordpress.android.fluxc.generated.MediaActionBuilder
67
import org.wordpress.android.fluxc.model.MediaModel
@@ -22,21 +23,39 @@ class PostListFeaturedImageTracker(private val dispatcher: Dispatcher, private v
2223
https://github.com/wordpress-mobile/WordPress-Android/issues/11487
2324
*/
2425
@SuppressLint("UseSparseArrays")
25-
private val featuredImageMap = HashMap<Long, String>()
26+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
27+
internal val featuredImageMap = HashMap<Long, String>()
28+
29+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
30+
internal val ongoingRequests = HashSet<Long>()
2631

2732
fun getFeaturedImageUrl(site: SiteModel, featuredImageId: Long): String? {
2833
if (featuredImageId == 0L) {
2934
return null
3035
}
31-
featuredImageMap[featuredImageId]?.let { return it }
36+
37+
featuredImageMap[featuredImageId]?.let {
38+
return it
39+
}
40+
41+
// Check if a request for this image is already ongoing
42+
if (ongoingRequests.contains(featuredImageId)) {
43+
// If the request is ongoing, just return. The callback will be invoked upon completion.
44+
return null
45+
}
46+
3247
mediaStore.getSiteMediaWithId(site, featuredImageId)?.let { media ->
3348
// This should be a pretty rare case, but some media seems to be missing url
3449
return if (media.url.isNotBlank()) {
3550
featuredImageMap[featuredImageId] = media.url
3651
media.url
3752
} else null
3853
}
54+
3955
// Media is not in the Store, we need to download it
56+
// Mark the request as ongoing
57+
ongoingRequests.add(featuredImageId)
58+
4059
val mediaToDownload = MediaModel(
4160
site.id,
4261
featuredImageId
@@ -47,6 +66,9 @@ class PostListFeaturedImageTracker(private val dispatcher: Dispatcher, private v
4766
}
4867

4968
fun invalidateFeaturedMedia(featuredImageIds: List<Long>) {
50-
featuredImageIds.forEach { featuredImageMap.remove(it) }
69+
featuredImageIds.forEach {
70+
featuredImageMap.remove(it)
71+
ongoingRequests.remove(it)
72+
}
5173
}
5274
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package org.wordpress.android.ui.posts
2+
3+
import kotlinx.coroutines.ExperimentalCoroutinesApi
4+
import org.junit.Assert.assertEquals
5+
import org.junit.Assert.assertNull
6+
import org.junit.Before
7+
import org.mockito.kotlin.any
8+
import org.mockito.kotlin.mock
9+
import org.mockito.kotlin.never
10+
import org.mockito.kotlin.verify
11+
import org.mockito.kotlin.whenever
12+
import org.wordpress.android.BaseUnitTest
13+
import org.wordpress.android.fluxc.Dispatcher
14+
import org.wordpress.android.fluxc.model.MediaModel
15+
import org.wordpress.android.fluxc.model.SiteModel
16+
import org.wordpress.android.fluxc.store.MediaStore
17+
import kotlin.test.Test
18+
19+
@Suppress("UNCHECKED_CAST")
20+
@ExperimentalCoroutinesApi
21+
class PostListFeaturedImageTrackerTest : BaseUnitTest() {
22+
private val dispatcher: Dispatcher = mock()
23+
private val mediaStore: MediaStore = mock()
24+
25+
private lateinit var tracker: PostListFeaturedImageTracker
26+
27+
private val site = SiteModel().apply { id = 123 }
28+
29+
@Before
30+
fun setup() {
31+
tracker = PostListFeaturedImageTracker(dispatcher, mediaStore)
32+
}
33+
34+
@Test
35+
fun `given id exists in map, when getFeaturedImageUrl invoked, then return url`() {
36+
val imageId = 123L
37+
val imageUrl = "https://example.com/image.jpg"
38+
tracker.featuredImageMap[imageId] = imageUrl
39+
40+
val result = tracker.getFeaturedImageUrl(site, imageId)
41+
42+
assertEquals(imageUrl, result)
43+
}
44+
45+
@Test
46+
fun `given id is 0, when getFeaturedImageUrl invoked, then return null`() {
47+
val result = tracker.getFeaturedImageUrl(site, 0L)
48+
49+
assertNull(result)
50+
}
51+
52+
@Test
53+
fun `given id not in map and exists in store, when invoked, then return url from media store`() {
54+
val imageId = 456L
55+
val imageUrl = "https://example.com/image.jpg"
56+
val mediaModel = MediaModel(site.id, imageId).apply {
57+
url = imageUrl
58+
}
59+
60+
whenever(mediaStore.getSiteMediaWithId(site, imageId)).thenReturn(mediaModel)
61+
62+
val result = tracker.getFeaturedImageUrl(site, imageId)
63+
64+
assertEquals(imageUrl, result)
65+
assertEquals(imageUrl, tracker.featuredImageMap[imageId])
66+
}
67+
68+
@Test
69+
fun `given id not in map or store, when invoked, then return null and dispatch fetch request`() {
70+
val imageId = 123L
71+
72+
whenever(mediaStore.getSiteMediaWithId(site, imageId)).thenReturn(null)
73+
74+
val result = tracker.getFeaturedImageUrl(site, imageId)
75+
76+
assertNull(result)
77+
verify(dispatcher).dispatch(any())
78+
assert(tracker.ongoingRequests.contains(imageId))
79+
}
80+
81+
@Test
82+
fun `given request ongoing for id, when invoked, should return null`() {
83+
val imageId = 123L
84+
85+
tracker.ongoingRequests.add(imageId)
86+
87+
val result = tracker.getFeaturedImageUrl(site, imageId)
88+
89+
assertNull(result)
90+
verify(mediaStore, never()).getSiteMediaWithId(site, imageId)
91+
verify(dispatcher, never()).dispatch(any())
92+
}
93+
94+
@Test
95+
fun `given id in map and ongoingRequests, when invalidate, then remove id from map and ongoingRequests`() {
96+
val imageId1 = 123L
97+
val imageId2 = 456L
98+
99+
tracker.featuredImageMap[imageId1] = "https://example.com/image1.jpg"
100+
tracker.featuredImageMap[imageId2] = "https://example.com/image2.jpg"
101+
tracker.ongoingRequests.add(imageId1)
102+
tracker.ongoingRequests.add(imageId2)
103+
104+
tracker.invalidateFeaturedMedia(listOf(imageId1, imageId2))
105+
106+
assertNull(tracker.featuredImageMap[imageId1])
107+
assertNull(tracker.featuredImageMap[imageId2])
108+
assert(!tracker.ongoingRequests.contains(imageId1))
109+
assert(!tracker.ongoingRequests.contains(imageId2))
110+
}
111+
}

0 commit comments

Comments
 (0)