Client reset (#1341)

This commit is contained in:
Kenneth Geisshirt 2017-10-02 19:44:24 +02:00 committed by GitHub
parent 33c4a886d0
commit fe121ea27b
6 changed files with 76 additions and 5 deletions

View File

@ -5,6 +5,7 @@ X.Y.Z-rc Release notes
### Enhancements
* Support migration from Realms sync 1.0 to sync 2.0 versions
* Handling of the situation when the client has to reset the Realm due to diverging histories (#795).
### Bug fixes
* None

View File

@ -62,7 +62,22 @@ class Sync {
*/
static setLogLevel(log_level) {}
/**
* Initiate a client reset. The Realm must be closed prior to the reset.
* @param {string} [path] - The Realm to reset.
* Throws error if reset is not possible.
* @example
* {
* const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } };
* config.sync.error = (sender, error) => {
* if (error.code === 7) { // 7 -> client reset
* Realm.Sync.initiateClientReset(original_path);
* // copy required objects from Realm at error.config.path
* }
* }
* }
*/
static initiateClientReset(path) {}
}
/**

View File

@ -79,7 +79,7 @@ module.exports = function(realmConstructor) {
openAsync(config, callback, progressCallback) {
const message = "Realm.openAsync is now deprecated in favor of Realm.open. This function will be removed in future versions.";
(console.warn || console.log).call(console, message);
realmConstructor._waitForDownload(config,
(syncSession) => {
if (progressCallback) {

1
lib/index.d.ts vendored
View File

@ -415,6 +415,7 @@ declare namespace Realm.Sync {
function removeAllListeners(name?: string): void;
function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): void;
function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void;
function initiateClientReset(path: string): void;
function setFeatureToken(token: string): void;
/**

View File

@ -229,10 +229,21 @@ public:
HANDLESCOPE
auto error_object = Object<T>::create_empty(m_ctx);
auto error_code = error.error_code.value();
if (error.is_client_reset_requested()) {
error_code = 7; // FIXME: define a proper constant
auto config_object = Object<T>::create_empty(m_ctx);
Object<T>::set_property(m_ctx, config_object, "path", Value<T>::from_string(m_ctx, error.user_info[SyncError::c_recovery_file_path_key]));
Object<T>::set_property(m_ctx, config_object, "readOnly", Value<T>::from_boolean(m_ctx, true));
Object<T>::set_property(m_ctx, error_object, "config", config_object);
}
Object<T>::set_property(m_ctx, error_object, "message", Value<T>::from_string(m_ctx, error.message));
Object<T>::set_property(m_ctx, error_object, "isFatal", Value<T>::from_boolean(m_ctx, error.is_fatal));
Object<T>::set_property(m_ctx, error_object, "category", Value<T>::from_string(m_ctx, error.error_code.category().name()));
Object<T>::set_property(m_ctx, error_object, "code", Value<T>::from_number(m_ctx, error.error_code.value()));
Object<T>::set_property(m_ctx, error_object, "code", Value<T>::from_number(m_ctx, error_code));
auto user_info = Object<T>::create_empty(m_ctx);
for (auto& kvp : error.user_info) {
@ -478,9 +489,7 @@ void SessionClass<T>::add_progress_notification(ContextType ctx, FunctionType, O
progressFunc = std::move(progress_handler);
auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, is_streaming);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete;
Object::set_property(ctx, callback_function, "_syncSession", syncSession, attributes);
@ -525,6 +534,7 @@ public:
static FunctionType create_constructor(ContextType);
static void set_sync_log_level(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void initiate_client_reset(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
// private
static std::function<SyncBindSessionHandler> session_bind_callback(ContextType ctx, ObjectType sync_constructor);
@ -535,6 +545,7 @@ public:
MethodMap<T> const static_methods = {
{"setLogLevel", wrap<set_sync_log_level>},
{"initiateClientReset", wrap<initiate_client_reset>},
};
};
@ -549,6 +560,15 @@ inline typename T::Function SyncClass<T>::create_constructor(ContextType ctx) {
return sync_constructor;
}
template<typename T>
void SyncClass<T>::initiate_client_reset(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue & return_value) {
validate_argument_count(argc, 1);
std::string path = Value::validated_to_string(ctx, arguments[0]);
if (!SyncManager::shared().immediately_run_file_actions(std::string(path))) {
throw std::runtime_error(util::format("Realm was not configured correctly. Client Reset could not be run for Realm at: %1", path));
}
}
template<typename T>
void SyncClass<T>::set_sync_log_level(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);

View File

@ -780,4 +780,38 @@ module.exports = {
});
});
},
testClientReset() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return Promise.resolve();
}
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
return new Promise((resolve, _reject) => {
var realm;
const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } };
config.sync.error = (sender, error) => {
try {
TestCase.assertEqual(error.code, 7); // 7 -> client reset
TestCase.assertDefined(error.config);
TestCase.assertNotEqual(error.config.path, '');
const original_path = realm.path;
realm.close();
Realm.Sync.initiateClientReset(original_path);
// copy required objects from Realm at error.config.path
resolve();
}
catch (e) {
_reject(e);
}
};
realm = new Realm(config);
const session = realm.syncSession;
TestCase.assertEqual(session.config.error, config.sync.error);
session._simulateError(211, 'ClientReset'); // 211 -> divering histories
});
});
}
}