Coverage Summary for Class: DesktopBackgroundSyncScheduler (com.georgeci.moneysurfer.sync.scheduler)

Class Method, % Branch, % Line, % Instruction, %
DesktopBackgroundSyncScheduler 0% (0/5) 0% (0/4) 0% (0/21) 0% (0/132)
DesktopBackgroundSyncScheduler$cancelAll$1
DesktopBackgroundSyncScheduler$scheduleOneShot$1
DesktopBackgroundSyncScheduler$scheduleOneShot$2$1$1 0% (0/1) 0% (0/1) 0% (0/42)
DesktopBackgroundSyncScheduler$scheduleOneShot$2$job$1 0% (0/1) 0% (0/2) 0% (0/30)
DesktopBackgroundSyncScheduler$schedulePeriodic$1
DesktopBackgroundSyncScheduler$schedulePeriodic$2$1 0% (0/1) 0% (0/2) 0% (0/3) 0% (0/41)
Total 0% (0/8) 0% (0/6) 0% (0/27) 0% (0/245)


 package com.georgeci.moneysurfer.sync.scheduler
 
 import com.georgeci.moneysurfer.sync.api.SyncReason
 import com.georgeci.moneysurfer.sync.coordinator.SyncCoordinator
 import com.georgeci.moneysurfer.sync.runtime.ApplicationScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.invoke
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import org.koin.core.annotation.Single
 import kotlin.time.Duration
 
 /**
  * Coroutine-driven scheduler for the desktop (JVM) target. There is no
  * native battery / network observability on plain JVM, so [SyncConstraints]
  * are accepted but ignored.
  *
  * Lives on [ApplicationScope] — survives window minimisation, dies with
  * the JVM process.
  */
 @Single(binds = [BackgroundSyncScheduler::class])
 class DesktopBackgroundSyncScheduler(
     private val appScope: ApplicationScope,
     private val coordinator: SyncCoordinator,
 ) : BackgroundSyncScheduler {
 
     private val mutex = Mutex()
     private var periodicJob: Job? = null
     private val oneShotJobs = mutableListOf<Job>()
 
     override suspend fun schedulePeriodic(interval: Duration, constraints: SyncConstraints) {
         mutex.withLock {
             periodicJob?.cancel()
             periodicJob = appScope.launch {
                 while (isActive) {
                     delay(interval)
                     coordinator.requestSync(SyncReason.BACKGROUND).result.await()
                 }
             }
         }
     }
 
     override suspend fun scheduleOneShot(delay: Duration, constraints: SyncConstraints) {
         mutex.withLock {
             val job = appScope.launch {
                 delay(delay)
                 coordinator.requestSync(SyncReason.BACKGROUND).result.await()
             }
             oneShotJobs += job
             job.invokeOnCompletion {
                 // Mutex serialises adds and removes; race is benign because
                 // ArrayList tolerates concurrent reads but not modifications.
                 appScope.launch { mutex.withLock { oneShotJobs.remove(job) } }
             }
         }
     }
 
     override suspend fun cancelAll() {
         mutex.withLock {
             periodicJob?.cancel()
             periodicJob = null
             oneShotJobs.forEach { it.cancel() }
             oneShotJobs.clear()
         }
     }
 }