Separate sync work for each service

pull/1061/head
Alex Baker 4 years ago
parent 15637e784e
commit 7078dfa2bf

@ -58,7 +58,7 @@ class TaskDao @Inject constructor(
suspend fun touch(ids: List<Long>) {
ids.eachChunk { taskDao.touch(ids) }
workManager.sync(false)
syncAdapters.sync()
}
suspend fun setParent(parent: Long, tasks: List<Long>) = taskDao.setParent(parent, tasks)

@ -9,6 +9,7 @@ import org.tasks.db.QueryUtils
import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.jobs.WorkManager
import org.tasks.preferences.Preferences
import org.tasks.sync.SyncAdapters
import java.util.*
import javax.inject.Inject
@ -18,7 +19,8 @@ class TaskDeleter @Inject constructor(
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val googleTaskDao: GoogleTaskDao,
private val preferences: Preferences) {
private val preferences: Preferences,
private val syncAdapters: SyncAdapters) {
suspend fun markDeleted(item: Task) = markDeleted(persistentListOf(item.id))
@ -28,7 +30,7 @@ class TaskDeleter @Inject constructor(
ids.addAll(taskIds.chunkedMap(taskDao::getChildren))
deletionDao.markDeleted(ids)
workManager.cleanup(ids)
workManager.sync(false)
syncAdapters.sync()
localBroadcastManager.broadcastRefresh()
return ids.chunkedMap(taskDao::fetch)
}

@ -146,7 +146,7 @@ class CaldavAccount : Parcelable {
}
companion object {
private const val TYPE_CALDAV = 0
const val TYPE_CALDAV = 0
const val TYPE_ETESYNC = 1
const val TYPE_LOCAL = 2

@ -33,6 +33,9 @@ abstract class CaldavDao {
@Query("SELECT COUNT(*) FROM caldav_accounts WHERE cda_account_type != 2")
abstract suspend fun accountCount(): Int
@Query("SELECT * FROM caldav_accounts WHERE cda_account_type = :type")
abstract suspend fun getAccounts(type: Int): List<CaldavAccount>
@Query("SELECT * FROM caldav_accounts ORDER BY cda_account_type, UPPER(cda_name)")
abstract suspend fun getAccounts(): List<CaldavAccount>
@ -127,6 +130,16 @@ abstract class CaldavDao {
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId")
abstract suspend fun getTasks(taskId: Long): List<CaldavTask>
@Query("""
SELECT EXISTS(SELECT 1
FROM caldav_tasks
INNER JOIN caldav_lists ON cdl_uuid = cd_calendar
INNER JOIN caldav_accounts ON cda_uuid = cdl_account
WHERE cd_task = :id
AND cda_account_type = :type)
""")
abstract suspend fun isAccountType(id: Long, type: Int): Boolean
@Query("SELECT * FROM caldav_tasks WHERE cd_task in (:taskIds) AND cd_deleted = 0")
abstract suspend fun getTasks(taskIds: List<Long>): List<CaldavTask>

@ -0,0 +1,42 @@
package org.tasks.jobs
import android.content.Context
import androidx.hilt.Assisted
import androidx.hilt.work.WorkerInject
import androidx.work.WorkerParameters
import kotlinx.coroutines.*
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.analytics.Firebase
import org.tasks.caldav.CaldavSynchronizer
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.data.CaldavDao
import org.tasks.preferences.Preferences
class SyncCaldavWork @WorkerInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
firebase: Firebase,
localBroadcastManager: LocalBroadcastManager,
preferences: Preferences,
private val caldavDao: CaldavDao,
private val caldavSynchronizer: CaldavSynchronizer
) : SyncWork(context, workerParams, firebase, localBroadcastManager, preferences) {
override suspend fun enabled() = caldavDao.getAccounts(TYPE_CALDAV).isNotEmpty()
override val syncStatus = R.string.p_sync_ongoing_caldav
override suspend fun doSync() {
caldavJobs().awaitAll()
}
private suspend fun caldavJobs(): List<Deferred<Unit>> = coroutineScope {
caldavDao.getAccounts(TYPE_CALDAV)
.map {
async(Dispatchers.IO) {
caldavSynchronizer.sync(it)
}
}
}
}

@ -0,0 +1,42 @@
package org.tasks.jobs
import android.content.Context
import androidx.hilt.Assisted
import androidx.hilt.work.WorkerInject
import androidx.work.WorkerParameters
import kotlinx.coroutines.*
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.analytics.Firebase
import org.tasks.data.CaldavAccount.Companion.TYPE_ETESYNC
import org.tasks.data.CaldavDao
import org.tasks.etesync.EteSynchronizer
import org.tasks.preferences.Preferences
class SyncEteSyncWork @WorkerInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
firebase: Firebase,
localBroadcastManager: LocalBroadcastManager,
preferences: Preferences,
private val caldavDao: CaldavDao,
private val eteSynchronizer: EteSynchronizer
) : SyncWork(context, workerParams, firebase, localBroadcastManager, preferences) {
override suspend fun enabled() = caldavDao.getAccounts(TYPE_ETESYNC).isNotEmpty()
override val syncStatus = R.string.p_sync_ongoing_etesync
override suspend fun doSync() {
etesyncJobs().awaitAll()
}
private suspend fun etesyncJobs(): List<Deferred<Unit>> = coroutineScope {
caldavDao.getAccounts(TYPE_ETESYNC)
.map {
async(Dispatchers.IO) {
eteSynchronizer.sync(it)
}
}
}
}

@ -0,0 +1,42 @@
package org.tasks.jobs
import android.content.Context
import androidx.hilt.Assisted
import androidx.hilt.work.WorkerInject
import androidx.work.WorkerParameters
import kotlinx.coroutines.*
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.analytics.Firebase
import org.tasks.data.GoogleTaskListDao
import org.tasks.gtasks.GoogleTaskSynchronizer
import org.tasks.preferences.Preferences
class SyncGoogleTasksWork @WorkerInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
firebase: Firebase,
localBroadcastManager: LocalBroadcastManager,
preferences: Preferences,
private val googleTaskListDao: GoogleTaskListDao,
private val googleTaskSynchronizer: GoogleTaskSynchronizer
) : SyncWork(context, workerParams, firebase, localBroadcastManager, preferences) {
override suspend fun enabled() = googleTaskListDao.getAccounts().isNotEmpty()
override val syncStatus = R.string.p_sync_ongoing_google_tasks
override suspend fun doSync() {
googleTaskJobs().awaitAll()
}
private suspend fun googleTaskJobs(): List<Deferred<Unit>> = coroutineScope {
googleTaskListDao
.getAccounts()
.mapIndexed { i, account ->
async(Dispatchers.IO) {
googleTaskSynchronizer.sync(account, i)
}
}
}
}

@ -1,80 +1,47 @@
package org.tasks.jobs
import android.content.Context
import androidx.hilt.Assisted
import androidx.hilt.work.WorkerInject
import androidx.work.WorkerParameters
import kotlinx.coroutines.*
import org.tasks.LocalBroadcastManager
import org.tasks.analytics.Firebase
import org.tasks.caldav.CaldavSynchronizer
import org.tasks.data.CaldavAccount.Companion.TYPE_LOCAL
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.etesync.EteSynchronizer
import org.tasks.gtasks.GoogleTaskSynchronizer
import org.tasks.injection.BaseWorker
import org.tasks.preferences.Preferences
import org.tasks.sync.SyncAdapters
class SyncWork @WorkerInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
abstract class SyncWork constructor(
context: Context,
workerParams: WorkerParameters,
firebase: Firebase,
private val caldavSynchronizer: CaldavSynchronizer,
private val eteSynchronizer: EteSynchronizer,
private val googleTaskSynchronizer: GoogleTaskSynchronizer,
private val localBroadcastManager: LocalBroadcastManager,
private val preferences: Preferences,
private val caldavDao: CaldavDao,
private val googleTaskListDao: GoogleTaskListDao,
private val syncAdapters: SyncAdapters) : BaseWorker(context, workerParams, firebase) {
private val preferences: Preferences) : BaseWorker(context, workerParams, firebase) {
override suspend fun run(): Result {
if (!syncAdapters.isSyncEnabled()) {
return Result.success()
final override suspend fun run(): Result {
if (!enabled()) {
return Result.failure()
}
synchronized(LOCK) {
if (preferences.isSyncOngoing) {
if (preferences.getBoolean(syncStatus, false)) {
return Result.retry()
}
preferences.setBoolean(syncStatus, true)
}
preferences.isSyncOngoing = true
localBroadcastManager.broadcastRefresh()
try {
caldavJobs().plus(googleTaskJobs()).awaitAll()
doSync()
} catch (e: Exception) {
firebase.reportException(e)
} finally {
preferences.isSyncOngoing = false
preferences.setBoolean(syncStatus, false)
localBroadcastManager.broadcastRefresh()
}
return Result.success()
}
private suspend fun caldavJobs(): List<Deferred<Unit>> = coroutineScope {
caldavDao.getAccounts()
.filterNot { it.accountType == TYPE_LOCAL }
.map {
async(Dispatchers.IO) {
if (it.isCaldavAccount) {
caldavSynchronizer.sync(it)
} else if (it.isEteSyncAccount) {
eteSynchronizer.sync(it)
}
}
}
}
protected abstract val syncStatus: Int
private suspend fun googleTaskJobs(): List<Deferred<Unit>> = coroutineScope {
googleTaskListDao
.getAccounts()
.mapIndexed { i, account ->
async(Dispatchers.IO) {
googleTaskSynchronizer.sync(account, i)
}
}
}
protected abstract suspend fun enabled(): Boolean
protected abstract suspend fun doSync()
companion object {
private val LOCK = Any()

@ -11,7 +11,11 @@ interface WorkManager {
suspend fun cleanup(ids: Iterable<Long>)
suspend fun sync(immediate: Boolean)
suspend fun googleTaskSync(immediate: Boolean)
suspend fun caldavSync(immediate: Boolean)
suspend fun eteSync(immediate: Boolean)
suspend fun reverseGeocode(place: Place)
@ -42,8 +46,12 @@ interface WorkManager {
const val TAG_BACKUP = "tag_backup"
const val TAG_REFRESH = "tag_refresh"
const val TAG_MIDNIGHT_REFRESH = "tag_midnight_refresh"
const val TAG_SYNC = "tag_sync"
const val TAG_BACKGROUND_SYNC = "tag_background_sync"
const val TAG_SYNC_GOOGLE_TASKS = "tag_sync_google_tasks"
const val TAG_SYNC_CALDAV = "tag_sync_caldav"
const val TAG_SYNC_ETESYNC = "tag_sync_etesync"
const val TAG_BACKGROUND_SYNC_GOOGLE_TASKS = "tag_background_sync_google_tasks"
const val TAG_BACKGROUND_SYNC_CALDAV = "tag_background_sync_caldav"
const val TAG_BACKGROUND_SYNC_ETESYNC = "tag_background_sync_etesync"
const val TAG_REMOTE_CONFIG = "tag_remote_config"
}
}

@ -20,12 +20,16 @@ import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.db.SuspendDbUtils.eachChunk
import org.tasks.jobs.WorkManager.Companion.MAX_CLEANUP_LENGTH
import org.tasks.jobs.WorkManager.Companion.REMOTE_CONFIG_INTERVAL_HOURS
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_CALDAV
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_ETESYNC
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_GOOGLE_TASKS
import org.tasks.jobs.WorkManager.Companion.TAG_BACKUP
import org.tasks.jobs.WorkManager.Companion.TAG_MIDNIGHT_REFRESH
import org.tasks.jobs.WorkManager.Companion.TAG_REFRESH
import org.tasks.jobs.WorkManager.Companion.TAG_REMOTE_CONFIG
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_CALDAV
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_ETESYNC
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_GOOGLE_TASKS
import org.tasks.notifications.Throttle
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils
@ -74,7 +78,16 @@ class WorkManagerImpl constructor(
}
}
override suspend fun sync(immediate: Boolean) {
override suspend fun googleTaskSync(immediate: Boolean) =
sync(immediate, TAG_SYNC_GOOGLE_TASKS, SyncGoogleTasksWork::class.java)
override suspend fun caldavSync(immediate: Boolean) =
sync(immediate, TAG_SYNC_CALDAV, SyncCaldavWork::class.java)
override suspend fun eteSync(immediate: Boolean) =
sync(immediate, TAG_SYNC_ETESYNC, SyncEteSyncWork::class.java)
private suspend fun sync(immediate: Boolean, tag: String, c: Class<out SyncWork>) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(
if (!immediate && preferences.getBoolean(R.string.p_background_sync_unmetered_only, false)) {
@ -83,15 +96,13 @@ class WorkManagerImpl constructor(
NetworkType.CONNECTED
})
.build()
val builder = OneTimeWorkRequest.Builder(SyncWork::class.java)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(constraints)
val builder = OneTimeWorkRequest.Builder(c).setConstraints(constraints)
if (!immediate) {
builder.setInitialDelay(1, TimeUnit.MINUTES)
}
throttle.run {
workManager
.beginUniqueWork(TAG_SYNC, ExistingWorkPolicy.REPLACE, builder.build())
.beginUniqueWork(tag, ExistingWorkPolicy.REPLACE, builder.build())
.enqueue()
}
}
@ -103,7 +114,6 @@ class WorkManagerImpl constructor(
enqueue(
OneTimeWorkRequest.Builder(ReverseGeocodeWork::class.java)
.setInputData(Data.Builder().putLong(ReverseGeocodeWork.PLACE_ID, place.id).build())
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()))
}
@ -129,17 +139,22 @@ class WorkManagerImpl constructor(
private suspend fun scheduleBackgroundSync(enabled: Boolean, onlyOnUnmetered: Boolean) {
Timber.d("background sync enabled: %s, onlyOnUnmetered: %s", enabled, onlyOnUnmetered)
scheduleBackgroundSync(enabled, onlyOnUnmetered, TAG_BACKGROUND_SYNC_GOOGLE_TASKS, SyncGoogleTasksWork::class.java)
scheduleBackgroundSync(enabled, onlyOnUnmetered, TAG_BACKGROUND_SYNC_CALDAV, SyncCaldavWork::class.java)
scheduleBackgroundSync(enabled, onlyOnUnmetered, TAG_BACKGROUND_SYNC_ETESYNC, SyncEteSyncWork::class.java)
}
private suspend fun scheduleBackgroundSync(enabled: Boolean, onlyOnUnmetered: Boolean, tag: String, c: Class<out SyncWork>) {
throttle.run {
if (enabled) {
workManager.enqueueUniquePeriodicWork(
TAG_BACKGROUND_SYNC,
tag,
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequest.Builder(SyncWork::class.java, 1, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
PeriodicWorkRequest.Builder(c, 1, TimeUnit.HOURS)
.setConstraints(getNetworkConstraints(onlyOnUnmetered))
.build())
} else {
workManager.cancelUniqueWork(TAG_BACKGROUND_SYNC)
workManager.cancelUniqueWork(tag)
}
}
}
@ -180,7 +195,6 @@ class WorkManagerImpl constructor(
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequest.Builder(
RemoteConfigWork::class.java, REMOTE_CONFIG_INTERVAL_HOURS, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build())

@ -411,9 +411,9 @@ class Preferences @JvmOverloads constructor(private val context: Context, name:
}
var isSyncOngoing: Boolean
get() = getBoolean(R.string.p_sync_ongoing, false)
get() = syncFlags.any { getBoolean(it, false) }
set(value) {
setBoolean(R.string.p_sync_ongoing, value)
syncFlags.forEach { setBoolean(it, value) }
}
fun useGooglePlaces(): Boolean {
@ -491,5 +491,9 @@ class Preferences @JvmOverloads constructor(private val context: Context, name:
private fun getSharedPreferencesName(context: Context): String {
return context.packageName + "_preferences"
}
private val syncFlags = listOf(
R.string.p_sync_ongoing_google_tasks,
R.string.p_sync_ongoing_caldav,
R.string.p_sync_ongoing_etesync)
}
}

@ -28,6 +28,7 @@ import org.tasks.injection.InjectingPreferenceFragment
import org.tasks.jobs.WorkManager
import org.tasks.preferences.Preferences
import org.tasks.sync.AddAccountDialog
import org.tasks.sync.SyncAdapters
import org.tasks.ui.Toaster
import javax.inject.Inject
@ -43,6 +44,7 @@ class Synchronization : InjectingPreferenceFragment() {
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var syncAdapters: SyncAdapters
override fun getPreferenceXml() = R.xml.preferences_synchronization
@ -95,14 +97,14 @@ class Synchronization : InjectingPreferenceFragment() {
if (requestCode == REQUEST_CALDAV_SETTINGS) {
if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch(NonCancellable) {
workManager.sync(true)
syncAdapters.sync(true)
workManager.updateBackgroundSync()
}
}
} else if (requestCode == REQUEST_GOOGLE_TASKS) {
if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch(NonCancellable) {
workManager.sync(true)
syncAdapters.sync(true)
workManager.updateBackgroundSync()
}
} else if (data != null) {

@ -4,7 +4,10 @@ import com.todoroo.astrid.data.SyncFlags
import com.todoroo.astrid.data.Task
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.data.CaldavAccount.Companion.TYPE_ETESYNC
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.jobs.WorkManager
import javax.inject.Inject
@ -12,19 +15,24 @@ import javax.inject.Inject
class SyncAdapters @Inject constructor(
private val workManager: WorkManager,
private val caldavDao: CaldavDao,
private val googleTaskDao: GoogleTaskDao,
private val googleTaskListDao: GoogleTaskListDao) {
suspend fun sync(task: Task, original: Task?) {
val suppress = task.checkTransitory(SyncFlags.SUPPRESS_SYNC)
val forceCaldav = task.checkTransitory(SyncFlags.FORCE_CALDAV_SYNC)
val pushGtasks = !suppress
&& !task.googleTaskUpToDate(original)
&& isGoogleTaskSyncEnabled()
val pushCaldav = !suppress
&& (forceCaldav || !task.caldavUpToDate(original))
&& isCaldavSyncEnabled()
if (pushGtasks || pushCaldav) {
sync()
if (task.checkTransitory(SyncFlags.SUPPRESS_SYNC)) {
return
}
if (!task.googleTaskUpToDate(original)
&& googleTaskDao.getAllByTaskId(task.id).isNotEmpty()) {
workManager.googleTaskSync(false)
}
if (task.checkTransitory(SyncFlags.FORCE_CALDAV_SYNC) || !task.caldavUpToDate(original)) {
if (caldavDao.isAccountType(task.id, TYPE_CALDAV)) {
workManager.caldavSync(false)
}
if (caldavDao.isAccountType(task.id, TYPE_ETESYNC)) {
workManager.eteSync(false)
}
}
}
@ -33,17 +41,27 @@ class SyncAdapters @Inject constructor(
}
suspend fun sync(immediate: Boolean): Boolean = withContext(NonCancellable) {
if (isSyncEnabled()) {
workManager.sync(immediate)
true
} else {
false
val googleTasks = isGoogleTaskSyncEnabled()
if (googleTasks) {
workManager.googleTaskSync(immediate)
}
val caldav = isCaldavSyncEnabled()
if (caldav) {
workManager.caldavSync(immediate)
}
val eteSync = isEteSyncEnabled()
if (eteSync) {
workManager.eteSync(immediate)
}
return@withContext googleTasks || caldav || eteSync
}
suspend fun isSyncEnabled(): Boolean = isGoogleTaskSyncEnabled() || isCaldavSyncEnabled()
suspend fun isGoogleTaskSyncEnabled() = googleTaskListDao.getAccounts().isNotEmpty()
suspend fun isGoogleTaskSyncEnabled(): Boolean = googleTaskListDao.getAccounts().isNotEmpty()
suspend fun isCaldavSyncEnabled() = caldavDao.getAccounts(TYPE_CALDAV).isNotEmpty()
suspend fun isCaldavSyncEnabled(): Boolean = caldavDao.accountCount() > 0
suspend fun isEteSyncEnabled() = caldavDao.getAccounts(TYPE_ETESYNC).isNotEmpty()
}

@ -333,7 +333,9 @@
<string name="p_crash_main_queries">debug_crash_main_queries</string>
<string name="warned_play_services">warned_play_services</string>
<string name="p_background_sync_unmetered_only">background_sync_unmetered_only</string>
<string name="p_sync_ongoing">sync_ongoing</string>
<string name="p_sync_ongoing_google_tasks">sync_ongoing_google_tasks</string>
<string name="p_sync_ongoing_caldav">sync_ongoing_caldav</string>
<string name="p_sync_ongoing_etesync">sync_ongoing_etesync</string>
<string name="p_last_backup">last_backup</string>
<string name="p_show_description">show_description</string>
<string name="p_show_full_description">show_full_description</string>

Loading…
Cancel
Save