Skip to content

Commit

Permalink
Implement LocalJtxCollectionStore
Browse files Browse the repository at this point in the history
  • Loading branch information
rfc2822 committed Nov 9, 2024
1 parent fc003c5 commit 29312b8
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 226 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.db.HomeSet
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.resource.LocalAddressBookStore
import at.bitfire.davdroid.resource.LocalTaskList
import at.bitfire.davdroid.servicedetection.DavResourceFinder
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
Expand Down Expand Up @@ -49,6 +49,7 @@ class AccountRepository @Inject constructor(
@ApplicationContext val context: Context,
private val collectionRepository: DavCollectionRepository,
private val homeSetRepository: DavHomeSetRepository,
private val localAddressBookStore: Lazy<LocalAddressBookStore>,
private val logger: Logger,
private val settingsManager: SettingsManager,
private val serviceRepository: DavServiceRepository,
Expand Down Expand Up @@ -140,7 +141,7 @@ class AccountRepository @Inject constructor(
// delete address books (= address book accounts)
serviceRepository.getByAccountAndType(accountName, Service.TYPE_CARDDAV)?.let { service ->
collectionRepository.getByService(service.id).forEach { collection ->
LocalAddressBook.deleteByCollection(context, collection.id)
localAddressBookStore.get().deleteByCollectionId(collection.id)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@ import android.provider.ContactsContract.CommonDataKinds.GroupMembership
import android.provider.ContactsContract.Groups
import android.provider.ContactsContract.RawContacts
import androidx.annotation.OpenForTesting
import androidx.annotation.VisibleForTesting
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.SyncState
import at.bitfire.davdroid.repository.DavCollectionRepository
import at.bitfire.davdroid.repository.DavServiceRepository
import at.bitfire.davdroid.resource.workaround.ContactDirtyVerifier
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.sync.account.SystemAccountUtils
import at.bitfire.davdroid.util.DavUtils.lastSegment
import at.bitfire.davdroid.util.setAndVerifyUserData
import at.bitfire.vcard4android.AndroidAddressBook
import at.bitfire.vcard4android.AndroidContact
Expand All @@ -36,11 +33,7 @@ import at.bitfire.vcard4android.GroupMethod
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import java.util.LinkedList
import java.util.Optional
import java.util.logging.Level
Expand Down Expand Up @@ -322,92 +315,16 @@ open class LocalAddressBook @AssistedInject constructor(

companion object {

@EntryPoint
@InstallIn(SingletonComponent::class)
interface LocalAddressBookCompanionEntryPoint {
fun localAddressBookFactory(): Factory
fun serviceRepository(): DavServiceRepository
fun logger(): Logger
}

const val USER_DATA_URL = "url"
const val USER_DATA_COLLECTION_ID = "collection_id"
const val USER_DATA_READ_ONLY = "read_only"


// create/query/delete

/**
* Finds a [LocalAddressBook] based on its corresponding collection.
*
* @param id collection ID to look for
*
* @return The [LocalAddressBook] for the given collection or *null* if not found
*/
fun findByCollection(context: Context, provider: ContentProviderClient, id: Long): LocalAddressBook? {
val entryPoint = EntryPointAccessors.fromApplication<LocalAddressBookCompanionEntryPoint>(context)
val factory = entryPoint.localAddressBookFactory()

val accountManager = AccountManager.get(context)
return accountManager
.getAccountsByType(context.getString(R.string.account_type_address_book))
.filter { account ->
accountManager.getUserData(account, USER_DATA_COLLECTION_ID)?.toLongOrNull() == id
}
.map { account -> factory.create(account, provider) }
.firstOrNull()
}

/**
* Deletes a [LocalAddressBook] based on its corresponding database collection.
*
* @param id collection ID to look for
*/
fun deleteByCollection(context: Context, id: Long) {
val accountManager = AccountManager.get(context)
val addressBookAccount = accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)).firstOrNull { account ->
accountManager.getUserData(account, USER_DATA_COLLECTION_ID)?.toLongOrNull() == id
}
if (addressBookAccount != null)
accountManager.removeAccountExplicitly(addressBookAccount)
}


// helpers

/**
* Creates a name for the address book account from its corresponding db collection info.
*
* The address book account name contains
* - the collection display name or last URL path segment
* - the actual account name
* - the collection ID, to make it unique.
*
* @param info The corresponding collection
*/
fun accountName(context: Context, info: Collection): String {
// Name the address book after given collection display name, otherwise use last URL path segment
val sb = StringBuilder(info.displayName.let {
if (it.isNullOrEmpty())
info.url.lastSegment
else
it
})
// Add the actual account name to the address book account name
val entryPoint = EntryPointAccessors.fromApplication<LocalAddressBookCompanionEntryPoint>(context)
val serviceRepository = entryPoint.serviceRepository()
serviceRepository.get(info.serviceId)?.let { service ->
sb.append(" (${service.accountName})")
}
// Add the collection ID for uniqueness
sb.append(" #${info.id}")
return sb.toString()
}
const val USER_DATA_READ_ONLY = "read_only"

internal fun initialUserData(url: String, collectionId: String): Bundle {
val bundle = Bundle(3)
bundle.putString(USER_DATA_COLLECTION_ID, collectionId)
bundle.putString(USER_DATA_URL, url)
// TODO read-only??
return bundle
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import at.bitfire.davdroid.repository.DavCollectionRepository
import at.bitfire.davdroid.repository.DavServiceRepository
import at.bitfire.davdroid.resource.LocalAddressBook.Companion.USER_DATA_COLLECTION_ID
import at.bitfire.davdroid.resource.LocalAddressBook.Companion.USER_DATA_URL
import at.bitfire.davdroid.resource.LocalAddressBook.Companion.accountName
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.sync.account.SystemAccountUtils
import at.bitfire.davdroid.util.DavUtils.lastSegment
import at.bitfire.davdroid.util.setAndVerifyUserData
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.logging.Level
Expand All @@ -35,18 +35,48 @@ class LocalAddressBookStore @Inject constructor(
val addressBookFactory: LocalAddressBook.Factory,
val collectionRepository: DavCollectionRepository,
@ApplicationContext val context: Context,
val localAddressBookFactory: LocalAddressBook.Factory,
val logger: Logger,
val settings: SettingsManager,
val serviceRepository: DavServiceRepository
): LocalDataStore<LocalAddressBook> {
val serviceRepository: DavServiceRepository,
val settings: SettingsManager
): LocalDataStore<LocalAddressBook> {

/** whether a (usually managed) setting wants all address-books to be read-only **/
val forceAllReadOnly: Boolean
get() = settings.getBoolean(Settings.FORCE_READ_ONLY_ADDRESSBOOKS)


/**
* Assembles a name for the address book (account) from its corresponding database [Collection].
*
* The address book account name contains
*
* - the collection display name or last URL path segment
* - the actual account name
* - the collection ID, to make it unique.
*
* @param info Collection to take info from
*/
fun accountName(info: Collection): String {
// Name the address book after given collection display name, otherwise use last URL path segment
val sb = StringBuilder(info.displayName.let {
if (it.isNullOrEmpty())
info.url.lastSegment
else
it
})
// Add the actual account name to the address book account name
serviceRepository.get(info.serviceId)?.let { service ->
sb.append(" (${service.accountName})")
}
// Add the collection ID for uniqueness
sb.append(" #${info.id}")
return sb.toString()
}


override fun create(provider: ContentProviderClient, fromCollection: Collection): LocalAddressBook? {
val name = accountName(context, fromCollection)
val name = accountName(fromCollection)
val account = createAccount(
name = name,
id = fromCollection.id,
Expand Down Expand Up @@ -78,12 +108,41 @@ class LocalAddressBookStore @Inject constructor(
return account
}


override fun getAll(account: Account, provider: ContentProviderClient): List<LocalAddressBook> =
serviceRepository.getByAccountAndType(account.name, Service.TYPE_CARDDAV)?.let { service ->
// get all collections for the CardDAV service
collectionRepository.getByService(service.id).mapNotNull { collection ->
// and map to a LocalAddressBook, if applicable
findByCollection(provider, collection.id)
}
}.orEmpty()

/**
* Finds a [LocalAddressBook] based on its corresponding collection.
*
* @param id collection ID to look for
*
* @return The [LocalAddressBook] for the given collection or *null* if not found
*/
private fun findByCollection(provider: ContentProviderClient, id: Long): LocalAddressBook? {
val accountManager = AccountManager.get(context)
return accountManager
.getAccountsByType(context.getString(R.string.account_type_address_book))
.filter { account ->
accountManager.getUserData(account, USER_DATA_COLLECTION_ID)?.toLongOrNull() == id
}
.map { account -> localAddressBookFactory.create(account, provider) }
.firstOrNull()
}


override fun update(provider: ContentProviderClient, localCollection: LocalAddressBook, fromCollection: Collection) {
var currentAccount = localCollection.addressBookAccount
logger.log(Level.INFO, "Updating local address book $currentAccount from collection $fromCollection")

// Update the account name
val newAccountName = accountName(context, fromCollection)
val newAccountName = accountName(fromCollection)
if (currentAccount.name != newAccountName) {
// rename, move contacts/groups and update [AndroidAddressBook.]account
localCollection.renameAccount(newAccountName)
Expand Down Expand Up @@ -130,18 +189,26 @@ class LocalAddressBookStore @Inject constructor(
localCollection.updateSyncFrameworkSettings()
}

override fun getAll(account: Account, provider: ContentProviderClient): List<LocalAddressBook> =
serviceRepository.getByAccountAndType(account.name, Service.TYPE_CARDDAV)?.let { service ->
collectionRepository.getByService(service.id).mapNotNull { collection ->
LocalAddressBook.findByCollection(context, provider, collection.id)
}
}.orEmpty()

override fun delete(localCollection: LocalAddressBook) {
val accountManager = AccountManager.get(context)
accountManager.removeAccountExplicitly(localCollection.addressBookAccount)
}

/**
* Deletes a [LocalAddressBook] based on its corresponding database collection.
*
* @param id [Collection.id] to look for
*/
fun deleteByCollectionId(id: Long) {
val accountManager = AccountManager.get(context)
val addressBookAccount = accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)).firstOrNull { account ->
accountManager.getUserData(account, USER_DATA_COLLECTION_ID)?.toLongOrNull() == id
}
if (addressBookAccount != null)
accountManager.removeAccountExplicitly(addressBookAccount)
}


companion object {

Expand Down
41 changes: 0 additions & 41 deletions app/src/main/kotlin/at/bitfire/davdroid/resource/LocalCalendar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@ import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentUris
import android.content.ContentValues
import android.net.Uri
import android.provider.CalendarContract.Calendars
import android.provider.CalendarContract.Events
import at.bitfire.davdroid.Constants
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.SyncState
import at.bitfire.davdroid.util.DavUtils.lastSegment
import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.AndroidCalendarFactory
import at.bitfire.ical4android.BatchOperation
import at.bitfire.ical4android.util.DateUtils
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
import java.util.LinkedList
import java.util.logging.Level
Expand All @@ -42,39 +37,6 @@ class LocalCalendar private constructor(
private val logger: Logger
get() = Logger.getGlobal()

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()) info.url.lastSegment else info.displayName)

if (withColor && info.color != null)
values.put(Calendars.CALENDAR_COLOR, info.color)

if (info.privWriteContent && !info.forceReadOnly) {
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER)
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1)
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1)
} else
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ)

info.timezone?.let { tzData ->
try {
val timeZone = DateUtils.parseVTimeZone(tzData)
timeZone.timeZoneId?.let { tzId ->
values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(tzId.value))
}
} catch(e: IllegalArgumentException) {
logger.log(Level.WARNING, "Couldn't parse calendar default time zone", e)
}
}

// add base values for Calendars
values.putAll(calendarBaseValues)

return values
}

}

override val collectionUrl: String?
Expand Down Expand Up @@ -109,9 +71,6 @@ class LocalCalendar private constructor(
accessLevel = info.getAsInteger(Calendars.CALENDAR_ACCESS_LEVEL) ?: Calendars.CAL_ACCESS_OWNER
}

fun update(info: Collection, updateColor: Boolean) =
update(valuesFromCollectionInfo(info, updateColor))


override fun findDeleted() =
queryEvents("${Events.DELETED} AND ${Events.ORIGINAL_ID} IS NULL", null)
Expand Down
Loading

0 comments on commit 29312b8

Please sign in to comment.