Skip to content

Commit

Permalink
[WIP] WebcalSyncWorker
Browse files Browse the repository at this point in the history
  • Loading branch information
rfc2822 committed Oct 17, 2023
1 parent 6069b3e commit 84bf695
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,29 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update

@Dao
interface WebcalSubscriptionDao {

@Query("SELECT * FROM webcal_subscription WHERE collectionId=:collectionId")
fun getByCollectionId(collectionId: Long): WebcalSubscription?
@Query("SELECT * FROM webcal_subscription ORDER BY displayName, url")
suspend fun getAllAsync(): List<WebcalSubscription>

@Query("SELECT * FROM webcal_subscription ORDER BY displayName, url")
fun getLive(): LiveData<List<WebcalSubscription>>
fun getAllLive(): LiveData<List<WebcalSubscription>>

@Query("SELECT COUNT(*) FROM webcal_subscription")
fun getCount(): Int

@Query("SELECT * FROM webcal_subscription WHERE collectionId=:collectionId")
fun getByCollectionId(collectionId: Long): WebcalSubscription?

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(subscription: WebcalSubscription): Long

@Update
fun update(subscription: WebcalSubscription)

@Delete
fun delete(subscription: WebcalSubscription)

Expand Down
29 changes: 24 additions & 5 deletions app/src/main/kotlin/at/bitfire/davdroid/resource/LocalCalendar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import at.bitfire.davdroid.Constants
import at.bitfire.davdroid.util.DavUtils
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.SyncState
import at.bitfire.davdroid.db.WebcalSubscription
import at.bitfire.davdroid.log.Logger
import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.AndroidCalendarFactory
Expand All @@ -34,9 +35,7 @@ class LocalCalendar private constructor(

private const val COLUMN_SYNC_STATE = Calendars.CAL_SYNC1

fun create(account: Account, provider: ContentProviderClient, info: Collection): Uri {
val values = valuesFromCollectionInfo(info, true)

fun create(account: Account, provider: ContentProviderClient, values: ContentValues): Uri {
// ACCOUNT_NAME and ACCOUNT_TYPE are required (see docs)! If it's missing, other apps will crash.
values.put(Calendars.ACCOUNT_NAME, account.name)
values.put(Calendars.ACCOUNT_TYPE, account.type)
Expand All @@ -48,13 +47,17 @@ class LocalCalendar private constructor(
// flag as visible & synchronizable at creation, might be changed by user at any time
values.put(Calendars.VISIBLE, 1)
values.put(Calendars.SYNC_EVENTS, 1)
return create(account, provider, values)
return AndroidCalendar.create(account, provider, values)
}

fun create(account: Account, provider: ContentProviderClient, info: Collection) =
create(account, provider, valuesFromCollectionInfo(info, true))

private fun valuesFromCollectionInfo(info: Collection, withColor: Boolean): ContentValues {
val values = ContentValues()
values.put(Calendars.NAME, info.url.toString())
values.put(Calendars.CALENDAR_DISPLAY_NAME, if (info.displayName.isNullOrBlank()) DavUtils.lastSegmentOfUrl(info.url) else info.displayName)
values.put(Calendars.CALENDAR_DISPLAY_NAME,
if (info.displayName.isNullOrBlank()) DavUtils.lastSegmentOfUrl(info.url) else info.displayName)

if (withColor)
values.put(Calendars.CALENDAR_COLOR, info.color ?: Constants.DAVDROID_GREEN_RGBA)
Expand Down Expand Up @@ -83,6 +86,22 @@ class LocalCalendar private constructor(
return values
}

fun create(account: Account, provider: ContentProviderClient, subscription: WebcalSubscription): Uri =
create(account, provider, valuesFromSubscription(subscription))

fun valuesFromSubscription(subscription: WebcalSubscription): ContentValues {
val values = ContentValues()
values.put(Calendars.NAME, subscription.url.toString())
values.put(Calendars.CALENDAR_DISPLAY_NAME,
if (subscription.displayName.isNullOrBlank()) DavUtils.lastSegmentOfUrl(subscription.url) else subscription.displayName)
values.put(Calendars.CALENDAR_COLOR, subscription.color)
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ)

values.putAll(calendarBaseValues)

return values
}

}

override val tag: String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package at.bitfire.davdroid.syncadapter

import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentUris
import android.content.Context
import android.provider.CalendarContract.Calendars
import androidx.hilt.work.HiltWorker
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.resource.LocalCalendar
import at.bitfire.davdroid.util.PermissionUtils
import at.bitfire.ical4android.AndroidCalendar
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import java.time.Duration

@HiltWorker
class WebcalSyncWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
val db: AppDatabase
): CoroutineWorker(appContext, workerParams) {

companion object {

const val NAME = "webcal-sync"

fun updateWorker(context: Context, db: AppDatabase) {
val workManager = WorkManager.getInstance(context)

if (db.webcalSubscriptionDao().getCount() > 0) {
Logger.log.info("There are Webcal subscriptions, updating sync worker")
val syncInterval = Duration.ofDays(1)
workManager.enqueueUniquePeriodicWork(NAME, ExistingPeriodicWorkPolicy.UPDATE,
PeriodicWorkRequestBuilder<WebcalSyncWorker>(syncInterval)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build()
)

} else {
Logger.log.info("No Webcal subscriptions, cancelling sync worker")
workManager.cancelUniqueWork(NAME)
}
}

}

override suspend fun doWork(): Result {
if (!PermissionUtils.havePermissions(applicationContext, PermissionUtils.CALENDAR_PERMISSIONS))
return Result.failure()

applicationContext.contentResolver.acquireContentProviderClient(Calendars.CONTENT_URI)?.use { providerClient ->
updateLocalCalendars(providerClient)
}

return Result.success()
}

private suspend fun updateLocalCalendars(providerClient: ContentProviderClient) {
val subscriptions = db.webcalSubscriptionDao().getAllAsync()
for (subscription in subscriptions) {
Logger.log.info("Subscription $subscription")

// TODO extra account for Webcal subscriptions?
val account: Account? = subscription.collectionId?.let { collectionId ->
db.collectionDao().get(collectionId)?.serviceId?.let { serviceId ->
db.serviceDao().get(serviceId)?.let { service ->
Account(service.accountName, applicationContext.getString(R.string.account_type))
}
}
}
if (account != null) {
val calendar =
subscription.calendarId?.let { calendarId ->
AndroidCalendar.findByID(account, providerClient, LocalCalendar.Factory, calendarId)
} ?: run {
val uri = LocalCalendar.create(account, providerClient, subscription)
val id = ContentUris.parseId(uri)
subscription.calendarId = id
db.webcalSubscriptionDao().update(subscription)
AndroidCalendar.findByID(account, providerClient, LocalCalendar.Factory, id)
}

Logger.log.info("Calendar $calendar")

} else {
Logger.log.warning("Standalone Webcal subscriptions not supported yet")
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,21 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.*
import androidx.compose.material.Typography
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Face
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.twotone.ShoppingCart
import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
Expand Down Expand Up @@ -133,7 +122,7 @@ class WebcalSubscriptionsActivity: ComponentActivity() {
db: AppDatabase
): ViewModel() {

val subscriptions = db.webcalSubscriptionDao().getLive()
val subscriptions = db.webcalSubscriptionDao().getAllLive()

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package at.bitfire.davdroid.ui.account

import android.app.Application
import android.content.Intent
import android.net.Uri
import android.view.*
Expand All @@ -16,6 +17,7 @@ import at.bitfire.davdroid.databinding.AccountCaldavItemBinding
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.WebcalSubscription
import at.bitfire.davdroid.syncadapter.WebcalSyncWorker
import at.bitfire.davdroid.util.PermissionUtils
import com.google.android.material.snackbar.Snackbar
import dagger.assisted.Assisted
Expand Down Expand Up @@ -162,9 +164,10 @@ class WebcalFragment: CollectionsFragment() {


class WebcalModel @AssistedInject constructor(
application: Application,
val db: AppDatabase,
@Assisted val serviceId: Long
): ViewModel() {
): AndroidViewModel(application) {

@AssistedFactory
interface Factory {
Expand All @@ -176,13 +179,15 @@ class WebcalFragment: CollectionsFragment() {
fun subscribe(item: Collection) = viewModelScope.launch(Dispatchers.IO) {
val subscription = WebcalSubscription.fromCollection(item)
dao.insertAndUpdateCollection(db.collectionDao(), subscription)
WebcalSyncWorker.updateWorker(getApplication(), db)
}

fun unsubscribe(item: Collection) = viewModelScope.launch(Dispatchers.IO) {
dao.getByCollectionId(item.id)?.let { subscription ->
dao.delete(subscription)
db.collectionDao().updateSync(item.id, false)
}
WebcalSyncWorker.updateWorker(getApplication(), db)
}

}
Expand Down

0 comments on commit 84bf695

Please sign in to comment.