swift: Refactor login for new configure/login flow.
Updates the configure/login flow to match the updates to the CGO API,
which fixes the blessings issue that was preventing unit tests in
from passing. Also has a few other bug fixes listed below:
- Checks if we're logged in at configure, and if so then starts serving.
- Refactors post-login logic (creating default db, create-and-join, etc)
to a central function that is only called once we have blessings.
- Makes sure the HLAPI's queue and LLAPI's queue are the same.
- Updates unit tests in SyncbaseCore to use new login flow.
- Makes database/collection/syncgroup adopt CustomStringConvertible
- Filter out internal userdata_syncgroup from listed collections
- Rename erroneous syncbaseId to syncgroupId
Change-Id: I60976427db035432dddae6015cfcbfdfe4f6b72a
diff --git a/Syncbase/Source/Collection.swift b/Syncbase/Source/Collection.swift
index 96f496f..98ea5db 100644
--- a/Syncbase/Source/Collection.swift
+++ b/Syncbase/Source/Collection.swift
@@ -7,7 +7,7 @@
/// Represents an ordered set of key-value pairs.
/// To get a Collection handle, call `Database.collection`.
-public class Collection {
+public class Collection: CustomStringConvertible {
let coreCollection: SyncbaseCore.Collection
let databaseHandle: DatabaseHandle
@@ -98,4 +98,8 @@
try (databaseHandle as! Database).runInBatch(op: op)
}
}
+
+ public var description: String {
+ return "[Syncbase.Collection id=\(collectionId)]"
+ }
}
diff --git a/Syncbase/Source/Database.swift b/Syncbase/Source/Database.swift
index 9f6436f..4a456e0 100644
--- a/Syncbase/Source/Database.swift
+++ b/Syncbase/Source/Database.swift
@@ -79,7 +79,7 @@
/// A set of collections and syncgroups.
/// To get a Database handle, call `Syncbase.database`.
-public class Database: DatabaseHandle {
+public class Database: DatabaseHandle, CustomStringConvertible {
let coreDatabase: SyncbaseCore.Database
// These are all static because we might have active handlers to a database while it goes
@@ -135,9 +135,11 @@
/// Returns all collections in the database.
public func collections() throws -> [Collection] {
return try SyncbaseError.wrap {
- let coreIds = try self.coreDatabase.listCollections()
+ let coreIds = try self.coreDatabase.listCollections().filter({ return $0.name != Syncbase.USERDATA_SYNCGROUP_NAME })
return try coreIds.map { coreId in
- return Collection(coreCollection: try self.coreDatabase.collection(coreId), databaseHandle: self)
+ return Collection(
+ coreCollection: try self.coreDatabase.collection(coreId),
+ databaseHandle: self)
}
}
}
@@ -404,4 +406,8 @@
}
}
}
+
+ public var description: String {
+ return "[Syncbase.Database id=\(databaseId)]"
+ }
}
diff --git a/Syncbase/Source/Error.swift b/Syncbase/Source/Error.swift
index 94216a9..cee530b 100644
--- a/Syncbase/Source/Error.swift
+++ b/Syncbase/Source/Error.swift
@@ -9,7 +9,6 @@
case AlreadyConfigured
case NotConfigured
case NotLoggedIn
- case IllegalArgument(detail: String)
case BatchError(detail: String)
case BlessingError(detail: String)
case UnknownError(err: ErrorType)
@@ -31,6 +30,7 @@
case InvalidOperation(reason: String)
case InvalidUTF8(invalidUtf8: String)
case CastError(obj: Any)
+ case IllegalArgument(detail: String)
init?(coreError: SyncbaseCore.SyncbaseError) {
switch coreError {
@@ -54,6 +54,7 @@
case .InvalidOperation(let reason): self = .InvalidOperation(reason: reason)
case .InvalidUTF8(let invalidUtf8): self = .InvalidUTF8(invalidUtf8: invalidUtf8)
case .CastError(let obj): self = .CastError(obj: obj)
+ case .IllegalArgument(let detail): self = .IllegalArgument(detail: detail)
}
}
@@ -71,7 +72,6 @@
extension SyncbaseError: CustomStringConvertible {
public var description: String {
switch self {
- case .IllegalArgument(let detail): return "Illegal argument: \(detail)"
case .BatchError(let detail): return "Batch error: \(detail)"
case .BlessingError(let detail): return "Blessing error: \(detail)"
case .UnknownError(let err): return "Unknown error: \(err)"
@@ -97,6 +97,7 @@
case .InvalidOperation(let reason): return "Invalid operation: \(reason)"
case .InvalidUTF8(let invalidUtf8): return "Unable to convert to utf8: \(invalidUtf8)"
case .CastError(let obj): return "Unable to convert to cast: \(obj)"
+ case .IllegalArgument(let detail): return "Illegal argument: \(detail)"
}
}
}
diff --git a/Syncbase/Source/Syncbase.swift b/Syncbase/Source/Syncbase.swift
index 2ec27d4..7d02ad2 100644
--- a/Syncbase/Source/Syncbase.swift
+++ b/Syncbase/Source/Syncbase.swift
@@ -14,8 +14,8 @@
DB_NAME = "db",
USERDATA_SYNCGROUP_NAME = "userdata__"
// Initialization state
- static var didCreateOrJoin = false
static var didInit = false
+ static var didPostLogin = false
// Main database.
static var db: Database?
// Options for opening a database.
@@ -26,7 +26,15 @@
static var mountPoints = ["/ns.dev.v.io:8101/tmp/todos/users/"]
static var rootDir = Syncbase.defaultRootDir
/// Queue used to dispatch all asynchronous callbacks. Defaults to main.
- public static var queue: dispatch_queue_t = dispatch_get_main_queue()
+ public static var queue: dispatch_queue_t {
+ // Map directly to SyncbaseCore.
+ get {
+ return SyncbaseCore.Syncbase.queue
+ }
+ set(queue) {
+ SyncbaseCore.Syncbase.queue = queue
+ }
+ }
static public var defaultRootDir: String {
return NSFileManager.defaultManager()
@@ -73,7 +81,7 @@
disableSyncgroupPublishing: Bool = false,
disableUserdataSyncgroup: Bool = false,
queue: dispatch_queue_t = dispatch_get_main_queue()) throws {
- if didInit {
+ if Syncbase.didInit {
throw SyncbaseError.AlreadyConfigured
}
Syncbase.adminUserId = adminUserId
@@ -82,56 +90,50 @@
Syncbase.defaultBlessingStringPrefix = defaultBlessingStringPrefix
Syncbase.disableSyncgroupPublishing = disableSyncgroupPublishing
Syncbase.disableUserdataSyncgroup = disableUserdataSyncgroup
-
- // TODO(zinman): Reconfigure this logic once we have CL #23295 merged.
- let database = try Syncbase.startSyncbaseAndInitDatabase()
- if (Syncbase.disableUserdataSyncgroup) {
- try database.collection(Syncbase.USERDATA_SYNCGROUP_NAME, withoutSyncgroup: true)
- } else {
- // This gets deferred to login as it's blocking. Once we've logged in we don't need it
- // anyway.
- didCreateOrJoin = false
- // FIXME(zinman): Implement create-or-join (and watch) of userdata syncgroup.
- throw SyncbaseError.IllegalArgument(detail: "Synced userdata collection is not yet supported")
+ Syncbase.didPostLogin = false
+ // We don't need to set Syncbase.queue as it is a proxy for SyncbaseCore's queue, which is
+ // set in the configure below.
+ try SyncbaseError.wrap {
+ try SyncbaseCore.Syncbase.configure(rootDir: Syncbase.rootDir, queue: queue)
}
- Syncbase.db = database
+ // We use SyncbaseCore's isLoggedIn because this frameworks would fail as didInit hasn't
+ // been set to true yet.
+ if (SyncbaseCore.Syncbase.isLoggedIn) {
+ do {
+ try Syncbase.postLoginCreateDefaults()
+ } catch let e {
+ // If we get an exception after configuring the low-level API, make sure we shutdown
+ // Syncbase so that any subsequent call to this configure method doesn't get a
+ // SyncbaseError.AlreadyConfigured exception from SyncbaseCore.Syncbase.configure.
+ SyncbaseCore.Syncbase.shutdown()
+ throw e
+ }
+ }
Syncbase.didInit = true
}
- private static func startSyncbaseAndInitDatabase() throws -> Database {
- if Syncbase.rootDir == "" {
- throw SyncbaseError.IllegalArgument(detail: "Missing rootDir")
+ private static func postLoginCreateDefaults() throws {
+ let coreDb = try SyncbaseCore.Syncbase.database(Syncbase.DB_NAME)
+ let database = Database(coreDatabase: coreDb)
+ try database.createIfMissing()
+ if (Syncbase.disableUserdataSyncgroup) {
+ try database.collection(Syncbase.USERDATA_SYNCGROUP_NAME, withoutSyncgroup: true)
+ } else {
+ // FIXME(zinman): Implement create-or-join (and watch) of userdata syncgroup.
+ throw SyncbaseError.IllegalArgument(detail: "Synced userdata collection is not yet supported")
}
- if !NSFileManager.defaultManager().fileExistsAtPath(Syncbase.rootDir) {
- try NSFileManager.defaultManager().createDirectoryAtPath(
- Syncbase.rootDir,
- withIntermediateDirectories: true,
- attributes: [NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication])
- }
- return try SyncbaseError.wrap {
- // TODO(zinman): Verify we should be using the user's blessing (by not explicitly passing
- // the blessings in an Identifier).
- try SyncbaseCore.Syncbase.configure(rootDir: Syncbase.rootDir, queue: Syncbase.queue)
- let coreDb = try SyncbaseCore.Syncbase.database(Syncbase.DB_NAME)
- let res = Database(coreDatabase: coreDb)
- try res.createIfMissing()
- return res
- }
+ Syncbase.db = database
+ Syncbase.didPostLogin = true
}
/// Returns the shared database handle. Must have already called `configure` and be logged in,
/// otherwise this will throw a `SyncbaseError.NotConfigured` or `SyncbaseError.NotLoggedIn`
/// error.
public static func database() throws -> Database {
- guard let db = Syncbase.db where Syncbase.didInit else {
+ if !Syncbase.didInit {
throw SyncbaseError.NotConfigured
}
- if !SyncbaseCore.Syncbase.isLoggedIn {
- throw SyncbaseError.NotLoggedIn
- }
- if !Syncbase.disableUserdataSyncgroup && !Syncbase.didCreateOrJoin {
- // Create-or-join of userdata syncgroup occurs in login. We must have failed between
- // login() and the create-or-join call.
+ guard let db = Syncbase.db where Syncbase.didPostLogin else {
throw SyncbaseError.NotLoggedIn
}
return db
@@ -150,7 +152,7 @@
SyncbaseCore.Syncbase.login(
SyncbaseCore.GoogleOAuthCredentials(token: credentials.token),
callback: { err in
- guard err != nil else {
+ guard err == nil else {
if let e = err as? SyncbaseCore.SyncbaseError {
callback(err: SyncbaseError(coreError: e))
} else {
@@ -158,21 +160,17 @@
}
return
}
- if Syncbase.disableUserdataSyncgroup {
- // Success
- dispatch_async(Syncbase.queue) {
- callback(err: nil)
+ // postLoginCreateDefaults can be blocking when performing create-or-join. Run on
+ // a background queue to prevent blocking from the Go callback.
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
+ var callbackErr: ErrorType?
+ do {
+ try postLoginCreateDefaults()
+ } catch let e {
+ callbackErr = e
}
- } else {
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
- callback(err: SyncbaseError.IllegalArgument(detail:
- "Synced userdata collection is not yet supported"))
- return
- // FIXME(zinman): Implement create-or-join (and watch) of userdata syncgroup.
- // Syncbase.didCreateOrJoin = true
- // dispatch_async(Syncbase.queue) {
- // callback(nil)
- // }
+ dispatch_async(Syncbase.queue) {
+ callback(err: callbackErr)
}
}
})
@@ -182,11 +180,6 @@
if !Syncbase.didInit {
throw SyncbaseError.NotConfigured
}
- if !Syncbase.disableUserdataSyncgroup && !Syncbase.didCreateOrJoin {
- // Create-or-join of userdata syncgroup occurs in login. We must have failed between
- // login() and the create-or-join call.
- throw SyncbaseError.NotLoggedIn
- }
return SyncbaseCore.Syncbase.isLoggedIn
}
}
diff --git a/Syncbase/Source/Syncgroup.swift b/Syncbase/Source/Syncgroup.swift
index 7645945..e27065e 100644
--- a/Syncbase/Source/Syncgroup.swift
+++ b/Syncbase/Source/Syncgroup.swift
@@ -7,7 +7,7 @@
/// Represents a set of collections, synced amongst a set of users.
/// To get a Syncgroup handle, call `Database.syncgroup`.
-public class Syncgroup {
+public class Syncgroup: CustomStringConvertible {
let database: Database
let coreSyncgroup: SyncbaseCore.Syncgroup
@@ -42,7 +42,7 @@
}
/// Returns the id of this syncgroup.
- public var syncbaseId: Identifier {
+ public var syncgroupId: Identifier {
return Identifier(coreId: coreSyncgroup.syncgroupId)
}
@@ -125,4 +125,8 @@
}
}
}
+
+ public var description: String {
+ return "[Syncbase.Syncgroup id=\(syncgroupId)]"
+ }
}
diff --git a/Syncbase/Tests/BasicDatabaseTests.swift b/Syncbase/Tests/BasicDatabaseTests.swift
index 7a8ed1f..2d90846 100644
--- a/Syncbase/Tests/BasicDatabaseTests.swift
+++ b/Syncbase/Tests/BasicDatabaseTests.swift
@@ -4,12 +4,31 @@
import XCTest
@testable import Syncbase
+import enum Syncbase.Syncbase
@testable import SyncbaseCore
+let testQueue = dispatch_queue_create("SyncbaseQueue", DISPATCH_QUEUE_SERIAL)
+
class BasicDatabaseTests: XCTestCase {
override class func setUp() {
SyncbaseCore.Syncbase.isUnitTest = true
- try! Syncbase.configure(adminUserId: "unittest@google.com")
+ let rootDir = NSFileManager.defaultManager()
+ .URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0]
+ .URLByAppendingPathComponent("SyncbaseUnitTest")
+ .absoluteString
+ // TODO(zinman): Once we have create-and-join implemented don't always set
+ // disableUserdataSyncgroup to true.
+ try! Syncbase.configure(
+ adminUserId: "unittest@google.com",
+ rootDir: rootDir,
+ disableUserdataSyncgroup: true,
+ queue: testQueue)
+ let semaphore = dispatch_semaphore_create(0)
+ Syncbase.login(GoogleOAuthCredentials(token: ""), callback: { err in
+ XCTAssertNil(err)
+ dispatch_semaphore_signal(semaphore)
+ })
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
override class func tearDown() {
diff --git a/SyncbaseCore/Source/Errors.swift b/SyncbaseCore/Source/Errors.swift
index 925a7e1..c434d73 100644
--- a/SyncbaseCore/Source/Errors.swift
+++ b/SyncbaseCore/Source/Errors.swift
@@ -25,6 +25,7 @@
case InvalidOperation(reason: String)
case InvalidUTF8(invalidUtf8: String)
case CastError(obj: Any)
+ case IllegalArgument(detail: String)
init?(_ err: VError) {
// TODO(zinman): Make VError better by having the proper arguments transmitted across
@@ -70,6 +71,7 @@
case .InvalidOperation(let reason): return "Invalid operation: \(reason)"
case .InvalidUTF8(let invalidUtf8): return "Unable to convert to UTF-8: \(invalidUtf8)"
case .CastError(let obj): return "Unable to convert to cast: \(obj)"
+ case .IllegalArgument(let detail): return "Illegal argument: \(detail)"
}
}
}
diff --git a/SyncbaseCore/Source/Principal.swift b/SyncbaseCore/Source/Principal.swift
index 70cc3f9..f37787a 100644
--- a/SyncbaseCore/Source/Principal.swift
+++ b/SyncbaseCore/Source/Principal.swift
@@ -41,14 +41,4 @@
v23_syncbase_BlessingStoreDebugString(&cStr)
return cStr.toString() ?? "ERROR"
}
-
- /// True if the blessings have been successfully retrieved via exchanging an oauth token.
- static func blessingsAreValid() -> Bool {
- do {
- try userBlessing()
- return true
- } catch {
- return false
- }
- }
}
diff --git a/SyncbaseCore/Source/Syncbase.swift b/SyncbaseCore/Source/Syncbase.swift
index 080ffc6..bbdbfcf 100644
--- a/SyncbaseCore/Source/Syncbase.swift
+++ b/SyncbaseCore/Source/Syncbase.swift
@@ -8,9 +8,8 @@
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(
@@ -23,13 +22,33 @@
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()
@@ -68,8 +87,9 @@
/// 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 {
- log.debug("Blessings debug string is \(Principal.blessingsDebugDescription)")
- return Principal.blessingsAreValid()
+ var ret = v23_syncbase_Bool(false)
+ v23_syncbase_IsLoggedIn(&ret)
+ return ret.toBool()
}
/// For debugging the current Syncbase user blessings.
diff --git a/SyncbaseCore/Tests/BasicDatabaseTests.swift b/SyncbaseCore/Tests/BasicDatabaseTests.swift
index b59ad1a..1ae4913 100644
--- a/SyncbaseCore/Tests/BasicDatabaseTests.swift
+++ b/SyncbaseCore/Tests/BasicDatabaseTests.swift
@@ -6,6 +6,13 @@
import XCTest
@testable import SyncbaseCore
+struct EmptyCredentials: OAuthCredentials {
+ let provider: OAuthProvider = OAuthProvider.Google
+ let token: String = ""
+}
+
+let testQueue = dispatch_queue_create("SyncbaseQueue", DISPATCH_QUEUE_SERIAL)
+
class BasicDatabaseTests: XCTestCase {
override class func setUp() {
Syncbase.isUnitTest = true
@@ -13,7 +20,13 @@
.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0]
.URLByAppendingPathComponent("SyncbaseUnitTest")
.absoluteString
- try! Syncbase.configure(rootDir: rootDir)
+ try! Syncbase.configure(rootDir: rootDir, queue: testQueue)
+ let semaphore = dispatch_semaphore_create(0)
+ Syncbase.login(EmptyCredentials(), callback: { err in
+ XCTAssertNil(err)
+ dispatch_semaphore_signal(semaphore)
+ })
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
override class func tearDown() {