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 @@
#import "RLMRealm+Sync.h"
#import "RLMObjectBase.h"
#import "RLMQueryUtil.hpp"
#import "RLMObjectSchema.h"
#import "RLMRealm_Private.hpp"
#import "RLMResults_Private.hpp"
......@@ -26,30 +27,56 @@
#import "RLMSyncSession.h"
#import "results.hpp"
#import "sync/partial_sync.hpp"
#import "shared_realm.hpp"
#import "sync/partial_sync.hpp"
#import "sync/subscription_state.hpp"
using namespace realm;
@implementation RLMRealm (Sync)
- (void)subscribeToObjects:(Class)type where:(NSString *)query callback:(RLMPartialSyncFetchCallback)callback {
NSString *className = [type className];
auto cb = [=](Results results, std::exception_ptr err) {
if (err) {
[self verifyThread];
RLMClassInfo& info = _info[[type className]];
Query q = RLMPredicateToQuery([NSPredicate predicateWithFormat:query],
info.rlmObjectSchema, self.schema, self.group);
struct Holder {
partial_sync::Subscription subscription;
partial_sync::SubscriptionNotificationToken token;
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;
}
switch (state->subscription.state()) {
case partial_sync::SubscriptionState::Invalidated:
case partial_sync::SubscriptionState::Pending:
case partial_sync::SubscriptionState::Creating:
return;
case partial_sync::SubscriptionState::Error:
try {
rethrow_exception(err);
rethrow_exception(state->subscription.error());
}
catch (...) {
NSError *error = nil;
RLMRealmTranslateException(&error);
callback(nil, error);
}
return;
break;
case partial_sync::SubscriptionState::Complete:
callback([RLMResults emptyDetachedResults], nil);
break;
}
callback([RLMResults resultsWithObjectInfo:_info[className] results:std::move(results)], nil);
};
partial_sync::register_query(_realm, className.UTF8String, query.UTF8String, std::move(cb));
callback = nil;
state->token = {};
});
}
- (RLMSyncSession *)syncSession {
......
......@@ -460,6 +460,10 @@ REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfigurati
if (!readOnly) {
// initializing the schema started a read transaction, so end it
[realm invalidate];
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management");
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock");
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note");
}
}
......@@ -470,10 +474,6 @@ REALM_NOINLINE static void translateSharedGroupOpenException(RLMRealmConfigurati
if (!readOnly) {
realm->_realm->m_binding_context = RLMCreateBindingContext(realm);
realm->_realm->m_binding_context->realm = realm->_realm;
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".management");
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".lock");
RLMAddSkipBackupAttributeToItemAtPath(config.path + ".note");
}
return RLMAutorelease(realm);
......
......@@ -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
results:(realm::Results)results {
RLMResults *ar = [[self alloc] initPrivate];
ar->_results = std::move(results);
ar->_realm = info.realm;
ar->_info = &info;
return ar;
results:(realm::Results&&)results {
return [[self alloc] initWithObjectInfo:info results:std::move(results)];
}
+ (instancetype)emptyDetachedResults {
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) {
ar->_realm->_realm->verify_thread();
ar->_realm->_realm->verify_in_write();
......@@ -349,7 +359,7 @@ static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMR
@throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
}
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
if (_results.get_mode() == Results::Mode::Empty) {
return self;
}
return [RLMResults resultsWithObjectInfo:*_info
results:_results.sort(RLMSortDescriptorsToKeypathArray(properties))];
return [self subresultsWithResults:_results.sort(RLMSortDescriptorsToKeypathArray(properties))];
});
}
......@@ -387,7 +396,7 @@ static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMR
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) {
return RLMSyncSystemErrorKindUnknown;
}
}
}
static BOOL isValidRealmURL(NSURL *url) {
BOOL isValidRealmURL(NSURL *url) {
NSString *scheme = [url scheme];
if (![scheme isEqualToString:@"realm"] && ![scheme isEqualToString:@"realms"]) {
return NO;
}
return YES;
return [scheme isEqualToString:@"realm"] || [scheme isEqualToString:@"realms"];
}
}
@interface RLMSyncConfiguration () {
......@@ -109,6 +106,28 @@ static BOOL isValidRealmURL(NSURL *url) {
_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 {
return (BOOL)_config->is_partial;
}
......@@ -171,7 +190,8 @@ static BOOL isValidRealmURL(NSURL *url) {
isPartial:(BOOL)isPartial
urlPrefix:(NSString *)urlPrefix
stopPolicy:(RLMSyncStopPolicy)stopPolicy
enableSSLValidation:(BOOL)enableSSLValidation {
enableSSLValidation:(BOOL)enableSSLValidation
certificatePath:(nullable NSURL *)certificatePath {
auto config = [self initWithUser:user
realmURL:url
customFileURL:nil
......@@ -180,6 +200,7 @@ static BOOL isValidRealmURL(NSURL *url) {
errorHandler:nullptr];
config.urlPrefix = urlPrefix;
config.enableSSLValidation = enableSSLValidation;
config.pinnedCertificateURL = certificatePath;
return config;
}
......
......@@ -29,6 +29,10 @@
#import "sync/sync_manager.hpp"
#import "sync/sync_session.hpp"
#if !defined(REALM_COCOA_VERSION)
#import "RLMVersion.h"
#endif
using namespace realm;
using Level = realm::util::Logger::Level;
......@@ -110,7 +114,13 @@ static RLMSyncManager *s_sharedManager = nil;
bool should_encrypt = !getenv("REALM_DISABLE_METADATA_ENCRYPTION") && !RLMIsRunningInPlayground();
auto mode = should_encrypt ? SyncManager::MetadataMode::Encryption : SyncManager::MetadataMode::NoEncryption;
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 nil;
......@@ -123,6 +133,11 @@ static RLMSyncManager *s_sharedManager = nil;
return _appID;
}
- (void)setUserAgent:(NSString *)userAgent {
SyncManager::shared().set_user_agent(RLMStringDataWithNSString(userAgent));
_userAgent = userAgent;
}
#pragma mark - Passthrough properties
- (RLMSyncLogLevel)logLevel {
......@@ -206,6 +221,7 @@ static RLMSyncManager *s_sharedManager = nil;
RLMNetworkRequestOptions *options = [[RLMNetworkRequestOptions alloc] init];
options.authorizationHeaderName = self.authorizationHeaderName;
options.customHeaders = self.customRequestHeaders;
options.pinnedCertificatePaths = self.pinnedCertificatePaths;
return options;
}
......
......@@ -80,6 +80,7 @@ using namespace realm;
@interface RLMSyncSession ()
@property (class, nonatomic, readonly) dispatch_queue_t notificationsQueue;
@property (atomic, readwrite) RLMSyncConnectionState connectionState;
@end
@implementation RLMSyncSession
......@@ -93,9 +94,23 @@ using namespace realm;
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]) {
_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 nil;
......@@ -103,10 +118,8 @@ using namespace realm;
- (RLMSyncConfiguration *)configuration {
if (auto session = _session.lock()) {
if (session->state() != SyncSession::PublicState::Error) {
return [[RLMSyncConfiguration alloc] initWithRawConfig:session->config()];
}
}
return nil;
}
......@@ -121,10 +134,8 @@ using namespace realm;
- (RLMSyncUser *)parentUser {
if (auto session = _session.lock()) {
if (session->state() != SyncSession::PublicState::Error) {
return [[RLMSyncUser alloc] initWithSyncUser:session->user()];
}
}
return nil;
}
......@@ -133,18 +144,25 @@ using namespace realm;
if (session->state() == SyncSession::PublicState::Inactive) {
return RLMSyncSessionStateInactive;
}
if (session->state() != SyncSession::PublicState::Error) {
return RLMSyncSessionStateActive;
}
}
return RLMSyncSessionStateInvalid;
}
- (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback {
- (void)suspend {
if (auto session = _session.lock()) {
if (session->state() == SyncSession::PublicState::Error) {
return NO;
session->log_out();
}
}
- (void)resume {
if (auto session = _session.lock()) {
session->revive_if_needed();
}
}
- (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback {
if (auto session = _session.lock()) {
queue = queue ?: dispatch_get_main_queue();
session->wait_for_upload_completion([=](std::error_code err) {
NSError *error = (err == std::error_code{}) ? nil : make_sync_error(err);
......@@ -159,9 +177,6 @@ using namespace realm;
- (BOOL)waitForDownloadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback {
if (auto session = _session.lock()) {
if (session->state() == SyncSession::PublicState::Error) {
return NO;
}
queue = queue ?: dispatch_get_main_queue();
session->wait_for_download_completion([=](std::error_code err) {
NSError *error = (err == std::error_code{}) ? nil : make_sync_error(err);
......@@ -178,9 +193,6 @@ using namespace realm;
mode:(RLMSyncProgressMode)mode
block:(RLMProgressNotificationBlock)block {
if (auto session = _session.lock()) {
if (session->state() == SyncSession::PublicState::Error) {
return nil;
}
dispatch_queue_t queue = RLMSyncSession.notificationsQueue;
auto notifier_direction = (direction == RLMSyncProgressDirectionUpload
? SyncSession::NotifierType::upload
......@@ -209,8 +221,7 @@ using namespace realm;
if (!config) {
return nil;
}
auto path = SyncManager::shared().path_for_realm(*config->user, config->realm_url());
if (auto session = config->user->session_for_on_disk_path(path)) {
if (auto session = config->user->session_for_on_disk_path(realm->_realm->config().path)) {
return [[RLMSyncSession alloc] initWithSyncSession:session];
}
return nil;
......
......@@ -133,7 +133,6 @@ static const NSTimeInterval RLMRefreshBuffer = 10;
/// Handler for network requests whose responses successfully parse into an auth response model.
- (BOOL)_handleSuccessfulRequest:(RLMAuthResponseModel *)model {
// Success
std::shared_ptr<SyncSession> session = _session.lock();
if (!session) {
// The session is dead or in a fatal error state.
......@@ -141,8 +140,7 @@ static const NSTimeInterval RLMRefreshBuffer = 10;
[self invalidate];
return NO;
}
bool success = session->state() != SyncSession::PublicState::Error;
if (success) {
// Calculate the resolved path.
NSString *resolvedURLString = nil;
RLMServerPath resolvedPath = model.accessToken.tokenData.path;
......@@ -156,23 +154,17 @@ static const NSTimeInterval RLMRefreshBuffer = 10;
}
// 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];
}
}
if (self.completionBlock) {
self.completionBlock(success ? nil : make_auth_error_client_issue());
self.completionBlock(nil);
}
return success;
return true;
}
/// Handler for network requests that failed before the JSON parsing stage.
......
......@@ -18,17 +18,19 @@
#import "RLMSyncSubscription.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObject_Private.hpp"
#import "RLMProperty_Private.hpp"
#import "RLMRealm_Private.hpp"
#import "RLMResults_Private.hpp"
#import "RLMUtil.hpp"
#import "object_store.hpp"
#import "sync/partial_sync.hpp"
using namespace realm;
@interface RLMSyncSubscription ()
- (instancetype)initWithName:(NSString *)name results:(Results&)results realm:(RLMRealm *)realm;
@property (nonatomic, readwrite) RLMSyncSubscriptionState state;
@property (nonatomic, readwrite, nullable) NSError *error;
@end
......@@ -39,17 +41,22 @@ using namespace realm;
RLMRealm *_realm;
}
- (instancetype)initWithName:(NSString *)name results:(Results&)results realm:(RLMRealm *)realm {
- (instancetype)initWithName:(NSString *)name results:(Results const&)results realm:(RLMRealm *)realm {
if (!(self = [super init]))
return nil;
_name = [name copy];
_realm = realm;
try {
_subscription = partial_sync::subscribe(results, name ? util::make_optional<std::string>(name.UTF8String) : util::none);
}
catch (std::exception const& e) {
@throw RLMException(e);
}
self.state = (RLMSyncSubscriptionState)_subscription->state();
__weak RLMSyncSubscription *weakSelf = self;
__weak auto weakSelf = self;
_token = _subscription->add_notification_callback([weakSelf] {
RLMSyncSubscription *self = weakSelf;
auto self = weakSelf;
if (!self)
return;
......@@ -66,12 +73,24 @@ using namespace realm;
self.error = nsError;
}
}
else if (self.error != nil)
else if (self.error) {
self.error = nil;
}
auto status = (RLMSyncSubscriptionState)self->_subscription->state();
if (status != self.state)
self.state = (RLMSyncSubscriptionState)status;
if (status != self.state) {
if (status == RLMSyncSubscriptionStateCreating) {
// If a subscription is deleted without going through this
// object's unsubscribe() method the subscription will transition
// back to Creating rather than Invalidated since it doesn't
// have a good way to track that it previously existed
if (self.state != RLMSyncSubscriptionStateInvalidated)
self.state = RLMSyncSubscriptionStateInvalidated;
}
else {
self.state = status;
}
}
});
return self;
......@@ -80,26 +99,173 @@ using namespace realm;
- (void)unsubscribe {
partial_sync::unsubscribe(*_subscription);
}
@end
@interface RLMSyncSubscriptionObject : RLMObjectBase
@end
@implementation RLMSyncSubscriptionObject {
util::Optional<NotificationToken> _token;
Object _obj;
}
- (NSString *)name {
return _row.is_attached() ? RLMStringDataToNSString(_row.get_string(_row.get_column_index("name"))) : nil;
}
- (RLMSyncSubscriptionState)state {
if (!_row.is_attached()) {
return RLMSyncSubscriptionStateInvalidated;
}
return (RLMSyncSubscriptionState)_row.get_int(_row.get_column_index("status"));
}
- (NSError *)error {
if (!_row.is_attached()) {
return nil;
}
StringData err = _row.get_string(_row.get_column_index("error_message"));
if (!err.size()) {
return nil;
}
return [NSError errorWithDomain:RLMErrorDomain
code:RLMErrorFail
userInfo:@{NSLocalizedDescriptionKey: RLMStringDataToNSString(err)}];
}
- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
if (depth == 0) {
return @"<Maximum depth exceeded>";
}
auto objectType = _row.get_string(_row.get_column_index("matches_property"));
objectType = objectType.substr(0, objectType.size() - strlen("_matches"));
return [NSString stringWithFormat:@"RLMSyncSubscription {\n\tname = %@\n\tobjectType = %@\n\tquery = %@\n\tstatus = %@\n\terror = %@\n}",
self.name, RLMStringDataToNSString(objectType),
RLMStringDataToNSString(_row.get_string(_row.get_column_index("query"))),
@(self.state), self.error];
}
- (void)unsubscribe {
if (_row) {
partial_sync::unsubscribe(Object(_realm->_realm, *_info->objectSchema, _row));
}
}
- (void)addObserver:(id)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context {
// Make the `state` property observable by using an object notifier to
// trigger changes. The normal KVO mechanisms don't work for this class due
// to it not being a normal part of the schema.
if (!_token) {
struct {
__weak RLMSyncSubscriptionObject *weakSelf;
void before(realm::CollectionChangeSet const&) {
@autoreleasepool {
[weakSelf willChangeValueForKey:@"state"];
}
}
void after(realm::CollectionChangeSet const&) {
@autoreleasepool {
[weakSelf didChangeValueForKey:@"state"];
}
}
void error(std::exception_ptr) {}
} callback{self};
_obj = Object(_realm->_realm, *_info->objectSchema, _row);
_token = _obj.add_notification_callback(callback);
}
[super addObserver:observer forKeyPath:keyPath options:options context:context];
}