diff --git a/app/src/main/kotlin/com/syncflow/worker/FileWatchService.kt b/app/src/main/kotlin/com/syncflow/worker/FileWatchService.kt index 95ef462..ef73b15 100644 --- a/app/src/main/kotlin/com/syncflow/worker/FileWatchService.kt +++ b/app/src/main/kotlin/com/syncflow/worker/FileWatchService.kt @@ -23,6 +23,8 @@ import com.syncflow.data.db.SyncPairDao import com.syncflow.domain.model.ScheduleType import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber import java.io.File import javax.inject.Inject @@ -35,6 +37,8 @@ class FileWatchService : Service() { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val mainHandler = Handler(Looper.getMainLooper()) + // Prevents concurrent refresh() calls from doubling watchers + catchup scans + private val refreshMutex = Mutex() // Multiple FileObserver instances per pair: one per directory (recursive) private val fileObservers = mutableMapOf>() @@ -81,7 +85,7 @@ class FileWatchService : Service() { override fun onBind(intent: Intent?): IBinder? = null - private suspend fun refresh() { + private suspend fun refresh() = refreshMutex.withLock { clearWatchers() val pairs = syncPairDao.getEnabled().filter { it.scheduleType == ScheduleType.ON_CHANGE } @@ -203,12 +207,23 @@ class FileWatchService : Service() { if (hasNew || hasModified || hasDeleted) { Timber.d("FileWatchService: catchup detected changes for pair $pairId, scheduling sync") val pair = syncPairDao.getById(pairId) ?: return + // Set cooldown so file writes during this sync don't immediately re-trigger + syncCooldownUntil[pairId] = System.currentTimeMillis() + 120_000 + val req = SyncWorker.buildOneTimeRequest(pairId, wifiOnly, chargingOnly) WorkManager.getInstance(applicationContext) - .enqueueUniqueWork( - "catchup_$pairId", - ExistingWorkPolicy.KEEP, - SyncWorker.buildOneTimeRequest(pairId, wifiOnly, chargingOnly), - ) + .enqueueUniqueWork("catchup_$pairId", ExistingWorkPolicy.KEEP, req) + scope.launch { + try { + WorkManager.getInstance(applicationContext) + .getWorkInfoByIdFlow(req.id) + .first { it?.state?.isFinished == true } + syncCooldownUntil[pairId] = System.currentTimeMillis() + 60_000 + } catch (e: CancellationException) { + throw e + } catch (_: Exception) { + syncCooldownUntil[pairId] = System.currentTimeMillis() + 60_000 + } + } } } @@ -253,8 +268,10 @@ class FileWatchService : Service() { } delay(12_000) updateNotificationDynamic(null) + } catch (e: CancellationException) { + throw e } catch (_: Exception) { - syncCooldownUntil[pairId] = 0L + syncCooldownUntil[pairId] = System.currentTimeMillis() + 60_000 updateNotificationDynamic(null) } } diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 1efe9e4..32d1ce9 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,25 +1,12 @@ - + - - - - - - - + android:fillColor="#000000"/> diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 0c3c190..8124678 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,67 +1,76 @@ - - - - - + - - - - + + - - - - + + - + + + + + + + + + + + + + + android:pathData="M 54,45 L 57,49 L 51,49 Z"/> - + + android:fillColor="#FFFFFF" + android:pathData="M 54,59 L 51,55 L 57,55 Z"/> diff --git a/version.properties b/version.properties index b30ae06..1acd08d 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -VERSION_NAME=1.0.29 -VERSION_CODE=30 +VERSION_NAME=1.0.30 +VERSION_CODE=31