Loading app/src/androidTest/java/at/bitfire/davdroid/model/DaoToolsTest.kt 0 → 100644 +64 −0 Original line number Diff line number Diff line package at.bitfire.davdroid.model import androidx.room.Room import androidx.room.RoomDatabase import androidx.test.platform.app.InstrumentationRegistry import okhttp3.HttpUrl import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Test class DaoToolsTest { private lateinit var db: AppDatabase @Before fun createDb() { val context = InstrumentationRegistry.getInstrumentation().context db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build() } @After fun closeDb() { db.close() } @Test fun testSyncAll() { val serviceDao = db.serviceDao() val service = Service(id=0, accountName="test", type=Service.TYPE_CALDAV, principal = null) service.id = serviceDao.insertOrReplace(service) val homeSetDao = db.homeSetDao() val entry1 = HomeSet(id=1, serviceId=service.id, url=HttpUrl.get("https://example.com/1")) val entry3 = HomeSet(id=3, serviceId=service.id, url=HttpUrl.get("https://example.com/3")) val oldItems = listOf( entry1, HomeSet(id=2, serviceId=service.id, url=HttpUrl.get("https://example.com/2")), entry3 ) homeSetDao.insert(oldItems) val newItems = mutableMapOf<HttpUrl, HomeSet>() newItems[entry1.url] = entry1 // no id, because identity is given by the url val updated = HomeSet(id=0, serviceId=service.id, url=HttpUrl.get("https://example.com/2"), displayName="Updated Entry") newItems[updated.url] = updated val created = HomeSet(id=4, serviceId=service.id, url=HttpUrl.get("https://example.com/4")) newItems[created.url] = created DaoTools(homeSetDao).syncAll(oldItems, newItems) { it.url } val afterSync = homeSetDao.getByService(service.id) assertEquals(afterSync.size, 3) assertFalse(afterSync.contains(entry3)) assertTrue(afterSync.contains(entry1)) assertTrue(afterSync.contains(updated)) assertTrue(afterSync.contains(created)) } } No newline at end of file app/src/main/java/at/bitfire/davdroid/DavService.kt +21 −54 Original line number Diff line number Diff line Loading @@ -23,10 +23,8 @@ import at.bitfire.dav4jvm.UrlUtils import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.dav4jvm.property.* import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.model.AppDatabase import at.bitfire.davdroid.model.* import at.bitfire.davdroid.model.Collection import at.bitfire.davdroid.model.HomeSet import at.bitfire.davdroid.model.Service import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.DebugInfoActivity import at.bitfire.davdroid.ui.NotificationUtils Loading Loading @@ -146,14 +144,8 @@ class DavService: android.app.Service() { val service = db.serviceDao().get(serviceId) ?: throw IllegalArgumentException("Service not found") val account = Account(service.accountName, getString(R.string.account_type)) val homeSets = homeSetDao.getByService(serviceId) .map { it.url } .toMutableSet() val collections = mutableMapOf<HttpUrl, Collection>() collectionDao.getByService(serviceId).forEach { collections[it.url] = it } val homeSets = homeSetDao.getByService(serviceId).associateBy { it.url }.toMutableMap() val collections = collectionDao.getByService(serviceId).associateBy { it.url }.toMutableMap() /** * Checks if the given URL defines home sets and adds them to the home set list. Loading Loading @@ -199,11 +191,12 @@ class DavService: android.app.Service() { when (service.type) { Service.TYPE_CARDDAV -> try { dav.propfind(0, AddressbookHomeSet.NAME, GroupMembership.NAME) { response, _ -> dav.propfind(0, DisplayName.NAME, AddressbookHomeSet.NAME, GroupMembership.NAME) { response, _ -> response[AddressbookHomeSet::class.java]?.let { homeSet -> for (href in homeSet.hrefs) dav.location.resolve(href)?.let { homeSets += UrlUtils.withTrailingSlash(it) val foundUrl = UrlUtils.withTrailingSlash(it) homeSets[foundUrl] = HomeSet(0, service.id, foundUrl, response[DisplayName::class.java]?.displayName) } } Loading @@ -218,11 +211,12 @@ class DavService: android.app.Service() { } Service.TYPE_CALDAV -> { try { dav.propfind(0, CalendarHomeSet.NAME, CalendarProxyReadFor.NAME, CalendarProxyWriteFor.NAME, GroupMembership.NAME) { response, _ -> dav.propfind(0, DisplayName.NAME, CalendarHomeSet.NAME, CalendarProxyReadFor.NAME, CalendarProxyWriteFor.NAME, GroupMembership.NAME) { response, _ -> response[CalendarHomeSet::class.java]?.let { homeSet -> for (href in homeSet.hrefs) dav.location.resolve(href)?.let { homeSets += UrlUtils.withTrailingSlash(it) val foundUrl = UrlUtils.withTrailingSlash(it) homeSets[foundUrl] = HomeSet(0, service.id, foundUrl, response[DisplayName::class.java]?.displayName) } } Loading @@ -244,46 +238,18 @@ class DavService: android.app.Service() { @Transaction fun saveHomesets() { val oldHomesets = homeSetDao.getByService(serviceId) oldHomesets.forEach { oldHomeset -> val url = oldHomeset.url if (homeSets.contains(url)) // URL is the same in oldHomesets and newHomesets, remove from "homesets" (which will be added later) homeSets.remove(url) else // URL is not in newHomesets, delete from database homeSetDao.delete(oldHomeset) } // insert new homesets homeSetDao.insert(homeSets.map { url -> HomeSet(0, serviceId, url) }) DaoTools(homeSetDao).syncAll( homeSetDao.getByService(serviceId), homeSets ) { it.url } } @Transaction fun saveCollections() { val oldCollections = collectionDao.getByService(serviceId) oldCollections.forEach { oldCollection -> val url = oldCollection.url val matchingNewCollection = collections[url] if (matchingNewCollection != null) { // old URL exists in newCollections, update database if content has been changed matchingNewCollection.id = oldCollection.id matchingNewCollection.serviceId = oldCollection.serviceId if (matchingNewCollection != oldCollection) collectionDao.update(matchingNewCollection) // remove from "collections" (which will be added later) collections.remove(url) } else // URL is not in newCollections, delete from database collectionDao.delete(oldCollection) } // insert new collections collections.forEach { (_, collection) -> collection.serviceId = serviceId } collectionDao.insert(collections.values.toList()) DaoTools(collectionDao).syncAll( collectionDao.getByService(serviceId), collections ) { it.url } } fun saveResults() { Loading Loading @@ -320,15 +286,16 @@ class DavService: android.app.Service() { // now refresh collections (taken from home sets) val itHomeSets = homeSets.iterator() while (itHomeSets.hasNext()) { val homeSetUrl = itHomeSets.next() Logger.log.fine("Listing home set $homeSetUrl") val homeSet = itHomeSets.next() Logger.log.fine("Listing home set ${homeSet.key}") try { DavResource(httpClient, homeSetUrl).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, _ -> DavResource(httpClient, homeSet.key).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, _ -> if (!response.isSuccess()) return@propfind val info = Collection.fromDavResponse(response) ?: return@propfind info.serviceId = serviceId info.confirmed = true Logger.log.log(Level.FINE, "Found collection", info) Loading app/src/main/java/at/bitfire/davdroid/model/AppDatabase.kt +9 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ import at.bitfire.davdroid.log.Logger Service::class, HomeSet::class, Collection::class ], version = 6) ], version = 7) @TypeConverters(Converters::class) abstract class AppDatabase: RoomDatabase() { Loading @@ -38,7 +38,8 @@ abstract class AppDatabase: RoomDatabase() { Migration2_3, Migration3_4, Migration4_5, Migration5_6 Migration5_6, Migration6_7 ) .fallbackToDestructiveMigration() // as a last fallback, recreate database instead of crashing .build() Loading Loading @@ -99,6 +100,12 @@ abstract class AppDatabase: RoomDatabase() { // migrations object Migration6_7: Migration(6, 7) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE homeset ADD COLUMN displayName TEXT DEFAULT NULL") } } object Migration5_6: Migration(5, 6) { override fun migrate(db: SupportSQLiteDatabase) { val sql = arrayOf( Loading app/src/main/java/at/bitfire/davdroid/model/Collection.kt +2 −2 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ import okhttp3.HttpUrl ) data class Collection( @PrimaryKey(autoGenerate = true) var id: Long = 0, override var id: Long = 0, var serviceId: Long = 0, Loading Loading @@ -52,7 +52,7 @@ data class Collection( /** whether this collection has been selected for synchronization */ var sync: Boolean = false ) { ): IdEntity() { companion object { Loading app/src/main/java/at/bitfire/davdroid/model/CollectionDao.kt +2 −11 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ import androidx.paging.DataSource import androidx.room.* @Dao interface CollectionDao { interface CollectionDao: SyncableDao<Collection> { @Query("SELECT * FROM collection WHERE id=:id") fun get(id: Long): Collection? Loading Loading @@ -37,13 +37,4 @@ interface CollectionDao { @Insert fun insert(collection: Collection) @Insert fun insert(collections: List<Collection>) @Update fun update(collection: Collection) @Delete fun delete(collection: Collection) } Loading
app/src/androidTest/java/at/bitfire/davdroid/model/DaoToolsTest.kt 0 → 100644 +64 −0 Original line number Diff line number Diff line package at.bitfire.davdroid.model import androidx.room.Room import androidx.room.RoomDatabase import androidx.test.platform.app.InstrumentationRegistry import okhttp3.HttpUrl import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Test class DaoToolsTest { private lateinit var db: AppDatabase @Before fun createDb() { val context = InstrumentationRegistry.getInstrumentation().context db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build() } @After fun closeDb() { db.close() } @Test fun testSyncAll() { val serviceDao = db.serviceDao() val service = Service(id=0, accountName="test", type=Service.TYPE_CALDAV, principal = null) service.id = serviceDao.insertOrReplace(service) val homeSetDao = db.homeSetDao() val entry1 = HomeSet(id=1, serviceId=service.id, url=HttpUrl.get("https://example.com/1")) val entry3 = HomeSet(id=3, serviceId=service.id, url=HttpUrl.get("https://example.com/3")) val oldItems = listOf( entry1, HomeSet(id=2, serviceId=service.id, url=HttpUrl.get("https://example.com/2")), entry3 ) homeSetDao.insert(oldItems) val newItems = mutableMapOf<HttpUrl, HomeSet>() newItems[entry1.url] = entry1 // no id, because identity is given by the url val updated = HomeSet(id=0, serviceId=service.id, url=HttpUrl.get("https://example.com/2"), displayName="Updated Entry") newItems[updated.url] = updated val created = HomeSet(id=4, serviceId=service.id, url=HttpUrl.get("https://example.com/4")) newItems[created.url] = created DaoTools(homeSetDao).syncAll(oldItems, newItems) { it.url } val afterSync = homeSetDao.getByService(service.id) assertEquals(afterSync.size, 3) assertFalse(afterSync.contains(entry3)) assertTrue(afterSync.contains(entry1)) assertTrue(afterSync.contains(updated)) assertTrue(afterSync.contains(created)) } } No newline at end of file
app/src/main/java/at/bitfire/davdroid/DavService.kt +21 −54 Original line number Diff line number Diff line Loading @@ -23,10 +23,8 @@ import at.bitfire.dav4jvm.UrlUtils import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.dav4jvm.property.* import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.model.AppDatabase import at.bitfire.davdroid.model.* import at.bitfire.davdroid.model.Collection import at.bitfire.davdroid.model.HomeSet import at.bitfire.davdroid.model.Service import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.DebugInfoActivity import at.bitfire.davdroid.ui.NotificationUtils Loading Loading @@ -146,14 +144,8 @@ class DavService: android.app.Service() { val service = db.serviceDao().get(serviceId) ?: throw IllegalArgumentException("Service not found") val account = Account(service.accountName, getString(R.string.account_type)) val homeSets = homeSetDao.getByService(serviceId) .map { it.url } .toMutableSet() val collections = mutableMapOf<HttpUrl, Collection>() collectionDao.getByService(serviceId).forEach { collections[it.url] = it } val homeSets = homeSetDao.getByService(serviceId).associateBy { it.url }.toMutableMap() val collections = collectionDao.getByService(serviceId).associateBy { it.url }.toMutableMap() /** * Checks if the given URL defines home sets and adds them to the home set list. Loading Loading @@ -199,11 +191,12 @@ class DavService: android.app.Service() { when (service.type) { Service.TYPE_CARDDAV -> try { dav.propfind(0, AddressbookHomeSet.NAME, GroupMembership.NAME) { response, _ -> dav.propfind(0, DisplayName.NAME, AddressbookHomeSet.NAME, GroupMembership.NAME) { response, _ -> response[AddressbookHomeSet::class.java]?.let { homeSet -> for (href in homeSet.hrefs) dav.location.resolve(href)?.let { homeSets += UrlUtils.withTrailingSlash(it) val foundUrl = UrlUtils.withTrailingSlash(it) homeSets[foundUrl] = HomeSet(0, service.id, foundUrl, response[DisplayName::class.java]?.displayName) } } Loading @@ -218,11 +211,12 @@ class DavService: android.app.Service() { } Service.TYPE_CALDAV -> { try { dav.propfind(0, CalendarHomeSet.NAME, CalendarProxyReadFor.NAME, CalendarProxyWriteFor.NAME, GroupMembership.NAME) { response, _ -> dav.propfind(0, DisplayName.NAME, CalendarHomeSet.NAME, CalendarProxyReadFor.NAME, CalendarProxyWriteFor.NAME, GroupMembership.NAME) { response, _ -> response[CalendarHomeSet::class.java]?.let { homeSet -> for (href in homeSet.hrefs) dav.location.resolve(href)?.let { homeSets += UrlUtils.withTrailingSlash(it) val foundUrl = UrlUtils.withTrailingSlash(it) homeSets[foundUrl] = HomeSet(0, service.id, foundUrl, response[DisplayName::class.java]?.displayName) } } Loading @@ -244,46 +238,18 @@ class DavService: android.app.Service() { @Transaction fun saveHomesets() { val oldHomesets = homeSetDao.getByService(serviceId) oldHomesets.forEach { oldHomeset -> val url = oldHomeset.url if (homeSets.contains(url)) // URL is the same in oldHomesets and newHomesets, remove from "homesets" (which will be added later) homeSets.remove(url) else // URL is not in newHomesets, delete from database homeSetDao.delete(oldHomeset) } // insert new homesets homeSetDao.insert(homeSets.map { url -> HomeSet(0, serviceId, url) }) DaoTools(homeSetDao).syncAll( homeSetDao.getByService(serviceId), homeSets ) { it.url } } @Transaction fun saveCollections() { val oldCollections = collectionDao.getByService(serviceId) oldCollections.forEach { oldCollection -> val url = oldCollection.url val matchingNewCollection = collections[url] if (matchingNewCollection != null) { // old URL exists in newCollections, update database if content has been changed matchingNewCollection.id = oldCollection.id matchingNewCollection.serviceId = oldCollection.serviceId if (matchingNewCollection != oldCollection) collectionDao.update(matchingNewCollection) // remove from "collections" (which will be added later) collections.remove(url) } else // URL is not in newCollections, delete from database collectionDao.delete(oldCollection) } // insert new collections collections.forEach { (_, collection) -> collection.serviceId = serviceId } collectionDao.insert(collections.values.toList()) DaoTools(collectionDao).syncAll( collectionDao.getByService(serviceId), collections ) { it.url } } fun saveResults() { Loading Loading @@ -320,15 +286,16 @@ class DavService: android.app.Service() { // now refresh collections (taken from home sets) val itHomeSets = homeSets.iterator() while (itHomeSets.hasNext()) { val homeSetUrl = itHomeSets.next() Logger.log.fine("Listing home set $homeSetUrl") val homeSet = itHomeSets.next() Logger.log.fine("Listing home set ${homeSet.key}") try { DavResource(httpClient, homeSetUrl).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, _ -> DavResource(httpClient, homeSet.key).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, _ -> if (!response.isSuccess()) return@propfind val info = Collection.fromDavResponse(response) ?: return@propfind info.serviceId = serviceId info.confirmed = true Logger.log.log(Level.FINE, "Found collection", info) Loading
app/src/main/java/at/bitfire/davdroid/model/AppDatabase.kt +9 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ import at.bitfire.davdroid.log.Logger Service::class, HomeSet::class, Collection::class ], version = 6) ], version = 7) @TypeConverters(Converters::class) abstract class AppDatabase: RoomDatabase() { Loading @@ -38,7 +38,8 @@ abstract class AppDatabase: RoomDatabase() { Migration2_3, Migration3_4, Migration4_5, Migration5_6 Migration5_6, Migration6_7 ) .fallbackToDestructiveMigration() // as a last fallback, recreate database instead of crashing .build() Loading Loading @@ -99,6 +100,12 @@ abstract class AppDatabase: RoomDatabase() { // migrations object Migration6_7: Migration(6, 7) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE homeset ADD COLUMN displayName TEXT DEFAULT NULL") } } object Migration5_6: Migration(5, 6) { override fun migrate(db: SupportSQLiteDatabase) { val sql = arrayOf( Loading
app/src/main/java/at/bitfire/davdroid/model/Collection.kt +2 −2 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ import okhttp3.HttpUrl ) data class Collection( @PrimaryKey(autoGenerate = true) var id: Long = 0, override var id: Long = 0, var serviceId: Long = 0, Loading Loading @@ -52,7 +52,7 @@ data class Collection( /** whether this collection has been selected for synchronization */ var sync: Boolean = false ) { ): IdEntity() { companion object { Loading
app/src/main/java/at/bitfire/davdroid/model/CollectionDao.kt +2 −11 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ import androidx.paging.DataSource import androidx.room.* @Dao interface CollectionDao { interface CollectionDao: SyncableDao<Collection> { @Query("SELECT * FROM collection WHERE id=:id") fun get(id: Long): Collection? Loading Loading @@ -37,13 +37,4 @@ interface CollectionDao { @Insert fun insert(collection: Collection) @Insert fun insert(collections: List<Collection>) @Update fun update(collection: Collection) @Delete fun delete(collection: Collection) }