fix String.inc() and String.dec() not being inverse operations (#2355)

This commit is contained in:
Konrad Pozniak 2022-03-01 21:29:05 +01:00 committed by GitHub
parent 60c32b3370
commit b145fc9d50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 53 deletions

View file

@ -143,7 +143,7 @@ class CachedTimelineViewModel @Inject constructor(
val nextPlaceholderId = timelineDao.getNextPlaceholderIdAfter(activeAccount.id, placeholderId) val nextPlaceholderId = timelineDao.getNextPlaceholderIdAfter(activeAccount.id, placeholderId)
val response = api.homeTimeline(maxId = placeholderId.inc(), sinceId = nextPlaceholderId, limit = 20).await() val response = api.homeTimeline(maxId = placeholderId.inc(), sinceId = nextPlaceholderId, limit = LOAD_AT_ONCE).await()
val statuses = response.body() val statuses = response.body()
if (!response.isSuccessful || statuses == null) { if (!response.isSuccessful || statuses == null) {

View file

@ -17,27 +17,39 @@ fun randomAlphanumericString(count: Int): String {
} }
// We sort statuses by ID. Something we need to invent some ID for placeholder. // We sort statuses by ID. Something we need to invent some ID for placeholder.
// Not sure if inc()/dec() should be made `operator` or not
/** /**
* "Increment" string so that during sorting it's bigger than [this]. * "Increment" string so that during sorting it's bigger than [this]. Inverse operation to [dec].
*/ */
fun String.inc(): String { fun String.inc(): String {
// We assume that we will stay in the safe range for now
val builder = this.toCharArray() val builder = this.toCharArray()
builder[lastIndex] = builder[lastIndex].inc() var i = builder.lastIndex
while (i >= 0) {
if (builder[i] < 'z') {
builder[i] = builder[i].inc()
return String(builder) return String(builder)
} else {
builder[i] = '0'
}
i--
}
return String(
CharArray(builder.size + 1) { index ->
if (index == 0) '0' else builder[index - 1]
}
)
} }
/** /**
* "Decrement" string so that during sorting it's smaller than [this]. * "Decrement" string so that during sorting it's smaller than [this]. Inverse operation to [inc].
*/ */
fun String.dec(): String { fun String.dec(): String {
if (this.isEmpty()) return this if (this.isEmpty()) return this
val builder = this.toCharArray() val builder = this.toCharArray()
var i = builder.lastIndex var i = builder.lastIndex
while (i > 0) { while (i >= 0) {
if (builder[i] > '0') { if (builder[i] > '0') {
builder[i] = builder[i].dec() builder[i] = builder[i].dec()
return String(builder) return String(builder)
@ -46,12 +58,7 @@ fun String.dec(): String {
} }
i-- i--
} }
return if (builder[0] > '1') { return String(builder.copyOfRange(1, builder.size))
builder[0] = builder[0].dec()
String(builder)
} else {
String(builder.copyOfRange(1, builder.size))
}
} }
/** /**
@ -71,15 +78,6 @@ fun String.isLessThan(other: String): Boolean {
} }
} }
fun String.idCompareTo(other: String): Int {
return when {
this === other -> 0
this.length < other.length -> -1
this.length > other.length -> 1
else -> this.compareTo(other)
}
}
fun Spanned.trimTrailingWhitespace(): Spanned { fun Spanned.trimTrailingWhitespace(): Spanned {
var i = length var i = length
do { do {

View file

@ -27,22 +27,37 @@ class StringUtilsTest {
@Test @Test
fun inc() { fun inc() {
listOf( listOf(
"10786565059022968z" to "107865650590229690",
"122" to "123", "122" to "123",
"12A" to "12B", "12A" to "12B",
"1" to "2" "11z" to "120",
"0zz" to "100",
"zz" to "000",
"4zzbz" to "4zzc0",
"" to "0",
"1" to "2",
"0" to "1",
"AGdxwSQqT3pW4xrLJA" to "AGdxwSQqT3pW4xrLJB",
"AGdfqi1HnlBFVl0tkz" to "AGdfqi1HnlBFVl0tl0"
).forEach { (l, r) -> assertEquals("$l + 1 = $r", r, l.inc()) } ).forEach { (l, r) -> assertEquals("$l + 1 = $r", r, l.inc()) }
} }
@Test @Test
fun dec() { fun dec() {
listOf( listOf(
"" to "",
"107865650590229690" to "10786565059022968z",
"123" to "122", "123" to "122",
"12B" to "12A", "12B" to "12A",
"120" to "11z", "120" to "11z",
"100" to "zz", "100" to "0zz",
"000" to "zz",
"4zzc0" to "4zzbz",
"0" to "", "0" to "",
"" to "", "2" to "1",
"2" to "1" "1" to "0",
"AGdxwSQqT3pW4xrLJB" to "AGdxwSQqT3pW4xrLJA",
"AGdfqi1HnlBFVl0tl0" to "AGdfqi1HnlBFVl0tkz"
).forEach { (l, r) -> assertEquals("$l - 1 = $r", r, l.dec()) } ).forEach { (l, r) -> assertEquals("$l - 1 = $r", r, l.dec()) }
} }
} }

View file

@ -144,7 +144,8 @@ class TimelineDaoTest {
makeStatus(statusId = 1) makeStatus(statusId = 1)
) )
timelineDao.deleteRange(1, newStatuses.last().first.serverId, newStatuses.first().first.serverId) val deletedCount = timelineDao.deleteRange(1, newStatuses.last().first.serverId, newStatuses.first().first.serverId)
assertEquals(3, deletedCount)
for ((status, author, reblogAuthor) in newStatuses) { for ((status, author, reblogAuthor) in newStatuses) {
timelineDao.insertAccount(author) timelineDao.insertAccount(author)
@ -169,9 +170,11 @@ class TimelineDaoTest {
fun deleteRange() = runBlocking { fun deleteRange() = runBlocking {
val statuses = listOf( val statuses = listOf(
makeStatus(statusId = 100), makeStatus(statusId = 100),
makeStatus(statusId = 50),
makeStatus(statusId = 15), makeStatus(statusId = 15),
makeStatus(statusId = 14), makeStatus(statusId = 14),
makeStatus(statusId = 13), makeStatus(statusId = 13),
makeStatus(statusId = 13, accountId = 2),
makeStatus(statusId = 12), makeStatus(statusId = 12),
makeStatus(statusId = 11), makeStatus(statusId = 11),
makeStatus(statusId = 9) makeStatus(statusId = 9)
@ -185,20 +188,31 @@ class TimelineDaoTest {
timelineDao.insertStatus(status) timelineDao.insertStatus(status)
} }
timelineDao.deleteRange(1, "12", "14") assertEquals(3, timelineDao.deleteRange(1, "12", "14"))
assertEquals(0, timelineDao.deleteRange(1, "80", "80"))
assertEquals(0, timelineDao.deleteRange(1, "60", "80"))
assertEquals(0, timelineDao.deleteRange(1, "5", "8"))
assertEquals(0, timelineDao.deleteRange(1, "101", "1000"))
assertEquals(1, timelineDao.deleteRange(1, "50", "50"))
val pagingSource = timelineDao.getStatusesForAccount(1) val loadParams: PagingSource.LoadParams<Int> = PagingSource.LoadParams.Refresh(null, 100, false)
val loadResult = pagingSource.load(PagingSource.LoadParams.Refresh(null, 100, false))
val loadedStatuses = (loadResult as PagingSource.LoadResult.Page).data
val remainingStatuses = listOf( val statusesAccount1 = (timelineDao.getStatusesForAccount(1).load(loadParams) as PagingSource.LoadResult.Page).data
val statusesAccount2 = (timelineDao.getStatusesForAccount(2).load(loadParams) as PagingSource.LoadResult.Page).data
val remainingStatusesAccount1 = listOf(
makeStatus(statusId = 100), makeStatus(statusId = 100),
makeStatus(statusId = 15), makeStatus(statusId = 15),
makeStatus(statusId = 11), makeStatus(statusId = 11),
makeStatus(statusId = 9) makeStatus(statusId = 9)
) )
assertStatuses(remainingStatuses, loadedStatuses) val remainingStatusesAccount2 = listOf(
makeStatus(statusId = 13, accountId = 2)
)
assertStatuses(remainingStatusesAccount1, statusesAccount1)
assertStatuses(remainingStatusesAccount2, statusesAccount2)
} }
@Test @Test
@ -325,7 +339,7 @@ class TimelineDaoTest {
} }
assertEquals("99", timelineDao.getNextPlaceholderIdAfter(1, "1000")) assertEquals("99", timelineDao.getNextPlaceholderIdAfter(1, "1000"))
assertEquals("94", timelineDao.getNextPlaceholderIdAfter(1, "97")) assertEquals("94", timelineDao.getNextPlaceholderIdAfter(1, "99"))
assertNull(timelineDao.getNextPlaceholderIdAfter(1, "90")) assertNull(timelineDao.getNextPlaceholderIdAfter(1, "90"))
} }
@ -364,28 +378,28 @@ class TimelineDaoTest {
domain: String = "mastodon.example" domain: String = "mastodon.example"
): Triple<TimelineStatusEntity, TimelineAccountEntity, TimelineAccountEntity?> { ): Triple<TimelineStatusEntity, TimelineAccountEntity, TimelineAccountEntity?> {
val author = TimelineAccountEntity( val author = TimelineAccountEntity(
authorServerId, serverId = authorServerId,
accountId, timelineUserId = accountId,
"localUsername@$domain", localUsername = "localUsername@$domain",
"username@$domain", username = "username@$domain",
"displayName", displayName = "displayName",
"blah", url = "blah",
"avatar", avatar = "avatar",
"[\"tusky\": \"http://tusky.cool/emoji.jpg\"]", emojis = "[\"tusky\": \"http://tusky.cool/emoji.jpg\"]",
false bot = false
) )
val reblogAuthor = if (reblog) { val reblogAuthor = if (reblog) {
TimelineAccountEntity( TimelineAccountEntity(
"R$authorServerId", serverId = "R$authorServerId",
accountId, timelineUserId = accountId,
"RlocalUsername", localUsername = "RlocalUsername",
"Rusername", username = "Rusername",
"RdisplayName", displayName = "RdisplayName",
"Rblah", url = "Rblah",
"Ravatar", avatar = "Ravatar",
"[]", emojis = "[]",
false bot = false
) )
} else null } else null