Skip to content

Commit

Permalink
Extended progress information
Browse files Browse the repository at this point in the history
Signed-off-by: Arnau Mora <[email protected]>
  • Loading branch information
ArnyminerZ committed Aug 23, 2023
1 parent 6e0632f commit 0f1f8fd
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 30 deletions.
12 changes: 10 additions & 2 deletions app/src/main/java/at/bitfire/icsdroid/ProcessEventsTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.core.app.NotificationCompat
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.util.DateUtils
Expand All @@ -18,6 +19,7 @@ import at.bitfire.icsdroid.db.AppDatabase
import at.bitfire.icsdroid.db.entity.Subscription
import at.bitfire.icsdroid.ui.EditCalendarActivity
import at.bitfire.icsdroid.ui.NotificationUtils
import kotlinx.coroutines.runBlocking
import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.model.PropertyList
import net.fortuna.ical4j.model.component.VAlarm
Expand Down Expand Up @@ -52,9 +54,13 @@ class ProcessEventsTask(
private val db = AppDatabase.getInstance(context)
private val subscriptionsDao = db.subscriptionsDao()

suspend fun sync() {
private var progress: (suspend (current: Int, max: Int) -> Unit)? = null

suspend fun sync(progress: (@WorkerThread suspend (current: Int, max: Int) -> Unit)? = null) {
Thread.currentThread().contextClassLoader = context.classLoader

this.progress = progress

try {
processEvents()
} catch (e: Exception) {
Expand Down Expand Up @@ -208,7 +214,9 @@ class ProcessEventsTask(
)
val uids = HashSet<String>(events.size)

for (ev in events) {
for ((i, ev) in events.withIndex()) {
runBlocking { progress?.invoke(i, events.size) }

val event = updateAlarms(ev)
val uid = event.uid!!
Log.d(Constants.TAG, "Found VEVENT: $uid")
Expand Down
112 changes: 98 additions & 14 deletions app/src/main/java/at/bitfire/icsdroid/SyncWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class SyncWorker(
const val PROGRESS_STEP = "step"
const val PROGRESS_MAX = "max"
const val PROGRESS_CURRENT = "current"
const val PROGRESS_INDETERMINATE = "indeterminate"
const val PROGRESS_SUBSCRIPTION = "subscription"

/**
* Enqueues a sync job for immediate execution. If the sync is forced,
Expand Down Expand Up @@ -109,7 +109,7 @@ class SyncWorker(
val onlyMigrate = inputData.getBoolean(ONLY_MIGRATE, false)
Log.i(TAG, "Synchronizing (forceReSync=$forceReSync,onlyMigrate=$onlyMigrate)")

setForeground(createForegroundInfo(SyncSteps.Start))
updateForegroundInfo(SyncSteps.Start)

provider =
try {
Expand Down Expand Up @@ -139,10 +139,15 @@ class SyncWorker(
// Make sure the subscription has a matching calendar
subscription.calendarId ?: continue

setForeground(createForegroundInfo(SyncSteps.Calendar(subscriptions.size, i)))
updateForegroundInfo(SyncSteps.Calendar(subscriptions.size, i, subscription.id))

val calendar = LocalCalendar.findById(account, provider, subscription.calendarId)
ProcessEventsTask(applicationContext, subscription, calendar, forceReSync).sync()
ProcessEventsTask(applicationContext, subscription, calendar, forceReSync)
.sync { progress, max ->
updateForegroundInfo(
SyncSteps.ProcessEvents(max, progress, subscription.id)
)
}
}
} catch (e: InterruptedException) {
Log.e(TAG, "Thread interrupted", e)
Expand All @@ -164,7 +169,7 @@ class SyncWorker(
* 3. If there's no matching [Subscription], create it.
*/
private suspend fun migrateLegacyCalendars() {
setForeground(createForegroundInfo(SyncSteps.Migration))
updateForegroundInfo(SyncSteps.Migration)

@Suppress("DEPRECATION")
val legacyCredentials by lazy { CalendarCredentials(applicationContext) }
Expand Down Expand Up @@ -215,7 +220,7 @@ class SyncWorker(

// synchronize them
for ((i, subscription) in subscriptions.withIndex()) {
setForeground(createForegroundInfo(SyncSteps.Subscriptions(subscriptions.size, i)))
updateForegroundInfo(SyncSteps.Subscriptions(subscriptions.size, i, subscription.id))

val calendarId = subscription.calendarId
val calendar = calendars.remove(calendarId)
Expand Down Expand Up @@ -245,7 +250,15 @@ class SyncWorker(
}
}

private suspend fun createForegroundInfo(step: SyncSteps): ForegroundInfo {
/**
* Updates the progress information for the worker. Sets the value of [foregroundInfo] so that
* it can be fetched by [getForegroundInfo] at any moment.
*
* Also updates the current progress of synchronization with [setProgress].
*
* @param step The current step of synchronization.
*/
private suspend fun updateForegroundInfo(step: SyncSteps): ForegroundInfo {
NotificationUtils.createChannels(applicationContext)

val notificationId = id.toString().hashCode()
Expand All @@ -268,7 +281,9 @@ class SyncWorker(
.build()

setProgress(
step.workData()
step.workData().also {
Log.i("SyncWorker", "Progress: ${step.id} - ${step.progress} / ${step.max} - ${step.subscriptionId}\nData: $it")
}
)

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Expand All @@ -279,29 +294,70 @@ class SyncWorker(
}

sealed class SyncSteps {
companion object {
/**
* Provides a sync step from the data given by a worker. Extracts its [SyncSteps.id],
* which is used for identifying which step is currently running.
*
* @param data The data to extract the step from.
*
* @return The instance of [SyncSteps] associated with the data stored at [data], or
* null if the data doesn't contain a step name, or it could not be recognized.
*/
fun fromData(data: Data): SyncSteps? = when(data.getString(PROGRESS_STEP)) {
Start.id -> Start
Migration.id -> Migration
Subscriptions.id -> Subscriptions(
data.getInt(PROGRESS_MAX, -1),
data.getInt(PROGRESS_CURRENT, -1),
data.getLong(PROGRESS_SUBSCRIPTION, -1)
)
ProcessEvents.id -> ProcessEvents(
data.getInt(PROGRESS_MAX, -1),
data.getInt(PROGRESS_CURRENT, -1),
data.getLong(PROGRESS_SUBSCRIPTION, -1)
)
Calendar.id -> Calendar(
data.getInt(PROGRESS_MAX, -1),
data.getInt(PROGRESS_CURRENT, -1),
data.getLong(PROGRESS_SUBSCRIPTION, -1)
)
else -> null
}
}

abstract val max: Int
abstract val progress: Int
abstract val indeterminate: Boolean

/** can be used for relating a progress step with a subscription */
abstract val subscriptionId: Long?

/** The step name, will be shown in the notification's text */
@get:StringRes
abstract val displayName: Int

/** The identifier of this step. Usually a short name of the object implementing the class */
abstract val id: String

/** Obtains a float from 0 to 1 representing the proportion between [progress] and [max] */
val percentage: Float
get() = progress.toFloat() / max

fun workData(): Data = workDataOf(
PROGRESS_STEP to displayName,
PROGRESS_STEP to id,
PROGRESS_CURRENT to progress,
PROGRESS_MAX to max,
PROGRESS_INDETERMINATE to indeterminate
PROGRESS_SUBSCRIPTION to subscriptionId
)

object Start: SyncSteps() {
override val max: Int = -1
override val progress: Int = -1
override val indeterminate: Boolean = true

override val subscriptionId: Long? = null

override val displayName: Int = R.string.notification_sync_start

override val id: String = "start"
Expand All @@ -312,31 +368,59 @@ class SyncWorker(
override val progress: Int = -1
override val indeterminate: Boolean = true

override val subscriptionId: Long? = null

override val displayName: Int = R.string.notification_sync_migration

override val id: String = "migration"
}

class Subscriptions(
override val max: Int,
override val progress: Int
override val progress: Int,
override val subscriptionId: Long
): SyncSteps() {
companion object {
const val id: String = "subscriptions"
}

override val indeterminate: Boolean = false

override val displayName: Int = R.string.notification_sync_calendar

override val id: String = "subscriptions"
override val id: String = Companion.id
}

class ProcessEvents(
override val max: Int,
override val progress: Int,
override val subscriptionId: Long
): SyncSteps() {
companion object {
const val id: String = "process_events"
}

override val indeterminate: Boolean = false

override val displayName: Int = R.string.notification_sync_events

override val id: String = Companion.id
}

class Calendar(
override val max: Int,
override val progress: Int
override val progress: Int,
override val subscriptionId: Long
): SyncSteps() {
companion object {
const val id: String = "calendar"
}

override val indeterminate: Boolean = false

override val displayName: Int = R.string.notification_sync_calendar

override val id: String = "calendar"
override val id: String = Companion.id
}
}

Expand Down
34 changes: 23 additions & 11 deletions app/src/main/java/at/bitfire/icsdroid/ui/CalendarListActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ class CalendarListActivity: AppCompatActivity() {
fun ActivityContent(paddingValues: PaddingValues) {
val context = LocalContext.current

val isRefreshing by model.isRefreshing.observeAsState(initial = true)
val syncWorker by model.syncWorker.observeAsState()
val pullRefreshState = rememberPullRefreshState(
refreshing = isRefreshing,
refreshing = syncWorker != null,
onRefresh = ::onRefreshRequested
)

Expand Down Expand Up @@ -230,16 +230,28 @@ class CalendarListActivity: AppCompatActivity() {
}

items(subscriptions ?: emptyList()) { subscription ->
CalendarListItem(subscription = subscription, onClick = {
val intent = Intent(context, EditCalendarActivity::class.java)
intent.putExtra(EditCalendarActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
startActivity(intent)
})
val syncStep = syncWorker
?.progress
?.let(SyncWorker.SyncSteps::fromData)
?.takeIf { it.subscriptionId == subscription.id }

CalendarListItem(
subscription = subscription,
syncStep = syncStep,
onClick = {
val intent = Intent(context, EditCalendarActivity::class.java)
intent.putExtra(
EditCalendarActivity.EXTRA_SUBSCRIPTION_ID,
subscription.id
)
startActivity(intent)
}
)
}
}

PullRefreshIndicator(
refreshing = isRefreshing,
refreshing = syncWorker != null,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter)
)
Expand Down Expand Up @@ -345,9 +357,9 @@ class CalendarListActivity: AppCompatActivity() {
val askForWhitelisting = MutableLiveData(false)


/** whether there are running sync workers */
val isRefreshing = SyncWorker.liveStatus(application).map { workInfos ->
workInfos.any { it.state == WorkInfo.State.RUNNING }
/** contains a list of all the workers currently running */
val syncWorker = SyncWorker.liveStatus(application).map { workInfos ->
workInfos.find { it.state == WorkInfo.State.RUNNING }
}

/** LiveData watching the subscriptions */
Expand Down
Loading

0 comments on commit 0f1f8fd

Please sign in to comment.