
appleMain.kotbase.Database.apple.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of couchbase-lite Show documentation
Show all versions of couchbase-lite Show documentation
Couchbase Lite Community Edition for Kotlin Multiplatform
/*
* Copyright 2022-2023 Jeff Lockhart
*
* 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 kotbase
import cocoapods.CouchbaseLite.CBLDatabase
import cocoapods.CouchbaseLite.isClosed
import kotbase.internal.DelegatedClass
import kotbase.ext.asDispatchQueue
import kotbase.ext.wrapCBLError
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.datetime.Instant
import kotlinx.datetime.toKotlinInstant
import kotlinx.datetime.toNSDate
import platform.objc.objc_sync_enter
import platform.objc.objc_sync_exit
import kotlin.coroutines.CoroutineContext
public actual class Database
internal constructor(actual: CBLDatabase) : DelegatedClass(actual) {
@Throws(CouchbaseLiteException::class)
public actual constructor(name: String) : this(
wrapCBLError { error ->
require(name.isNotEmpty()) { "db name must not be empty" }
CBLDatabase(name, error)
}
)
@Throws(CouchbaseLiteException::class)
public actual constructor(name: String, config: DatabaseConfiguration) : this(
wrapCBLError { error ->
CBLDatabase(name, config.actual, error)
}
)
public actual companion object {
public actual val log: Log by lazy {
Log(CBLDatabase.log())
}
@Throws(CouchbaseLiteException::class)
public actual fun delete(name: String, directory: String?) {
// Java SDK throws not found error
if (!exists(name, directory ?: DatabaseConfiguration(null).directory)) {
throw CouchbaseLiteException(
"Database not found for delete",
CBLError.Domain.CBLITE,
CBLError.Code.NOT_FOUND
)
}
wrapCBLError { error ->
CBLDatabase.deleteDatabase(name, directory, error)
}
}
public actual fun exists(name: String, directory: String?): Boolean =
CBLDatabase.databaseExists(name, directory)
@Throws(CouchbaseLiteException::class)
public actual fun copy(path: String, name: String, config: DatabaseConfiguration?) {
wrapCBLError { error ->
CBLDatabase.copyFromPath(path, name, config?.actual, error)
}
}
}
public actual val name: String
get() = actual.name
public actual val path: String?
get() = actual.path
public actual val count: Long
get() = actual.count.toLong()
public actual val config: DatabaseConfiguration
get() = DatabaseConfiguration(actual.config)
public actual fun getDocument(id: String): Document? {
return mustBeOpen {
actual.documentWithID(id)?.asDocument()
}
}
@Throws(CouchbaseLiteException::class)
public actual fun save(document: MutableDocument) {
mustBeOpen {
wrapCBLError { error ->
actual.saveDocument(document.actual, error)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun save(
document: MutableDocument,
concurrencyControl: ConcurrencyControl
): Boolean {
return try {
mustBeOpen {
wrapCBLError { error ->
actual.saveDocument(document.actual, concurrencyControl.actual, error)
}
}
} catch (e: CouchbaseLiteException) {
if (e.code == CBLError.Code.CONFLICT && e.domain == CBLError.Domain.CBLITE) {
// Java SDK doesn't throw exception on conflict, only returns false
false
} else {
throw e
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun save(document: MutableDocument, conflictHandler: ConflictHandler): Boolean {
return mustBeOpen {
wrapCBLError { error ->
try {
actual.saveDocument(document.actual, conflictHandler.convert(), error)
} catch (e: Exception) {
throw CouchbaseLiteException(
"Conflict handler threw an exception",
e,
CBLError.Domain.CBLITE,
CBLError.Code.CONFLICT
)
}
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun delete(document: Document) {
mustBeOpen {
wrapCBLError { error ->
actual.deleteDocument(document.actual, error)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun delete(document: Document, concurrencyControl: ConcurrencyControl): Boolean {
return try {
mustBeOpen {
wrapCBLError { error ->
actual.deleteDocument(document.actual, concurrencyControl.actual, error)
}
}
} catch (e: CouchbaseLiteException) {
if (e.code == CBLError.Code.CONFLICT && e.domain == CBLError.Domain.CBLITE) {
// Java SDK doesn't throw exception on conflict, only returns false
false
} else {
throw e
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun purge(document: Document) {
try {
mustBeOpen {
wrapCBLError { error ->
actual.purgeDocument(document.actual, error)
}
}
} catch (e: CouchbaseLiteException) {
// Java SDK ignores not found error, except for new document
val isNew = document.revisionID == null
if (isNew || e.code != CBLError.Code.NOT_FOUND || e.domain != CBLError.Domain.CBLITE) {
throw e
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun purge(id: String) {
mustBeOpen {
wrapCBLError { error ->
actual.purgeDocumentWithID(id, error)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun setDocumentExpiration(id: String, expiration: Instant?) {
mustBeOpen {
wrapCBLError { error ->
actual.setDocumentExpirationWithID(id, expiration?.toNSDate(), error)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun getDocumentExpiration(id: String): Instant? {
return mustBeOpen {
actual.getDocumentExpirationWithID(id)?.toKotlinInstant()
}
}
@Throws(CouchbaseLiteException::class)
public actual fun inBatch(work: Database.() -> R): R {
return mustBeOpen {
@Suppress("UNCHECKED_CAST")
wrapCBLError { error ->
var result: R? = null
actual.inBatch(error) {
result = [email protected]()
}
result
} as R
}
}
public actual fun addChangeListener(listener: DatabaseChangeListener): ListenerToken {
return DelegatedListenerToken(
mustBeOpen {
actual.addChangeListener(listener.convert())
}
)
}
@OptIn(ExperimentalStdlibApi::class)
public actual fun addChangeListener(context: CoroutineContext, listener: DatabaseChangeSuspendListener): ListenerToken {
return mustBeOpen {
val scope = CoroutineScope(SupervisorJob() + context)
val token = actual.addChangeListenerWithQueue(
context[CoroutineDispatcher]?.asDispatchQueue(),
listener.convert(scope)
)
SuspendListenerToken(scope, DelegatedListenerToken(token))
}
}
@OptIn(ExperimentalStdlibApi::class)
public actual fun addChangeListener(scope: CoroutineScope, listener: DatabaseChangeSuspendListener) {
mustBeOpen {
val token = actual.addChangeListenerWithQueue(
scope.coroutineContext[CoroutineDispatcher]?.asDispatchQueue(),
listener.convert(scope)
)
scope.coroutineContext[Job]?.invokeOnCompletion {
actual.removeChangeListenerWithToken(token)
}
}
}
public actual fun removeChangeListener(token: ListenerToken) {
if (token is SuspendListenerToken) {
actual.removeChangeListenerWithToken(token.token.actual)
token.scope.cancel()
} else {
token as DelegatedListenerToken
actual.removeChangeListenerWithToken(token.actual)
}
}
public actual fun addDocumentChangeListener(id: String, listener: DocumentChangeListener): ListenerToken {
return DelegatedListenerToken(
mustBeOpen {
actual.addDocumentChangeListenerWithID(id, listener.convert())
}
)
}
@OptIn(ExperimentalStdlibApi::class)
public actual fun addDocumentChangeListener(
id: String,
context: CoroutineContext,
listener: DocumentChangeSuspendListener
): ListenerToken {
return mustBeOpen {
val scope = CoroutineScope(SupervisorJob() + context)
val token = actual.addDocumentChangeListenerWithID(
id,
context[CoroutineDispatcher]?.asDispatchQueue(),
listener.convert(scope)
)
SuspendListenerToken(scope, DelegatedListenerToken(token))
}
}
@OptIn(ExperimentalStdlibApi::class)
public actual fun addDocumentChangeListener(
id: String,
scope: CoroutineScope,
listener: DocumentChangeSuspendListener
) {
mustBeOpen {
val token = actual.addDocumentChangeListenerWithID(
id,
scope.coroutineContext[CoroutineDispatcher]?.asDispatchQueue(),
listener.convert(scope)
)
scope.coroutineContext[Job]?.invokeOnCompletion {
actual.removeChangeListenerWithToken(token)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun close() {
wrapCBLError { error ->
actual.close(error)
}
}
@Throws(CouchbaseLiteException::class)
public actual fun delete() {
mustBeOpen {
wrapCBLError { error ->
actual.delete(error)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun createQuery(query: String): Query {
val actualQuery = mustBeOpen {
wrapCBLError { error ->
actual.createQuery(query, error)
}
}
return DelegatedQuery(actualQuery!!)
}
@Suppress("UNCHECKED_CAST")
@Throws(CouchbaseLiteException::class)
public actual fun getIndexes(): List {
return mustBeOpen {
actual.indexes as List
}
}
@Throws(CouchbaseLiteException::class)
public actual fun createIndex(name: String, index: Index) {
mustBeOpen {
wrapCBLError { error ->
actual.createIndex(index.actual, name, error)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun createIndex(name: String, config: IndexConfiguration) {
mustBeOpen {
wrapCBLError { error ->
actual.createIndexWithConfig(config.actual, name, error)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun deleteIndex(name: String) {
mustBeOpen {
wrapCBLError { error ->
actual.deleteIndexForName(name, error)
}
}
}
@Throws(CouchbaseLiteException::class)
public actual fun performMaintenance(type: MaintenanceType): Boolean {
return mustBeOpen {
wrapCBLError { error ->
actual.performMaintenance(type.actual, error)
}
}
}
internal fun mustBeOpen() {
mustBeOpen { }
}
internal inline fun withLock(action: () -> R): R {
// TODO: uses _mutex instead of self as lock in 3.1, use - (id) mutex
objc_sync_enter(actual)
return try {
action()
} finally {
objc_sync_exit(actual)
}
}
private fun mustBeOpen(action: () -> R): R {
return withLock {
if (actual.isClosed()) {
throw IllegalStateException("Attempt to perform an operation on a closed database.")
}
action()
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy