This commit is contained in:
tateisu 2021-06-13 12:30:40 +09:00
parent 68f401c420
commit c08a5dfd8e
5 changed files with 1565 additions and 1767 deletions

View File

@ -220,6 +220,7 @@ fun Column.parseRange(
// int scroll_hack; // int scroll_hack;
// return true if list bottom may have unread remain // return true if list bottom may have unread remain
// カラムが既に範囲を持ってる場合、その範囲を拡張する。
fun Column.saveRange( fun Column.saveRange(
bBottom: Boolean, bBottom: Boolean,
bTop: Boolean, bTop: Boolean,
@ -261,10 +262,13 @@ fun Column.saveRange(
fun Column.saveRangeBottom(result: TootApiResult?, list: List<TimelineItem>?) = fun Column.saveRangeBottom(result: TootApiResult?, list: List<TimelineItem>?) =
saveRange(true, bTop = false, result = result, list = list) saveRange(true, bTop = false, result = result, list = list)
// return true if list bottom may have unread remain // no return value: can't find there may be more items.
fun Column.saveRangeTop(result: TootApiResult?, list: List<TimelineItem>?) = fun Column.saveRangeTop(result: TootApiResult?, list: List<TimelineItem>?){
saveRange(false, bTop = true, result = result, list = list) saveRange(false, bTop = true, result = result, list = list)
}
fun Column.addRange( fun Column.addRange(
bBottom: Boolean, bBottom: Boolean,
path: String, path: String,

View File

@ -247,8 +247,8 @@ class ColumnTask_Gap(
params.toPostRequestBuilder() params.toPostRequestBuilder()
) )
val jsonObject = r2?.jsonObject
if (jsonObject != null) r2.data = arrayFinder(jsonObject) r2?.jsonObject?.let { r2.data = arrayFinder(it) }
val jsonArray = r2?.jsonArray val jsonArray = r2?.jsonArray
if (jsonArray == null) { if (jsonArray == null) {
@ -265,12 +265,11 @@ class ColumnTask_Gap(
// 成功した場合はそれを返したい // 成功した場合はそれを返したい
result = r2 result = r2
var src: List<T> = listParser(parser, jsonArray) var src = listParser(parser, jsonArray)
if (olderLimit != null) if (olderLimit != null)
src = src.filter { it.isInjected() || it.getOrderId() > olderLimit } src = src.filter { it.isInjected() || it.getOrderId() > olderLimit }
if (src.none { !it.isInjected() }) { if (src.none { !it.isInjected() }) {
// 直前の取得でカラのデータが帰ってきたら終了 // 直前の取得でカラのデータが帰ってきたら終了
log.d("$logCaption: empty.") log.d("$logCaption: empty.")
@ -329,8 +328,7 @@ class ColumnTask_Gap(
params.toPostRequestBuilder() params.toPostRequestBuilder()
) )
val jsonObject = r2?.jsonObject r2?.jsonObject?.let { r2.data = arrayFinder(it) }
if (jsonObject != null) r2.data = arrayFinder(jsonObject)
val jsonArray = r2?.jsonArray val jsonArray = r2?.jsonArray
if (jsonArray == null) { if (jsonArray == null) {
@ -347,7 +345,7 @@ class ColumnTask_Gap(
// 成功した場合はそれを返したい // 成功した場合はそれを返したい
result = r2 result = r2
var src: List<T> = listParser(parser, jsonArray) var src = listParser(parser, jsonArray)
if (newerLimit != null) if (newerLimit != null)
src = src.filter { it.isInjected() || it.getOrderId() < newerLimit } src = src.filter { it.isInjected() || it.getOrderId() < newerLimit }
@ -440,7 +438,7 @@ class ColumnTask_Gap(
// 成功した場合はそれを返したい // 成功した場合はそれを返したい
result = r2 result = r2
var src: List<T> = listParser(parser, jsonArray) var src = listParser(parser, jsonArray)
if (olderLimit != null) if (olderLimit != null)
src = src.filter { it.getOrderId() > olderLimit } src = src.filter { it.getOrderId() > olderLimit }
@ -526,7 +524,7 @@ class ColumnTask_Gap(
// 成功した場合はそれを返したい // 成功した場合はそれを返したい
result = r2 result = r2
var src: List<T> = listParser(parser, jsonArray) var src = listParser(parser, jsonArray)
if (newerLimit != null) if (newerLimit != null)
src = src.filter { it.getOrderId() < newerLimit } src = src.filter { it.getOrderId() < newerLimit }

View File

@ -147,103 +147,67 @@ class ColumnTask_Loading(
adder: (List<T>, Boolean) -> Unit, adder: (List<T>, Boolean) -> Unit,
initialMaxId: EntityId? = null initialMaxId: EntityId? = null
): TootApiResult? { ): TootApiResult? {
val time_start = SystemClock.elapsedRealtime()
val addToHead = false val addToHead = false
// 初回の取得 fun parseResult(result: TootApiResult?): Boolean {
var result = requester(initialMaxId, null) val first = list_tmp?.isEmpty() != false
val firstResult = result
var jsonObject = result?.jsonObject result ?: return log.d("$logCaption: cancelled.")
if (jsonObject != null) {
result.jsonObject?.let {
if (column.pagingType == ColumnPagingType.Cursor) { if (column.pagingType == ColumnPagingType.Cursor) {
column.idOld = EntityId.mayNull(jsonObject.string("next")) column.idOld = EntityId.mayNull(it.string("next"))
} }
result?.data = arrayFinder(jsonObject) result.data = arrayFinder(it)
} }
var array = result?.jsonArray val array = result.jsonArray
?: return log.w("$logCaption: missing item list")
if (array != null) {
var src = listParser(parser, array)
val src = listParser(parser, array)
if (list_tmp == null) list_tmp = ArrayList(src.size) if (list_tmp == null) list_tmp = ArrayList(src.size)
adder(src, addToHead) adder(src, addToHead)
addEmptyMessage(emptyMessage) if (first) addEmptyMessage(emptyMessage)
val more = when (column.pagingType) {
when (column.pagingType) { ColumnPagingType.Default ->
ColumnPagingType.Default -> { if (first) {
column.saveRange(bBottom = true, bTop = true, result = result, list = src) column.saveRange(bBottom = true, bTop = true, result = result, list = src)
} else {
column.saveRangeBottom(result, src)
} }
ColumnPagingType.Offset -> { ColumnPagingType.Offset -> {
column.offsetNext += src.size column.offsetNext += src.size
true
} }
else -> true
else -> { }
return when {
!more -> log.d("$logCaption: no more items")
src.isEmpty() -> log.d("$logCaption: empty list")
else -> true
} }
} }
while (true) { val time_start = SystemClock.elapsedRealtime()
if (isCancelled) { // 初回の取得
log.d("$logCaption: cancelled.") val firstResult = requester(initialMaxId, null)
break var more = parseResult(firstResult)
}
if (!column.isFilterEnabled) {
log.d("$logCaption: isFiltered is false.")
break
}
if (column.idOld == null) {
log.d("$logCaption: idOld is empty.")
break
}
if ((list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH) {
log.d("$logCaption: read enough data.")
break
}
if (src.isEmpty()) {
log.d("$logCaption: previous response is empty.")
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) {
log.d("$logCaption: timeout.")
break
}
// フィルタなどが有効な場合は2回目以降の取得 // フィルタなどが有効な場合は2回目以降の取得
result = requester(column.idOld, null) while (more) more = when {
isCancelled ->
jsonObject = result?.jsonObject log.d("$logCaption: cancelled.")
if (jsonObject != null) { !column.isFilterEnabled ->
if (column.pagingType == ColumnPagingType.Cursor) { log.d("$logCaption: isFiltered is false.")
column.idOld = EntityId.mayNull(jsonObject.string("next")) column.idOld == null ->
} log.d("$logCaption: idOld is empty.")
result?.data = arrayFinder(jsonObject) (list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH ->
} log.d("$logCaption: read enough data.")
array = result?.jsonArray SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT ->
log.d("$logCaption: timeout.")
if (array == null) { else -> parseResult(requester(column.idOld, null))
log.d("$logCaption: error or cancelled.")
break
}
src = listParser(parser, array)
adder(src, addToHead)
if (!column.saveRangeBottom(result, src)) {
log.d("$logCaption: saveRangeBottom returns false, no more items.")
break
}
}
} }
return firstResult return firstResult
} }
@ -260,26 +224,22 @@ class ColumnTask_Loading(
): TootApiResult? { ): TootApiResult? {
val addToHead = true val addToHead = true
val time_start = SystemClock.elapsedRealtime()
// 初回の取得 fun parseResult(result: TootApiResult?): Boolean {
var result = requester(null, initialMinId) val first = list_tmp?.isEmpty() != false
val firstResult = result
var jsonObject = result?.jsonObject result ?: return log.d("$logCaption: cancelled")
if (jsonObject != null) result?.data = arrayFinder(jsonObject)
var array = result?.jsonArray result.jsonObject?.let { result.data = arrayFinder(it) }
if (array != null) {
var src = listParser(parser, array) val array = result.jsonArray
?: return log.w("$logCaption: missing item list")
val src = listParser(parser, array)
if (list_tmp == null) list_tmp = ArrayList(src.size) if (list_tmp == null) list_tmp = ArrayList(src.size)
adder(src, addToHead) adder(src, addToHead)
column.saveRange(bBottom = true, bTop = true, result = result, list = src) if (first && emptyMessage != null && list_tmp?.isEmpty() == true) {
if (emptyMessage != null && list_tmp?.isEmpty() == true) {
// フォロー/フォロワー一覧には警告の表示が必要だった // フォロー/フォロワー一覧には警告の表示が必要だった
val who = column.who_account?.get() val who = column.who_account?.get()
if (!access_info.isMe(who)) { if (!access_info.isMe(who)) {
@ -294,60 +254,42 @@ class ColumnTask_Loading(
} }
} }
while (true) { val more = if (first) {
if (isCancelled) { column.saveRange(bBottom = true, bTop = true, result = result, list = src)
log.d("$logCaption: cancelled.") } else {
break column.saveRangeTop(result, src)
} }
if (!column.isFilterEnabled) { return when {
log.d("$logCaption: isFiltered is false.") !more -> log.d("$logCaption: no more items.")
break src.isEmpty() -> log.d("$logCaption: empty item list.")
else -> true
}
} }
if (column.idRecent == null) { val time_start = SystemClock.elapsedRealtime()
log.d("$logCaption: idRecent is empty.")
break
}
if ((list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH) { // 初回の取得
log.d("$logCaption: read enough data.") val firstResult = requester(null, initialMinId)
break var more = parseResult(firstResult)
}
if (src.isEmpty()) {
log.d("$logCaption: previous response is empty.")
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) {
log.d("$logCaption: timeout.")
break
}
// フィルタなどが有効な場合は2回目以降の取得 // フィルタなどが有効な場合は2回目以降の取得
result = requester(null, column.idRecent) while (more) more = when {
isCancelled ->
jsonObject = result?.jsonObject log.d("$logCaption: cancelled.")
if (jsonObject != null) result?.data = arrayFinder(jsonObject) !column.isFilterEnabled ->
log.d("$logCaption: isFiltered is false.")
array = result?.jsonArray column.idRecent == null ->
log.d("$logCaption: idRecent is empty.")
if (array == null) { (list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH ->
log.d("$logCaption: error or cancelled.") log.d("$logCaption: read enough data.")
break SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT ->
log.d("$logCaption: timeout.")
else -> parseResult(requester(null, column.idRecent))
} }
src = listParser(parser, array)
adder(src, addToHead)
if (!column.saveRangeTop(result, src)) {
log.d("$logCaption: saveRangeTop returns false, no more items.")
break
}
}
list_tmp?.sortByDescending { it.getOrderId() } list_tmp?.sortByDescending { it.getOrderId() }
}
return firstResult return firstResult
} }
@ -361,100 +303,69 @@ class ColumnTask_Loading(
adder: (List<T>, Boolean) -> Unit, adder: (List<T>, Boolean) -> Unit,
initialMaxId: EntityId? = null initialMaxId: EntityId? = null
): TootApiResult? { ): TootApiResult? {
val time_start = SystemClock.elapsedRealtime()
val addToHead = false val addToHead = false
// 初回の取得
var result = requester(initialMaxId, null)
val firstResult = result
var jsonObject = result?.jsonObject fun parseResult(result: TootApiResult?): Boolean {
if (jsonObject != null) { val first = list_tmp?.isEmpty() != false
result ?: return log.d("$logCaption: cancelled.")
result.jsonObject?.let {
if (column.pagingType == ColumnPagingType.Cursor) { if (column.pagingType == ColumnPagingType.Cursor) {
column.idOld = EntityId.mayNull(jsonObject.string("next")) column.idOld = EntityId.mayNull(it.string("next"))
} }
result?.data = arrayFinder(jsonObject) result.data = arrayFinder(it)
} }
var array = result?.jsonArray val array = result.jsonArray
if (array != null) { ?: return log.w("$logCaption: missing item list")
var src = listParser(parser, array) val src = listParser(parser, array)
if (list_tmp == null) list_tmp = ArrayList(src.size) if (list_tmp == null) list_tmp = ArrayList(src.size)
adder(src, addToHead) adder(src, addToHead)
addEmptyMessage(emptyMessage) if (first) addEmptyMessage(emptyMessage)
when (column.pagingType) { val more = when (column.pagingType) {
ColumnPagingType.Default -> { ColumnPagingType.Default ->
if (first) {
column.saveRange(bBottom = true, bTop = true, result = result, list = src) column.saveRange(bBottom = true, bTop = true, result = result, list = src)
} else {
column.saveRangeBottom(result, src)
} }
ColumnPagingType.Offset -> { ColumnPagingType.Offset -> {
// idOldがないので2回目以降は発生しない
column.offsetNext += src.size column.offsetNext += src.size
true
}
else -> true
} }
else -> { return when {
!more -> log.d("$logCaption: no more items.")
src.isEmpty() -> log.d("$logCaption: empty list.")
else -> true
} }
} }
// 初回の取得
val time_start = SystemClock.elapsedRealtime()
val firstResult = requester(initialMaxId, null)
var more = parseResult(firstResult)
// フィルタなどが有効な場合は2回目以降の取得 // フィルタなどが有効な場合は2回目以降の取得
while (true) { while (more) more = when {
if (isCancelled) { isCancelled ->
log.d("$logCaption: cancelled.") log.d("$logCaption: cancelled.")
break !column.isFilterEnabled ->
}
if (!column.isFilterEnabled) {
// フィルタしない場合は繰り返さない
log.d("$logCaption: isFiltered is false.") log.d("$logCaption: isFiltered is false.")
break column.idOld == null ->
}
if (column.idOld == null) {
log.d("$logCaption: idOld is empty.") log.d("$logCaption: idOld is empty.")
break (list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH ->
}
if ((list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH) {
log.d("$logCaption: read enough data.") log.d("$logCaption: read enough data.")
break SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT ->
}
if (src.isEmpty()) {
log.d("$logCaption: previous response is empty.")
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) {
log.d("$logCaption: timeout.") log.d("$logCaption: timeout.")
break else -> parseResult(requester(column.idOld, null))
}
result = requester(column.idOld, null)
jsonObject = result?.jsonObject
if (jsonObject != null) {
if (column.pagingType == ColumnPagingType.Cursor) {
column.idOld = EntityId.mayNull(jsonObject.string("next"))
}
result?.data = arrayFinder(jsonObject)
}
array = result?.jsonArray
if (array == null) {
log.d("$logCaption: error or cancelled.")
break
}
src = listParser(parser, array)
adder(src, addToHead)
if (!column.saveRangeBottom(result, src)) {
log.d("$logCaption: saveRangeBottom returns false, no more items")
break
}
}
} }
return firstResult return firstResult
} }
@ -468,27 +379,26 @@ class ColumnTask_Loading(
adder: (List<T>, Boolean) -> Unit, adder: (List<T>, Boolean) -> Unit,
initialMinId: EntityId? = null initialMinId: EntityId? = null
): TootApiResult? { ): TootApiResult? {
val time_start = SystemClock.elapsedRealtime()
val addToHead = true val addToHead = true
// 初回の取得
var result = requester(null, initialMinId)
val firstResult = result
var jsonObject = result?.jsonObject fun parseResult(result: TootApiResult?): Boolean {
if (jsonObject != null) result?.data = arrayFinder(jsonObject) val first = list_tmp?.isEmpty() != false
var array = result?.jsonArray result ?: return log.d("cancelled.")
if (array != null) {
var src = listParser(parser, array) result.jsonObject?.let { result.data = arrayFinder(it) }
val array = result.jsonArray
?: return log.d("$logCaption: missing item list")
val src = listParser(parser, array)
if (list_tmp == null) list_tmp = ArrayList(src.size) if (list_tmp == null) list_tmp = ArrayList(src.size)
adder(src, addToHead) adder(src, addToHead)
column.saveRange(bBottom = true, bTop = true, result = result, list = src)
if (emptyMessage != null && list_tmp?.isEmpty() == true) {
// フォロー/フォロワー一覧には警告の表示が必要だった // フォロー/フォロワー一覧には警告の表示が必要だった
if (first && emptyMessage != null && list_tmp?.isEmpty() == true) {
val who = column.who_account?.get() val who = column.who_account?.get()
if (!access_info.isMe(who)) { if (!access_info.isMe(who)) {
if (who != null && access_info.isRemoteUser(who)) { if (who != null && access_info.isRemoteUser(who)) {
@ -502,58 +412,38 @@ class ColumnTask_Loading(
} }
} }
while (true) { val more = if (first) {
if (isCancelled) { column.saveRange(bBottom = true, bTop = true, result = result, list = src)
log.d("$logCaption: cancelled.") } else {
break column.saveRangeTop(result, src)
} }
if (!column.isFilterEnabled) { return when {
log.d("$logCaption: isFiltered is false.") !more -> log.d("$logCaption: no more items.")
break src.isEmpty() -> log.d("$logCaption: empty item list.")
else -> true
}
} }
if (column.idRecent == null) { val time_start = SystemClock.elapsedRealtime()
log.d("$logCaption: idRecent is empty.")
break
}
if ((list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH) { // 初回の取得
log.d("$logCaption: read enough data.") val firstResult = requester(null, initialMinId)
break var more = parseResult(firstResult)
}
if (src.isEmpty()) {
log.d("$logCaption: previous response is empty.")
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) {
log.d("$logCaption: timeout.")
break
}
// フィルタなどが有効な場合は2回目以降の取得 // フィルタなどが有効な場合は2回目以降の取得
result = requester(null, column.idRecent) while (more) more = when {
isCancelled ->
log.d("$logCaption: cancelled.")
jsonObject = result?.jsonObject !column.isFilterEnabled ->
if (jsonObject != null) result?.data = arrayFinder(jsonObject) log.d("$logCaption: isFiltered is false.")
column.idRecent == null ->
array = result?.jsonArray log.d("$logCaption: idRecent is empty.")
if (array == null) { (list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH ->
log.d("$logCaption: error or cancelled.") log.d("$logCaption: read enough data.")
break SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT ->
} log.d("$logCaption: timeout.")
else -> parseResult(requester(null, column.idRecent))
src = listParser(parser, array)
adder(src, addToHead)
if (!column.saveRangeTop(result, src)) {
log.d("$logCaption: saveRangeTop returns false, no more items.")
break
}
}
} }
return firstResult return firstResult
} }
@ -577,7 +467,7 @@ class ColumnTask_Loading(
// pinned tootにはページングの概念はない // pinned tootにはページングの概念はない
} }
log.d("getStatusesPinned: list size=%s", list_pinned?.size ?: -1) log.d("getStatusesPinned: list size=${list_pinned?.size ?: -1}")
} }
suspend fun getStatusList( suspend fun getStatusList(

View File

@ -220,71 +220,64 @@ class ColumnTask_Refresh(
val addToHead = true val addToHead = true
val time_start = SystemClock.elapsedRealtime()
var result = requester(true)
val firstResult = result
var jsonObject = result?.jsonObject
if (jsonObject != null)
result?.data = arrayFinder(jsonObject)
var array = result?.jsonArray
if (array != null) {
list_tmp = ArrayList()
var src = listParser(parser, array)
adder(src, addToHead)
column.saveRangeTop(result, src)
// misskeyの場合、sinceIdを指定したら未読範囲の古い方から読んでしまう // misskeyの場合、sinceIdを指定したら未読範囲の古い方から読んでしまう
// 最新まで読めるとは限らない // 最新まで読めるとは限らない
// 先頭にギャップを置くかもしれない // 先頭にギャップを置くかもしれない
var willAddGap = false var willAddGap = false
while (true) {
// 頭の方を読む時は隙間を減らすため、フィルタの有無に関係なく繰り返しを行う
if (isCancelled) { fun parseResult(result: TootApiResult?): Boolean {
log.d("$logCaption: cancelled.") val first = list_tmp?.isEmpty() != false
break
if (result == null) {
if (!first) willAddGap = true
return log.d("$logCaption:cancelled.")
} }
if (src.isEmpty()) { result.jsonObject?.let { result.data = arrayFinder(it) }
// 直前のデータが0個なら終了とみなす
log.d("$logCaption: previous size == 0.")
break
}
if ((list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH) { val array = result.jsonArray
// 既に十分読んだなら止める
log.d("$logCaption: read enough. make gap.")
willAddGap = true
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) {
log.d("$logCaption: timeout.")
willAddGap = true
break
}
result = requester(false)
jsonObject = result?.jsonObject
if (jsonObject != null)
result?.data = arrayFinder(jsonObject)
array = result?.jsonArray
if (array == null) { if (array == null) {
log.d("$logCaption: error or cancelled.") if (!first) willAddGap = true
willAddGap = true return log.w("$logCaption: missing item list")
break
} }
src = listParser(parser, array) val src = listParser(parser, array)
if (list_tmp == null) list_tmp = ArrayList(src.size)
adder(src, addToHead) adder(src, addToHead)
column.saveRangeTop(result = result, list = src) column.saveRangeTop(result = result, list = src)
return when {
// より新しいデータがあるかどうかはわからない。
// カラのデータを読めたら終端とする
src.isEmpty() -> log.d("$logCaption: empty item list")
else -> true
}
}
val time_start = SystemClock.elapsedRealtime()
// 初回のリクエスト
val firstResult = requester(true)
var more = parseResult(firstResult)
// 頭の方を読む時は隙間を減らすため、フィルタの有無に関係なく繰り返しを行う
while (more) more = when {
isCancelled ->
log.d("$logCaption: cancelled.")
(list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH -> {
// 既に十分読んだなら止める
willAddGap = true
log.d("$logCaption: read enough. make gap.")
}
SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT -> {
willAddGap = true
log.d("$logCaption: timeout.")
}
else -> parseResult(requester(false))
} }
// MisskeyはsinceIdを指定するとID昇順のデータが得られるので、ID降順に並べ直す // MisskeyはsinceIdを指定するとID昇順のデータが得られるので、ID降順に並べ直す
@ -296,7 +289,7 @@ class ColumnTask_Refresh(
) { ) {
addOne(list_tmp, TootGap.mayNull(null, column.idRecent), head = addToHead) addOne(list_tmp, TootGap.mayNull(null, column.idRecent), head = addToHead)
} }
}
return firstResult return firstResult
} }
@ -321,98 +314,70 @@ class ColumnTask_Refresh(
} }
} }
val time_start = SystemClock.elapsedRealtime()
val addToHead = false val addToHead = false
var result = requester(true) fun parseResult(result: TootApiResult?): Boolean {
val firstResult = result
fun saveRange(src: List<T>): Boolean = when (column.pagingType) { result ?: return log.d("$logCaption: cancelled.")
result.jsonObject?.let { jsonObject ->
if (column.pagingType == ColumnPagingType.Cursor) {
column.idOld = EntityId.mayNull(jsonObject.string("next"))
}
result.data = arrayFinder(jsonObject)
}
val array = result.jsonArray
?: return log.w("$logCaption: missing item list.")
val src = listParser(parser, array)
if (list_tmp == null) list_tmp = ArrayList(src.size)
adder(src, addToHead)
val more = when (column.pagingType) {
ColumnPagingType.Offset -> { ColumnPagingType.Offset -> {
column.offsetNext += src.size column.offsetNext += src.size
true true
} }
else -> // ColumnPagingType.Default // ColumnPagingType.Default
column.saveRangeBottom(result, src) else -> column.saveRangeBottom(result, src)
.also {
if (!it) log.d("$logCaption: saveRangeBottom returns false. no more unread contents.")
}
}
var jsonObject = result?.jsonObject
if (jsonObject != null) {
if (column.pagingType == ColumnPagingType.Cursor) {
column.idOld = EntityId.mayNull(jsonObject.string("next"))
}
result?.data = arrayFinder(jsonObject)
}
var array = result?.jsonArray
if (array != null) {
list_tmp = ArrayList()
var src = listParser(parser, array)
adder(src, addToHead)
var hasMoreRange = saveRange(src)
while (hasMoreRange && repeatReading) {
if (isCancelled) {
log.d("$logCaption: cancelled.")
break
}
// bottomの場合、フィルタなしなら繰り返さない
if (!column.isFilterEnabled) {
log.d("$logCaption: isFilterEnabled is false.")
break
} }
return when {
!more -> log.d("$logCaption: no more items.")
// max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない // max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない
// 直前のデータが0個なら終了とみなすしかなさそう // 直前のデータが0個なら終了とみなすしかなさそう
if (src.isEmpty()) { src.isEmpty() -> log.d("$logCaption: empty item list.")
log.d("$logCaption: previous size == 0.") else -> true
break }
} }
if (column.idOld == null) {
val time_start = SystemClock.elapsedRealtime()
val firstResult = requester(true)
var more = parseResult(firstResult) && repeatReading
while (more) more = when {
isCancelled ->
log.d("$logCaption: cancelled.")
// bottomの場合、フィルタなしなら繰り返さない
!column.isFilterEnabled ->
log.d("$logCaption: isFilterEnabled is false.")
column.idOld == null ->
log.d("$logCaption: idOld is null.") log.d("$logCaption: idOld is null.")
break
}
// 十分読んだらそれで終了 // 十分読んだらそれで終了
if ((list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH) { (list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH ->
log.d("$logCaption: read enough data.") log.d("$logCaption: read enough data.")
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) { SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT ->
// タイムアウト
log.d("$logCaption: loop timeout.") log.d("$logCaption: loop timeout.")
break
else -> parseResult(requester(false))
} }
result = requester(false)
jsonObject = result?.jsonObject
if (jsonObject != null) {
if (column.pagingType == ColumnPagingType.Cursor) {
column.idOld = EntityId.mayNull(jsonObject.string("next"))
}
result?.data = arrayFinder(jsonObject)
}
array = result?.jsonArray
if (array == null) {
log.d("$logCaption: error or cancelled.")
break
}
src = listParser(parser, array)
adder(src, addToHead)
hasMoreRange = saveRange(src)
}
}
return firstResult return firstResult
} }
@ -428,81 +393,83 @@ class ColumnTask_Refresh(
if (column.pagingType != ColumnPagingType.Default) if (column.pagingType != ColumnPagingType.Default)
return TootApiResult("can't refresh top.") return TootApiResult("can't refresh top.")
val time_start = SystemClock.elapsedRealtime()
val addToHead = false val addToHead = false
var result = requester(true, null, null)
val firstResult = result
var jsonObject = result?.jsonObject
if (jsonObject != null) result?.data = arrayFinder(jsonObject)
var array = result?.jsonArray
if (array != null) {
val last_since_id = column.idRecent
list_tmp = ArrayList()
var src = listParser(parser, array)
adder(src, addToHead)
column.saveRangeTop(result, src)
// TLは (新しいデータ)(ギャップ)(古いデータ) となるので、レンジを保存するのはここだけで良い
// 続く読み込みはギャップを埋めるものなのでレンジを保存してはいけない
// 頭の方を読む時は隙間を減らすため、フィルタの有無に関係なく繰り返しを行う // 頭の方を読む時は隙間を減らすため、フィルタの有無に関係なく繰り返しを行う
var willAddGap = false var willAddGap = false
// 2回目以降のリクエスト範囲はギャップを意識したものになる
val last_since_id = column.idRecent
var max_id: EntityId? = null var max_id: EntityId? = null
while (true) {
if (isCancelled) { fun parseResult(result: TootApiResult?): Boolean {
log.d("$logCaption: cancelled.") val first = list_tmp?.isEmpty() != false
break
if (result == null) {
if (!first) willAddGap = true
return log.d("$logCaption: cancelled.")
} }
if (src.isEmpty()) { result.jsonObject?.let { result.data = arrayFinder(it) }
// max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない
// 直前のデータが0個なら終了とみなすしかなさそう
log.d("$logCaption: previous size == 0.")
break
}
// 直前に読んだ範囲のmaxIdを調べる
max_id = column.parseRange(result, src).first
if (max_id == null) {
log.d("$logCaption: max_id is null.")
break
}
if ((list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH) {
log.d("$logCaption: read enough. make gap.")
willAddGap = true
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) {
// タイムアウト
log.d("$logCaption: timeout. make gap.")
willAddGap = true
break
}
result = requester(false, max_id, last_since_id)
jsonObject = result?.jsonObject
if (jsonObject != null) result?.data = arrayFinder(jsonObject)
array = result?.jsonArray
val array = result.jsonArray
if (array == null) { if (array == null) {
// エラー if (!first) willAddGap = true
log.d("$logCaption: error or cancelled. make gap.") return log.w("$logCaption: missing item list")
willAddGap = true
break
} }
src = listParser(parser, array) val src = listParser(parser, array)
if (list_tmp == null) list_tmp = ArrayList(src.size)
adder(src, addToHead) adder(src, addToHead)
when {
first -> {
// TLは (新しいデータ)(ギャップ)(古いデータ) となるので、レンジを保存するのはここだけで良い
// 続く読み込みはギャップを埋めるものなのでレンジを保存してはいけない
column.saveRangeTop(result, src)
}
else -> {
// 今読んだのはギャップなので範囲を保存してはいけない // 今読んだのはギャップなので範囲を保存してはいけない
} }
}
return when {
// max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない
// 直前のデータが0個なら終了とみなすしかなさそう
src.isEmpty() -> log.d("$logCaption: empty list.")
else -> {
// 直前に読んだ範囲のmaxIdを調べる
max_id = column.parseRange(result, src).first
true
}
}
}
val time_start = SystemClock.elapsedRealtime()
// 初回リクエスト
val firstResult = requester(true, null, null)
var more = parseResult(firstResult)
// 2回目以降
while (more) more = when {
isCancelled ->
log.d("$logCaption: cancelled.")
max_id == null ->
log.d("$logCaption: max_id is null.")
(list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH -> {
willAddGap = true
log.d("$logCaption: read enough. make gap.")
}
SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT -> {
willAddGap = true
log.d("$logCaption: timeout. make gap.")
}
else -> parseResult(requester(false, max_id, last_since_id))
}
if (!isCancelled if (!isCancelled
&& list_tmp?.isNotEmpty() == true && list_tmp?.isNotEmpty() == true
@ -510,7 +477,6 @@ class ColumnTask_Refresh(
) { ) {
addOne(list_tmp, TootGap.mayNull(max_id, last_since_id), head = addToHead) addOne(list_tmp, TootGap.mayNull(max_id, last_since_id), head = addToHead)
} }
}
return firstResult return firstResult
} }
@ -521,83 +487,74 @@ class ColumnTask_Refresh(
listParser: (parser: TootParser, jsonArray: JsonArray) -> List<T>, listParser: (parser: TootParser, jsonArray: JsonArray) -> List<T>,
adder: (List<T>, Boolean) -> Unit adder: (List<T>, Boolean) -> Unit
): TootApiResult? { ): TootApiResult? {
list_tmp = ArrayList()
// 上端の差分更新に対応できるのは ColumnPagingType.Default だけ // 上端の差分更新に対応できるのは ColumnPagingType.Default だけ
if (column.pagingType != ColumnPagingType.Default) if (column.pagingType != ColumnPagingType.Default)
return TootApiResult("can't refresh top.") return TootApiResult("can't refresh top.")
val last_since_id = column.idRecent
val addToHead = true val addToHead = true
var willAddGap = false
// 2回目以降のリクエスト範囲
val last_since_id = column.idRecent
var max_id: EntityId? = null
fun parseResult(result: TootApiResult?): Boolean {
val first = list_tmp?.isEmpty() != false
if (result == null) {
if (!first) willAddGap = true
return log.d("$logCaption:cancelled.")
}
result.jsonObject?.let { result.data = arrayFinder(it) }
val array = result.jsonArray
if (array == null) {
if (!first) willAddGap = true
return log.w("$logCaption: missing item list")
}
val src = listParser(parser, array)
if (list_tmp == null) list_tmp = ArrayList(src.size)
adder(src, addToHead)
column.saveRangeTop(result, src)
// Linkヘッダからはより新しいデータがあるかどうかはわからない
return when {
// max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない
// 直前のデータが0個なら終了とみなすしかなさそう
src.isEmpty() -> log.d("$logCaption: empty list.")
else -> {
// 直前に読んだ範囲のmaxIdを調べる
max_id = column.parseRange(result, src).first
true
}
}
}
val time_start = SystemClock.elapsedRealtime() val time_start = SystemClock.elapsedRealtime()
var result = requester(true, null, null) val firstResult = requester(true, null, null)
var more = parseResult(firstResult)
val firstResult = result while (more) more = when {
isCancelled ->
var jsonObject = result?.jsonObject
if (jsonObject != null) {
result?.data = arrayFinder(jsonObject)
}
var array = result?.jsonArray
if (array != null) {
var src = listParser(parser, array)
adder(src, addToHead)
var willAddGap = false
var max_id: EntityId? = null
while (true) {
column.saveRangeTop(result, src)
if (isCancelled) {
log.d("$logCaption: cancelled.") log.d("$logCaption: cancelled.")
break
}
// max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない max_id == null ->
// 直前のデータが0個なら終了とみなすしかなさそう
if (src.isEmpty()) {
log.d("$logCaption: previous size == 0.")
break
}
// 直前に読んだ範囲のmaxIdを調べる
max_id = column.parseRange(result, src).first
if (max_id == null) {
log.d("$logCaption: max_id is null.") log.d("$logCaption: max_id is null.")
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) { SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT -> {
log.d("$logCaption: timeout. make gap.")
// タイムアウト // タイムアウト
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない // 隙間ができるかもしれない。後ほど手動で試してもらうしかない
willAddGap = true willAddGap = true
break log.d("$logCaption: timeout. make gap.")
} }
result = requester(false, null, null) else -> parseResult(requester(false, null, null))
jsonObject = result?.jsonObject
if (jsonObject != null)
result?.data = arrayFinder(jsonObject)
array = result?.jsonArray
if (array == null) {
log.d("$logCaption: error or cancelled. make gap.")
// エラー
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
willAddGap = true
break
}
src = listParser(parser, array)
adder(src, addToHead)
} }
if (!isCancelled if (!isCancelled
@ -606,7 +563,7 @@ class ColumnTask_Refresh(
) { ) {
addOne(list_tmp, TootGap.mayNull(max_id, last_since_id), head = addToHead) addOne(list_tmp, TootGap.mayNull(max_id, last_since_id), head = addToHead)
} }
}
return firstResult return firstResult
} }
@ -629,28 +586,29 @@ class ColumnTask_Refresh(
return TootApiResult(context.getString(R.string.end_of_list)) return TootApiResult(context.getString(R.string.end_of_list))
} }
val time_start = SystemClock.elapsedRealtime()
val addToHead = false val addToHead = false
var result = requester(true, null, null) // parse result and add to list_tmp
val firstResult = result // returns false if no more result
fun parseResult(result: TootApiResult?): Boolean {
result ?: return log.d("$logCaption: cancelled.")
var jsonObject = result?.jsonObject result.jsonObject?.let { it ->
if (jsonObject != null) {
if (column.pagingType == ColumnPagingType.Cursor) { if (column.pagingType == ColumnPagingType.Cursor) {
column.idOld = EntityId.mayNull(jsonObject.string("next")) column.idOld = EntityId.mayNull(it.string("next"))
} }
result?.data = arrayFinder(jsonObject) result.data = arrayFinder(it)
} }
val array = result.jsonArray
?: return log.d("$logCaption: missing item list.")
var array = result?.jsonArray val src = listParser(parser, array)
if (array != null) { if (list_tmp == null) list_tmp = ArrayList(src.size)
list_tmp = ArrayList()
var src = listParser(parser, array)
adder(src, addToHead) adder(src, addToHead)
// returns false if no more result // save range to column
fun saveBottomRange(src: List<T>) = when (column.pagingType) { // false if no more result
val more = when (column.pagingType) {
ColumnPagingType.Offset -> { ColumnPagingType.Offset -> {
column.offsetNext += src.size column.offsetNext += src.size
true true
@ -660,67 +618,40 @@ class ColumnTask_Refresh(
else -> column.saveRangeBottom(result, src) else -> column.saveRangeBottom(result, src)
} }
if(!repeatReading){ return when {
// 繰り返しなしでも範囲の保存は行う !more -> log.d("$logCaption: no more items.")
saveBottomRange(src)
}else while(true){
if (!saveBottomRange(src)) {
log.d("$logCaption: saveRangeBottom returns false. no more items.")
break
}
if (isCancelled) {
log.d("$logCaption: cancelled.")
break
}
if (!column.isFilterEnabled) {
// bottomの場合、フィルタなしなら繰り返さない
log.d("$logCaption: isFiltered is false.")
break
}
if (src.isEmpty()) {
// max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない // max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない
// 直前のデータが0個なら終了とみなすしかなさそう // 直前のデータが0個なら終了とみなすしかなさそう
log.d("$logCaption: previous size == 0.") src.isEmpty() -> log.d("$logCaption: empty item list.")
break
else -> true
}
} }
if (column.idOld == null) { val time_start = SystemClock.elapsedRealtime()
val firstResult = requester(true, null, null)
var more = parseResult(firstResult) && repeatReading
while (more) more = when {
isCancelled ->
log.d("$logCaption: cancelled.")
// bottomの場合、フィルタなしなら繰り返さない
!column.isFilterEnabled ->
log.d("$logCaption: isFiltered is false.")
column.idOld == null ->
log.d("$logCaption: idOld is null.") log.d("$logCaption: idOld is null.")
break
}
if ((list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH) { (list_tmp?.size ?: 0) >= Column.LOOP_READ_ENOUGH ->
// 十分読んだらそれで終了
log.d("$logCaption: read enough data.") log.d("$logCaption: read enough data.")
break
}
if (SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT) { SystemClock.elapsedRealtime() - time_start > Column.LOOP_TIMEOUT ->
// タイムアウト
log.d("$logCaption: loop timeout.") log.d("$logCaption: loop timeout.")
break
else -> parseResult(requester(false, null, null))
} }
result = requester(false, null, null)
jsonObject = result?.jsonObject
if (jsonObject != null)
result?.data = arrayFinder(jsonObject)
array = result?.jsonArray
if (array == null) {
log.d("$logCaption: error or cancelled.")
break
}
src = listParser(parser, array)
adder(src, addToHead)
}
}
return firstResult return firstResult
} }
@ -1077,13 +1008,13 @@ class ColumnTask_Refresh(
} }
} }
// リスト一覧にはページネーションがない // リスト一覧にはページネーションがない
// fun getListList(client : TootApiClient, path_base : String) : TootApiResult? { // fun getListList(client : TootApiClient, path_base : String) : TootApiResult? {
// //
// if(isMisskey) return TootApiResult("misskey support is not yet implemented.") // if(isMisskey) return TootApiResult("misskey support is not yet implemented.")
// //
// return TootApiResult("Mastodon's /api/v1/lists has no pagination.") // return TootApiResult("Mastodon's /api/v1/lists has no pagination.")
// } // }
suspend fun getReportList( suspend fun getReportList(
client: TootApiClient, client: TootApiClient,

View File

@ -2,96 +2,71 @@ package jp.juggler.util
import android.content.res.Resources import android.content.res.Resources
import android.util.Log import android.util.Log
import androidx.annotation.StringRes
class LogCategory(category : String) { fun Throwable.withCaption(caption: String) =
"${caption} :${javaClass.simpleName} ${message}"
class LogCategory(category: String) {
companion object { companion object {
private const val TAG = "SubwayTooter" private const val TAG = "SubwayTooter"
private fun format(fmt : String, args : Array<out Any?>) =
if(args.isEmpty()) fmt else String.format(fmt, *args)
private fun format(res : Resources, string_id : Int, args : Array<out Any?>) =
res.getString(string_id, *args)
private fun Throwable.withCaption(caption : String) =
"${caption} :${javaClass.simpleName} ${message}"
} }
private val tag = "$TAG:$category" private val tag = "$TAG:$category"
fun e(fmt : String, vararg args : Any?) { ///////////////////////////////
Log.e(tag, format(fmt, args)) // string
fun msg(priority: Int, msg: String): Boolean {
Log.println(priority, tag, msg)
return false
} }
fun w(fmt : String, vararg args : Any?) { fun e(msg: String) = msg(Log.ERROR, msg)
Log.w(tag, format(fmt, args)) fun w(msg: String) = msg(Log.WARN, msg)
} fun i(msg: String) = msg(Log.INFO, msg)
fun d(msg: String) = msg(Log.DEBUG, msg)
fun v(msg: String) = msg(Log.VERBOSE, msg)
fun i(fmt : String, vararg args : Any?) { ///////////////////////////////
Log.i(tag, format(fmt, args)) // Resources.getString()
}
fun d(fmt : String, vararg args : Any?) { fun msg(priority: Int, res: Resources, @StringRes stringId: Int, args: Array<out Any?>) =
Log.d(tag, format(fmt, args)) msg(priority, res.getString(stringId, *args))
}
fun v(fmt : String, vararg args : Any?) { fun e(res: Resources, @StringRes stringId: Int, vararg args: Any) =
Log.v(tag, format(fmt, args)) msg(Log.ERROR, res, stringId, args)
}
//////////////////////// fun w(res: Resources, @StringRes stringId: Int, vararg args: Any) =
// getString() msg(Log.WARN, res, stringId, args)
fun e(res : Resources, string_id : Int, vararg args : Any?) { fun i(res: Resources, @StringRes stringId: Int, vararg args: Any) =
Log.e(tag, format(res, string_id, args)) msg(Log.INFO, res, stringId, args)
}
fun w(res : Resources, string_id : Int, vararg args : Any?) { fun d(res: Resources, @StringRes stringId: Int, vararg args: Any) =
Log.w(tag, format(res, string_id, args)) msg(Log.DEBUG, res, stringId, args)
}
fun i(res : Resources, string_id : Int, vararg args : Any?) { fun v(res: Resources, @StringRes stringId: Int, vararg args: Any) =
Log.i(tag, format(res, string_id, args)) msg(Log.VERBOSE, res, stringId, args)
}
fun d(res : Resources, string_id : Int, vararg args : Any?) { ///////////////////////////////
Log.d(tag, format(res, string_id, args)) // Throwable + string
}
fun v(res : Resources, string_id : Int, vararg args : Any?) { fun msg(priority: Int, ex: Throwable, caption: String = "exception.") =
Log.v(tag, format(res, string_id, args)) msg(priority, ex.withCaption(caption))
}
////////////////////////
// exception
fun e(ex : Throwable, fmt : String, vararg args : Any?) { fun e(ex: Throwable, caption: String) = msg(Log.ERROR, ex, caption)
Log.e(tag, ex.withCaption(format(fmt, args))) fun w(ex: Throwable, caption: String) = msg(Log.WARN, ex, caption)
} fun i(ex: Throwable, caption: String) = msg(Log.INFO, ex, caption)
fun d(ex: Throwable, caption: String) = msg(Log.DEBUG, ex, caption)
fun v(ex: Throwable, caption: String) = msg(Log.VERBOSE, ex, caption)
fun w(ex : Throwable, fmt : String, vararg args : Any?) {
Log.w(tag, ex.withCaption(format(fmt, args)))
}
fun i(ex : Throwable, fmt : String, vararg args : Any?) {
Log.i(tag, ex.withCaption(format(fmt, args)))
}
fun d(ex : Throwable, fmt : String, vararg args : Any?) {
Log.d(tag, ex.withCaption(format(fmt, args)))
}
fun v(ex : Throwable, fmt : String, vararg args : Any?) {
Log.v(tag, ex.withCaption(format(fmt, args)))
}
//////////////////////// ////////////////////////
// stack trace // stack trace
fun trace(ex : Throwable, fmt : String, vararg args : Any?) { fun trace(ex: Throwable, caption: String = "exception."): Boolean {
Log.e(tag, format(fmt, args), ex) Log.e(tag, caption, ex)
} return false
fun trace(ex : Throwable) {
Log.e(tag, "exception.", ex)
} }
} }