diff --git a/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt b/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt index 4d2e425..183eaae 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt @@ -1460,6 +1460,10 @@ class MainActivity : AppCompatActivity() { runOnUiThread({ setLoading(false) + // Refresh list - merge may have added events from other devices + logbook?.sort() + recyclerView.adapter?.notifyDataSetChanged() + Toast.makeText( this@MainActivity, if (lastEventAdded != null) diff --git a/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt b/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt index f23da2e..e016bcf 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/WebDAVLogbookRepository.kt @@ -133,13 +133,16 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p } private fun saveLogbook(context: Context, logbook: Logbook) { - // Lock logbook on WebDAV to avoid concurrent changes - //sardine.lock(getUrl()) - // Reload logbook from WebDAV - // Merge logbooks (based on time) - // Write logbook - // Unlock logbook on WebDAV - //sardine.unlock(getUrl()) + // Load remote logbook and merge before saving to avoid overwriting + // events added by other devices + try { + val remoteLogbook = loadLogbook(logbook.name) + mergeRemoteEvents(logbook, remoteLogbook) + Log.d(TAG, "Merged remote events, logbook now has ${logbook.logs.size} events") + } catch (e: Exception) { + // Remote not available (404, network error, etc.) - save local version as-is + Log.w(TAG, "Could not load remote logbook for merge: $e") + } val ja = JSONArray() for (l in logbook.logs) { @@ -148,6 +151,61 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p sardine.put(getUrl(logbook.name), ja.toString().toByteArray()) } + /** + * Merges remote events into the local logbook. + * Events from the remote that don't exist locally are added. + * Ongoing events that were finalized locally are skipped. + */ + private fun mergeRemoteEvents(local: Logbook, remote: Logbook) { + val localFingerprints = local.logs.map { eventFingerprint(it) }.toHashSet() + var added = 0 + + for (remoteEvent in remote.logs) { + val fingerprint = eventFingerprint(remoteEvent) + if (localFingerprints.contains(fingerprint)) { + continue // Already exists locally + } + + // If remote event is ongoing, check if it was finalized locally + if (remoteEvent.ongoing && isOngoingSuperseded(remoteEvent, local.logs)) { + continue + } + + local.logs.add(remoteEvent) + localFingerprints.add(fingerprint) + added++ + } + + if (added > 0) { + local.sort() + Log.d(TAG, "Added $added events from remote during merge") + } + } + + private fun eventFingerprint(event: LunaEvent): String { + return "${event.time}_${event.type}_${event.quantity}_${event.notes}_${event.signature}_${event.ongoing}" + } + + /** + * Checks if a remote ongoing event was already finalized locally. + * Compares the ongoing event's start time with the calculated start time + * of finalized events of the same type. + */ + private fun isOngoingSuperseded(ongoingEvent: LunaEvent, localEvents: List): Boolean { + val ongoingStartTime = ongoingEvent.time + for (local in localEvents) { + if (local.ongoing) continue + if (local.type != ongoingEvent.type) continue + // Finalized events: time = end time, quantity = duration in minutes + // Calculate approximate start time + val localStartTime = local.time - local.quantity * 60 + if (Math.abs(ongoingStartTime - localStartTime) < 120) { + return true + } + } + return false + } + /** * Connect to server and check if a logbook already exists. * If it does not exist, try to upload the local one.