diff --git a/paging/lib/src/main/java/org/signal/paging/DataStatus.java b/paging/lib/src/main/java/org/signal/paging/DataStatus.java index 3bef3e4ee4..84be5923f8 100644 --- a/paging/lib/src/main/java/org/signal/paging/DataStatus.java +++ b/paging/lib/src/main/java/org/signal/paging/DataStatus.java @@ -13,7 +13,8 @@ class DataStatus { private static final Pools.Pool POOL = new Pools.SynchronizedPool<>(1); private final BitSet state; - private final int size; + + private int size; public static DataStatus obtain(int size) { BitSet bitset = POOL.acquire(); @@ -31,6 +32,10 @@ class DataStatus { this.state = bitset; } + void mark(int position) { + state.set(position, true); + } + void markRange(int startInclusive, int endExclusive) { state.set(startInclusive, endExclusive, true); } @@ -53,6 +58,24 @@ class DataStatus { return -1; } + boolean get(int position) { + return state.get(position); + } + + void insertState(int position, boolean value) { + if (position < 0 || position > size + 1) { + throw new IndexOutOfBoundsException(); + } + + for (int i = size; i > position; i--) { + state.set(i, state.get(i - 1)); + } + + state.set(position, value); + + this.size = size + 1; + } + int size() { return size; } diff --git a/paging/lib/src/main/java/org/signal/paging/FixedSizePagingController.java b/paging/lib/src/main/java/org/signal/paging/FixedSizePagingController.java index e2fe5d601b..61ba65f799 100644 --- a/paging/lib/src/main/java/org/signal/paging/FixedSizePagingController.java +++ b/paging/lib/src/main/java/org/signal/paging/FixedSizePagingController.java @@ -62,38 +62,43 @@ class FixedSizePagingController implements PagingController { return; } - if (loadState.size() == 0) { - liveData.postValue(Collections.emptyList()); - return; + final int loadStart; + final int loadEnd; + + synchronized (loadState) { + if (loadState.size() == 0) { + liveData.postValue(Collections.emptyList()); + return; + } + + int leftPageBoundary = (aroundIndex / config.pageSize()) * config.pageSize(); + int rightPageBoundary = leftPageBoundary + config.pageSize(); + int buffer = config.bufferPages() * config.pageSize(); + + int leftLoadBoundary = Math.max(0, leftPageBoundary - buffer); + int rightLoadBoundary = Math.min(loadState.size(), rightPageBoundary + buffer); + + loadStart = loadState.getEarliestUnmarkedIndexInRange(leftLoadBoundary, rightLoadBoundary); + + if (loadStart < 0) { + if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "loadStart < 0")); + return; + } + + loadEnd = loadState.getLatestUnmarkedIndexInRange(Math.max(leftLoadBoundary, loadStart), rightLoadBoundary) + 1; + + if (loadEnd <= loadStart) { + if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "loadEnd <= loadStart, loadEnd: " + loadEnd + ", loadStart: " + loadStart)); + return; + } + + int totalSize = loadState.size(); + + loadState.markRange(loadStart, loadEnd); + + if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "start: " + loadStart + ", end: " + loadEnd + ", totalSize: " + totalSize)); } - int leftPageBoundary = (aroundIndex / config.pageSize()) * config.pageSize(); - int rightPageBoundary = leftPageBoundary + config.pageSize(); - int buffer = config.bufferPages() * config.pageSize(); - - int leftLoadBoundary = Math.max(0, leftPageBoundary - buffer); - int rightLoadBoundary = Math.min(loadState.size(), rightPageBoundary + buffer); - - int loadStart = loadState.getEarliestUnmarkedIndexInRange(leftLoadBoundary, rightLoadBoundary); - - if (loadStart < 0) { - if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "loadStart < 0")); - return; - } - - int loadEnd = loadState.getLatestUnmarkedIndexInRange(Math.max(leftLoadBoundary, loadStart), rightLoadBoundary) + 1; - - if (loadEnd <= loadStart) { - if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "loadEnd <= loadStart, loadEnd: " + loadEnd + ", loadStart: " + loadStart)); - return; - } - - int totalSize = loadState.size(); - - loadState.markRange(loadStart, loadEnd); - - if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "start: " + loadStart + ", end: " + loadEnd + ", totalSize: " + totalSize)); - FETCH_EXECUTOR.execute(() -> { if (invalidated) { Log.w(TAG, buildLog(aroundIndex, "Invalidated! At beginning of load task.")); @@ -147,6 +152,10 @@ class FixedSizePagingController implements PagingController { return; } + synchronized (loadState) { + loadState.mark(position); + } + Data item = dataSource.load(key); if (item == null) { @@ -180,6 +189,10 @@ class FixedSizePagingController implements PagingController { return; } + synchronized (loadState) { + loadState.insertState(position, true); + } + Data item = dataSource.load(key); if (item == null) { diff --git a/paging/lib/src/test/java/org/signal/paging/DataStatusTest.java b/paging/lib/src/test/java/org/signal/paging/DataStatusTest.java new file mode 100644 index 0000000000..604a777490 --- /dev/null +++ b/paging/lib/src/test/java/org/signal/paging/DataStatusTest.java @@ -0,0 +1,58 @@ +package org.signal.paging; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class DataStatusTest { + + @Test + public void insertState_initiallyEmpty_InsertAtZero() { + DataStatus subject = DataStatus.obtain(0); + subject.insertState(0, true); + + assertEquals(1, subject.size()); + assertTrue(subject.get(0)); + } + + @Test + public void insertState_someData_InsertAtZero() { + DataStatus subject = DataStatus.obtain(2); + subject.mark(1); + + subject.insertState(0, true); + + assertEquals(3, subject.size()); + assertTrue(subject.get(0)); + assertFalse(subject.get(1)); + assertTrue(subject.get(2)); + } + + @Test + public void insertState_someData_InsertAtOne() { + DataStatus subject = DataStatus.obtain(3); + subject.mark(1); + + subject.insertState(1, true); + + assertEquals(4, subject.size()); + assertFalse(subject.get(0)); + assertTrue(subject.get(1)); + assertTrue(subject.get(2)); + assertFalse(subject.get(3)); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void insertState_negativeThrows() { + DataStatus subject = DataStatus.obtain(0); + subject.insertState(-1, true); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void insertState_largerThanSizePlusOneThrows() { + DataStatus subject = DataStatus.obtain(0); + subject.insertState(2, true); + } +}