From 97f25cc1ab42e418a0746522319ed6bb8bcf7263 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 27 Jun 2017 23:45:46 +0300 Subject: [PATCH 1/3] Add progress notifications Support download progress notifications for Realm open and openAsync methods --- src/js_realm.hpp | 43 +++++++++++++-- tests/js/session-tests.js | 108 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index a06dad00..9bcf6448 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -159,6 +159,8 @@ public: using ConstructorMap = typename Schema::ConstructorMap; using WaitHandler = void(std::error_code); + using ProgressHandler = void(uint64_t transferred_bytes, uint64_t transferrable_bytes); + static FunctionType create_constructor(ContextType); @@ -559,7 +561,7 @@ void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, Protected protected_callback(ctx, callback_function); Protected protected_this(ctx, this_object); Protected protected_ctx(Context::get_global_context(ctx)); - + EventLoopDispatcher wait_handler([=](std::error_code error_code) { HANDLESCOPE if (!error_code) { @@ -579,13 +581,48 @@ void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, }); std::function waitFunc = std::move(wait_handler); + std::function progressFunc; + auto realm = realm::Realm::get_shared_realm(config); - if (auto sync_config = config.sync_config) { + if (auto sync_config = config.sync_config) + { + static const String progressFuncName = "_onProgress"; + bool progressFuncDefined = false; + if (!Value::is_boolean(ctx, sync_config_value) && !Value::is_undefined(ctx, sync_config_value)) + { + auto sync_config_object = Value::validated_to_object(ctx, sync_config_value); + + ValueType progressFuncValue = Object::get_property(ctx, sync_config_object, progressFuncName); + progressFuncDefined = !Value::is_undefined(ctx, progressFuncValue); + + if (progressFuncDefined) + { + Protected protected_progressCallback(protected_ctx, Value::validated_to_function(protected_ctx, progressFuncValue)); + EventLoopDispatcher progress_handler([=](uint64_t transferred_bytes, uint64_t transferrable_bytes) { + HANDLESCOPE + ValueType callback_arguments[2]; + callback_arguments[0] = Value::from_number(protected_ctx, transferred_bytes); + callback_arguments[1] = Value::from_number(protected_ctx, transferrable_bytes); + + Function::callback(protected_ctx, protected_progressCallback, protected_this, 2, callback_arguments); + }); + + progressFunc = std::move(progress_handler); + } + } + std::shared_ptr user = sync_config->user; if (user && user->state() != SyncUser::State::Error) { if (auto session = user->session_for_on_disk_path(config.path)) { + if (progressFuncDefined) { + session->register_progress_notifier([=](uint64_t transferred_bytes, uint64_t transferrable_bytes) { + progressFunc(transferred_bytes, transferrable_bytes); + }, SyncSession::NotifierType::download, false); + } + + session->wait_for_download_completion([=](std::error_code error_code) { - realm->close(); //capture and keep realm instance for till here + realm->close(); //capture and keep realm instance for until here waitFunc(error_code); }); return; diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index cccbf48f..6d9a7a6e 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -237,6 +237,114 @@ module.exports = { }); }, + testProgressNotificationsForRealmOpen() { + if (!isNodeProccess) { + return Promise.resolve(); + } + + const username = uuid(); + const realmName = uuid(); + const expectedObjectsCount = 3; + + let tmpDir = tmp.dirSync(); + let content = fs.readFileSync(__dirname + '/download-api-helper.js', 'utf8'); + let tmpFile = tmp.fileSync({ dir: tmpDir.name }); + fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); + + return new Promise((resolve, reject) => { + //execute download-api-helper which inserts predefined number of objects into the synced realm. + const child = execFile('node', [tmpFile.name, username, realmName, REALM_MODULE_PATH], { cwd: tmpDir.name }, (error, stdout, stderr) => { + if (error) { + reject(new Error('testProgressNotifications: Error executing download api helper for Realm.open ' + error)); + } + resolve(); + }); + }) + .then(() => { + return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + const accessTokenRefreshed = this; + let successCounter = 0; + let progressNotificationCalled = false; + let config = { + sync: { + user, + url: `realm://localhost:9080/~/${realmName}`, + _onProgress: (transferred, remaining) => { + progressNotificationCalled = true + }, + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; + + return Realm.open(config) + .then(realm => { + }).then(session => { + TestCase.assertTrue(progressNotificationCalled, "Progress notification not called for Realm.open"); + }); + }); + }); + }, + + testProgressNotificationsForRealmOpenAsync() { + if (!isNodeProccess) { + return Promise.resolve(); + } + + const username = uuid(); + const realmName = uuid(); + const expectedObjectsCount = 3; + + let tmpDir = tmp.dirSync(); + let content = fs.readFileSync(__dirname + '/download-api-helper.js', 'utf8'); + let tmpFile = tmp.fileSync({ dir: tmpDir.name }); + fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); + + return new Promise((resolve, reject) => { + //execute download-api-helper which inserts predefined number of objects into the synced realm. + const child = execFile('node', [tmpFile.name, username, realmName, REALM_MODULE_PATH], { cwd: tmpDir.name }, (error, stdout, stderr) => { + if (error) { + reject(new Error('testProgressNotifications: Error executing download api helper for Realm.openAsync ' + error)); + } + resolve(); + }); + }) + .then(() => { + return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return new Promise((resolve, reject) => { + let progressNotificationCalled = false; + let config = { + sync: { user, url: `realm://localhost:9080/~/${realmName}`, + _onProgress: (transferred, remaining) => { + progressNotificationCalled = true + }, + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; + + Realm.openAsync(config, (error, realm) => { + try { + if (error) { + reject(error); + } + + setTimeout(() => { + try { + TestCase.assertTrue(progressNotificationCalled, "Progress notification not called for Realm.openAsync"); + resolve(); + } catch (e) { + reject(e); + } + }, 50); + } + catch (e) { + reject(e); + } + }); + }); + }); + }); + }, + testRealmOpenAsyncNoSchema() { if (!isNodeProccess) { return Promise.resolve(); From 55542a492e6663d78ad1ed3aacc7a28e168b88ea Mon Sep 17 00:00:00 2001 From: blagoev Date: Wed, 28 Jun 2017 13:39:31 +0300 Subject: [PATCH 2/3] refactor code that runs helper out of process as per review comments --- tests/js/session-tests.js | 176 ++++++++++++++------------------------ 1 file changed, 64 insertions(+), 112 deletions(-) diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 6d9a7a6e..bf1e753f 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -74,6 +74,23 @@ function promisifiedLogin(server, username, password) { }); } +function runOutOfProcess(nodeJsFilePath) { + var nodeArgs = Array.prototype.slice.call(arguments); + let tmpDir = tmp.dirSync(); + let content = fs.readFileSync(nodeJsFilePath, 'utf8'); + let tmpFile = tmp.fileSync({ dir: tmpDir.name }); + fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); + nodeArgs[0] = tmpFile.name; + return new Promise((resolve, reject) => { + const child = execFile('node', nodeArgs, { cwd: tmpDir.name }, (error, stdout, stderr) => { + if (error) { + reject(new Error(`Error executing ${nodeJsFilePath} Error: ${error}`)); + } + resolve(); + }); + }) +} + module.exports = { testLocalRealmHasNoSession() { @@ -131,44 +148,31 @@ module.exports = { const realmName = uuid(); const expectedObjectsCount = 3; - let tmpDir = tmp.dirSync(); - let content = fs.readFileSync(__dirname + '/download-api-helper.js', 'utf8'); - let tmpFile = tmp.fileSync({ dir: tmpDir.name }); - fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); + runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + const accessTokenRefreshed = this; + let successCounter = 0; - return new Promise((resolve, reject) => { - //execute download-api-helper which inserts predefined number of objects into the synced realm. - const child = execFile('node', [tmpFile.name, username, realmName, REALM_MODULE_PATH], { cwd: tmpDir.name }, (error, stdout, stderr) => { - if (error) { - reject(new Error('Error executing download api helper' + error)); - } - resolve(); + let config = { + sync: { user, url: `realm://localhost:9080/~/${realmName}` }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; + + return Realm.open(config) + .then(realm => { + let actualObjectsCount = realm.objects('Dog').length; + TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); + return realm.syncSession; + }).then(session => { + TestCase.assertInstanceOf(session, Realm.Sync.Session); + TestCase.assertEqual(session.user.identity, user.identity); + TestCase.assertEqual(session.config.url, config.sync.url); + TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); + TestCase.assertEqual(session.state, 'active'); + }); + }); }); - }) - .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { - const accessTokenRefreshed = this; - let successCounter = 0; - - let config = { - sync: { user, url: `realm://localhost:9080/~/${realmName}` }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; - - return Realm.open(config) - .then(realm => { - let actualObjectsCount = realm.objects('Dog').length; - TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); - return realm.syncSession; - }).then(session => { - TestCase.assertInstanceOf(session, Realm.Sync.Session); - TestCase.assertEqual(session.user.identity, user.identity); - TestCase.assertEqual(session.config.url, config.sync.url); - TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); - TestCase.assertEqual(session.state, 'active'); - }); - }); - }); }, testRealmOpenAsync() { @@ -180,20 +184,7 @@ module.exports = { const realmName = uuid(); const expectedObjectsCount = 3; - let tmpDir = tmp.dirSync(); - let content = fs.readFileSync(__dirname + '/download-api-helper.js', 'utf8'); - let tmpFile = tmp.fileSync({ dir: tmpDir.name }); - fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); - - return new Promise((resolve, reject) => { - //execute download-api-helper which inserts predefined number of objects into the synced realm. - const child = execFile('node', [tmpFile.name, username, realmName, REALM_MODULE_PATH], { cwd: tmpDir.name }, (error, stdout, stderr) => { - if (error) { - reject(new Error('Error executing download api helper' + error)); - } - resolve(); - }); - }) + runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { @@ -246,43 +237,30 @@ module.exports = { const realmName = uuid(); const expectedObjectsCount = 3; - let tmpDir = tmp.dirSync(); - let content = fs.readFileSync(__dirname + '/download-api-helper.js', 'utf8'); - let tmpFile = tmp.fileSync({ dir: tmpDir.name }); - fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); - - return new Promise((resolve, reject) => { - //execute download-api-helper which inserts predefined number of objects into the synced realm. - const child = execFile('node', [tmpFile.name, username, realmName, REALM_MODULE_PATH], { cwd: tmpDir.name }, (error, stdout, stderr) => { - if (error) { - reject(new Error('testProgressNotifications: Error executing download api helper for Realm.open ' + error)); - } - resolve(); - }); - }) - .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { - const accessTokenRefreshed = this; - let successCounter = 0; - let progressNotificationCalled = false; - let config = { - sync: { - user, - url: `realm://localhost:9080/~/${realmName}`, - _onProgress: (transferred, remaining) => { - progressNotificationCalled = true + runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + const accessTokenRefreshed = this; + let successCounter = 0; + let progressNotificationCalled = false; + let config = { + sync: { + user, + url: `realm://localhost:9080/~/${realmName}`, + _onProgress: (transferred, remaining) => { + progressNotificationCalled = true + }, }, - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - return Realm.open(config) - .then(realm => { - }).then(session => { - TestCase.assertTrue(progressNotificationCalled, "Progress notification not called for Realm.open"); - }); + return Realm.open(config) + .then(realm => { + }).then(session => { + TestCase.assertTrue(progressNotificationCalled, "Progress notification not called for Realm.open"); + }); + }); }); - }); }, testProgressNotificationsForRealmOpenAsync() { @@ -294,20 +272,7 @@ module.exports = { const realmName = uuid(); const expectedObjectsCount = 3; - let tmpDir = tmp.dirSync(); - let content = fs.readFileSync(__dirname + '/download-api-helper.js', 'utf8'); - let tmpFile = tmp.fileSync({ dir: tmpDir.name }); - fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); - - return new Promise((resolve, reject) => { - //execute download-api-helper which inserts predefined number of objects into the synced realm. - const child = execFile('node', [tmpFile.name, username, realmName, REALM_MODULE_PATH], { cwd: tmpDir.name }, (error, stdout, stderr) => { - if (error) { - reject(new Error('testProgressNotifications: Error executing download api helper for Realm.openAsync ' + error)); - } - resolve(); - }); - }) + runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { @@ -354,20 +319,7 @@ module.exports = { const realmName = uuid(); const expectedObjectsCount = 3; - let tmpDir = tmp.dirSync(); - let content = fs.readFileSync(__dirname + '/download-api-helper.js', 'utf8'); - let tmpFile = tmp.fileSync({ dir: tmpDir.name }); - fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); - - return new Promise((resolve, reject) => { - //execute download-api-helper which inserts predefined number of objects into the synced realm. - const child = execFile('node', [tmpFile.name, username, realmName, REALM_MODULE_PATH], { cwd: tmpDir.name }, (error, stdout, stderr) => { - if (error) { - reject(new Error('Error executing download api helper' + error)); - } - resolve(); - }); - }) + runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { From f54487c6cca38e1b30dad61160d5ed39524c1572 Mon Sep 17 00:00:00 2001 From: blagoev Date: Wed, 28 Jun 2017 15:02:28 +0300 Subject: [PATCH 3/3] Rename the progress function to better reflect purpose refactored registration code to remove the unneccesary lambda --- src/js_realm.hpp | 9 +++------ tests/js/session-tests.js | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 9bcf6448..7614667d 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -586,7 +586,7 @@ void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, auto realm = realm::Realm::get_shared_realm(config); if (auto sync_config = config.sync_config) { - static const String progressFuncName = "_onProgress"; + static const String progressFuncName = "_onDownloadProgress"; bool progressFuncDefined = false; if (!Value::is_boolean(ctx, sync_config_value) && !Value::is_undefined(ctx, sync_config_value)) { @@ -615,11 +615,8 @@ void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, if (user && user->state() != SyncUser::State::Error) { if (auto session = user->session_for_on_disk_path(config.path)) { if (progressFuncDefined) { - session->register_progress_notifier([=](uint64_t transferred_bytes, uint64_t transferrable_bytes) { - progressFunc(transferred_bytes, transferrable_bytes); - }, SyncSession::NotifierType::download, false); - } - + session->register_progress_notifier(std::move(progressFunc), SyncSession::NotifierType::download, false); + } session->wait_for_download_completion([=](std::error_code error_code) { realm->close(); //capture and keep realm instance for until here diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index bf1e753f..2a27b354 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -247,7 +247,7 @@ module.exports = { sync: { user, url: `realm://localhost:9080/~/${realmName}`, - _onProgress: (transferred, remaining) => { + _onDownloadProgress: (transferred, remaining) => { progressNotificationCalled = true }, }, @@ -279,7 +279,7 @@ module.exports = { let progressNotificationCalled = false; let config = { sync: { user, url: `realm://localhost:9080/~/${realmName}`, - _onProgress: (transferred, remaining) => { + _onDownloadProgress: (transferred, remaining) => { progressNotificationCalled = true }, },