tell the user when a sync fails

The failure was only recorded as SyncStatus.Failed, which nothing shows, so a
bad username or an unreachable server folded silently. It goes through the same
toast the rest of the data layer uses, with the server's own message.
This commit is contained in:
austinried
2026-10-04 00:30:04 +00:00
parent cdbecb9a9d
commit 5cbe4c6839
3 changed files with 17 additions and 3 deletions
@@ -29,6 +29,7 @@ class SyncManager(
private val db: SubtracksDatabase, private val db: SubtracksDatabase,
private val sourceRepository: SourceRepository, private val sourceRepository: SourceRepository,
private val queueRepository: QueueRepository, private val queueRepository: QueueRepository,
private val showMessage: (String) -> Unit = {},
) { ) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val requests = Channel<Unit>(Channel.CONFLATED) private val requests = Channel<Unit>(Channel.CONFLATED)
@@ -56,7 +57,9 @@ class SyncManager(
throw cancellation throw cancellation
} catch (failure: Exception) { } catch (failure: Exception) {
Log.w(TAG, "Sync failed", failure) Log.w(TAG, "Sync failed", failure)
SyncStatus.Failed(failure.message ?: "Sync failed") val message = failure.message ?: "unknown error"
showMessage("Sync failed: $message")
SyncStatus.Failed(message)
} }
queueRepository.invalidateLibraryCache() queueRepository.invalidateLibraryCache()
_status.value = result _status.value = result
@@ -57,7 +57,7 @@ fun appModule(
single<ServerActionSink> { NetworkServerActionSink(get()) } single<ServerActionSink> { NetworkServerActionSink(get()) }
single { LibraryRepository(get(), get(), get(), toast) } single { LibraryRepository(get(), get(), get(), toast) }
single<ArtworkSeedStore> { ArtworkSeedRepository(get()) } single<ArtworkSeedStore> { ArtworkSeedRepository(get()) }
single { SyncManager(get(), get(), get()) } single { SyncManager(get(), get(), get(), showMessage = toast) }
single { QueueRepository(get()) } single { QueueRepository(get()) }
single { ArtworkStore(downloadsRoot(context)) } single { ArtworkStore(downloadsRoot(context)) }
single<ArtworkFetcher> { OkHttpArtworkFetcher(get()) } single<ArtworkFetcher> { OkHttpArtworkFetcher(get()) }
@@ -21,6 +21,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.io.File import java.io.File
import java.util.concurrent.CopyOnWriteArrayList
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class SyncManagerTest { class SyncManagerTest {
@@ -28,6 +29,7 @@ class SyncManagerTest {
private lateinit var sourceRepository: SourceRepository private lateinit var sourceRepository: SourceRepository
private lateinit var queueRepository: QueueRepository private lateinit var queueRepository: QueueRepository
private lateinit var manager: SyncManager private lateinit var manager: SyncManager
private val messages = CopyOnWriteArrayList<String>()
@Before @Before
fun setUp() { fun setUp() {
@@ -39,7 +41,7 @@ class SyncManagerTest {
.build() .build()
sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art"))) sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences(), ArtworkStore(File(context.cacheDir, "art")))
queueRepository = QueueRepository(db) queueRepository = QueueRepository(db)
manager = SyncManager(db, sourceRepository, queueRepository) manager = SyncManager(db, sourceRepository, queueRepository, showMessage = { messages += it })
} }
@After @After
@@ -48,6 +50,15 @@ class SyncManagerTest {
db.close() db.close()
} }
@Test
fun aFailedSyncTellsTheUser() =
runBlocking {
manager.requestSync()
withTimeout(10_000) { manager.status.first { it is SyncStatus.Failed } }
assertEquals(listOf("Sync failed: No server configured"), messages)
}
@Test @Test
fun aFailedSyncStillInvalidatesTheLibraryCache() = fun aFailedSyncStillInvalidatesTheLibraryCache() =
runBlocking { runBlocking {