blob: 25a151e516d60a49ed11e3f1b4c8ab082dc124e1 [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
/// The singleton instance of Syncbase that represents the local store.
public enum Syncbase {
/// The dispatch queue to run callbacks on. Defaults to main.
public static var queue: dispatch_queue_t = dispatch_get_main_queue()
// Internal variables
static var didInit = false
static var isUnitTest = false
public static func configure(
// Default to Application Support/Syncbase.
rootDir rootDir: String = NSFileManager.defaultManager()
.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0]
.URLByAppendingPathComponent("Syncbase")
.path!,
queue: dispatch_queue_t = dispatch_get_main_queue()) throws {
if didInit {
throw SyncbaseError.AlreadyConfigured
}
if rootDir == "" {
throw SyncbaseError.IllegalArgument(detail: "Missing rootDir")
}
if !NSFileManager.defaultManager().fileExistsAtPath(rootDir) {
try NSFileManager.defaultManager().createDirectoryAtPath(
rootDir,
withIntermediateDirectories: true,
attributes: [NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication])
}
v23_syncbase_Init(
v23_syncbase_Bool(false),
try rootDir.toCgoString(),
v23_syncbase_Bool(isUnitTest))
if isLoggedIn {
try serve()
}
Syncbase.queue = queue
Syncbase.didInit = true
}
/// Starts serving Syncbase post-initialization and login. Internal use only.
static func serve() throws {
try VError.maybeThrow { errPtr in
v23_syncbase_Serve(errPtr)
}
}
/// Shuts down the Syncbase service. You must call configure again before any calls will work.
public static func shutdown() {
v23_syncbase_Shutdown()
Syncbase.didInit = false
}
/// Create a database using the relative name and user's blessings.
public static func database(name: String) throws -> Database {
if !Syncbase.didInit {
throw SyncbaseError.NotConfigured
}
return try database(Identifier(name: name, blessing: try Principal.appBlessing()))
}
/// DatabaseForId returns the Database with the given app blessing and name (from the Id struct).
public static func database(databaseId: Identifier) throws -> Database {
if !Syncbase.didInit {
throw SyncbaseError.NotConfigured
}
return try Database(databaseId: databaseId, batchHandle: nil)
}
/// ListDatabases returns a list of all Database ids that the caller is allowed to see.
/// The list is sorted by blessing, then by name.
public static func listDatabases() throws -> [Identifier] {
if !Syncbase.didInit {
throw SyncbaseError.NotConfigured
}
var ids = v23_syncbase_Ids()
return try VError.maybeThrow { err in
v23_syncbase_ServiceListDatabases(&ids, err)
return ids.toIdentifiers()
}
}
/// Must return true before any Syncbase operation can work. Authorize using GoogleCredentials
/// created from a Google OAuth token (you should use the Google Sign In SDK to get this).
public static var isLoggedIn: Bool {
var ret = v23_syncbase_Bool(false)
v23_syncbase_IsLoggedIn(&ret)
return ret.toBool()
}
/// For debugging the current Syncbase user blessings.
public static var loggedInBlessingDebugDescription: String {
return Principal.blessingsDebugDescription
}
public typealias LoginCallback = (err: ErrorType?) -> Void
/// Authorize using an oauth token. Right now only Google OAuth token is supported
/// (you should use the Google Sign In SDK to get this), and you should use the
/// GoogleOAuthCredentials struct to pass the token.
///
/// You must login and have valid credentials before any Syncbase operation will work.
///
/// Calls `callback` on `Syncbase.queue` with any error that occured, or nil on success.
///
/// TODO(zinman): Make sure the blessings cache works so we don't actually have to login
/// every single time.
public static func login(credentials: OAuthCredentials, callback: LoginCallback) {
if !Syncbase.didInit {
callback(err: SyncbaseError.NotConfigured)
}
// Go's login is blocking, so call on a background concurrent queue.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
var err: ErrorType? = nil
do {
try VError.maybeThrow { errPtr in
v23_syncbase_Login(
try credentials.provider.rawValue.toCgoString(),
try credentials.token.toCgoString(),
errPtr)
}
} catch (let e) {
err = e
}
dispatch_async(Syncbase.queue) {
callback(err: err)
}
}
}
public static func getPermissions() throws -> (Permissions, PermissionsVersion) {
if !Syncbase.didInit {
throw SyncbaseError.NotConfigured
}
var cPermissions = v23_syncbase_Permissions()
var cVersion = v23_syncbase_String()
try VError.maybeThrow { errPtr in
v23_syncbase_ServiceGetPermissions(
&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 static func setPermissions(permissions: Permissions, version: PermissionsVersion) throws {
if !Syncbase.didInit {
throw SyncbaseError.NotConfigured
}
try VError.maybeThrow { errPtr in
v23_syncbase_ServiceSetPermissions(
try v23_syncbase_Permissions(permissions),
try version.toCgoString(),
errPtr)
}
}
}