forked from penguin86/luna-tracker
Add merge-on-save to WebDAV sync to prevent event loss
Before saving to WebDAV, the remote logbook is loaded and merged with the local version. Events from other devices that don't exist locally are added before uploading. This prevents one device from overwriting events added by another device between sync intervals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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<LunaEvent>): 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.
|
||||
|
||||
Reference in New Issue
Block a user