Skip to content

Commit

Permalink
Provide sync arguments at syncer creation
Browse files Browse the repository at this point in the history
  • Loading branch information
sunkup committed Jul 15, 2024
1 parent cd02f64 commit b319b2d
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class SyncerTest {

@Test
fun testOnPerformSync_runsSyncAndSetsClassLoader() {
val syncer = TestSyncer(context, db)
syncer.onPerformSync(account, arrayOf(), mockAuthority, SyncResult())
val syncer = TestSyncer(context, db, account, arrayOf(), mockAuthority, SyncResult())
syncer.onPerformSync()

// check whether onPerformSync() actually calls sync()
assertEquals(1, syncer.syncCalled.get())
Expand All @@ -56,18 +56,18 @@ class SyncerTest {
}


class TestSyncer(context: Context, db: AppDatabase) : Syncer(context, db) {
class TestSyncer(
context: Context,
db: AppDatabase,
account: Account,
extras: Array<String>,
authority: String,
syncResult: SyncResult
) : Syncer(context, db, account, extras, authority, syncResult) {

val syncCalled = AtomicInteger()

override fun sync(
account: Account,
extras: Array<String>,
authority: String,
httpClient: Lazy<HttpClient>,
provider: ContentProviderClient,
syncResult: SyncResult
) {
override fun sync(provider: ContentProviderClient) {
Thread.sleep(1000)
syncCalled.incrementAndGet()
}
Expand Down
30 changes: 17 additions & 13 deletions app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,38 @@ import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.util.setAndVerifyUserData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.logging.Level
import javax.inject.Inject

/**
* Sync logic for address books
*/
class AddressBookSyncer @Inject constructor(
class AddressBookSyncer @AssistedInject constructor(
@ApplicationContext context: Context,
db: AppDatabase,
private val contactsSyncManagerFactory: ContactsSyncManager.Factory,
private val settingsManager: SettingsManager
) : Syncer(context, db) {
private val settingsManager: SettingsManager,
db: AppDatabase,
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted authority: String,
@Assisted syncResult: SyncResult,
): Syncer(context, db, account, extras, authority, syncResult) {

@AssistedFactory
interface Factory {
fun create(account: Account, extras: Array<String>, authority: String, syncResult: SyncResult): AddressBookSyncer
}

companion object {
const val PREVIOUS_GROUP_METHOD = "previous_group_method"
}

override fun sync(
account: Account,
extras: Array<String>,
authority: String,
httpClient: Lazy<HttpClient>,
provider: ContentProviderClient,
syncResult: SyncResult
) {
override fun sync(provider: ContentProviderClient) {

// 1. find address book collections to be synced
val remoteAddressBooks = mutableMapOf<HttpUrl, Collection>()
Expand Down
29 changes: 16 additions & 13 deletions app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,36 @@ import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.resource.LocalCalendar
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.ical4android.AndroidCalendar
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.logging.Level
import javax.inject.Inject

/**
* Sync logic for calendars
*/
class CalendarSyncer @Inject constructor(
class CalendarSyncer @AssistedInject constructor(
@ApplicationContext context: Context,
db: AppDatabase,
private val calendarSyncManagerFactory: CalendarSyncManager.Factory
): Syncer(context, db) {
private val calendarSyncManagerFactory: CalendarSyncManager.Factory,
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted authority: String,
@Assisted syncResult: SyncResult
): Syncer(context, db, account, extras, authority, syncResult) {

override fun sync(
account: Account,
extras: Array<String>,
authority: String,
httpClient: Lazy<HttpClient>,
provider: ContentProviderClient,
syncResult: SyncResult
) {
@AssistedFactory
interface Factory {
fun create(account: Account, extras: Array<String>, authority: String, syncResult: SyncResult): CalendarSyncer
}

override fun sync(provider: ContentProviderClient) {

// 0. preparations
val accountSettings = AccountSettings(context, account)
Expand Down
29 changes: 16 additions & 13 deletions app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,38 @@ import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.resource.LocalJtxCollection
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.util.TaskUtils
import at.bitfire.ical4android.JtxCollection
import at.bitfire.ical4android.TaskProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.logging.Level
import javax.inject.Inject

/**
* Sync logic for jtx board
*/
class JtxSyncer @Inject constructor(
class JtxSyncer @AssistedInject constructor(
@ApplicationContext context: Context,
db: AppDatabase,
private val jtxSyncManagerFactory: JtxSyncManager.Factory
): Syncer(context, db) {
private val jtxSyncManagerFactory: JtxSyncManager.Factory,
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted authority: String,
@Assisted syncResult: SyncResult
): Syncer(context, db, account, extras, authority, syncResult) {

override fun sync(
account: Account,
extras: Array<String>,
authority: String,
httpClient: Lazy<HttpClient>,
provider: ContentProviderClient,
syncResult: SyncResult
) {
@AssistedFactory
interface Factory {
fun create(account: Account, extras: Array<String>, authority: String, syncResult: SyncResult): JtxSyncer
}

override fun sync(provider: ContentProviderClient) {

// 0. preparations

Expand Down
25 changes: 11 additions & 14 deletions app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ import java.util.logging.Level
*/
abstract class Syncer(
val context: Context,
val db: AppDatabase
val db: AppDatabase,
protected val account: Account,
protected val extras: Array<String>,
protected val authority: String,
protected val syncResult: SyncResult
) {

companion object {
Expand All @@ -54,20 +58,17 @@ abstract class Syncer(

}

val accountSettings by lazy { AccountSettings(context, account) }
val httpClient = lazy { HttpClient.Builder(context, accountSettings).build() }

/**
* Creates and/or deletes local collections (calendars, address books, etc) and updates them
* with remote information. Then syncs the actual entries (events, tasks, contacts, etc) of all
* collections.
*/
abstract fun sync(account: Account, extras: Array<String>, authority: String, httpClient: Lazy<HttpClient>, provider: ContentProviderClient, syncResult: SyncResult)

fun onPerformSync(
account: Account,
extras: Array<String>,
authority: String,
syncResult: SyncResult
) {
abstract fun sync(provider: ContentProviderClient)

fun onPerformSync() {
Logger.log.log(Level.INFO, "$authority sync of $account initiated", extras.joinToString(", "))

// use contacts provider for address books
Expand All @@ -77,9 +78,6 @@ abstract class Syncer(
else
authority

val accountSettings by lazy { AccountSettings(context, account) }
val httpClient = lazy { HttpClient.Builder(context, accountSettings).build() }

// acquire ContentProviderClient of authority to be synced
val provider = try {
context.contentResolver.acquireContentProviderClient(contentAuthority)
Expand All @@ -101,9 +99,8 @@ abstract class Syncer(
// run sync
try {
val runSync = /* ose */ true

if (runSync)
sync(account, extras, contentAuthority, httpClient, provider, syncResult)
sync(provider)

} catch (e: DeadObjectException) {
/* May happen when the remote process dies or (since Android 14) when IPC (for instance with the calendar provider)
Expand Down
27 changes: 15 additions & 12 deletions app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,39 @@ import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.resource.LocalTaskList
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.util.TaskUtils
import at.bitfire.ical4android.DmfsTaskList
import at.bitfire.ical4android.TaskProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.dmfs.tasks.contract.TaskContract
import java.util.logging.Level
import javax.inject.Inject

/**
* Sync logic for tasks in CalDAV collections ({@code VTODO}).
*/
class TaskSyncer @Inject constructor(
class TaskSyncer @AssistedInject constructor(
@ApplicationContext context: Context,
db: AppDatabase,
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted authority: String,
@Assisted syncResult: SyncResult,
private val tasksSyncManagerFactory: TasksSyncManager.Factory
): Syncer(context, db) {
): Syncer(context, db, account, extras, authority, syncResult) {

override fun sync(
account: Account,
extras: Array<String>,
authority: String,
httpClient: Lazy<HttpClient>,
provider: ContentProviderClient,
syncResult: SyncResult
) {
@AssistedFactory
interface Factory {
fun create(account: Account, extras: Array<String>, authority: String, syncResult: SyncResult): TaskSyncer
}

override fun sync(provider: ContentProviderClient) {

// 0. preparations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import java.util.Collections
import java.util.logging.Level

abstract class BaseSyncWorker(
context: Context,
Expand Down Expand Up @@ -189,15 +188,15 @@ abstract class BaseSyncWorker(

}

// We don't inject the Syncers in our constructor because that would generate
// We don't inject the Syncer factories in our constructor because that would generate
// every syncer object regardless of whether it's even used for the synced authority (useless overhead).
@EntryPoint
@InstallIn(SingletonComponent::class)
interface BaseSyncWorkerEntryPoint {
fun addressBookSyncer(): AddressBookSyncer
fun calendarSyncer(): CalendarSyncer
fun jtxSyncer(): JtxSyncer
fun taskSyncer(): TaskSyncer
fun addressBookSyncer(): AddressBookSyncer.Factory
fun calendarSyncer(): CalendarSyncer.Factory
fun jtxSyncer(): JtxSyncer.Factory
fun taskSyncer(): TaskSyncer.Factory
}
private val entryPoint = EntryPointAccessors.fromApplication<BaseSyncWorkerEntryPoint>(context)

Expand Down Expand Up @@ -265,36 +264,38 @@ abstract class BaseSyncWorker(
): Result = withContext(syncDispatcher) {
Logger.log.info("Running ${javaClass.name}: account=$account, authority=$authority")

// pass possibly supplied flags to the selected syncer
val extrasList = mutableListOf<String>()
when (inputData.getInt(OneTimeSyncWorker.ARG_RESYNC, OneTimeSyncWorker.NO_RESYNC)) {
OneTimeSyncWorker.RESYNC -> extrasList.add(Syncer.SYNC_EXTRAS_RESYNC)
OneTimeSyncWorker.FULL_RESYNC -> extrasList.add(Syncer.SYNC_EXTRAS_FULL_RESYNC)
}
if (inputData.getBoolean(OneTimeSyncWorker.ARG_UPLOAD, false))
// Comes in through SyncAdapterService and is used only by ContactsSyncManager for an Android 7 workaround.
extrasList.add(ContentResolver.SYNC_EXTRAS_UPLOAD)

val extras = extrasList.toTypedArray()
val result = SyncResult()

// What are we going to sync? Select syncer based on authority
val syncer: Syncer = when (authority) {
applicationContext.getString(R.string.address_books_authority) ->
entryPoint.addressBookSyncer()
entryPoint.addressBookSyncer().create(account, extras, authority, result)
CalendarContract.AUTHORITY ->
entryPoint.calendarSyncer()
entryPoint.calendarSyncer().create(account, extras, authority, result)
TaskProvider.ProviderName.JtxBoard.authority ->
entryPoint.jtxSyncer()
entryPoint.jtxSyncer().create(account, extras, authority, result)
TaskProvider.ProviderName.OpenTasks.authority,
TaskProvider.ProviderName.TasksOrg.authority ->
entryPoint.taskSyncer()
entryPoint.taskSyncer().create(account, extras, authority, result)
else ->
throw IllegalArgumentException("Invalid authority $authority")
}

// pass possibly supplied flags to the selected syncer
val extras = mutableListOf<String>()
when (inputData.getInt(OneTimeSyncWorker.ARG_RESYNC, OneTimeSyncWorker.NO_RESYNC)) {
OneTimeSyncWorker.RESYNC -> extras.add(Syncer.SYNC_EXTRAS_RESYNC)
OneTimeSyncWorker.FULL_RESYNC -> extras.add(Syncer.SYNC_EXTRAS_FULL_RESYNC)
}
if (inputData.getBoolean(OneTimeSyncWorker.ARG_UPLOAD, false))
// Comes in through SyncAdapterService and is used only by ContactsSyncManager for an Android 7 workaround.
extras.add(ContentResolver.SYNC_EXTRAS_UPLOAD)

val result = SyncResult()
// Start syncing. We still use the sync adapter framework's SyncResult to pass the sync results, but this
// is only for legacy reasons and can be replaced by an own result class in the future.
runInterruptible {
syncer.onPerformSync(account, extras.toTypedArray(), authority, result)
syncer.onPerformSync()
}

// Check for errors
Expand Down

0 comments on commit b319b2d

Please sign in to comment.