Unverified Commit 23c1cdb1 authored by Zach Knox's avatar Zach Knox
Browse files

notifications work—still work to do but it's progress

also updated pods
parent fa006df2
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#import "RLMRealm+Sync.h" #import "RLMRealm+Sync.h"
#import "RLMObjectBase.h" #import "RLMObjectBase.h"
#import "RLMQueryUtil.hpp"
#import "RLMObjectSchema.h" #import "RLMObjectSchema.h"
#import "RLMRealm_Private.hpp" #import "RLMRealm_Private.hpp"
#import "RLMResults_Private.hpp" #import "RLMResults_Private.hpp"
...@@ -26,30 +27,56 @@ ...@@ -26,30 +27,56 @@
#import "RLMSyncSession.h" #import "RLMSyncSession.h"
#import "results.hpp" #import "results.hpp"
#import "sync/partial_sync.hpp"
#import "shared_realm.hpp" #import "shared_realm.hpp"
#import "sync/partial_sync.hpp"
#import "sync/subscription_state.hpp"
using namespace realm; using namespace realm;
@implementation RLMRealm (Sync) @implementation RLMRealm (Sync)
- (void)subscribeToObjects:(Class)type where:(NSString *)query callback:(RLMPartialSyncFetchCallback)callback { - (void)subscribeToObjects:(Class)type where:(NSString *)query callback:(RLMPartialSyncFetchCallback)callback {
NSString *className = [type className]; [self verifyThread];
auto cb = [=](Results results, std::exception_ptr err) {
if (err) { RLMClassInfo& info = _info[[type className]];
try { Query q = RLMPredicateToQuery([NSPredicate predicateWithFormat:query],
rethrow_exception(err); info.rlmObjectSchema, self.schema, self.group);
} struct Holder {
catch (...) { partial_sync::Subscription subscription;
NSError *error = nil; partial_sync::SubscriptionNotificationToken token;
RLMRealmTranslateException(&error);
callback(nil, error); Holder(partial_sync::Subscription&& s) : subscription(std::move(s)) { }
} };
auto state = std::make_shared<Holder>(partial_sync::subscribe(Results(_realm, std::move(q)), util::none));
state->token = state->subscription.add_notification_callback([=]() mutable {
if (!callback) {
return; return;
} }
callback([RLMResults resultsWithObjectInfo:_info[className] results:std::move(results)], nil); switch (state->subscription.state()) {
}; case partial_sync::SubscriptionState::Invalidated:
partial_sync::register_query(_realm, className.UTF8String, query.UTF8String, std::move(cb)); case partial_sync::SubscriptionState::Pending:
case partial_sync::SubscriptionState::Creating:
return;
case partial_sync::SubscriptionState::Error:
try {
rethrow_exception(state->subscription.error());
}
catch (...) {
NSError *error = nil;
RLMRealmTranslateException(&error);
callback(nil, error);
}
break;
case partial_sync::SubscriptionState::Complete:
callback([RLMResults emptyDetachedResults], nil);
break;
}
callback = nil;
state->token = {};
});
} }
- (RLMSyncSession *)syncSession { - (RLMSyncSession *)syncSession {
......
...@@ -460,6 +460,10 @@ REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfigurati ...@@ -460,6 +460,10 @@ REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfigurati
if (!readOnly) { if (!readOnly) {
// initializing the schema started a read transaction, so end it // initializing the schema started a read transaction, so end it
[realm invalidate]; [realm invalidate];
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management");
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock");
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note");
} }
} }
...@@ -470,10 +474,6 @@ REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfigurati ...@@ -470,10 +474,6 @@ REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfigurati
if (!readOnly) { if (!readOnly) {
realm->_realm->m_binding_context = RLMCreateBindingContext(realm); realm->_realm->m_binding_context = RLMCreateBindingContext(realm);
realm->_realm->m_binding_context->realm = realm->_realm; realm->_realm->m_binding_context->realm = realm->_realm;
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management");
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock");
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note");
} }
return RLMAutorelease(realm); return RLMAutorelease(realm);
......
...@@ -111,19 +111,29 @@ void RLMThrowResultsError(NSString *aggregateMethod) { ...@@ -111,19 +111,29 @@ void RLMThrowResultsError(NSString *aggregateMethod) {
} }
} }
- (instancetype)initWithObjectInfo:(RLMClassInfo&)info
results:(realm::Results&&)results {
if (self = [super init]) {
_results = std::move(results);
_realm = info.realm;
_info = &info;
}
return self;
}
+ (instancetype)resultsWithObjectInfo:(RLMClassInfo&)info + (instancetype)resultsWithObjectInfo:(RLMClassInfo&)info
results:(realm::Results)results { results:(realm::Results&&)results {
RLMResults *ar = [[self alloc] initPrivate]; return [[self alloc] initWithObjectInfo:info results:std::move(results)];
ar->_results = std::move(results);
ar->_realm = info.realm;
ar->_info = &info;
return ar;
} }
+ (instancetype)emptyDetachedResults { + (instancetype)emptyDetachedResults {
return [[self alloc] initPrivate]; return [[self alloc] initPrivate];
} }
- (instancetype)subresultsWithResults:(realm::Results)results {
return [self.class resultsWithObjectInfo:*_info results:std::move(results)];
}
static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMResults *const ar) { static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMResults *const ar) {
ar->_realm->_realm->verify_thread(); ar->_realm->_realm->verify_thread();
ar->_realm->_realm->verify_in_write(); ar->_realm->_realm->verify_in_write();
...@@ -349,7 +359,7 @@ static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMR ...@@ -349,7 +359,7 @@ static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMR
@throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects"); @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
} }
auto query = RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group); auto query = RLMPredicateToQuery(predicate, _info->rlmObjectSchema, _realm.schema, _realm.group);
return [RLMResults resultsWithObjectInfo:*_info results:_results.filter(std::move(query))]; return [self subresultsWithResults:_results.filter(std::move(query))];
}); });
} }
...@@ -365,8 +375,7 @@ static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMR ...@@ -365,8 +375,7 @@ static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMR
if (_results.get_mode() == Results::Mode::Empty) { if (_results.get_mode() == Results::Mode::Empty) {
return self; return self;
} }
return [RLMResults resultsWithObjectInfo:*_info return [self subresultsWithResults:_results.sort(RLMSortDescriptorsToKeypathArray(properties))];
results:_results.sort(RLMSortDescriptorsToKeypathArray(properties))];
}); });
} }
...@@ -387,7 +396,7 @@ static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMR ...@@ -387,7 +396,7 @@ static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMR
keyPathsVector.push_back(keyPath.UTF8String); keyPathsVector.push_back(keyPath.UTF8String);
} }
return [RLMResults resultsWithObjectInfo:*_info results:_results.distinct(keyPathsVector)]; return [self subresultsWithResults:_results.distinct(keyPathsVector)];
}); });
} }
......
...@@ -53,14 +53,11 @@ RLMSyncSystemErrorKind errorKindForSyncError(SyncError error) { ...@@ -53,14 +53,11 @@ RLMSyncSystemErrorKind errorKindForSyncError(SyncError error) {
return RLMSyncSystemErrorKindUnknown; return RLMSyncSystemErrorKindUnknown;
} }
} }
}
static BOOL isValidRealmURL(NSURL *url) { BOOL isValidRealmURL(NSURL *url) {
NSString *scheme = [url scheme]; NSString *scheme = [url scheme];
if (![scheme isEqualToString:@"realm"] && ![scheme isEqualToString:@"realms"]) { return [scheme isEqualToString:@"realm"] || [scheme isEqualToString:@"realms"];
return NO; }
}
return YES;
} }
@interface RLMSyncConfiguration () { @interface RLMSyncConfiguration () {
...@@ -109,6 +106,28 @@ static BOOL isValidRealmURL(NSURL *url) { ...@@ -109,6 +106,28 @@ static BOOL isValidRealmURL(NSURL *url) {
_config->is_partial = (bool)isPartial; _config->is_partial = (bool)isPartial;
} }
- (NSURL *)pinnedCertificateURL {
if (auto& path = _config->ssl_trust_certificate_path) {
return [NSURL fileURLWithPath:RLMStringDataToNSString(*path)];
}
return nil;
}
- (void)setPinnedCertificateURL:(NSURL *)pinnedCertificateURL {
if (pinnedCertificateURL) {
if ([pinnedCertificateURL respondsToSelector:@selector(UTF8String)]) {
_config->ssl_trust_certificate_path = std::string([(id)pinnedCertificateURL UTF8String]);
}
else {
_config->ssl_trust_certificate_path = std::string(pinnedCertificateURL.path.UTF8String);
}
}
else {
_config->ssl_trust_certificate_path = util::none;
}
}
- (BOOL)isPartial { - (BOOL)isPartial {
return (BOOL)_config->is_partial; return (BOOL)_config->is_partial;
} }
...@@ -171,7 +190,8 @@ static BOOL isValidRealmURL(NSURL *url) { ...@@ -171,7 +190,8 @@ static BOOL isValidRealmURL(NSURL *url) {
isPartial:(BOOL)isPartial isPartial:(BOOL)isPartial
urlPrefix:(NSString *)urlPrefix urlPrefix:(NSString *)urlPrefix
stopPolicy:(RLMSyncStopPolicy)stopPolicy stopPolicy:(RLMSyncStopPolicy)stopPolicy
enableSSLValidation:(BOOL)enableSSLValidation { enableSSLValidation:(BOOL)enableSSLValidation
certificatePath:(nullable NSURL *)certificatePath {
auto config = [self initWithUser:user auto config = [self initWithUser:user
realmURL:url realmURL:url
customFileURL:nil customFileURL:nil
...@@ -180,6 +200,7 @@ static BOOL isValidRealmURL(NSURL *url) { ...@@ -180,6 +200,7 @@ static BOOL isValidRealmURL(NSURL *url) {
errorHandler:nullptr]; errorHandler:nullptr];
config.urlPrefix = urlPrefix; config.urlPrefix = urlPrefix;
config.enableSSLValidation = enableSSLValidation; config.enableSSLValidation = enableSSLValidation;
config.pinnedCertificateURL = certificatePath;
return config; return config;
} }
......
...@@ -29,6 +29,10 @@ ...@@ -29,6 +29,10 @@
#import "sync/sync_manager.hpp" #import "sync/sync_manager.hpp"
#import "sync/sync_session.hpp" #import "sync/sync_session.hpp"
#if !defined(REALM_COCOA_VERSION)
#import "RLMVersion.h"
#endif
using namespace realm; using namespace realm;
using Level = realm::util::Logger::Level; using Level = realm::util::Logger::Level;
...@@ -110,7 +114,13 @@ static RLMSyncManager *s_sharedManager = nil; ...@@ -110,7 +114,13 @@ static RLMSyncManager *s_sharedManager = nil;
bool should_encrypt = !getenv("REALM_DISABLE_METADATA_ENCRYPTION") && !RLMIsRunningInPlayground(); bool should_encrypt = !getenv("REALM_DISABLE_METADATA_ENCRYPTION") && !RLMIsRunningInPlayground();
auto mode = should_encrypt ? SyncManager::MetadataMode::Encryption : SyncManager::MetadataMode::NoEncryption; auto mode = should_encrypt ? SyncManager::MetadataMode::Encryption : SyncManager::MetadataMode::NoEncryption;
rootDirectory = rootDirectory ?: [NSURL fileURLWithPath:RLMDefaultDirectoryForBundleIdentifier(nil)]; rootDirectory = rootDirectory ?: [NSURL fileURLWithPath:RLMDefaultDirectoryForBundleIdentifier(nil)];
SyncManager::shared().configure_file_system(rootDirectory.path.UTF8String, mode, none, true); @autoreleasepool {
bool isSwift = !!NSClassFromString(@"RealmSwiftObjectUtil");
auto userAgent = [[NSMutableString alloc] initWithFormat:@"Realm%@/%@",
isSwift ? @"Swift" : @"ObjectiveC", REALM_COCOA_VERSION];
SyncManager::shared().configure(rootDirectory.path.UTF8String, mode, RLMStringDataWithNSString(userAgent), none, true);
SyncManager::shared().set_user_agent(RLMStringDataWithNSString(self.appID));
}
return self; return self;
} }
return nil; return nil;
...@@ -123,6 +133,11 @@ static RLMSyncManager *s_sharedManager = nil; ...@@ -123,6 +133,11 @@ static RLMSyncManager *s_sharedManager = nil;
return _appID; return _appID;
} }
- (void)setUserAgent:(NSString *)userAgent {
SyncManager::shared().set_user_agent(RLMStringDataWithNSString(userAgent));
_userAgent = userAgent;
}
#pragma mark - Passthrough properties #pragma mark - Passthrough properties
- (RLMSyncLogLevel)logLevel { - (RLMSyncLogLevel)logLevel {
...@@ -206,6 +221,7 @@ static RLMSyncManager *s_sharedManager = nil; ...@@ -206,6 +221,7 @@ static RLMSyncManager *s_sharedManager = nil;
RLMNetworkRequestOptions *options = [[RLMNetworkRequestOptions alloc] init]; RLMNetworkRequestOptions *options = [[RLMNetworkRequestOptions alloc] init];
options.authorizationHeaderName = self.authorizationHeaderName; options.authorizationHeaderName = self.authorizationHeaderName;
options.customHeaders = self.customRequestHeaders; options.customHeaders = self.customRequestHeaders;
options.pinnedCertificatePaths = self.pinnedCertificatePaths;
return options; return options;
} }
......
...@@ -80,6 +80,7 @@ using namespace realm; ...@@ -80,6 +80,7 @@ using namespace realm;
@interface RLMSyncSession () @interface RLMSyncSession ()
@property (class, nonatomic, readonly) dispatch_queue_t notificationsQueue; @property (class, nonatomic, readonly) dispatch_queue_t notificationsQueue;
@property (atomic, readwrite) RLMSyncConnectionState connectionState;
@end @end
@implementation RLMSyncSession @implementation RLMSyncSession
...@@ -93,9 +94,23 @@ using namespace realm; ...@@ -93,9 +94,23 @@ using namespace realm;
return queue; return queue;
} }
- (instancetype)initWithSyncSession:(std::shared_ptr<SyncSession>)session { static RLMSyncConnectionState convertConnectionState(SyncSession::ConnectionState state) {
switch (state) {
case SyncSession::ConnectionState::Disconnected: return RLMSyncConnectionStateDisconnected;
case SyncSession::ConnectionState::Connecting: return RLMSyncConnectionStateConnecting;
case SyncSession::ConnectionState::Connected: return RLMSyncConnectionStateConnected;
}
}
- (instancetype)initWithSyncSession:(std::shared_ptr<SyncSession> const&)session {
if (self = [super init]) { if (self = [super init]) {
_session = session; _session = session;
_connectionState = convertConnectionState(session->connection_state());
// No need to save the token as RLMSyncSession always outlives the
// underlying SyncSession
session->register_connection_change_callback([=](auto, auto newState) {
self.connectionState = convertConnectionState(newState);
});
return self; return self;
} }
return nil; return nil;
...@@ -103,9 +118,7 @@ using namespace realm; ...@@ -103,9 +118,7 @@ using namespace realm;
- (RLMSyncConfiguration *)configuration { - (RLMSyncConfiguration *)configuration {
if (auto session = _session.lock()) { if (auto session = _session.lock()) {
if (session->state() != SyncSession::PublicState::Error) { return [[RLMSyncConfiguration alloc] initWithRawConfig:session->config()];
return [[RLMSyncConfiguration alloc] initWithRawConfig:session->config()];
}
} }
return nil; return nil;
} }
...@@ -121,9 +134,7 @@ using namespace realm; ...@@ -121,9 +134,7 @@ using namespace realm;
- (RLMSyncUser *)parentUser { - (RLMSyncUser *)parentUser {
if (auto session = _session.lock()) { if (auto session = _session.lock()) {
if (session->state() != SyncSession::PublicState::Error) { return [[RLMSyncUser alloc] initWithSyncUser:session->user()];
return [[RLMSyncUser alloc] initWithSyncUser:session->user()];
}
} }
return nil; return nil;
} }
...@@ -133,18 +144,25 @@ using namespace realm; ...@@ -133,18 +144,25 @@ using namespace realm;
if (session->state() == SyncSession::PublicState::Inactive) { if (session->state() == SyncSession::PublicState::Inactive) {
return RLMSyncSessionStateInactive; return RLMSyncSessionStateInactive;
} }
if (session->state() != SyncSession::PublicState::Error) { return RLMSyncSessionStateActive;
return RLMSyncSessionStateActive;
}
} }
return RLMSyncSessionStateInvalid; return RLMSyncSessionStateInvalid;
} }
- (void)suspend {
if (auto session = _session.lock()) {
session->log_out();
}
}
- (void)resume {
if (auto session = _session.lock()) {
session->revive_if_needed();
}
}
- (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback { - (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback {
if (auto session = _session.lock()) { if (auto session = _session.lock()) {
if (session->state() == SyncSession::PublicState::Error) {
return NO;
}
queue = queue ?: dispatch_get_main_queue(); queue = queue ?: dispatch_get_main_queue();
session->wait_for_upload_completion([=](std::error_code err) { session->wait_for_upload_completion([=](std::error_code err) {
NSError *error = (err == std::error_code{}) ? nil : make_sync_error(err); NSError *error = (err == std::error_code{}) ? nil : make_sync_error(err);
...@@ -159,9 +177,6 @@ using namespace realm; ...@@ -159,9 +177,6 @@ using namespace realm;
- (BOOL)waitForDownloadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback { - (BOOL)waitForDownloadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback {
if (auto session = _session.lock()) { if (auto session = _session.lock()) {
if (session->state() == SyncSession::PublicState::Error) {
return NO;
}
queue = queue ?: dispatch_get_main_queue(); queue = queue ?: dispatch_get_main_queue();
session->wait_for_download_completion([=](std::error_code err) { session->wait_for_download_completion([=](std::error_code err) {
NSError *error = (err == std::error_code{}) ? nil : make_sync_error(err); NSError *error = (err == std::error_code{}) ? nil : make_sync_error(err);
...@@ -178,9 +193,6 @@ using namespace realm; ...@@ -178,9 +193,6 @@ using namespace realm;
mode:(RLMSyncProgressMode)mode mode:(RLMSyncProgressMode)mode
block:(RLMProgressNotificationBlock)block { block:(RLMProgressNotificationBlock)block {
if (auto session = _session.lock()) { if (auto session = _session.lock()) {
if (session->state() == SyncSession::PublicState::Error) {
return nil;
}
dispatch_queue_t queue = RLMSyncSession.notificationsQueue; dispatch_queue_t queue = RLMSyncSession.notificationsQueue;
auto notifier_direction = (direction == RLMSyncProgressDirectionUpload auto notifier_direction = (direction == RLMSyncProgressDirectionUpload
? SyncSession::NotifierType::upload ? SyncSession::NotifierType::upload
...@@ -209,8 +221,7 @@ using namespace realm; ...@@ -209,8 +221,7 @@ using namespace realm;
if (!config) { if (!config) {
return nil; return nil;
} }
auto path = SyncManager::shared().path_for_realm(*config->user, config->realm_url()); if (auto session = config->user->session_for_on_disk_path(realm->_realm->config().path)) {
if (auto session = config->user->session_for_on_disk_path(path)) {
return [[RLMSyncSession alloc] initWithSyncSession:session]; return [[RLMSyncSession alloc] initWithSyncSession:session];
} }
return nil; return nil;
......
...@@ -133,7 +133,6 @@ static const NSTimeInterval RLMRefreshBuffer = 10; ...@@ -133,7 +133,6 @@ static const NSTimeInterval RLMRefreshBuffer = 10;
/// Handler for network requests whose responses successfully parse into an auth response model. /// Handler for network requests whose responses successfully parse into an auth response model.
- (BOOL)_handleSuccessfulRequest:(RLMAuthResponseModel *)model { - (BOOL)_handleSuccessfulRequest:(RLMAuthResponseModel *)model {
// Success
std::shared_ptr<SyncSession> session = _session.lock(); std::shared_ptr<SyncSession> session = _session.lock();
if (!session) { if (!session) {
// The session is dead or in a fatal error state. // The session is dead or in a fatal error state.
...@@ -141,38 +140,31 @@ static const NSTimeInterval RLMRefreshBuffer = 10; ...@@ -141,38 +140,31 @@ static const NSTimeInterval RLMRefreshBuffer = 10;
[self invalidate]; [self invalidate];
return NO; return NO;
} }
bool success = session->state() != SyncSession::PublicState::Error;
if (success) { // Calculate the resolved path.
// Calculate the resolved path. NSString *resolvedURLString = nil;
NSString *resolvedURLString = nil; RLMServerPath resolvedPath = model.accessToken.tokenData.path;
RLMServerPath resolvedPath = model.accessToken.tokenData.path; // Munge the path back onto the original URL, because the `sync` API expects an entire URL.
// Munge the path back onto the original URL, because the `sync` API expects an entire URL. NSURLComponents *urlBuffer = [NSURLComponents componentsWithURL:self.realmURL
NSURLComponents *urlBuffer = [NSURLComponents componentsWithURL:self.realmURL resolvingAgainstBaseURL:YES];
resolvingAgainstBaseURL:YES]; urlBuffer.path = resolvedPath;
urlBuffer.path = resolvedPath; resolvedURLString = [[urlBuffer URL] absoluteString];
resolvedURLString = [[urlBuffer URL] absoluteString]; if (!resolvedURLString) {
if (!resolvedURLString) { @throw RLMException(@"Resolved path returned from the server was invalid (%@).", resolvedPath);
@throw RLMException(@"Resolved path returned from the server was invalid (%@).", resolvedPath);
}
// Pass the token and resolved path to the underlying sync subsystem.
session->refresh_access_token([model.accessToken.token UTF8String], {resolvedURLString.UTF8String});
success = session->state() != SyncSession::PublicState::Error;
if (success) {
// Schedule a refresh. If we're successful we must already have `bind()`ed the session
// initially, so we can null out the strong pointer.
_strongSession = nullptr;
NSDate *expires = [NSDate dateWithTimeIntervalSince1970:model.accessToken.tokenData.expires];
[self scheduleRefreshTimer:expires];
} else {
// The session is dead or in a fatal error state.
unregisterRefreshHandle(_user, _path);
[self invalidate];
}
} }
// Pass the token and resolved path to the underlying sync subsystem.
session->refresh_access_token([model.accessToken.token UTF8String], {resolvedURLString.UTF8String});
// Schedule a refresh. If we're successful we must already have `bind()`ed the session
// initially, so we can null out the strong pointer.
_strongSession = nullptr;
NSDate *expires = [NSDate dateWithTimeIntervalSince1970:model.accessToken.tokenData.expires];
[self scheduleRefreshTimer:expires];