| // 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 defaultSyncbasePerms(), |
| 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. Cloud is one thing the remote |
| // blessings should include. |
| try coreSyncgroup.join("", 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.userId] = 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.userId] = 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 = AccessList.applyDelta(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)) |
| // 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)]" |
| } |
| } |