From 1ae0657bb096c0c6f58d9157ebb3b393be8fd31b Mon Sep 17 00:00:00 2001 From: Kayos Date: Wed, 11 Mar 2026 14:56:53 -0700 Subject: [PATCH] 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 --- app/build.gradle.kts | 4 +- .../com/adamaps/varroa/api/BeeApiClient.kt | 141 +++++++++++++++++- .../varroa/service/AdaMapsUploadWorker.kt | 25 ++++ 3 files changed, 166 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4bbe081..df2239c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 diff --git a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt index 8c4e9ed..ec6feaa 100644 --- a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt @@ -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> = 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>() {}.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): ApiResult = 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): ApiResult = 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): ApiResult = 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}") + } + } } diff --git a/app/src/main/java/com/adamaps/varroa/service/AdaMapsUploadWorker.kt b/app/src/main/java/com/adamaps/varroa/service/AdaMapsUploadWorker.kt index 8b04f7f..19ac6d2 100644 --- a/app/src/main/java/com/adamaps/varroa/service/AdaMapsUploadWorker.kt +++ b/app/src/main/java/com/adamaps/varroa/service/AdaMapsUploadWorker.kt @@ -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