commonMain.io.realm.kotlin.Configuration.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of library-base-jvm Show documentation
Show all versions of library-base-jvm Show documentation
Library code for Realm Kotlin. This artifact is not supposed to be consumed directly, but through 'io.realm.kotlin:gradle-plugin:1.11.1' instead.
/*
* Copyright 2020 Realm Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.realm.kotlin
import io.realm.kotlin.Configuration.SharedBuilder
import io.realm.kotlin.internal.MISSING_PLUGIN_MESSAGE
import io.realm.kotlin.internal.REALM_FILE_EXTENSION
import io.realm.kotlin.internal.platform.PATH_SEPARATOR
import io.realm.kotlin.internal.realmObjectCompanionOrNull
import io.realm.kotlin.types.BaseRealmObject
import kotlinx.coroutines.CoroutineDispatcher
import kotlin.reflect.KClass
/**
* This interface is used to determine if a Realm file should be compacted the first time the file
* is opened and before the instance is returned.
*
* Note that compacting a file can take a while, so compacting should generally only be done as
* part of opening a Realm on a background thread.
*/
public fun interface CompactOnLaunchCallback {
/**
* This method determines if the Realm file should be compacted before opened and returned to
* the user.
*
* @param totalBytes the total file size (data + free space).
* @param usedBytes the total bytes used by data in the file.
* @return `true` to indicate an attempt to compact the file should be made. Otherwise,
* compaction will be skipped.
*/
public fun shouldCompact(totalBytes: Long, usedBytes: Long): Boolean
}
/**
* This interface is used to write data to a Realm file when the file is first created.
* It will be used in a way similar to using [Realm.writeBlocking].
*
* Note that writing data to a Realm file will involve IO, so it should generally only be done as
* part of opening a Realm on a background thread.
*/
public fun interface InitialDataCallback {
/**
* Creates a write transaction in which the initial data can be written with
* [MutableRealm] as a receiver. This mirrors the API when using [Realm.write]
* and allows for the following pattern:
*
* ```
* val config = RealmConfiguration.Builder()
* .initialData { // this: MutableRealm
* copyToRealm(Person("Jane Doe"))
* }
* .build()
* val realm = Realm.open(config)
* ```
*/
public fun MutableRealm.write()
}
/**
* Configuration for pre-bundled asset files used as initial state of the realm file.
*/
public data class InitialRealmFileConfiguration(
/**
* Path to the realm file. This will be interpreted differently depending on the platform. See [SharedBuilder.initialRealmFile] for details.
*/
val assetFile: String,
/**
* Asset file SHA256-checksum used to verify the integrity of the asset file. See
* [SharedBuilder.initialRealmFile] for details.
*/
val checksum: String?
)
/**
* Base configuration options shared between all realm configuration types.
*/
public interface Configuration {
/**
* Path to the realm file.
*/
public val path: String
/**
* Filename of the realm file.
*/
public val name: String
/**
* The set of classes included in the schema for the realm.
*/
public val schema: Set>
/**
* Maximum number of active versions.
*
* Holding references to objects from previous version of the data in the realm will also
* require keeping the data in the actual file. This can cause growth of the file. See
* [SharedBuilder.maxNumberOfActiveVersions] for details.
*/
public val maxNumberOfActiveVersions: Long
/**
* The schema version.
*/
public val schemaVersion: Long
/**
* 64 byte key used to encrypt and decrypt the Realm file.
*
* @return null on unencrypted Realms.
*/
public val encryptionKey: ByteArray?
/**
* Callback that determines if the realm file should be compacted as part of opening it.
*
* @return `null` if the realm file should not be compacted when opened. Otherwise, the callback
* returned is the one that will be invoked in order to determine if the file should be
* compacted or not.
* @see [RealmConfiguration.Builder.compactOnLaunch]
*/
public val compactOnLaunchCallback: CompactOnLaunchCallback?
/**
* Callback that will be triggered in order to write initial data when the Realm file is
* created for the first time.
*
* The callback has a [MutableRealm]] as a receiver, which allows for the following pattern:
*
* ```
* val config = RealmConfiguration.Builder()
* .initialData { // this: MutableRealm
* copyToRealm(Person("Jane Doe"))
* }
* .build()
* val realm = Realm.open(config)
* ```
*
* @return `null` if no initial data should be written when opening a Realm file, otherwise
* the callback return is the one responsible for writing the data.
* @see [RealmConfiguration.Builder.initialDataCallback]
*/
public val initialDataCallback: InitialDataCallback?
/**
* Describes whether the realm should reside in memory or on disk.
*/
public val inMemory: Boolean
/**
* Configuration that holds details of a bundled asset file used as initial state of the realm
* file. See [SharedBuilder.initialRealmFile] for details. `null` is returned if no initial realm
* file has been configured.
*/
public val initialRealmFileConfiguration: InitialRealmFileConfiguration?
/**
* Base class for configuration builders that holds properties available to both
* [RealmConfiguration] and [SyncConfiguration].
*
* @param T the type of [Configuration] the builder should generate.
* @param S the type of builder, needed to distinguish between local and sync variants.
*/
// The property functions in this builder return the type of the builder itself, represented by
// [S]. This is due to `library-base` not having visibility over `library-sync` and therefore
// all function return types have to be typecast as [S].
@Suppress("UnnecessaryAbstractClass", "UNCHECKED_CAST") // Actual implementations should rewire build() to companion map variant
public abstract class SharedBuilder>(
protected var schema: Set> = setOf()
) {
init {
// Verify that the schema only contains subclasses of RealmObject and EmbeddedRealmObject
schema.forEach { clazz: KClass ->
if (clazz.realmObjectCompanionOrNull() == null) {
throw IllegalArgumentException(
"Only subclasses of RealmObject and " +
"EmbeddedRealmObject are allowed in the schema. Found: ${clazz.qualifiedName}. " +
"If ${clazz.qualifiedName} is a valid subclass: $MISSING_PLUGIN_MESSAGE"
)
}
}
}
// 'name' must be nullable as it is optional when getting SyncClient's default path!
protected abstract var name: String?
protected var maxNumberOfActiveVersions: Long = Long.MAX_VALUE
protected var notificationDispatcher: CoroutineDispatcher? = null
protected var writeDispatcher: CoroutineDispatcher? = null
protected var schemaVersion: Long = 0
protected var encryptionKey: ByteArray? = null
protected var compactOnLaunchCallback: CompactOnLaunchCallback? = null
protected var initialDataCallback: InitialDataCallback? = null
protected var inMemory: Boolean = false
protected var initialRealmFileConfiguration: InitialRealmFileConfiguration? = null
/**
* Sets the filename of the realm file.
*
* @throws IllegalArgumentException if the name includes a path separator or the name is
* `.realm`.
*/
public abstract fun name(name: String): S
/**
* Creates the RealmConfiguration based on the builder properties.
*
* @return the created RealmConfiguration.
*
* @throws IllegalStateException if trying to build a configuration with incompatible
* options.
*/
public abstract fun build(): T
/**
* Sets the maximum number of live versions in the Realm file before an
* [IllegalStateException] is thrown when attempting to write more data.
*
* Realm is capable of concurrently handling many different versions of Realm objects, this
* can e.g. happen if a flow is slow to process data from the database while a fast writer
* is putting data into the Realm.
*
* Under normal circumstances this is not a problem, but if the number of active versions
* grow too large, it will have a negative effect on the file size on disk. Setting this
* parameters can therefore be used to prevent uses of Realm that can result in very large
* file sizes.
*
* For details see the *Large Realm file size*-section of the
* [FAQ](https://docs.mongodb.com/realm-legacy/docs/java/latest/index.html#faq)
*
* @param number the maximum number of active versions before an exception is thrown.
*/
public fun maxNumberOfActiveVersions(maxVersions: Long = 8): S = apply {
if (maxVersions < 1) {
throw IllegalArgumentException("Only positive numbers above 0 are allowed. Yours was: $maxVersions")
}
this.maxNumberOfActiveVersions = maxVersions
} as S
/**
* Dispatcher on which Realm notifications are run. It is possible to listen for changes to
* Realm objects from any thread, but the underlying logic will run on this dispatcher
* before any changes are returned to the receiving context.
*
* Defaults to a single threaded dispatcher started when the configuration is built.
*
* NOTE On Android the dispatcher's thread must have an initialized
* [Looper](https://developer.android.com/reference/android/os/Looper#prepare()).
*
* @param dispatcher dispatcher on which notifications are run. It is required to be backed
* by a single thread only.
*/
internal fun notificationDispatcher(dispatcher: CoroutineDispatcher) = apply {
this.notificationDispatcher = dispatcher
} as S
/**
* Dispatcher used to run background writes to the Realm. *
*
* NOTE On Android the dispatcher's thread must have an initialized
* [Looper](https://developer.android.com/reference/android/os/Looper#prepare()).
*
* @param dispatcher dispatcher on which writes are run. It is required to be backed by a
* single thread only.
*/
internal fun writeDispatcher(dispatcher: CoroutineDispatcher) = apply {
this.writeDispatcher = dispatcher
} as S
/**
* Sets the schema version of the Realm. This must be equal to or higher than the schema
* version of the existing Realm file, if any. If the schema version is higher than the
* already existing Realm, a migration is needed.
*/
public fun schemaVersion(schemaVersion: Long): S {
if (schemaVersion < 0) {
throw IllegalArgumentException("Realm schema version numbers must be 0 (zero) or higher. Yours was: $schemaVersion")
}
return apply { this.schemaVersion = schemaVersion } as S
}
/**
* Sets the 64 byte key used to encrypt and decrypt the Realm file. If no key is provided
* the Realm file will be unencrypted.
*
* It is important that this key is created and stored securely. See
* [this link](https://docs.mongodb.com/realm/sdk/android/advanced-guides/encryption/) for
* suggestions on how to do that.
*
* @param encryptionKey 64-byte encryption key.
*/
public fun encryptionKey(encryptionKey: ByteArray): S =
apply { this.encryptionKey = validateEncryptionKey(encryptionKey) } as S
/**
* Sets a callback for controlling whether the realm should be compacted when opened.
*
* Due to the way Realm allocates space on disk, it is sometimes the case that more space
* is allocated than what is actually needed, making the realm file larger than what it
* needs to be. This mostly occurs when writing larger binary blobs to the file.
*
* The space will be used by subsequent writes, but in the interim period the file will
* be larger than what is strictly needed.
*
* This method makes it possible to define a function that determines whether or not
* the file should be compacted when the realm is opened, optimizing how much disk size
* is used.
*
* @param callback The callback called when opening the realm file. The return value
* determines whether or not the file should be compacted. If not user defined callback
* is defined, the default callback will be used. See
* [Realm.DEFAULT_COMPACT_ON_LAUNCH_CALLBACK] for more details.
*/
public fun compactOnLaunch(callback: CompactOnLaunchCallback = Realm.DEFAULT_COMPACT_ON_LAUNCH_CALLBACK): S =
apply { this.compactOnLaunchCallback = callback } as S
/**
* Writes initial data to the Realm file. This callback will be executed only once, when
* the database file is created. This also include cases where
* [RealmConfiguration.Builder.deleteRealmIfMigrationNeeded] was set causing the file to be
* deleted.
*
* The callback will happen on the same thread used when using [Realm.writeBlocking].
*
* @param callback callback used to write data to the Realm file.
*/
public fun initialData(callback: InitialDataCallback): S =
apply { initialDataCallback = callback } as S
/**
* Setting this will create an in-memory Realm instead of saving it to disk. In-memory Realms might still use
* disk space if memory is running low, but all files created by an in-memory Realm will be deleted when the
* Realm is closed.
*
* Note that because in-memory Realms are not persisted, you must be sure to hold on to at least one non-closed
* reference to the in-memory Realm instance as long as you want the data to last.
*/
public fun inMemory(): S =
apply { this.inMemory = true } as S
/**
* Initializes a realm file with a bundled asset realm file.
*
* When opening the realm for the first time the realm file is initialized from the given
* [assetFile]. This only happens if the realm files at [path] not already exists.
*
* The asset file is sought located on the platform's conventional locations for bundled
* assets/resources:
* - Android: Through android.content.res.AssetManager.open(assetFilename)
* - JVM: Class.javaClass.classLoader.getResource(assetFilename)
* - Darwin: NSBundle.mainBundle.pathForResource(assetFilenameBase, assetFilenameExtension)
* And it is the responsibility of the developer to place the files at the appropriate
* location.
*
* This cannot be combined with [inMemory] or
* [RealmConfiguration.Builder.deleteRealmIfMigrationNeeded]. Attempts to do so will cause
* [build] to throw an [IllegalStateException].
*
* NOTE: This could potentially be a lengthy operation, so opening a Realm with a predefined
* asset file should ideally be done on a background thread.
* NOTE: There is currently no protection against multiple processes trying to copy the
* asset file in place at the same time, so user must ensure that only one process is trying
* to trigger this at a time.
*
* @param assetFile the name of the assetFile in the platform's default asset/resource
* location. If the asset file cannot be located when opening the realm for the first time
* [Realm.open] will fail with an [IllegalArgumentException].
* @param sha256checkSum a SHA256-checksum used to verify the integrity of the asset file.
* If this is specified and the checksum does not match the computed checksum of the
* [assetFile] when the realm is opened the first time [Realm.open] will fail with a
* [IllegalArgumentException].
*
* @throws IllegalArgumentException if called with an empty [assetFile].
*/
public fun initialRealmFile(assetFile: String, sha256checkSum: String? = null): S {
require(assetFile.isNotEmpty()) {
"Asset file must be a non-empty filename."
}
require(sha256checkSum == null || sha256checkSum.isNotEmpty()) {
"Checksum must be null or a non-empty string."
}
this.initialRealmFileConfiguration = InitialRealmFileConfiguration(assetFile, sha256checkSum)
return this as S
}
protected fun validateEncryptionKey(encryptionKey: ByteArray): ByteArray {
if (encryptionKey.size != Realm.ENCRYPTION_KEY_LENGTH) {
throw IllegalArgumentException("The provided key must be ${Realm.ENCRYPTION_KEY_LENGTH} bytes. The provided key was ${encryptionKey.size} bytes.")
}
return encryptionKey
}
protected fun checkName(name: String) {
require(name.isNotEmpty()) {
"A non-empty filename must be provided."
}
require(!name.contains(PATH_SEPARATOR)) {
"Name cannot contain path separator '$PATH_SEPARATOR': '$name'"
}
require(name != REALM_FILE_EXTENSION) {
"'$REALM_FILE_EXTENSION' is not a valid filename"
}
}
protected open fun verifyConfig() {
initialRealmFileConfiguration?.let {
if (inMemory) {
throw IllegalStateException("Cannot combine `initialRealmFile` and `inMemory` configuration options")
}
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy