mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-07-23 19:50:18 +00:00
New setting to preselect only personal collections (bitfireAT/davx5#276)
* Add new preselect_collections option and deprecate sync_all_collections option * Optimize imports * At refresh, decide on whether a collection should be preselected * Add preselect_collections_blacklist setting and restriction * Adhere to preselect_collections_blacklist setting * Add preselect_collections values * Also check for empty regex string and use new setting values * Add unit tests * Remove sync_all_collections setting and restriction * Blacklist nextclouds recently contacted addressbook in restriction and setting by default * Improve kdoc * KDoc, changed setting names, minor code optimizations --------- Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
parent
3b2246e74b
commit
caf7be5e11
|
@ -14,22 +14,34 @@ import androidx.work.WorkManager
|
|||
import androidx.work.testing.WorkManagerTestInitHelper
|
||||
import at.bitfire.davdroid.HttpClient
|
||||
import at.bitfire.davdroid.TestUtils.workScheduledOrRunning
|
||||
import at.bitfire.davdroid.db.*
|
||||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.db.Collection
|
||||
import at.bitfire.davdroid.db.Credentials
|
||||
import at.bitfire.davdroid.db.HomeSet
|
||||
import at.bitfire.davdroid.db.Principal
|
||||
import at.bitfire.davdroid.db.Service
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.NotificationUtils
|
||||
import at.bitfire.davdroid.ui.setup.LoginModel
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import okhttp3.mockwebserver.Dispatcher
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import okhttp3.mockwebserver.RecordedRequest
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.junit.*
|
||||
import org.junit.Assert.*
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assume
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.net.URI
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -514,6 +526,147 @@ class RefreshCollectionsWorkerTest {
|
|||
assertEquals(0, principals.size)
|
||||
}
|
||||
|
||||
// Others
|
||||
|
||||
@Test
|
||||
fun shouldPreselect_none() {
|
||||
val service = createTestService(Service.TYPE_CARDDAV)!!
|
||||
|
||||
val settings = mockk<SettingsManager>()
|
||||
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_NONE
|
||||
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns ""
|
||||
|
||||
val collection = Collection(
|
||||
0,
|
||||
service.id,
|
||||
0,
|
||||
type = Collection.TYPE_ADDRESSBOOK,
|
||||
url = mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/")
|
||||
)
|
||||
val homesets = listOf(
|
||||
HomeSet(0, service.id, true, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET"))
|
||||
)
|
||||
|
||||
val refresher = RefreshCollectionsWorker.Refresher(db, service, settings, client.okHttpClient)
|
||||
assertFalse(refresher.shouldPreselect(collection, homesets))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldPreselect_all() {
|
||||
val service = createTestService(Service.TYPE_CARDDAV)!!
|
||||
|
||||
val settings = mockk<SettingsManager>()
|
||||
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_ALL
|
||||
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns ""
|
||||
|
||||
val collection = Collection(
|
||||
0,
|
||||
service.id,
|
||||
0,
|
||||
type = Collection.TYPE_ADDRESSBOOK,
|
||||
url = mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/")
|
||||
)
|
||||
val homesets = listOf(
|
||||
HomeSet(0, service.id, false, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET"))
|
||||
)
|
||||
|
||||
val refresher = RefreshCollectionsWorker.Refresher(db, service, settings, client.okHttpClient)
|
||||
assertTrue(refresher.shouldPreselect(collection, homesets))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldPreselect_all_blacklisted() {
|
||||
val service = createTestService(Service.TYPE_CARDDAV)!!
|
||||
val url = mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/")
|
||||
|
||||
val settings = mockk<SettingsManager>()
|
||||
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_ALL
|
||||
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns url.toString()
|
||||
|
||||
val collection = Collection(
|
||||
0,
|
||||
service.id,
|
||||
0,
|
||||
type = Collection.TYPE_ADDRESSBOOK,
|
||||
url = url
|
||||
)
|
||||
val homesets = listOf(
|
||||
HomeSet(0, service.id, false, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET"))
|
||||
)
|
||||
|
||||
val refresher = RefreshCollectionsWorker.Refresher(db, service, settings, client.okHttpClient)
|
||||
assertFalse(refresher.shouldPreselect(collection, homesets))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldPreselect_personal_notPersonal() {
|
||||
val service = createTestService(Service.TYPE_CARDDAV)!!
|
||||
|
||||
val settings = mockk<SettingsManager>()
|
||||
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_PERSONAL
|
||||
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns ""
|
||||
|
||||
val collection = Collection(
|
||||
0,
|
||||
service.id,
|
||||
0,
|
||||
type = Collection.TYPE_ADDRESSBOOK,
|
||||
url = mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/")
|
||||
)
|
||||
val homesets = listOf(
|
||||
HomeSet(0, service.id, false, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET"))
|
||||
)
|
||||
|
||||
val refresher = RefreshCollectionsWorker.Refresher(db, service, settings, client.okHttpClient)
|
||||
assertFalse(refresher.shouldPreselect(collection, homesets))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldPreselect_personal_isPersonal() {
|
||||
val service = createTestService(Service.TYPE_CARDDAV)!!
|
||||
|
||||
val settings = mockk<SettingsManager>()
|
||||
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_PERSONAL
|
||||
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns ""
|
||||
|
||||
val collection = Collection(
|
||||
0,
|
||||
service.id,
|
||||
0,
|
||||
type = Collection.TYPE_ADDRESSBOOK,
|
||||
url = mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/")
|
||||
)
|
||||
val homesets = listOf(
|
||||
HomeSet(0, service.id, true, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET"))
|
||||
)
|
||||
|
||||
val refresher = RefreshCollectionsWorker.Refresher(db, service, settings, client.okHttpClient)
|
||||
assertTrue(refresher.shouldPreselect(collection, homesets))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldPreselect_personal_isPersonalButBlacklisted() {
|
||||
val service = createTestService(Service.TYPE_CARDDAV)!!
|
||||
val collectionUrl = mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/")
|
||||
|
||||
val settings = mockk<SettingsManager>()
|
||||
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_PERSONAL
|
||||
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns collectionUrl.toString()
|
||||
|
||||
val collection = Collection(
|
||||
0,
|
||||
service.id,
|
||||
0,
|
||||
type = Collection.TYPE_ADDRESSBOOK,
|
||||
url = collectionUrl
|
||||
)
|
||||
val homesets = listOf(
|
||||
HomeSet(0, service.id, true, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET"))
|
||||
)
|
||||
|
||||
val refresher = RefreshCollectionsWorker.Refresher(db, service, settings, client.okHttpClient)
|
||||
assertFalse(refresher.shouldPreselect(collection, homesets))
|
||||
}
|
||||
|
||||
// Test helpers and dependencies
|
||||
|
||||
|
|
|
@ -12,15 +12,45 @@ import androidx.core.app.NotificationCompat
|
|||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.lifecycle.map
|
||||
import androidx.work.*
|
||||
import at.bitfire.dav4jvm.*
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import at.bitfire.dav4jvm.DavResource
|
||||
import at.bitfire.dav4jvm.MultiResponseCallback
|
||||
import at.bitfire.dav4jvm.Property
|
||||
import at.bitfire.dav4jvm.Response
|
||||
import at.bitfire.dav4jvm.UrlUtils
|
||||
import at.bitfire.dav4jvm.exception.HttpException
|
||||
import at.bitfire.dav4jvm.property.*
|
||||
import at.bitfire.dav4jvm.property.AddressbookDescription
|
||||
import at.bitfire.dav4jvm.property.AddressbookHomeSet
|
||||
import at.bitfire.dav4jvm.property.CalendarColor
|
||||
import at.bitfire.dav4jvm.property.CalendarDescription
|
||||
import at.bitfire.dav4jvm.property.CalendarHomeSet
|
||||
import at.bitfire.dav4jvm.property.CalendarProxyReadFor
|
||||
import at.bitfire.dav4jvm.property.CalendarProxyWriteFor
|
||||
import at.bitfire.dav4jvm.property.CurrentUserPrivilegeSet
|
||||
import at.bitfire.dav4jvm.property.DisplayName
|
||||
import at.bitfire.dav4jvm.property.GroupMembership
|
||||
import at.bitfire.dav4jvm.property.HrefListProperty
|
||||
import at.bitfire.dav4jvm.property.Owner
|
||||
import at.bitfire.dav4jvm.property.ResourceType
|
||||
import at.bitfire.dav4jvm.property.Source
|
||||
import at.bitfire.dav4jvm.property.SupportedAddressData
|
||||
import at.bitfire.dav4jvm.property.SupportedCalendarComponentSet
|
||||
import at.bitfire.davdroid.HttpClient
|
||||
import at.bitfire.davdroid.InvalidAccountException
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.db.*
|
||||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.db.Collection
|
||||
import at.bitfire.davdroid.db.HomeSet
|
||||
import at.bitfire.davdroid.db.Principal
|
||||
import at.bitfire.davdroid.db.Service
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker.Companion.ARG_SERVICE_ID
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
|
@ -84,7 +114,7 @@ class RefreshCollectionsWorker @AssistedInject constructor(
|
|||
|
||||
/**
|
||||
* Uniquely identifies a refresh worker. Useful for stopping work, or querying its state.
|
||||
*
|
||||
*
|
||||
* @param serviceId what service (CalDAV/CardDAV) the worker is running for
|
||||
*/
|
||||
fun workerName(serviceId: Long): String = "$REFRESH_COLLECTIONS_WORKER_TAG-$serviceId"
|
||||
|
@ -276,8 +306,9 @@ class RefreshCollectionsWorker @AssistedInject constructor(
|
|||
for (href in homeSet.hrefs)
|
||||
dav.location.resolve(href)?.let {
|
||||
val foundUrl = UrlUtils.withTrailingSlash(it)
|
||||
// Save the homeset - personal if outer call of recursion
|
||||
db.homeSetDao().insertOrUpdateByUrl(HomeSet(0, service.id, forPersonalHomeset, foundUrl))
|
||||
db.homeSetDao().insertOrUpdateByUrl(
|
||||
HomeSet(0, service.id, forPersonalHomeset, foundUrl)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,7 +382,7 @@ class RefreshCollectionsWorker @AssistedInject constructor(
|
|||
|
||||
collection.serviceId = service.id
|
||||
collection.homeSetId = localHomeset.id
|
||||
collection.sync = settings.getBoolean(Settings.SYNC_ALL_COLLECTIONS)
|
||||
collection.sync = shouldPreselect(collection, homesets.values)
|
||||
|
||||
// .. and save the principal url (collection owner)
|
||||
response[Owner::class.java]?.href
|
||||
|
@ -467,6 +498,49 @@ class RefreshCollectionsWorker @AssistedInject constructor(
|
|||
(service.type == Service.TYPE_CARDDAV && collection.type == Collection.TYPE_ADDRESSBOOK) ||
|
||||
(service.type == Service.TYPE_CALDAV && arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(collection.type)) ||
|
||||
(collection.type == Collection.TYPE_WEBCAL && collection.source != null)
|
||||
|
||||
/**
|
||||
* Whether to preselect the given collection for synchronisation, according to the
|
||||
* settings [Settings.PRESELECT_COLLECTIONS] (see there for allowed values) and
|
||||
* [Settings.PRESELECT_COLLECTIONS_EXCLUDED].
|
||||
*
|
||||
* A collection is considered _personal_ if it is found in one of the current-user-principal's home-sets.
|
||||
*
|
||||
* Before a collection is pre-selected, we check whether its URL matches the regexp in
|
||||
* [Settings.PRESELECT_COLLECTIONS_EXCLUDED], in which case *false* is returned.
|
||||
*
|
||||
* @param collection the collection to check
|
||||
* @param homesets list of home-sets (to check whether collection is in a personal home-set)
|
||||
* @return *true* if the collection should be preselected for synchronization; *false* otherwise
|
||||
*/
|
||||
internal fun shouldPreselect(collection: Collection, homesets: Iterable<HomeSet>): Boolean {
|
||||
val shouldPreselect = settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS)
|
||||
|
||||
val excluded by lazy {
|
||||
val excludedRegex = settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED)
|
||||
if (!excludedRegex.isNullOrEmpty())
|
||||
Regex(excludedRegex).containsMatchIn(collection.url.toString())
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
return when (shouldPreselect) {
|
||||
Settings.PRESELECT_COLLECTIONS_ALL ->
|
||||
// preselect if collection url is not excluded
|
||||
!excluded
|
||||
|
||||
Settings.PRESELECT_COLLECTIONS_PERSONAL ->
|
||||
// preselect if is personal (in a personal home-set), but not excluded
|
||||
homesets
|
||||
.filter { homeset -> homeset.personal }
|
||||
.map { homeset -> homeset.id }
|
||||
.contains(collection.homeSetId)
|
||||
&& !excluded
|
||||
|
||||
else -> // don't preselect
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,13 +4,7 @@
|
|||
|
||||
package at.bitfire.davdroid.settings
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Build
|
||||
import androidx.core.content.getSystemService
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
@ -26,11 +20,11 @@ class DefaultsProvider(
|
|||
|
||||
override val booleanDefaults = mutableMapOf(
|
||||
Pair(Settings.DISTRUST_SYSTEM_CERTIFICATES, false),
|
||||
Pair(Settings.SYNC_ALL_COLLECTIONS, false),
|
||||
Pair(Settings.FORCE_READ_ONLY_ADDRESSBOOKS, false)
|
||||
)
|
||||
|
||||
override val intDefaults = mapOf(
|
||||
Pair(Settings.PRESELECT_COLLECTIONS, Settings.PRESELECT_COLLECTIONS_NONE),
|
||||
Pair(Settings.PROXY_TYPE, Settings.PROXY_TYPE_SYSTEM),
|
||||
Pair(Settings.PROXY_PORT, 9050) // Orbot SOCKS
|
||||
)
|
||||
|
@ -40,7 +34,8 @@ class DefaultsProvider(
|
|||
)
|
||||
|
||||
override val stringDefaults = mapOf(
|
||||
Pair(Settings.PROXY_HOST, "localhost")
|
||||
Pair(Settings.PROXY_HOST, "localhost"),
|
||||
Pair(Settings.PRESELECT_COLLECTIONS_EXCLUDED, "/z-app-generated--contactsinteraction--recent/") // Nextcloud "Recently Contacted" address book
|
||||
)
|
||||
|
||||
class Factory @Inject constructor(): SettingsProviderFactory {
|
||||
|
|
|
@ -39,8 +39,18 @@ object Settings {
|
|||
|
||||
const val PREFERRED_TASKS_PROVIDER = "preferred_tasks_provider"
|
||||
|
||||
/** whether detected collections are selected for synchronization for default */
|
||||
const val SYNC_ALL_COLLECTIONS = "sync_all_collections"
|
||||
/** whether collections are automatically selected for synchronization after their initial detection */
|
||||
const val PRESELECT_COLLECTIONS = "preselect_collections"
|
||||
/** collections are not automatically selected for synchronization */
|
||||
const val PRESELECT_COLLECTIONS_NONE = 0
|
||||
/** all collections (except those matching [PRESELECT_COLLECTIONS_EXCLUDED]) are automatically selected for synchronization */
|
||||
const val PRESELECT_COLLECTIONS_ALL = 1
|
||||
/** personal collections (except those matching [PRESELECT_COLLECTIONS_EXCLUDED]) are automatically selected for synchronization */
|
||||
const val PRESELECT_COLLECTIONS_PERSONAL = 2
|
||||
|
||||
/** regular expression to match URLs of collections to be excluded from pre-selection */
|
||||
const val PRESELECT_COLLECTIONS_EXCLUDED = "preselect_collections_excluded"
|
||||
|
||||
|
||||
/** whether all address books are forced to be read-only */
|
||||
const val FORCE_READ_ONLY_ADDRESSBOOKS = "force_read_only_addressbooks"
|
||||
|
|
Loading…
Reference in a new issue