blob: 16c750d1c337e58b9c740aa7c880e93fd36964be [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import Foundation
/// DatabaseHandle is the set of methods that work both with and without a batch.
/// It allows clients to pass the handle to helper methods that are batch-agnostic.
public protocol DatabaseHandle {
/// Id returns the id of this DatabaseHandle..
var databaseId: Identifier { get }
/// Collection returns the Collection with the given relative name.
/// The user blessing is derived from the context.
/// Throws if the name is invalid, or the blessings are invalid.
func collection(name: String) throws -> Collection
/// CollectionForId returns the Collection with the given user blessing and name.
/// Throws if the id cannot be encoded into UTF-8.
func collection(collectionId: Identifier) throws -> Collection
/// ListCollections returns a list of all Collection ids that the caller is
/// allowed to see. The list is sorted by blessing, then by name.
func listCollections() throws -> [Identifier]
/// Returns a ResumeMarker that points to the current end of the event log.
func getResumeMarker() throws -> ResumeMarker
}
public class SyncgroupInvitesScanHandler {
public let onInvite: SyncgroupInvite -> Void
var isStopped: Bool = false
let isStoppedMu: NSLock = NSLock()
public init(onInvite: SyncgroupInvite -> Void) {
self.onInvite = onInvite
}
}
public class Database {
public let databaseId: Identifier
let batchHandle: String?
let encodedDatabaseName: String
init(databaseId: Identifier, batchHandle: String?) throws {
self.databaseId = databaseId
self.batchHandle = batchHandle
self.encodedDatabaseName = try databaseId.encodeId().toString()!
}
/// Create creates this Database.
/// TODO(sadovsky): Specify what happens if perms is nil.
public func create(permissions: Permissions?) throws {
try VError.maybeThrow { errPtr in
v23_syncbase_DbCreate(
try encodedDatabaseName.toCgoString(),
try v23_syncbase_Permissions(permissions),
errPtr)
}
}
/// Destroy destroys this Database, permanently removing all of its data.
/// TODO(sadovsky): Specify what happens to syncgroups.
public func destroy() throws {
try VError.maybeThrow { errPtr in
v23_syncbase_DbDestroy(try encodedDatabaseName.toCgoString(), errPtr)
}
}
// Exists returns true only if this Database exists. Insufficient permissions
// cause Exists to return false instead of an error.
public func exists() throws -> Bool {
return try VError.maybeThrow { errPtr in
var exists = v23_syncbase_Bool(false)
v23_syncbase_DbExists(try encodedDatabaseName.toCgoString(), &exists, errPtr)
return exists.toBool()
}
}
/**
BeginBatch creates a new batch. Instead of calling this function directly,
clients are encouraged to use the RunInBatch() helper function, which
detects "concurrent batch" errors and handles retries internally.
Default concurrency semantics:
- Reads (e.g. gets, scans) inside a batch operate over a consistent
snapshot taken during BeginBatch(), and will see the effects of prior
writes performed inside the batch.
- Commit() may fail with ErrConcurrentBatch, indicating that after
BeginBatch() but before Commit(), some concurrent routine wrote to a key
that matches a key or row-range read inside this batch.
- Other methods will never fail with error ErrConcurrentBatch, even if it
is known that Commit() will fail with this error.
Once a batch has been committed or aborted, subsequent method calls will
fail with no effect.
Concurrency semantics can be configured using BatchOptions.
TODO(sadovsky): Use varargs for options.
*/
public func beginBatch(options: BatchOptions?) throws -> BatchDatabase {
var cHandle = v23_syncbase_String()
try VError.maybeThrow { errPtr in
v23_syncbase_DbBeginBatch(
try encodedDatabaseName.toCgoString(),
try v23_syncbase_BatchOptions(options),
&cHandle,
errPtr)
}
guard let handle = cHandle.toString() else {
throw SyncbaseError.InvalidUTF8(invalidUtf8: "\(cHandle)")
}
return try BatchDatabase(databaseId: databaseId, batchHandle: handle)
}
/// Watch allows a client to watch for updates to the database. At least one
/// pattern must be specified. For each watch request, the client will receive
/// a reliable stream of watch events without reordering. Only rows matching at
/// least one of the patterns are returned. Rows in collections with no Read
/// access are also filtered out.
///
/// If a nil ResumeMarker is provided, the WatchStream will begin with a Change
/// batch containing the initial state. Otherwise, the WatchStream will contain
/// only changes since the provided ResumeMarker.
///
/// The response stream consists of a sequence of Change messages. Each
/// Change message contains an optional continued bit
/// (default=false). A sub-sequence of Change messages with
/// continued=true followed by a Change message with continued=false
/// forms an "atomic group". We expect that most callers will ignore the
/// notion of atomic delivery and the continued bit, i.e., they will just process
/// each Change message as it is received.
public func watch(patterns: [CollectionRowPattern], resumeMarker: ResumeMarker? = nil) throws -> WatchStream {
return try Watch.watch(encodedDatabaseName: encodedDatabaseName, patterns: patterns, resumeMarker: resumeMarker)
}
/// Syncgroup returns a handle to the syncgroup with the given name.
public func syncgroup(name: String) throws -> Syncgroup {
return Syncgroup(
encodedDatabaseName: encodedDatabaseName,
syncgroupId: Identifier(name: name, blessing: try Principal.userBlessing()))
}
/// Syncgroup returns a handle to the syncgroup with the given identifier.
public func syncgroup(syncgroupId: Identifier) -> Syncgroup {
return Syncgroup(encodedDatabaseName: encodedDatabaseName, syncgroupId: syncgroupId)
}
/// ListSyncgroups returns the identifiers of all syncgroups attached to this database.
public func listSyncgroups() throws -> [Identifier] {
var ids = v23_syncbase_Ids()
try VError.maybeThrow { errPtr in
v23_syncbase_DbListSyncgroups(
try encodedDatabaseName.toCgoString(),
&ids,
errPtr)
}
return ids.toIdentifiers()
}
public func scanForSyncgroupInvites(name: String, handler: SyncgroupInvitesScanHandler) throws {
let unmanaged = Unmanaged.passRetained(handler)
let oHandle = UnsafeMutablePointer<Void>(unmanaged.toOpaque())
do {
try VError.maybeThrow { errPtr in
v23_syncbase_DbSyncgroupInvitesNewScan(
try encodedDatabaseName.toCgoString(),
v23_syncbase_DbSyncgroupInvitesCallbacks(
handle: v23_syncbase_Handle(unsafeBitCast(oHandle, UInt.self)),
onInvite: { Database.onInvite($0, invite: $1) }
),
errPtr)
}
} catch let e {
unmanaged.release()
throw e
}
}
// Callback handlers that convert the Cgo bridge types to native Swift types and pass them to
// the functions inside the passed handle.
private static func onInvite(handle: v23_syncbase_Handle, invite: v23_syncbase_Invite) {
let invite = invite.toSyncgroupInvite()!
let handle = Unmanaged<SyncgroupInvitesScanHandler>.fromOpaque(
COpaquePointer(bitPattern: handle)).takeUnretainedValue()
dispatch_async(Syncbase.queue) {
handle.onInvite(invite)
}
}
public func stopSyncgroupInvitesScan(handler: SyncgroupInvitesScanHandler) {
handler.isStoppedMu.lock()
defer { handler.isStoppedMu.unlock() }
if handler.isStopped {
// Prevent double-free.
return
}
let unmanaged = Unmanaged.passRetained(handler)
let oHandle = UnsafeMutablePointer<Void>(unmanaged.toOpaque())
v23_syncbase_DbSyncgroupInvitesStopScan(unsafeBitCast(oHandle, UInt.self))
unmanaged.release()
handler.isStopped = true
}
/// CreateBlob creates a new blob and returns a handle to it.
public func createBlob() throws -> Blob {
preconditionFailure("stub")
}
/// Blob returns a handle to the blob with the given BlobRef.
public func blob(blobRef: BlobRef) -> Blob {
preconditionFailure("stub")
}
/// PauseSync pauses sync for this database. Incoming sync, as well as outgoing
/// sync of subsequent writes, will be disabled until ResumeSync is called.
/// PauseSync is idempotent.
public func pauseSync() throws {
preconditionFailure("stub")
}
/// ResumeSync resumes sync for this database. ResumeSync is idempotent.
public func resumeSync() throws {
preconditionFailure("stub")
}
/// Close cleans up any state associated with this database handle, including
/// closing the conflict resolution stream (if open).
public func close() {
preconditionFailure("stub")
}
}
extension Database: DatabaseHandle {
public func collection(name: String) throws -> Collection {
return try collection(Identifier(name: name, blessing: try Principal.userBlessing()))
}
public func collection(collectionId: Identifier) throws -> Collection {
return try Collection(
databaseId: databaseId,
collectionId: collectionId,
batchHandle: batchHandle)
}
public func listCollections() throws -> [Identifier] {
return try VError.maybeThrow { errPtr in
var ids = v23_syncbase_Ids()
v23_syncbase_DbListCollections(
try encodedDatabaseName.toCgoString(),
try batchHandle?.toCgoString() ?? v23_syncbase_String(),
&ids,
errPtr)
return ids.toIdentifiers()
}
}
public func getResumeMarker() throws -> ResumeMarker {
var cMarker = v23_syncbase_Bytes()
try VError.maybeThrow { errPtr in
v23_syncbase_DbGetResumeMarker(
try encodedDatabaseName.toCgoString(),
try batchHandle?.toCgoString() ?? v23_syncbase_String(),
&cMarker,
errPtr)
}
return ResumeMarker(data: cMarker.toNSData() ?? NSData())
}
}
extension Database: AccessController {
public func getPermissions() throws -> (Permissions, PermissionsVersion) {
var cPermissions = v23_syncbase_Permissions()
var cVersion = v23_syncbase_String()
try VError.maybeThrow { errPtr in
v23_syncbase_DbGetPermissions(
try encodedDatabaseName.toCgoString(),
&cPermissions,
&cVersion,
errPtr)
}
// TODO(zinman): Verify that permissions defaulting to zero-value is correct for Permissions.
// We force cast of cVersion because we know it can be UTF-8 converted.
return (try cPermissions.toPermissions() ?? Permissions(), cVersion.toString()!)
}
public func setPermissions(permissions: Permissions, version: PermissionsVersion) throws {
try VError.maybeThrow { errPtr in
v23_syncbase_DbSetPermissions(
try encodedDatabaseName.toCgoString(),
try v23_syncbase_Permissions(permissions),
try version.toCgoString(),
errPtr)
}
}
}