From 86f976777f849f92881be6cd4dc612253bed4135 Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Fri, 1 Dec 2023 00:37:14 +0100
Subject: [PATCH] fix: initial sync

---
 .../de/chaosdorf/meteroid/sync/SyncManager.kt |  6 +++-
 .../sync/base/BaseIncrementalSyncHandler.kt   | 32 ++++++++++++-------
 .../de/chaosdorf/meteroid/ui/AppViewModel.kt  | 13 ++++++++
 .../meteroid/ui/NavigationViewModel.kt        | 16 ++++++++++
 .../meteroid/ui/navigation/MeteroidTopBar.kt  |  2 +-
 .../meteroid/ui/wrapped/WrappedSlide.kt       |  8 ++---
 6 files changed, 60 insertions(+), 17 deletions(-)

diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncManager.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncManager.kt
index e548fc1..1a3cbf3 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncManager.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/SyncManager.kt
@@ -57,6 +57,10 @@ class SyncManager @Inject constructor(
       userSyncHandler.sync(server)
       drinkSyncHandler.sync(server)
       if (user != null) {
+        Log.i(
+          "SyncManager",
+          "syncing transactions for user ${user.userId}, incremental ${incremental}"
+        )
         if (incremental) {
           transactionSyncHandler.syncIncremental(Pair(server, user.userId))
         } else {
@@ -65,7 +69,7 @@ class SyncManager @Inject constructor(
       }
     } catch (e: Exception) {
       Log.e(
-        "Sync",
+        "SyncManager",
         "Could not finish transaction for ${user?.name} (${user?.userId}) on ${server.url}",
         e
       )
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseIncrementalSyncHandler.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseIncrementalSyncHandler.kt
index 16ae412..0da6217 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseIncrementalSyncHandler.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/sync/base/BaseIncrementalSyncHandler.kt
@@ -32,34 +32,44 @@ abstract class BaseIncrementalSyncHandler<Context, Entry, Key> :
   abstract suspend fun loadLatestEntry(context: Context): Entry?
   abstract suspend fun loadAdded(context: Context, latest: Entry): List<Entry>
 
+  override suspend fun sync(context: Context) {
+    super.sync(context)
+  }
+
   override suspend fun syncIncremental(context: Context) {
     if (syncState.compareAndSet(State.Idle, State.Loading) ||
-      syncState.compareAndSet(State.Error(), State.Loading)) {
+      syncState.compareAndSet(State.Error(), State.Loading)
+    ) {
       Log.w(this::class.simpleName, "Started incremental sync")
-      val success = try {
-        val result = withTransaction {
+      try {
+        withTransaction {
           val latestEntry = loadLatestEntry(context)
           if (latestEntry != null) {
             val addedEntries = loadAdded(context, latestEntry)
             for (loadedEntry in addedEntries) {
               store(loadedEntry)
             }
-            true
           } else {
-            false
+            // If we can't do an incremental sync, do a full sync
+            val loadedEntries = loadCurrent(context)
+            val storedEntries = loadStored(context)
+            val storedKeys = storedEntries.map(::entryToKey).toSet()
+            val loadedKeys = loadedEntries.map(::entryToKey).toSet()
+            val removedKeys = storedKeys - loadedKeys
+            for (removedKey in removedKeys) {
+              Log.e("SyncHandler", "deleting: $removedKey")
+              delete(removedKey)
+            }
+            for (loadedEntry in loadedEntries) {
+              store(loadedEntry)
+            }
           }
         }
         syncState.value = State.Idle
         Log.w(this::class.simpleName, "Finished incremental sync")
-        result
       } catch (e: Exception) {
         Log.e(this::class.simpleName, "Error while syncing data", e)
         syncState.value = State.Error("Error while syncing data: $e")
-        false
-      }
-      // If we can't do an incremental sync, do a full sync
-      if (!success) {
-        sync(context)
       }
     } else {
       Log.w(this::class.simpleName, "Already syncing, disregarding sync request")
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
index c940440..cd55bf2 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/AppViewModel.kt
@@ -33,11 +33,14 @@ import de.chaosdorf.meteroid.storage.AccountPreferences
 import de.chaosdorf.meteroid.sync.SyncManager
 import de.chaosdorf.meteroid.ui.navigation.Routes
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 @HiltViewModel
@@ -81,4 +84,14 @@ class AppViewModel @Inject constructor(
   }.mapLatest { server ->
     server?.let { syncManager.checkOffline(it) } ?: false
   }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
+
+  init {
+    viewModelScope.launch {
+      serverRepository.getAllFlow().distinctUntilChanged().collectLatest { list ->
+        for (server in list) {
+          syncManager.sync(server, null, incremental = false)
+        }
+      }
+    }
+  }
 }
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/NavigationViewModel.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/NavigationViewModel.kt
index 6346fe5..29cd7a4 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/NavigationViewModel.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/NavigationViewModel.kt
@@ -33,9 +33,12 @@ import de.chaosdorf.meteroid.model.ServerId
 import de.chaosdorf.meteroid.model.ServerRepository
 import de.chaosdorf.meteroid.model.UserRepository
 import de.chaosdorf.meteroid.sync.AccountProvider
+import de.chaosdorf.meteroid.sync.SyncManager
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
@@ -44,6 +47,7 @@ import javax.inject.Inject
 
 @HiltViewModel
 class NavigationViewModel @Inject constructor(
+  syncManager: SyncManager,
   serverRepository: ServerRepository,
   userRepository: UserRepository,
   pinnedUserRepository: PinnedUserRepository,
@@ -73,6 +77,18 @@ class NavigationViewModel @Inject constructor(
     else pinnedUserRepository.isPinnedFlow(it.first, it.second)
   }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
 
+  init {
+    viewModelScope.launch {
+      combine(server, user) { server, user ->
+        server?.let { Pair(server, user) }
+      }.distinctUntilChanged().collectLatest { account ->
+        account?.let { (server, user) ->
+          syncManager.sync(server, user, incremental = true)
+        }
+      }
+    }
+  }
+
   fun togglePin(serverId: ServerId, userId: UserId) {
     viewModelScope.launch {
       accountProvider.togglePin(serverId, userId)
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt
index a018e1b..a653449 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/navigation/MeteroidTopBar.kt
@@ -83,7 +83,7 @@ fun MeteroidTopBar(
         ?.replace("{server}", it.arguments?.getLong("server")?.toString() ?: "{server}")
         ?.replace("{user}", it.arguments?.getLong("user")?.toString() ?: "{user}")
     }
-    Log.e("Navigation", "BACKSTACK: [${backstackEntries.joinToString(" › ")}]")
+    Log.i("Navigation", "BACKSTACK: [${backstackEntries.joinToString(" › ")}]")
   }
 
   val avatarPainter = rememberAvatarPainter(
diff --git a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedSlide.kt b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedSlide.kt
index ec262ad..c326c1a 100644
--- a/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedSlide.kt
+++ b/app/src/main/kotlin/de/chaosdorf/meteroid/ui/wrapped/WrappedSlide.kt
@@ -109,13 +109,13 @@ sealed class WrappedSlide {
       override fun create(
           transactions: List<Transaction>,
           drinks: Map<DrinkId, Drink>
-      ): WrappedSlide = transactions
+      ): WrappedSlide? = transactions
         .map { it.timestamp.toLocalDateTime(TimeZone.currentSystemDefault()) }
         .groupingBy { Pair(it.dayOfWeek, it.hour) }
         .eachCount()
-        .maxBy { it.value }
-        .key
-        .let { (dayOfWeek, hour) ->
+        .maxByOrNull { it.value }
+        ?.key
+        ?.let { (dayOfWeek, hour) ->
           MostActive(dayOfWeek, hour)
         }
     }
-- 
GitLab