Skip to content

Commit

Permalink
Move BaseSyncWorker.exists to SyncWorkerManager
Browse files Browse the repository at this point in the history
  • Loading branch information
rfc2822 committed Dec 5, 2024
1 parent 53bc5a6 commit 7097bf9
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 102 deletions.
124 changes: 45 additions & 79 deletions app/src/main/kotlin/at/bitfire/davdroid/sync/worker/BaseSyncWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.WorkerParameters
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.R
Expand All @@ -33,8 +32,6 @@ import at.bitfire.davdroid.ui.NotificationRegistry
import at.bitfire.ical4android.TaskProvider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import java.util.Collections
Expand All @@ -47,81 +44,6 @@ abstract class BaseSyncWorker(
private val syncDispatcher: CoroutineDispatcher
) : CoroutineWorker(context, workerParams) {

companion object {

// common worker input parameters
const val INPUT_ACCOUNT_NAME = "accountName"
const val INPUT_ACCOUNT_TYPE = "accountType"
const val INPUT_AUTHORITY = "authority"

/** set to `true` for user-initiated sync that skips network checks */
const val INPUT_MANUAL = "manual"

/** set to `true` for syncs that are caused by local changes */
const val INPUT_UPLOAD = "upload"

/** Whether re-synchronization is requested. One of [NO_RESYNC] (default), [RESYNC] or [FULL_RESYNC]. */
const val INPUT_RESYNC = "resync"
@IntDef(NO_RESYNC, RESYNC, FULL_RESYNC)
annotation class InputResync
const val NO_RESYNC = 0
/** Re-synchronization is requested. See [Syncer.SYNC_EXTRAS_RESYNC] for details. */
const val RESYNC = 1
/** Full re-synchronization is requested. See [Syncer.SYNC_EXTRAS_FULL_RESYNC] for details. */
const val FULL_RESYNC = 2

/**
* How often this work will be retried to run after soft (network) errors.
*
* Retry strategy is defined in work request ([enqueue]).
*/
internal const val MAX_RUN_ATTEMPTS = 5

/**
* Set of currently running syncs, identified by their [commonTag].
*/
private val runningSyncs = Collections.synchronizedSet(HashSet<String>())

/**
* This tag shall be added to every worker that is enqueued by a subclass.
*/
fun commonTag(account: Account, authority: String): String =
"sync-$authority ${account.type}/${account.name}"

/**
* Observes whether >0 sync workers (both [PeriodicSyncWorker] and [OneTimeSyncWorker])
* exist, belonging to given account and authorities, and which are/is in the given worker state.
*
* @param workStates list of states of workers to match
* @param account the account which the workers belong to
* @param authorities type of sync work, ie [CalendarContract.AUTHORITY]
* @param whichTag function to generate tag that should be observed for given account and authority
*
* @return flow that emits `true` if at least one worker with matching query was found; `false` otherwise
*/
fun exists(
context: Context,
workStates: List<WorkInfo.State>,
account: Account? = null,
authorities: List<String>? = null,
whichTag: (account: Account, authority: String) -> String = { account, authority ->
commonTag(account, authority)
}
): Flow<Boolean> {
val workQuery = WorkQuery.Builder.fromStates(workStates)
if (account != null && authorities != null)
workQuery.addTags(
authorities.map { authority -> whichTag(account, authority) }
)
return WorkManager.getInstance(context)
.getWorkInfosFlow(workQuery.build())
.map { workInfoList ->
workInfoList.isNotEmpty()
}
}

}

@Inject
lateinit var accountSettingsFactory: AccountSettings.Factory

Expand Down Expand Up @@ -172,7 +94,7 @@ abstract class BaseSyncWorker(
try {
val accountSettings = try {
accountSettingsFactory.create(account)
} catch (e: InvalidAccountException) {
} catch (_: InvalidAccountException) {
val workId = workerParams.id
logger.warning("Account $account doesn't exist anymore, cancelling worker $workId")

Expand Down Expand Up @@ -312,4 +234,48 @@ abstract class BaseSyncWorker(
return@withContext Result.success()
}


companion object {

// common worker input parameters
const val INPUT_ACCOUNT_NAME = "accountName"
const val INPUT_ACCOUNT_TYPE = "accountType"
const val INPUT_AUTHORITY = "authority"

/** set to `true` for user-initiated sync that skips network checks */
const val INPUT_MANUAL = "manual"

/** set to `true` for syncs that are caused by local changes */
const val INPUT_UPLOAD = "upload"

/** Whether re-synchronization is requested. One of [NO_RESYNC] (default), [RESYNC] or [FULL_RESYNC]. */
const val INPUT_RESYNC = "resync"
@IntDef(NO_RESYNC, RESYNC, FULL_RESYNC)
annotation class InputResync
const val NO_RESYNC = 0
/** Re-synchronization is requested. See [Syncer.SYNC_EXTRAS_RESYNC] for details. */
const val RESYNC = 1
/** Full re-synchronization is requested. See [Syncer.SYNC_EXTRAS_FULL_RESYNC] for details. */
const val FULL_RESYNC = 2

/**
* How often this work will be retried to run after soft (network) errors.
*
* Retry strategy is defined in work request ([enqueue]).
*/
internal const val MAX_RUN_ATTEMPTS = 5

/**
* Set of currently running syncs, identified by their [commonTag].
*/
private val runningSyncs = Collections.synchronizedSet(HashSet<String>())

/**
* This tag shall be added to every worker that is enqueued by a subclass.
*/
fun commonTag(account: Account, authority: String): String =
"sync-$authority ${account.type}/${account.name}"

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,6 @@ class OneTimeSyncWorker @AssistedInject constructor(
syncDispatcher: SyncDispatcher
) : BaseSyncWorker(appContext, workerParams, syncDispatcher.dispatcher) {

companion object {

/**
* Unique work name of this worker. Can also be used as tag.
*
* Mainly used to query [WorkManager] for work state (by unique work name or tag).
*
* @param account the account this worker is running for
* @param authority the authority this worker is running for
* @return Name of this worker composed as "onetime-sync $authority ${account.type}/${account.name}"
*/
fun workerName(account: Account, authority: String): String =
"onetime-sync $authority ${account.type}/${account.name}"

}


/**
* Used by WorkManager to show a foreground service notification for expedited jobs on Android <12.
*/
Expand All @@ -65,4 +48,21 @@ class OneTimeSyncWorker @AssistedInject constructor(
return ForegroundInfo(NotificationRegistry.NOTIFY_SYNC_EXPEDITED, notification)
}


companion object {

/**
* Unique work name of this worker. Can also be used as tag.
*
* Mainly used to query [WorkManager] for work state (by unique work name or tag).
*
* @param account the account this worker is running for
* @param authority the authority this worker is running for
* @return Name of this worker composed as "onetime-sync $authority ${account.type}/${account.name}"
*/
fun workerName(account: Account, authority: String): String =
"onetime-sync $authority ${account.type}/${account.name}"

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import androidx.work.Operation
import androidx.work.OutOfQuotaPolicy
import androidx.work.PeriodicWorkRequest
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.WorkRequest
import at.bitfire.davdroid.R
import at.bitfire.davdroid.push.PushNotificationManager
Expand All @@ -36,6 +38,8 @@ import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.NO_RESYNC
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.commonTag
import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.util.concurrent.TimeUnit
import java.util.logging.Logger
import javax.inject.Inject
Expand Down Expand Up @@ -246,6 +250,37 @@ class SyncWorkerManager @Inject constructor(
}
}

/**
* Observes whether >0 sync workers (both [PeriodicSyncWorker] and [OneTimeSyncWorker])
* exist, belonging to given account and authorities, and which are/is in the given worker state.
*
* @param workStates list of states of workers to match
* @param account the account which the workers belong to
* @param authorities type of sync work, ie [CalendarContract.AUTHORITY]
* @param whichTag function to generate tag that should be observed for given account and authority
*
* @return flow that emits `true` if at least one worker with matching query was found; `false` otherwise
*/
fun hasAnyFlow(
workStates: List<WorkInfo.State>,
account: Account? = null,
authorities: List<String>? = null,
whichTag: (account: Account, authority: String) -> String = { account, authority ->
commonTag(account, authority)
}
): Flow<Boolean> {
val workQuery = WorkQuery.Builder.fromStates(workStates)
if (account != null && authorities != null)
workQuery.addTags(
authorities.map { authority -> whichTag(account, authority) }
)
return WorkManager.getInstance(context)
.getWorkInfosFlow(workQuery.build())
.map { workInfoList ->
workInfoList.isNotEmpty()
}
}

/**
* Returns a list of all available sync authorities:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import android.content.Context
import androidx.work.WorkInfo
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
import at.bitfire.davdroid.sync.worker.BaseSyncWorker
import at.bitfire.davdroid.sync.worker.OneTimeSyncWorker
import at.bitfire.davdroid.sync.worker.SyncWorkerManager
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
Expand All @@ -20,7 +20,8 @@ import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject

class AccountProgressUseCase @Inject constructor(
@ApplicationContext val context: Context
@ApplicationContext val context: Context,
private val syncWorkerManager: SyncWorkerManager
) {

operator fun invoke(
Expand Down Expand Up @@ -53,8 +54,7 @@ class AccountProgressUseCase @Inject constructor(
@OptIn(ExperimentalCoroutinesApi::class)
fun isSyncPending(account: Account, authoritiesFlow: Flow<List<String>>): Flow<Boolean> =
authoritiesFlow.flatMapLatest { authorities ->
BaseSyncWorker.exists(
context = context,
syncWorkerManager.hasAnyFlow(
workStates = listOf(WorkInfo.State.ENQUEUED),
account = account,
authorities = authorities,
Expand All @@ -68,8 +68,7 @@ class AccountProgressUseCase @Inject constructor(
@OptIn(ExperimentalCoroutinesApi::class)
fun isSyncRunning(account: Account, authoritiesFlow: Flow<List<String>>): Flow<Boolean> =
authoritiesFlow.flatMapLatest { authorities ->
BaseSyncWorker.exists(
context = context,
syncWorkerManager.hasAnyFlow(
workStates = listOf(WorkInfo.State.RUNNING),
account = account,
authorities = authorities
Expand Down

0 comments on commit 7097bf9

Please sign in to comment.