diff --git a/CHANGELOG.md b/CHANGELOG.md index ab07856029..293168a080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +3.5.0 (TBD) +------------------ + +### Enhancements +* Added `Session.Start()` and `Session.Stop()` methods that allow you to pause/resume synchronization with the Realm Object Server. ([Issue #138](https://github.com/realm/realm-dotnet-private/issues/138)) + +### Fixed +* None + +### Compatibility +* Realm Object Server: 3.11.0 or later. + 3.4.0 (2019-01-09) ------------------ @@ -7,6 +19,8 @@ NOTE!!! You will need to upgrade your Realm Object Server to at least version 3. * Download progress is now reported to the server, even when there are no local changes. This allows the server to do history compaction much more aggressively, especially when there are many clients that rarely or never make local changes. ([#1772](https://github.com/realm/realm-dotnet/pull/1772)) * Reduce memory usage when integrating synchronized changes sent by ROS. * Added ability to supply a custom log function for handling logs emitted by Sync by specifying `SyncConfigurationBase.CustomLogger`. It must be set before opening a synchronized Realm. ([#1824](https://github.com/realm/realm-dotnet/pull/1824)) +* Clients using protocol 25 now report download progress to the server, even when they make no local changes. This allows the server to do history compaction much more aggressively, especially when there are many clients that rarely or never make local changes. ([#1772](https://github.com/realm/realm-dotnet/pull/1772)) +* Add a User-Agent header to HTTP requests made to the Realm Object Server. By default, this contains information about the Realm library version and .NET platform. Additional details may be provided (such as the application name/version) by setting `SyncConfigurationBase.UserAgent` prior to opening a synchronized Realm. If developing a Xamarin app, you can use the Xamarin.Essentials plugin to automate that: `SyncConfiguration.UserAgent = $"{AppInfo.Name} ({AppInfo.PackageName} {AppInfo.VersionString})"`. ### Fixed * Fixed a bug that could lead to crashes with a message such as `Assertion failed: ndx < size() with (ndx, size()) = [742, 742]`. @@ -17,10 +31,6 @@ NOTE!!! You will need to upgrade your Realm Object Server to at least version 3. * Realm Object Server: 3.11.0 or later. The sync protocol version has been bumped to version 25. The server is backwards-compatible with clients using protocol version 24 or below, but clients at version 25 are not backwards-compatible with a server at protocol version 24. The server must be upgraded before any clients are upgraded. -### Enahancements -* Clients using protocol 25 now report download progress to the server, even when they make no local changes. This allows the server to do history compaction much more aggressively, especially when there are many clients that rarely or never make local changes. ([#1772](https://github.com/realm/realm-dotnet/pull/1772)) -* Add a User-Agent header to HTTP requests made to the Realm Object Server. By default, this contains information about the Realm library version and .NET platform. Additional details may be provided (such as the application name/version) by setting `SyncConfigurationBase.UserAgent` prior to opening a synchronized Realm. If developing a Xamarin app, you can use the Xamarin.Essentials plugin to automate that: `SyncConfiguration.UserAgent = $"{AppInfo.Name} ({AppInfo.PackageName} {AppInfo.VersionString})"`. - ### Fixed diff --git a/Platform.PCL/Realm.Sync.PCL/SessionPCL.cs b/Platform.PCL/Realm.Sync.PCL/SessionPCL.cs index 7427dd9249..4d80a1a32a 100644 --- a/Platform.PCL/Realm.Sync.PCL/SessionPCL.cs +++ b/Platform.PCL/Realm.Sync.PCL/SessionPCL.cs @@ -157,5 +157,31 @@ public Task WaitForDownloadAsync() RealmPCLHelpers.ThrowProxyShouldNeverBeUsed(); return null; } + + /// + /// Stops any synchronization with the Realm Object Server until the Realm is re-opened again + /// after fully closing it. + ///
+ /// Synchronization can be re-enabled by calling again. + ///
+ /// + /// If the session is already stopped, calling this method will do nothing. + /// + public void Stop() + { + RealmPCLHelpers.ThrowProxyShouldNeverBeUsed(); + } + + /// + /// Attempts to resume the session and enable synchronization with the Realm Object Server. + /// + /// + /// All sessions will be active by default and calling this method only makes sense if + /// was called before that. + /// + public void Start() + { + RealmPCLHelpers.ThrowProxyShouldNeverBeUsed(); + } } } diff --git a/Realm/Realm.Sync/Handles/SessionHandle.cs b/Realm/Realm.Sync/Handles/SessionHandle.cs index 6c09b5f2a7..7489180027 100644 --- a/Realm/Realm.Sync/Handles/SessionHandle.cs +++ b/Realm/Realm.Sync/Handles/SessionHandle.cs @@ -69,6 +69,12 @@ public static extern ulong register_progress_notifier(SessionHandle session, [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_report_error_for_testing", CallingConvention = CallingConvention.Cdecl)] public static extern void report_error_for_testing(SessionHandle session, int error_code, [MarshalAs(UnmanagedType.LPWStr)] string message, IntPtr message_len, [MarshalAs(UnmanagedType.I1)] bool is_fatal); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_stop", CallingConvention = CallingConvention.Cdecl)] + public static extern void stop(SessionHandle session, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_start", CallingConvention = CallingConvention.Cdecl)] + public static extern void start(SessionHandle session, out NativeException ex); } static SessionHandle() @@ -157,6 +163,18 @@ public void ReportErrorForTesting(int errorCode, string errorMessage, bool isFat NativeMethods.report_error_for_testing(this, errorCode, errorMessage, (IntPtr)errorMessage.Length, isFatal); } + public void Stop() + { + NativeMethods.stop(this, out var ex); + ex.ThrowIfNecessary(); + } + + public void Start() + { + NativeMethods.start(this, out var ex); + ex.ThrowIfNecessary(); + } + public static SessionHandle GetSessionForPath(string path) { var ptr = NativeMethods.get_from_path(path, (IntPtr)path.Length, out var ex); diff --git a/Realm/Realm.Sync/Session.cs b/Realm/Realm.Sync/Session.cs index f53f6aa445..dedd4a8b60 100644 --- a/Realm/Realm.Sync/Session.cs +++ b/Realm/Realm.Sync/Session.cs @@ -136,6 +136,32 @@ public Task WaitForDownloadAsync() return tcs.Task; } + /// + /// Stops any synchronization with the Realm Object Server until the Realm is re-opened again + /// after fully closing it. + ///
+ /// Synchronization can be re-enabled by calling again. + ///
+ /// + /// If the session is already stopped, calling this method will do nothing. + /// + public void Stop() + { + Handle.Stop(); + } + + /// + /// Attempts to resume the session and enable synchronization with the Realm Object Server. + /// + /// + /// All sessions will be active by default and calling this method only makes sense if + /// was called before that. + /// + public void Start() + { + Handle.Start(); + } + internal readonly SessionHandle Handle; internal Session(SessionHandle handle) diff --git a/Tests/Tests.Sync.Shared/SessionTests.cs b/Tests/Tests.Sync.Shared/SessionTests.cs index 829efe7f07..e24524b93e 100644 --- a/Tests/Tests.Sync.Shared/SessionTests.cs +++ b/Tests/Tests.Sync.Shared/SessionTests.cs @@ -286,6 +286,57 @@ public void Session_ProgressObservable_IntegrationTests(ProgressMode mode) }); } + [Test] + public void Session_Stop_StopsSession() + { + AsyncContext.Run(async () => + { + // OpenRealmAndStopSession will call Stop and assert the state changed + await OpenRealmAndStopSession(); + }); + } + + [Test] + public void Session_Start_ResumesSession() + { + AsyncContext.Run(async () => + { + var session = await OpenRealmAndStopSession(); + + session.Start(); + Assert.That(session.State, Is.EqualTo(SessionState.Active)); + }); + } + + [Test] + public void Session_Stop_IsIdempotent() + { + AsyncContext.Run(async () => + { + var session = await OpenRealmAndStopSession(); + + // Stop it again + session.Stop(); + Assert.That(session.State, Is.EqualTo(SessionState.Inactive)); + }); + } + + [Test] + public void Session_Start_IsIdempotent() + { + AsyncContext.Run(async () => + { + var session = await OpenRealmAndStopSession(); + + session.Start(); + Assert.That(session.State, Is.EqualTo(SessionState.Active)); + + // Start it again + session.Start(); + Assert.That(session.State, Is.EqualTo(SessionState.Active)); + }); + } + [TestCase(ProgressDirection.Upload, ProgressMode.ReportIndefinitely)] [TestCase(ProgressDirection.Download, ProgressMode.ReportIndefinitely)] [TestCase(ProgressDirection.Upload, ProgressMode.ForCurrentlyOutstandingWork)] @@ -519,5 +570,24 @@ public void Session_RXThrottleTests() } }); } + + /// + /// Opens a random realm and calls session.Stop(). It will assert state changes + /// to Inactive. + /// + /// The stopped session. + private async Task OpenRealmAndStopSession() + { + var config = await SyncTestHelpers.GetFakeConfigAsync(); + var realm = GetRealm(config); + var session = GetSession(realm); + + Assert.That(session.State, Is.EqualTo(SessionState.Active)); + + session.Stop(); + Assert.That(session.State, Is.EqualTo(SessionState.Inactive)); + + return session; + } } } diff --git a/Tests/Tests.Sync.Shared/SyncTestHelpers.cs b/Tests/Tests.Sync.Shared/SyncTestHelpers.cs index 4ac624ca5b..8829399e1b 100644 --- a/Tests/Tests.Sync.Shared/SyncTestHelpers.cs +++ b/Tests/Tests.Sync.Shared/SyncTestHelpers.cs @@ -128,7 +128,7 @@ public static Task GetAdminUserAsync() public static async Task GetFakeConfigAsync(string userId = null, string optionalPath = null) { var user = await GetFakeUserAsync(userId); - var serverUri = new Uri("realm://localhost:9080/foobar"); + var serverUri = new Uri($"realm://localhost:9080/{Guid.NewGuid().ToString()}"); return new FullSyncConfiguration(serverUri, user, optionalPath); } diff --git a/wrappers/src/sync_session_cs.cpp b/wrappers/src/sync_session_cs.cpp index c62723f361..fc9e7f424d 100644 --- a/wrappers/src/sync_session_cs.cpp +++ b/wrappers/src/sync_session_cs.cpp @@ -182,5 +182,19 @@ REALM_EXPORT void realm_syncsession_report_error_for_testing(const SharedSyncSes SyncSession::OnlyForTesting::handle_error(*session, SyncError{error_code, std::move(message), is_fatal}); } +REALM_EXPORT void realm_syncsession_stop(const SharedSyncSession& session, NativeException::Marshallable& ex) +{ + handle_errors(ex, [&] { + session->log_out(); + }); +} + +REALM_EXPORT void realm_syncsession_start(const SharedSyncSession& session, NativeException::Marshallable& ex) +{ + handle_errors(ex, [&] { + session->revive_if_needed(); + }); +} + }