-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added `BaseSyncWorker` Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Fixed test Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me> * Optimized imports Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Fixed suspensions Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Disabled deprecation Signed-off-by: Arnau Mora <arnyminerz@proton.me> --------- Signed-off-by: Arnau Mora <arnyminerz@proton.me> Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
- Loading branch information
1 parent
39bc1ac
commit dffddc7
Showing
4 changed files
with
209 additions
and
179 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
190 changes: 190 additions & 0 deletions
190
app/src/main/java/at/bitfire/icsdroid/BaseSyncWorker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package at.bitfire.icsdroid | ||
|
||
import android.content.ContentProviderClient | ||
import android.content.ContentUris | ||
import android.content.Context | ||
import android.util.Log | ||
import androidx.work.CoroutineWorker | ||
import androidx.work.WorkerParameters | ||
import at.bitfire.ical4android.AndroidCalendar | ||
import at.bitfire.ical4android.util.MiscUtils.closeCompat | ||
import at.bitfire.icsdroid.calendar.LocalCalendar | ||
import at.bitfire.icsdroid.db.AppDatabase | ||
import at.bitfire.icsdroid.db.CalendarCredentials | ||
import at.bitfire.icsdroid.db.entity.Credential | ||
import at.bitfire.icsdroid.db.entity.Subscription | ||
import at.bitfire.icsdroid.ui.NotificationUtils | ||
|
||
abstract class BaseSyncWorker( | ||
context: Context, | ||
workerParams: WorkerParameters | ||
) : CoroutineWorker(context, workerParams) { | ||
companion object { | ||
/** | ||
* An input data (Boolean) for the Worker that tells whether the synchronization should be performed | ||
* without taking into account the current network condition. | ||
*/ | ||
const val FORCE_RESYNC = "forceResync" | ||
|
||
/** | ||
* An input data (Boolean) for the Worker that tells if only migration should be performed, without | ||
* fetching data. | ||
*/ | ||
const val ONLY_MIGRATE = "onlyMigration" | ||
} | ||
|
||
private val database = AppDatabase.getInstance(applicationContext) | ||
private val subscriptionsDao = database.subscriptionsDao() | ||
private val credentialsDao = database.credentialsDao() | ||
|
||
private val account = AppAccount.get(applicationContext) | ||
lateinit var provider: ContentProviderClient | ||
|
||
private var forceReSync: Boolean = false | ||
|
||
override suspend fun doWork(): Result { | ||
forceReSync = inputData.getBoolean(FORCE_RESYNC, false) | ||
val onlyMigrate = inputData.getBoolean(ONLY_MIGRATE, false) | ||
Log.i(Constants.TAG, "Synchronizing (forceReSync=$forceReSync,onlyMigrate=$onlyMigrate)") | ||
|
||
provider = | ||
try { | ||
LocalCalendar.getCalendarProvider(applicationContext) | ||
} catch (e: SecurityException) { | ||
NotificationUtils.showCalendarPermissionNotification(applicationContext) | ||
return Result.failure() | ||
} | ||
|
||
try { | ||
// migrate old calendar-based subscriptions to database | ||
migrateLegacyCalendars() | ||
|
||
// Do not synchronize if onlyMigrate is true | ||
if (onlyMigrate) return Result.success() | ||
|
||
// update local calendars according to the subscriptions | ||
updateLocalCalendars() | ||
|
||
// provide iCalendar event color values to Android | ||
val account = AppAccount.get(applicationContext) | ||
AndroidCalendar.insertColors(provider, account) | ||
|
||
// sync local calendars | ||
for (subscription in subscriptionsDao.getAll()) { | ||
// Make sure the subscription has a matching calendar | ||
subscription.calendarId ?: continue | ||
val calendar = LocalCalendar.findById(account, provider, subscription.calendarId) | ||
ProcessEventsTask(applicationContext, subscription, calendar, forceReSync).sync() | ||
} | ||
} catch (e: InterruptedException) { | ||
Log.e(Constants.TAG, "Thread interrupted", e) | ||
return Result.retry() | ||
} finally { | ||
provider.closeCompat() | ||
} | ||
|
||
return Result.success() | ||
} | ||
|
||
/** | ||
* Migrates all the legacy calendar-based subscriptions to the database. Performs these steps: | ||
* | ||
* 1. Searches for all the calendars created | ||
* 2. Checks that those calendars have a matching [Subscription] in the database. | ||
* 3. If there's no matching [Subscription], create it. | ||
*/ | ||
private suspend fun migrateLegacyCalendars() { | ||
@Suppress("DEPRECATION") | ||
val legacyCredentials by lazy { CalendarCredentials(applicationContext) } | ||
|
||
// if there's a provider available, get all the calendars available in the system | ||
for (calendar in LocalCalendar.findUnmanaged(account, provider)) { | ||
Log.i(Constants.TAG, "Found unmanaged (<= v2.1.1) calendar ${calendar.id}, migrating") | ||
@Suppress("DEPRECATION") | ||
val url = calendar.url ?: continue | ||
|
||
// Special case v2.1: it created subscriptions, but did not set the COLUMN_MANAGED_BY_DB flag. | ||
val subscription = subscriptionsDao.getByUrl(url) | ||
if (subscription != null) { | ||
// So we already have a subscription and only net to set its calendar_id. | ||
Log.i( | ||
Constants.TAG, | ||
"Migrating from v2.1: updating subscription ${subscription.id} with calendar ID" | ||
) | ||
subscriptionsDao.updateCalendarId(subscription.id, calendar.id) | ||
|
||
} else { | ||
// before v2.1: if there's no subscription with the same URL | ||
val newSubscription = Subscription.fromLegacyCalendar(calendar) | ||
Log.i( | ||
Constants.TAG, | ||
"Migrating from < v2.1: creating subscription $newSubscription" | ||
) | ||
val subscriptionId = subscriptionsDao.add(newSubscription) | ||
|
||
// migrate credentials, too (if available) | ||
val (legacyUsername, legacyPassword) = legacyCredentials.get(calendar) | ||
if (legacyUsername != null && legacyPassword != null) | ||
credentialsDao.create( | ||
Credential( | ||
subscriptionId, | ||
legacyUsername, | ||
legacyPassword | ||
) | ||
) | ||
} | ||
|
||
// set MANAGED_BY_DB=1 so that the calendar won't be migrated anymore | ||
calendar.setManagedByDB() | ||
} | ||
} | ||
|
||
/** | ||
* Updates the local calendars according to the available [Subscription]s. A local calendar is | ||
* | ||
* - created if there's a [Subscription] without calendar, | ||
* - updated (e.g. display name) if there's a [Subscription] for this calendar, | ||
* - deleted if there's no [Subscription] for this calendar. | ||
*/ | ||
private suspend fun updateLocalCalendars() { | ||
// subscriptions from DB | ||
val subscriptions = subscriptionsDao.getAll() | ||
|
||
// local calendars from provider as Map: <Calendar ID, LocalCalendar> | ||
val calendars = | ||
LocalCalendar.findManaged(account, provider).associateBy { it.id }.toMutableMap() | ||
|
||
// synchronize them | ||
for (subscription in subscriptions) { | ||
val calendarId = subscription.calendarId | ||
val calendar = calendars.remove(calendarId) | ||
// note that calendar might still be null even if calendarId is not null, | ||
// for instance when the calendar has been removed from the system | ||
|
||
if (calendar == null) { | ||
// no local calendar yet, create it | ||
Log.d( | ||
Constants.TAG, | ||
"Creating local calendar from subscription #${subscription.id}" | ||
) | ||
// create local calendar | ||
val uri = | ||
AndroidCalendar.create(account, provider, subscription.toCalendarProperties()) | ||
// update calendar ID in DB | ||
val newCalendarId = ContentUris.parseId(uri) | ||
subscriptionsDao.updateCalendarId(subscription.id, newCalendarId) | ||
|
||
} else { | ||
// local calendar already existing, update accordingly | ||
Log.d(Constants.TAG, "Updating local calendar #$calendarId from subscription") | ||
calendar.update(subscription.toCalendarProperties()) | ||
} | ||
} | ||
|
||
// remove remaining calendars | ||
for (calendar in calendars.values) { | ||
Log.d(Constants.TAG, "Removing local calendar #${calendar.id} without subscription") | ||
calendar.delete() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.