v7.4: fetch all landmarks + cleanup after upload
- Updated version to 1.7.4 (versionCode 10) - Modified BeeApiClient.getLandmarks() to fetch 50,000 landmarks instead of 200 - Added cleanupLandmarks() method to attempt deletion from Bee device after upload - Enhanced AdaMapsUploadWorker to call cleanup after successful upload to ADAMaps - Cleanup tries multiple potential DELETE endpoints and cmd interface - Documents limitation: Bee API may not support landmark deletion Note: Cleanup functionality is exploratory - no confirmed DELETE endpoint found in Bee API docs
This commit is contained in:
parent
19ab72d8ea
commit
1ae0657bb0
3 changed files with 166 additions and 4 deletions
|
|
@ -13,8 +13,8 @@ android {
|
|||
applicationId = "com.adamaps.varroa"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 9
|
||||
versionName = "1.7.3"
|
||||
versionCode = 10
|
||||
versionName = "1.7.4"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
|
@ -204,9 +205,9 @@ class BeeApiClient(
|
|||
}
|
||||
|
||||
suspend fun getLandmarks(): ApiResult<List<BeeDetection>> = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "getLandmarks() called")
|
||||
Log.d(TAG, "getLandmarks() called - fetching ALL landmarks")
|
||||
|
||||
when (val r = getRaw("/api/1/landmarks/last/200")) {
|
||||
when (val r = getRaw("/api/1/landmarks/last/50000")) {
|
||||
is ApiResult.Success -> try {
|
||||
Log.d(TAG, "Raw landmarks response received - parsing JSON...")
|
||||
val type = object : TypeToken<List<BeeDetection>>() {}.type
|
||||
|
|
@ -323,4 +324,140 @@ class BeeApiClient(
|
|||
}
|
||||
return configured to ApiResult.Error("No camera endpoint responded")
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to delete landmarks from Bee device after successful upload.
|
||||
* This tries various potential DELETE endpoints.
|
||||
*
|
||||
* @param landmarkIds List of landmark IDs to delete
|
||||
* @return ApiResult indicating success/failure of cleanup operation
|
||||
*/
|
||||
suspend fun cleanupLandmarks(landmarkIds: List<Long>): ApiResult<String> = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "cleanupLandmarks() called with ${landmarkIds.size} IDs: ${landmarkIds.take(10)}...")
|
||||
|
||||
if (landmarkIds.isEmpty()) {
|
||||
Log.d(TAG, "No landmark IDs provided - skipping cleanup")
|
||||
return@withContext ApiResult.Success("No landmarks to clean up")
|
||||
}
|
||||
|
||||
// Try various potential DELETE endpoints
|
||||
val endpoints = listOf(
|
||||
"/api/1/landmarks/delete",
|
||||
"/api/1/landmarks/clear",
|
||||
"/api/1/landmarks/cleanup",
|
||||
"/api/1/landmarks/remove",
|
||||
"/api/1/cmd" // As a last resort using the cmd endpoint
|
||||
)
|
||||
|
||||
for (endpoint in endpoints) {
|
||||
Log.d(TAG, "Trying cleanup endpoint: $endpoint")
|
||||
|
||||
val result = when (endpoint) {
|
||||
"/api/1/cmd" -> {
|
||||
// Use the cmd endpoint to try deleting landmarks via system commands
|
||||
Log.d(TAG, "Attempting cleanup via cmd endpoint...")
|
||||
tryCleanupViaCmd(landmarkIds)
|
||||
}
|
||||
else -> {
|
||||
// Try standard DELETE/POST requests
|
||||
tryCleanupEndpoint(endpoint, landmarkIds)
|
||||
}
|
||||
}
|
||||
|
||||
when (result) {
|
||||
is ApiResult.Success -> {
|
||||
Log.i(TAG, "Cleanup successful via $endpoint: ${result.data}")
|
||||
return@withContext result
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
Log.w(TAG, "Cleanup failed via $endpoint: ${result.message}")
|
||||
// Continue trying other endpoints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "All cleanup endpoints failed - Bee may not support landmark deletion")
|
||||
return@withContext ApiResult.Error("No working DELETE endpoint found - cleanup not supported by Bee device")
|
||||
}
|
||||
|
||||
private suspend fun tryCleanupEndpoint(endpoint: String, landmarkIds: List<Long>): ApiResult<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val jsonBody = gson.toJson(mapOf("ids" to landmarkIds))
|
||||
val requestBody = okhttp3.RequestBody.create(
|
||||
"application/json".toMediaType(),
|
||||
jsonBody
|
||||
)
|
||||
|
||||
// Try both DELETE and POST methods
|
||||
for (method in listOf("DELETE", "POST")) {
|
||||
Log.d(TAG, "Trying $method $endpoint")
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$apiUrl$endpoint")
|
||||
.method(method, if (method == "DELETE") null else requestBody)
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().use { resp ->
|
||||
val body = resp.body?.string() ?: ""
|
||||
if (resp.isSuccessful) {
|
||||
Log.i(TAG, "$method $endpoint succeeded: $body")
|
||||
return@withContext ApiResult.Success("$method $endpoint: $body")
|
||||
} else {
|
||||
Log.d(TAG, "$method $endpoint failed: ${resp.code} ${resp.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return@withContext ApiResult.Error("$endpoint not supported (tried DELETE and POST)")
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Exception trying $endpoint: ${e.message}")
|
||||
return@withContext ApiResult.Error("Exception: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryCleanupViaCmd(landmarkIds: List<Long>): ApiResult<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
// Try to find where landmarks are stored and delete them via filesystem commands
|
||||
val commands = listOf(
|
||||
"find /data -name '*landmark*' -type f -ls",
|
||||
"find /tmp -name '*landmark*' -type f -ls",
|
||||
"ls -la /data/recording/",
|
||||
"redis-cli KEYS '*landmark*'",
|
||||
"redis-cli KEYS '*detection*'"
|
||||
)
|
||||
|
||||
for (cmd in commands) {
|
||||
Log.d(TAG, "Trying cmd: $cmd")
|
||||
val jsonBody = gson.toJson(mapOf("cmd" to cmd))
|
||||
val requestBody = okhttp3.RequestBody.create(
|
||||
"application/json".toMediaType(),
|
||||
jsonBody
|
||||
)
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$apiUrl/api/1/cmd")
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().use { resp ->
|
||||
val body = resp.body?.string() ?: ""
|
||||
if (resp.isSuccessful) {
|
||||
Log.d(TAG, "Cmd '$cmd' result: $body")
|
||||
// For now, just log the results to understand the data structure
|
||||
if (body.contains("landmark") || body.contains("detection")) {
|
||||
Log.i(TAG, "Found potential landmark storage: $body")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We're not actually deleting anything via cmd yet, just exploring
|
||||
Log.w(TAG, "Cleanup via cmd endpoint: exploration complete, actual deletion not implemented yet")
|
||||
return@withContext ApiResult.Error("Cleanup via cmd: exploration only, deletion not yet implemented")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to explore via cmd endpoint", e)
|
||||
return@withContext ApiResult.Error("Cmd exploration failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.work.PeriodicWorkRequestBuilder
|
|||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import com.adamaps.varroa.api.AdaMapsApiClient
|
||||
import com.adamaps.varroa.api.BeeApiClient
|
||||
import com.adamaps.varroa.data.AdaMapsDetection
|
||||
import com.adamaps.varroa.data.ApiResult
|
||||
import com.adamaps.varroa.data.SettingsDataStore
|
||||
|
|
@ -108,6 +109,7 @@ class AdaMapsUploadWorker(
|
|||
|
||||
private val database = VarroaDatabase.getInstance(applicationContext)
|
||||
private val adamapsClient = AdaMapsApiClient()
|
||||
private val beeClient = BeeApiClient()
|
||||
private val settingsStore = SettingsDataStore(applicationContext)
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
|
|
@ -148,6 +150,29 @@ class AdaMapsUploadWorker(
|
|||
totalUploaded += batch.size
|
||||
_totalUploaded.value += batch.size
|
||||
Log.i(TAG, "Batch #$batchNum uploaded successfully - running total: $totalUploaded")
|
||||
|
||||
// Attempt cleanup from Bee device after successful upload
|
||||
val beeDetectionIds = batch.map { it.beeDetectionId }
|
||||
Log.d(TAG, "Attempting to cleanup ${beeDetectionIds.size} landmarks from Bee device...")
|
||||
|
||||
// Configure BeeClient - this may fail if not connected to Bee network
|
||||
try {
|
||||
val settings = settingsStore.settings.first()
|
||||
beeClient.updateUrl(settings.beeApiUrl)
|
||||
|
||||
when (val cleanupResult = beeClient.cleanupLandmarks(beeDetectionIds)) {
|
||||
is ApiResult.Success -> {
|
||||
Log.i(TAG, "Cleanup successful: ${cleanupResult.data}")
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
Log.w(TAG, "Cleanup failed (expected - likely no DELETE endpoint): ${cleanupResult.message}")
|
||||
// This is not a failure of the upload process
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Cleanup attempt failed (likely not connected to Bee network): ${e.message}")
|
||||
// This is expected when uploading via internet connection, not Bee AP
|
||||
}
|
||||
}
|
||||
is Result.Retry -> {
|
||||
// Network error, WorkManager will retry
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue