feat(agent): implement #91 controlled recollection
This commit is contained in:
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "cn.ilapage.goauto.agent"
|
||||
minSdk = 23
|
||||
targetSdk = 34
|
||||
versionCode = 6
|
||||
versionName = "0.4.1"
|
||||
versionCode = 7
|
||||
versionName = "0.5.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ data class CollectionHistoryDetail(
|
||||
val missing: List<String>,
|
||||
)
|
||||
|
||||
data class CollectionResetResult(val taskId: Long, val status: String, val replayed: Boolean)
|
||||
|
||||
data class PurchaseHistoryItem(
|
||||
val taskId: Long,
|
||||
val status: String,
|
||||
@@ -242,6 +244,12 @@ class AgentApiClient(private val serverUrl: String) {
|
||||
)
|
||||
}
|
||||
|
||||
fun resetCollectionTask(taskId: Long, requestId: String, token: String): CollectionResetResult {
|
||||
val payload = JSONObject().put("requestId", requestId)
|
||||
val data = requireNotNull(request("POST", "/api/agent/v1/collection-tasks/$taskId/reset", payload, token)).getJSONObject("data")
|
||||
return CollectionResetResult(data.getLong("taskId"), data.getString("status"), data.optBoolean("replayed"))
|
||||
}
|
||||
|
||||
fun purchaseHistory(token: String, page: Int, status: String?, taskNo: String?): HistoryPage<PurchaseHistoryItem> {
|
||||
val data = requireNotNull(request("GET", historyPath("/api/agent/v1/purchase-tasks", page, status, taskNo), null, token)).getJSONObject("data")
|
||||
return HistoryPage(
|
||||
|
||||
@@ -20,9 +20,12 @@ import cn.ilapage.goauto.agent.network.CollectionHistoryItem
|
||||
import cn.ilapage.goauto.agent.network.HistoryPage
|
||||
import cn.ilapage.goauto.agent.network.PurchaseHistoryDetail
|
||||
import cn.ilapage.goauto.agent.network.PurchaseHistoryItem
|
||||
import cn.ilapage.goauto.agent.service.AgentForegroundService
|
||||
import cn.ilapage.goauto.agent.service.AgentSettingsStore
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
|
||||
internal object TaskNumberParser {
|
||||
fun normalize(raw: String, collection: Boolean): String? {
|
||||
@@ -40,6 +43,18 @@ internal object TaskNumberParser {
|
||||
}
|
||||
}
|
||||
|
||||
internal object CollectionResetPolicy {
|
||||
private val terminalStatuses = setOf("completed", "completed_partial", "failed")
|
||||
|
||||
fun isAllowed(status: String): Boolean = status in terminalStatuses
|
||||
|
||||
fun confirmationMessage(status: String): String = if (status == "failed") {
|
||||
"重新采集会清除本任务现有的错误和采集结果,然后重新加入当前设备的任务队列。"
|
||||
} else {
|
||||
"重新采集会清除本任务现有的全部采集结果,然后重新加入当前设备的任务队列。"
|
||||
}
|
||||
}
|
||||
|
||||
class TaskHistoryFragment : Fragment() {
|
||||
private val collection: Boolean get() = requireArguments().getBoolean(ARG_COLLECTION)
|
||||
private lateinit var pageColumn: LinearLayout
|
||||
@@ -291,6 +306,56 @@ class TaskHistoryFragment : Fragment() {
|
||||
}), collectionCardParams())
|
||||
if (detail.missing.isNotEmpty()) resultColumn.addView(context.centeredMessage("缺失项", detail.missing.joinToString("、")))
|
||||
if (task.errorMessage != null) resultColumn.addView(context.centeredMessage(task.errorMessage, "错误代码:${task.errorCode ?: "—"}"))
|
||||
if (CollectionResetPolicy.isAllowed(task.status)) {
|
||||
resultColumn.addView(MaterialButton(context).apply {
|
||||
text = "重新采集"
|
||||
minimumHeight = context.dp(48)
|
||||
contentDescription = "重新采集任务 ${task.taskId}"
|
||||
setOnClickListener { confirmReset(task) }
|
||||
}, collectionCardParams())
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirmReset(task: CollectionHistoryItem) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle("重新采集 #${task.taskId}?")
|
||||
.setMessage(CollectionResetPolicy.confirmationMessage(task.status))
|
||||
.setNegativeButton("取消", null)
|
||||
.setPositiveButton("确认重新采集") { _, _ -> resetCollectionTask(task.taskId) }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun resetCollectionTask(taskId: Long) {
|
||||
val context = requireContext()
|
||||
val credentials = runCatching { SecureDeviceStore(context).credentials() }.getOrNull()
|
||||
val serverUrl = AgentSettingsStore(context).serverUrl()
|
||||
if (credentials == null || serverUrl.isBlank()) {
|
||||
showMessage("无法重新采集", "设备尚未连接服务端,请先检查设置。", "返回任务详情") { loadCollectionDetail(taskId) }
|
||||
return
|
||||
}
|
||||
val generation = ++requestGeneration
|
||||
showLoading("正在加入重新采集队列…")
|
||||
Thread {
|
||||
runCatching { AgentApiClient(serverUrl).resetCollectionTask(taskId, UUID.randomUUID().toString(), credentials.token) }
|
||||
.onSuccess { result ->
|
||||
resultColumn.post {
|
||||
if (!isAdded || generation != requestGeneration) return@post
|
||||
AgentForegroundService.start(requireContext())
|
||||
showMessage(
|
||||
"已加入重新采集队列",
|
||||
"任务 #${result.taskId} 将由当前设备通过正常调度重新执行。",
|
||||
"返回采集记录",
|
||||
) { page = 1; load() }
|
||||
}
|
||||
}
|
||||
.onFailure { error ->
|
||||
resultColumn.post {
|
||||
if (isAdded && generation == requestGeneration) {
|
||||
showMessage("无法重新采集", error.message ?: "请检查设备状态后重试。", "返回任务详情") { loadCollectionDetail(taskId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun renderPurchaseDetail(detail: PurchaseHistoryDetail) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.ilapage.goauto.agent
|
||||
|
||||
import cn.ilapage.goauto.agent.ui.CollectionResetPolicy
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class CollectionResetPolicyTest {
|
||||
@Test
|
||||
fun `only collection terminal states can reset`() {
|
||||
assertTrue(CollectionResetPolicy.isAllowed("completed"))
|
||||
assertTrue(CollectionResetPolicy.isAllowed("completed_partial"))
|
||||
assertTrue(CollectionResetPolicy.isAllowed("failed"))
|
||||
assertFalse(CollectionResetPolicy.isAllowed("pending"))
|
||||
assertFalse(CollectionResetPolicy.isAllowed("running"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `confirmation always explains result deletion`() {
|
||||
assertTrue(CollectionResetPolicy.confirmationMessage("failed").contains("清除"))
|
||||
assertTrue(CollectionResetPolicy.confirmationMessage("completed").contains("全部采集结果"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user