@@ -29,6 +29,7 @@ internal data class DownloadResult(
2929 val conflictCount : Int ,
3030 val deletedOnServerCount : Int = 0 ,
3131 val folderReconciledCount : Int = 0 ,
32+ val trashedDownloadedCount : Int = 0 ,
3233 val downloadFailed : Boolean = false ,
3334 val downloadError : String? = null
3435)
@@ -90,6 +91,7 @@ internal class NoteDownloader(
9091 var conflictCount = 0
9192 var skippedDeleted = 0 // Track skipped deleted notes
9293 var folderReconciledCount = 0
94+ var trashedDownloadedCount = 0
9395 val processedIds = mutableSetOf<String >() // 🆕 v1.2.2: Track already loaded notes
9496
9597 Logger .d(TAG , " 📥 downloadAll() called:" )
@@ -378,7 +380,7 @@ internal class NoteDownloader(
378380 localNote == null -> {
379381 // New note from server
380382 storage.saveNote(remoteNoteFoldered.copy(syncStatus = SyncStatus .SYNCED ))
381- downloadedCount++
383+ if (remoteNote.trashedAt == null ) downloadedCount++ else trashedDownloadedCount ++
382384 Logger .d(TAG , " ✅ Downloaded from /$activeSyncFolderName /: ${remoteNote.id} " )
383385
384386 // ⚡ Batch E-Tag for later
@@ -389,7 +391,7 @@ internal class NoteDownloader(
389391 forceOverwrite -> {
390392 // OVERWRITE mode: Always replace regardless of timestamps
391393 storage.saveNote(remoteNoteFoldered.copy(syncStatus = SyncStatus .SYNCED ))
392- downloadedCount++
394+ if (remoteNote.trashedAt == null ) downloadedCount++ else trashedDownloadedCount ++
393395 Logger .d(
394396 TAG ,
395397 " ♻️ Overwritten from /$activeSyncFolderName /: ${remoteNote.id} "
@@ -409,7 +411,7 @@ internal class NoteDownloader(
409411 } else {
410412 // Safe to overwrite
411413 storage.saveNote(remoteNoteFoldered.copy(syncStatus = SyncStatus .SYNCED ))
412- downloadedCount++
414+ if (remoteNote.trashedAt == null ) downloadedCount++ else trashedDownloadedCount ++
413415 Logger .d(TAG , " ✅ Updated from /$activeSyncFolderName /: ${remoteNote.id} " )
414416
415417 if (result.etag != null ) {
@@ -566,13 +568,13 @@ internal class NoteDownloader(
566568 when {
567569 localNote == null -> {
568570 storage.saveNote(remoteNote.copy(syncStatus = SyncStatus .SYNCED ))
569- downloadedCount++
571+ if (remoteNote.trashedAt == null ) downloadedCount++ else trashedDownloadedCount ++
570572 Logger .d(TAG , " ✅ Downloaded from ROOT: ${remoteNote.id} " )
571573 }
572574 forceOverwrite -> {
573575 // OVERWRITE mode: Always replace regardless of timestamps
574576 storage.saveNote(remoteNote.copy(syncStatus = SyncStatus .SYNCED ))
575- downloadedCount++
577+ if (remoteNote.trashedAt == null ) downloadedCount++ else trashedDownloadedCount ++
576578 Logger .d(TAG , " ♻️ Overwritten from ROOT: ${remoteNote.id} " )
577579 }
578580 localNote.updatedAt < remoteNote.updatedAt -> {
@@ -581,7 +583,7 @@ internal class NoteDownloader(
581583 conflictCount++
582584 } else {
583585 storage.saveNote(remoteNote.copy(syncStatus = SyncStatus .SYNCED ))
584- downloadedCount++
586+ if (remoteNote.trashedAt == null ) downloadedCount++ else trashedDownloadedCount ++
585587 Logger .d(TAG , " ✅ Updated from ROOT: ${remoteNote.id} " )
586588 }
587589 }
@@ -623,7 +625,7 @@ internal class NoteDownloader(
623625
624626 // 🆕 v1.8.0: Server-Deletions erkennen (nach Downloads)
625627 val allLocalNotes = storage.loadAllNotes()
626- val deletedOnServerCount = detectDeletions(serverNoteIds, allLocalNotes)
628+ val deletedOnServerCount = detectDeletions(serverNoteIds, allLocalNotes, deletionTracker )
627629
628630 if (deletedOnServerCount > 0 ) {
629631 Logger .d(TAG , " $deletedOnServerCount note(s) detected as deleted on server" )
@@ -635,6 +637,7 @@ internal class NoteDownloader(
635637 conflictCount = conflictCount,
636638 deletedOnServerCount = deletedOnServerCount,
637639 folderReconciledCount = folderReconciledCount,
640+ trashedDownloadedCount = trashedDownloadedCount,
638641 downloadFailed = downloadException != null ,
639642 downloadError = downloadException?.message
640643 )
@@ -651,7 +654,11 @@ internal class NoteDownloader(
651654 * @param localNotes Alle lokalen Notizen
652655 * @return Anzahl der als DELETED_ON_SERVER markierten Notizen
653656 */
654- suspend fun detectDeletions (serverNoteIds : Set <String >, localNotes : List <Note >): Int {
657+ suspend fun detectDeletions (
658+ serverNoteIds : Set <String >,
659+ localNotes : List <Note >,
660+ deletionTracker : dev.dettmer.simplenotes.models.DeletionTracker = dev.dettmer.simplenotes.models.DeletionTracker ()
661+ ): Int {
655662 // 🆕 v2.8.0 (Local-Only Folders): Notizen in lokalen Ordnern nie als server-gelöscht markieren.
656663 val localOnlyFolders = folderStore.getLocalOnlyFolderNames().map { it.lowercase() }.toSet()
657664 val syncedNotes = localNotes.filter {
@@ -706,15 +713,17 @@ internal class NoteDownloader(
706713 // - CONFLICT: Wird separat behandelt
707714 // - DELETED_ON_SERVER: Bereits markiert
708715 if (note.id !in serverNoteIds) {
709- if (note.trashedAt != null ) {
710- // 🆕 v2.9.0 (Trash): Die Notiz lag bereits im Papierkorb und ist jetzt auch vom
711- // Server verschwunden → ein anderes Gerät hat sie endgültig gelöscht (purge).
712- // Lokal hart löschen; der DeletionTracker-Eintrag verhindert Zombie-Wiederkehr.
716+ val purgedRemotely = deletionTracker.isDeleted(note.id) &&
717+ (deletionTracker.getDeletionTimestamp(note.id) ? : 0L ) >= note.updatedAt
718+ if (note.trashedAt != null || purgedRemotely) {
719+ // 🆕 v2.9.0 (Trash): already trashed + gone from server → purged on another device.
720+ // 🆕 shared ledger: id in deletions.json with deletedAt ≥ updatedAt → hard-delete.
721+ // Timestamp guard preserves a note legitimately re-created after the recorded purge.
713722 storage.deleteNote(note.id)
714723 Logger .d(
715724 TAG ,
716- " 🔥 Trashed note '${note.title} ' (${note.id} ) purged on another device → " +
717- " hard-deleted locally "
725+ " 🔥 Note '${note.title} ' (${note.id} ) purged remotely → hard-deleted locally " +
726+ if (purgedRemotely && note.trashedAt == null ) " (shared ledger) " else " "
718727 )
719728 } else {
720729 // 🆕 v2.9.0 (Trash): Echte Server-Löschung → in den Papierkorb verschieben.
0 commit comments