Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make room entity properties immutable #1218

Merged
merged 9 commits into from
Jan 10, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ class DavHomeSetRepositoryTest {
val entry1 = HomeSet(id=0, serviceId=serviceId, personal=true, url="https://example.com/1".toHttpUrl())
val insertId1 = repository.insertOrUpdateByUrl(entry1)
assertEquals(1L, insertId1)
assertEquals(entry1.apply { id = 1L }, repository.getById(1L))
assertEquals(entry1.copy(id = 1L), repository.getById(1L))

val updatedEntry1 = HomeSet(id=0, serviceId=serviceId, personal=true, url="https://example.com/1".toHttpUrl(), displayName="Updated Entry")
val updateId1 = repository.insertOrUpdateByUrl(updatedEntry1)
assertEquals(1L, updateId1)
assertEquals(updatedEntry1.apply { id = 1L }, repository.getById(1L))
assertEquals(updatedEntry1.copy(id = 1L), repository.getById(1L))

val entry2 = HomeSet(id=0, serviceId=serviceId, personal=true, url= "https://example.com/2".toHttpUrl())
val insertId2 = repository.insertOrUpdateByUrl(entry2)
assertEquals(2L, insertId2)
assertEquals(entry2.apply { id = 2L }, repository.getById(2L))
assertEquals(entry2.copy(id = 2L), repository.getById(2L))
}

@Test
Expand Down
44 changes: 22 additions & 22 deletions app/src/main/kotlin/at/bitfire/davdroid/db/Collection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,24 @@ annotation class CollectionType
)
data class Collection(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
val id: Long = 0,

/**
* Service, which this collection belongs to. Services are unique, so a [Collection] is uniquely
* identifiable via its [serviceId] and [url].
*/
var serviceId: Long = 0,
val serviceId: Long = 0,

/**
* A home set this collection belongs to. Multiple homesets are not supported.
* If *null* the collection is considered homeless.
*/
var homeSetId: Long? = null,
val homeSetId: Long? = null,

/**
* Principal who is owner of this collection.
*/
var ownerId: Long? = null,
val ownerId: Long? = null,

/**
* Type of service. CalDAV or CardDAV
Expand All @@ -82,68 +82,68 @@ data class Collection(
/**
* Address where this collection lives - with trailing slash
*/
var url: HttpUrl,
val url: HttpUrl,

/**
* Whether we have the permission to change contents of the collection on the server.
* Even if this flag is set, there may still be other reasons why a collection is effectively read-only.
*/
var privWriteContent: Boolean = true,
val privWriteContent: Boolean = true,
/**
* Whether we have the permission to delete the collection on the server
*/
var privUnbind: Boolean = true,
val privUnbind: Boolean = true,
/**
* Whether the user has manually set the "force read-only" flag.
* Even if this flag is not set, there may still be other reasons why a collection is effectively read-only.
*/
var forceReadOnly: Boolean = false,
val forceReadOnly: Boolean = false,

/**
* Human-readable name of the collection
*/
var displayName: String? = null,
val displayName: String? = null,
/**
* Human-readable description of the collection
*/
var description: String? = null,
val description: String? = null,

// CalDAV only
var color: Int? = null,
val color: Int? = null,

/** default timezone (only timezone ID, like `Europe/Vienna`) */
var timezoneId: String? = null,
val timezoneId: String? = null,

/** whether the collection supports VEVENT; in case of calendars: null means true */
var supportsVEVENT: Boolean? = null,
val supportsVEVENT: Boolean? = null,

/** whether the collection supports VTODO; in case of calendars: null means true */
var supportsVTODO: Boolean? = null,
val supportsVTODO: Boolean? = null,

/** whether the collection supports VJOURNAL; in case of calendars: null means true */
var supportsVJOURNAL: Boolean? = null,
val supportsVJOURNAL: Boolean? = null,

/** Webcal subscription source URL */
var source: HttpUrl? = null,
val source: HttpUrl? = null,

/** whether this collection has been selected for synchronization */
var sync: Boolean = false,
val sync: Boolean = false,

/** WebDAV-Push topic */
var pushTopic: String? = null,
val pushTopic: String? = null,

/** WebDAV-Push: whether this collection supports the Web Push Transport */
@ColumnInfo(defaultValue = "0")
var supportsWebPush: Boolean = false,
val supportsWebPush: Boolean = false,

/** WebDAV-Push subscription URL */
var pushSubscription: String? = null,
val pushSubscription: String? = null,

/** when the [pushSubscription] expires (timestamp, used to determine whether we need to re-subscribe) */
var pushSubscriptionExpires: Long? = null,
val pushSubscriptionExpires: Long? = null,

/** when the [pushSubscription] was created/updated (timestamp) */
var pushSubscriptionCreated: Long? = null
val pushSubscriptionCreated: Long? = null

) {

Expand Down
12 changes: 6 additions & 6 deletions app/src/main/kotlin/at/bitfire/davdroid/db/HomeSet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ import okhttp3.HttpUrl
)
data class HomeSet(
@PrimaryKey(autoGenerate = true)
var id: Long,
val id: Long,

var serviceId: Long,
val serviceId: Long,

/**
* Whether this homeset belongs to the [Service.principal] given by [serviceId].
*/
var personal: Boolean,
val personal: Boolean,

var url: HttpUrl,
val url: HttpUrl,

var privBind: Boolean = true,
val privBind: Boolean = true,

var displayName: String? = null
val displayName: String? = null
) {

fun title() = displayName ?: url.lastSegment
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/kotlin/at/bitfire/davdroid/db/Principal.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ import okhttp3.HttpUrl
)
data class Principal(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
var serviceId: Long,
val id: Long = 0,
val serviceId: Long,
/** URL of the principal, always without trailing slash */
var url: HttpUrl,
var displayName: String? = null
val url: HttpUrl,
val displayName: String? = null
) {

companion object {
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/kotlin/at/bitfire/davdroid/db/Service.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ annotation class ServiceType
])
data class Service(
@PrimaryKey(autoGenerate = true)
var id: Long,
val id: Long,

var accountName: String,
val accountName: String,

@ServiceType
var type: String,
val type: String,

var principal: HttpUrl?
val principal: HttpUrl?
) {

companion object {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/kotlin/at/bitfire/davdroid/db/SyncStats.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ data class SyncStats(
val collectionId: Long,
val authority: String,

var lastSync: Long
val lastSync: Long
)
28 changes: 14 additions & 14 deletions app/src/main/kotlin/at/bitfire/davdroid/db/WebDavDocument.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,30 @@ import java.time.Instant
data class WebDavDocument(

@PrimaryKey(autoGenerate = true)
var id: Long = 0,
val id: Long = 0,

/** refers to the [WebDavMount] the document belongs to */
val mountId: Long,

/** refers to parent document (*null* when this document is a root document) */
var parentId: Long?,
val parentId: Long?,

/** file name (without any slashes) */
var name: String,
var isDirectory: Boolean = false,
val name: String,
val isDirectory: Boolean = false,

var displayName: String? = null,
var mimeType: MediaType? = null,
var eTag: String? = null,
var lastModified: Long? = null,
var size: Long? = null,
val displayName: String? = null,
val mimeType: MediaType? = null,
val eTag: String? = null,
val lastModified: Long? = null,
val size: Long? = null,

var mayBind: Boolean? = null,
var mayUnbind: Boolean? = null,
var mayWriteContent: Boolean? = null,
val mayBind: Boolean? = null,
val mayUnbind: Boolean? = null,
val mayWriteContent: Boolean? = null,

var quotaAvailable: Long? = null,
var quotaUsed: Long? = null
val quotaAvailable: Long? = null,
val quotaUsed: Long? = null

) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ interface WebDavDocumentDao {
displayName = mount.name
)
val id = insertOrReplace(newDoc)
newDoc.id = id
return newDoc
return newDoc.copy(id = id)
}

}
6 changes: 3 additions & 3 deletions app/src/main/kotlin/at/bitfire/davdroid/db/WebDavMount.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import okhttp3.HttpUrl
@Entity(tableName = "webdav_mount")
data class WebDavMount(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
val id: Long = 0,

/** display name of the WebDAV mount */
var name: String,
val name: String,

/** URL of the WebDAV service, including trailing slash */
var url: HttpUrl
val url: HttpUrl

// credentials are stored using CredentialsStore

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ class AccountRepository @Inject constructor(

// insert collections
for (collection in info.collections.values) {
collection.serviceId = serviceId
collectionRepository.insertOrUpdateByUrl(collection)
collectionRepository.insertOrUpdateByUrl(collection.copy(serviceId = serviceId))
}

return serviceId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ import javax.inject.Inject
*/
class DavCollectionRepository @Inject constructor(
private val accountSettingsFactory: AccountSettings.Factory,
@ApplicationContext val context: Context,
db: AppDatabase,
@ApplicationContext private val context: Context,
private val db: AppDatabase,
defaultListeners: Lazy<Set<@JvmSuppressWildcards OnChangeListener>>,
private val serviceRepository: DavServiceRepository
) {
Expand All @@ -68,8 +68,7 @@ class DavCollectionRepository @Inject constructor(
/**
* Whether there are any collections that are registered for push.
*/
suspend fun anyPushCapable() =
dao.anyPushCapable()
suspend fun anyPushCapable() = dao.anyPushCapable()

/**
* Creates address book collection on server and locally
Expand Down Expand Up @@ -217,20 +216,28 @@ class DavCollectionRepository @Inject constructor(
dao.getPushRegisteredAndNotSyncable()

/**
* Inserts or updates the collection. On update it will not update flag values ([Collection.sync],
* [Collection.forceReadOnly]), but use the values of the already existing collection.
* Inserts or updates the collection.
*
* On update, it will _not_ update the flags
* - [Collection.sync] and
* - [Collection.forceReadOnly],
* but use the values of the already existing collection.
*
* @param newCollection Collection to be inserted or updated
*/
fun insertOrUpdateByUrlAndRememberFlags(newCollection: Collection) {
// remember locally set flags
dao.getByServiceAndUrl(newCollection.serviceId, newCollection.url.toString())?.let { oldCollection ->
newCollection.sync = oldCollection.sync
newCollection.forceReadOnly = oldCollection.forceReadOnly
db.runInTransaction {
// remember locally set flags
val oldCollection = dao.getByServiceAndUrl(newCollection.serviceId, newCollection.url.toString())
val newCollectionWithFlags =
if (oldCollection != null)
newCollection.copy(sync = oldCollection.sync, forceReadOnly = oldCollection.forceReadOnly)
else
newCollection

// commit new collection to database
insertOrUpdateByUrl(newCollectionWithFlags)
}

// commit to database
insertOrUpdateByUrl(newCollection)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,28 @@ class LocalCalendarStore @Inject constructor(
val account = Account(service.accountName, context.getString(R.string.account_type))

// If the collection doesn't have a color, use a default color.
if (fromCollection.color != null)
fromCollection.color = Constants.DAVDROID_GREEN_RGBA

val values = valuesFromCollectionInfo(fromCollection, withColor = true)

// 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)

// Email address for scheduling. Used by the calendar provider to determine whether the
// user is ORGANIZER/ATTENDEE for a certain event.
values.put(Calendars.OWNER_ACCOUNT, account.name)

// flag as visible & syncable at creation, might be changed by user at any time
values.put(Calendars.VISIBLE, 1)
values.put(Calendars.SYNC_EVENTS, 1)
val collectionWithColor =
if (fromCollection.color != null)
fromCollection
else
fromCollection.copy(color = Constants.DAVDROID_GREEN_RGBA)

val values = valuesFromCollectionInfo(
info = collectionWithColor,
withColor = true
).apply {
// ACCOUNT_NAME and ACCOUNT_TYPE are required (see docs)! If it's missing, other apps will crash.
put(Calendars.ACCOUNT_NAME, account.name)
put(Calendars.ACCOUNT_TYPE, account.type)

// Email address for scheduling. Used by the calendar provider to determine whether the
// user is ORGANIZER/ATTENDEE for a certain event.
put(Calendars.OWNER_ACCOUNT, account.name)

// flag as visible & syncable at creation, might be changed by user at any time
put(Calendars.VISIBLE, 1)
put(Calendars.SYNC_EVENTS, 1)
}

logger.log(Level.INFO, "Adding local calendar", values)
val uri = AndroidCalendar.create(account, provider, values)
Expand Down
Loading
Loading