blob: d788c67e383c9a6e7340887d4b361fd9b03f9eef [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
import SyncbaseCore
/// Represents a set of collections, synced amongst a set of users.
/// To get a Syncgroup handle, call `Database.syncgroup`.
public class Syncgroup: CustomStringConvertible {
let database: Database
let coreSyncgroup: SyncbaseCore.Syncgroup
static var syncgroupMemberInfo: SyncgroupMemberInfo {
// TODO(zinman): Validate these are correct.
return SyncgroupMemberInfo(syncPriority: UInt8(3), blobDevType: BlobDevType.Leaf)
}
func createIfMissing(collections: [Collection]) throws {
let cxCoreIds = collections.map { $0.collectionId.toCore() }
let spec = SyncgroupSpec(
description: "",
collections: cxCoreIds,
permissions: try defaultSyncgroupPerms(),
publishSyncbaseName: Syncbase.publishSyncbaseName,
mountTables: Syncbase.mountPoints,
isPrivate: false)
do {
try SyncbaseError.wrap {
try self.coreSyncgroup.create(spec, myInfo: Syncgroup.syncgroupMemberInfo)
}
} catch SyncbaseError.Exist {
// Syncgroup already exists.
// TODO(sadovsky): Verify that the existing syncgroup has the specified configuration,
// e.g. the specified collections?
}
}
init(coreSyncgroup: SyncbaseCore.Syncgroup, database: Database) {
self.coreSyncgroup = coreSyncgroup
self.database = database
}
/// Returns the id of this syncgroup.
public var syncgroupId: Identifier {
return Identifier(coreId: coreSyncgroup.syncgroupId)
}
func join() throws {
// TODO(razvanm): Find a way to restrict the remote blessing.
try coreSyncgroup.join(
Syncbase.cloudName ?? "",
expectedSyncbaseBlessings: ["..."],
myInfo: Syncgroup.syncgroupMemberInfo)
}
/// Returns the `AccessList` for this syncgroup.
public func accessList() throws -> AccessList {
return try AccessList(perms: try coreSyncgroup.getSpec().spec.permissions)
}
/// **FOR ADVANCED USERS**. Adds the given users to the syncgroup, with the specified access level.
///
/// - parameter users: Users to add to the syncgroup.
/// - parameter level: Access level for the specified `users`.
/// - parameter syncgroupOnly: If false (the default), update the `AccessList` for the syncgroup
/// and its associated collections. If true, only update the `AccessList` for the syncgroup.
public func inviteUsers(users: [User], level: AccessList.AccessLevel, syncgroupOnly: Bool = false) throws {
var delta = AccessList()
for user in users {
delta.users[user.alias] = level
}
try updateAccessList(delta, syncgroupOnly: syncgroupOnly)
}
/// Adds the given user to the syncgroup, with the specified access level.
///
/// - parameter user: User to add to the syncgroup.
/// - parameter level: Access level for the specified `user`.
/// - parameter syncgroupOnly: If false (the default), update the `AccessList` for the syncgroup
/// and its associated collections. If true, only update the `AccessList` for the syncgroup.
public func inviteUser(user: User, level: AccessList.AccessLevel, syncgroupOnly: Bool = false) throws {
try inviteUsers([user], level: level, syncgroupOnly: syncgroupOnly)
}
/// **FOR ADVANCED USERS**. Removes the given users from the syncgroup.
///
/// - parameter users: Users to eject from the Syncgroup.
/// - parameter syncgroupOnly: If false (the default), update the `AccessList` for the syncgroup
/// and its associated collections. If true, only update the `AccessList` for the syncgroup.
public func ejectUsers(users: [User], syncgroupOnly: Bool = false) throws {
var delta = AccessList()
for user in users {
delta.users[user.alias] = AccessList.AccessLevel.INTERNAL_ONLY_REMOVE
}
try updateAccessList(delta, syncgroupOnly: syncgroupOnly)
}
/// Removes the given user from the syncgroup.
///
/// - parameter user: User to eject from the Syncgroup.
/// - parameter syncgroupOnly: If false (the default), update the `AccessList` for the syncgroup
/// and its associated collections. If true, only update the `AccessList` for the syncgroup.
public func ejectUser(user: User, syncgroupOnly: Bool = false) throws {
try ejectUsers([user], syncgroupOnly: syncgroupOnly)
}
/// **FOR ADVANCED USERS**. Applies `delta` to the `AccessList`.
///
/// - parameter delta: AccessList changes to the Syncgroup.
/// - parameter syncgroupOnly: If false (the default), update the `AccessList` for the syncgroup
/// and its associated collections. If true, only update the `AccessList` for the syncgroup.
public func updateAccessList(delta: AccessList, syncgroupOnly: Bool = false) throws {
try SyncbaseError.wrap {
// TODO(sadovsky): Make it so SyncgroupSpec can be updated as part of a batch?
let versionedSpec = try self.coreSyncgroup.getSpec()
let permissions = try AccessList.applyDeltaForSyncgroup(versionedSpec.spec.permissions, delta: delta)
let oldSpec = versionedSpec.spec
try self.coreSyncgroup.setSpec(VersionedSpec(
spec: SyncgroupSpec(description: oldSpec.description,
collections: oldSpec.collections,
permissions: permissions,
publishSyncbaseName: oldSpec.publishSyncbaseName,
mountTables: oldSpec.mountTables,
isPrivate: oldSpec.isPrivate),
version: versionedSpec.version))
if !syncgroupOnly {
// TODO(sadovsky): There's a race here - it's possible for a collection to get destroyed
// after spec.getCollections() but before db.getCollection().
try self.database.runInBatch { db in
for id in oldSpec.collections {
try db.collection(Identifier(coreId: id)).updateAccessList(delta)
}
}
}
}
}
public var description: String {
return "[Syncbase.Syncgroup id=\(syncgroupId)]"
}
}