feat(v1.8.0): IMPL_022 Multi-Client Deletion Enhancement
Defensive improvements for server deletion detection: 1. Enhanced logging in detectServerDeletions(): - Statistics: server/local/synced note counts - Summary log when deletions found 2. Explicit documentation: - Comment clarifying checklists are included - Both Notes and Checklists use same detection mechanism 3. Sync banner now shows deletion count: - '3 synced · 2 deleted on server' - New strings: sync_deleted_on_server_count (en + de) 4. DELETED_ON_SERVER → PENDING on edit: - Verified existing logic works correctly - All edited notes → PENDING (re-upload to server) - Added comments for clarity Cross-client analysis confirmed: - ✅ Android/Desktop/Web deletions detected correctly - ⚠️ Obsidian .md-only deletions not detected (by design: JSON = source of truth) IMPL_022_MULTI_CLIENT_DELETION.md
This commit is contained in:
@@ -1055,6 +1055,10 @@ class WebDavSyncService(private val context: Context) {
|
|||||||
* Keine zusätzlichen HTTP-Requests! Nutzt die bereits geladene
|
* Keine zusätzlichen HTTP-Requests! Nutzt die bereits geladene
|
||||||
* serverNoteIds-Liste aus dem PROPFIND-Request.
|
* serverNoteIds-Liste aus dem PROPFIND-Request.
|
||||||
*
|
*
|
||||||
|
* Prüft ALLE Notizen (Notes + Checklists), da beide als
|
||||||
|
* JSON in /notes/{id}.json gespeichert werden.
|
||||||
|
* NoteType (NOTE vs CHECKLIST) spielt keine Rolle für die Detection.
|
||||||
|
*
|
||||||
* @param serverNoteIds Set aller Note-IDs auf dem Server (aus PROPFIND)
|
* @param serverNoteIds Set aller Note-IDs auf dem Server (aus PROPFIND)
|
||||||
* @param localNotes Alle lokalen Notizen
|
* @param localNotes Alle lokalen Notizen
|
||||||
* @return Anzahl der als DELETED_ON_SERVER markierten Notizen
|
* @return Anzahl der als DELETED_ON_SERVER markierten Notizen
|
||||||
@@ -1064,23 +1068,35 @@ class WebDavSyncService(private val context: Context) {
|
|||||||
localNotes: List<Note>
|
localNotes: List<Note>
|
||||||
): Int {
|
): Int {
|
||||||
var deletedCount = 0
|
var deletedCount = 0
|
||||||
|
val syncedNotes = localNotes.filter { it.syncStatus == SyncStatus.SYNCED }
|
||||||
|
|
||||||
localNotes.forEach { note ->
|
// 🆕 v1.8.0 (IMPL_022): Statistik-Log für Debugging
|
||||||
|
Logger.d(TAG, "🔍 detectServerDeletions: " +
|
||||||
|
"serverNotes=${serverNoteIds.size}, " +
|
||||||
|
"localSynced=${syncedNotes.size}, " +
|
||||||
|
"localTotal=${localNotes.size}")
|
||||||
|
|
||||||
|
syncedNotes.forEach { note ->
|
||||||
// Nur SYNCED-Notizen prüfen:
|
// Nur SYNCED-Notizen prüfen:
|
||||||
// - LOCAL_ONLY: War nie auf Server → irrelevant
|
// - LOCAL_ONLY: War nie auf Server → irrelevant
|
||||||
// - PENDING: Soll hochgeladen werden → nicht überschreiben
|
// - PENDING: Soll hochgeladen werden → nicht überschreiben
|
||||||
// - CONFLICT: Wird separat behandelt
|
// - CONFLICT: Wird separat behandelt
|
||||||
// - DELETED_ON_SERVER: Bereits markiert
|
// - DELETED_ON_SERVER: Bereits markiert
|
||||||
if (note.syncStatus == SyncStatus.SYNCED && note.id !in serverNoteIds) {
|
if (note.id !in serverNoteIds) {
|
||||||
val updatedNote = note.copy(syncStatus = SyncStatus.DELETED_ON_SERVER)
|
val updatedNote = note.copy(syncStatus = SyncStatus.DELETED_ON_SERVER)
|
||||||
storage.saveNote(updatedNote)
|
storage.saveNote(updatedNote)
|
||||||
deletedCount++
|
deletedCount++
|
||||||
|
|
||||||
Logger.d(TAG, "Note '${note.title}' (${note.id}) " +
|
Logger.d(TAG, "🗑️ Note '${note.title}' (${note.id}) " +
|
||||||
"was deleted on server, marked as DELETED_ON_SERVER")
|
"was deleted on server, marked as DELETED_ON_SERVER")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deletedCount > 0) {
|
||||||
|
Logger.d(TAG, "📊 Server deletion detection complete: " +
|
||||||
|
"$deletedCount of ${syncedNotes.size} synced notes deleted on server")
|
||||||
|
}
|
||||||
|
|
||||||
return deletedCount
|
return deletedCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,6 +231,8 @@ class NoteEditorViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val note = if (existingNote != null) {
|
val note = if (existingNote != null) {
|
||||||
|
// 🆕 v1.8.0 (IMPL_022): syncStatus wird immer auf PENDING gesetzt
|
||||||
|
// beim Bearbeiten - gilt für SYNCED, CONFLICT, DELETED_ON_SERVER, etc.
|
||||||
existingNote!!.copy(
|
existingNote!!.copy(
|
||||||
title = title,
|
title = title,
|
||||||
content = content,
|
content = content,
|
||||||
@@ -272,6 +274,8 @@ class NoteEditorViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val note = if (existingNote != null) {
|
val note = if (existingNote != null) {
|
||||||
|
// 🆕 v1.8.0 (IMPL_022): syncStatus wird immer auf PENDING gesetzt
|
||||||
|
// beim Bearbeiten - gilt für SYNCED, CONFLICT, DELETED_ON_SERVER, etc.
|
||||||
existingNote!!.copy(
|
existingNote!!.copy(
|
||||||
title = title,
|
title = title,
|
||||||
content = "", // Empty for checklists
|
content = "", // Empty for checklists
|
||||||
|
|||||||
@@ -559,10 +559,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
val bannerMessage = if (result.syncedCount > 0) {
|
// 🆕 v1.8.0 (IMPL_022): Erweiterte Banner-Nachricht mit Löschungen
|
||||||
getString(R.string.toast_sync_success, result.syncedCount)
|
val bannerMessage = buildString {
|
||||||
} else {
|
if (result.syncedCount > 0) {
|
||||||
getString(R.string.snackbar_nothing_to_sync)
|
append(getString(R.string.toast_sync_success, result.syncedCount))
|
||||||
|
}
|
||||||
|
if (result.deletedOnServerCount > 0) {
|
||||||
|
if (isNotEmpty()) append(" · ")
|
||||||
|
append(getString(R.string.sync_deleted_on_server_count, result.deletedOnServerCount))
|
||||||
|
}
|
||||||
|
if (isEmpty()) {
|
||||||
|
append(getString(R.string.snackbar_nothing_to_sync))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SyncStateManager.markCompleted(bannerMessage)
|
SyncStateManager.markCompleted(bannerMessage)
|
||||||
loadNotes()
|
loadNotes()
|
||||||
|
|||||||
@@ -73,6 +73,9 @@
|
|||||||
<string name="sync_legend_deleted_label">Auf Server gelöscht</string>
|
<string name="sync_legend_deleted_label">Auf Server gelöscht</string>
|
||||||
<string name="sync_legend_deleted_desc">Diese Notiz wurde auf einem anderen Gerät oder direkt auf dem Server gelöscht. Sie existiert noch lokal.</string>
|
<string name="sync_legend_deleted_desc">Diese Notiz wurde auf einem anderen Gerät oder direkt auf dem Server gelöscht. Sie existiert noch lokal.</string>
|
||||||
|
|
||||||
|
<!-- 🆕 v1.8.0 (IMPL_022): Sync-Banner Löschungsanzahl -->
|
||||||
|
<string name="sync_deleted_on_server_count">%d auf Server gelöscht</string>
|
||||||
|
|
||||||
<!-- ============================= -->
|
<!-- ============================= -->
|
||||||
<!-- DELETE DIALOGS -->
|
<!-- DELETE DIALOGS -->
|
||||||
<!-- ============================= -->
|
<!-- ============================= -->
|
||||||
|
|||||||
@@ -80,6 +80,9 @@
|
|||||||
<string name="sync_legend_deleted_label">Deleted on server</string>
|
<string name="sync_legend_deleted_label">Deleted on server</string>
|
||||||
<string name="sync_legend_deleted_desc">This note was deleted on another device or directly on the server. It still exists locally.</string>
|
<string name="sync_legend_deleted_desc">This note was deleted on another device or directly on the server. It still exists locally.</string>
|
||||||
|
|
||||||
|
<!-- 🆕 v1.8.0 (IMPL_022): Sync banner deletion count -->
|
||||||
|
<string name="sync_deleted_on_server_count">%d deleted on server</string>
|
||||||
|
|
||||||
<!-- ============================= -->
|
<!-- ============================= -->
|
||||||
<!-- DELETE DIALOGS -->
|
<!-- DELETE DIALOGS -->
|
||||||
<!-- ============================= -->
|
<!-- ============================= -->
|
||||||
|
|||||||
Reference in New Issue
Block a user