Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose base file path and base uri on App #3385

Merged
merged 4 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
## vNext (TBD)

### Enhancements
* None
* Added `App.BaseFilePath`, `App.BaseUri`, and `App.Id` properties that return the values supplied in `AppConfiguration`. (PR [#3385](https://github.com/realm/realm-dotnet/pull/3385))
* Added `AppConfiguration.UseAppCache` property that controls whether the `App` instance returned from `App.Create` should be cached or not. The general recommendation is to not set it (i.e. leave the default value of `true`), but it can be useful when writing unit tests. (Issue [#3382](https://github.com/realm/realm-dotnet/issues/3382)).

### Fixed
* None
* Fixed the implementation `App.Equals` and `App.GetHashCode` to return correct results, particularly when the `App` instance is cached. (PR [#3385](https://github.com/realm/realm-dotnet/pull/3385))

### Compatibility
* Realm Studio: 13.0.0 or later.
Expand Down
2 changes: 1 addition & 1 deletion Realm/Realm/Configurations/PartitionSyncConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private PartitionSyncConfiguration(User user, RealmValue partition, string? path
internal override IDisposable? OnBeforeRealmOpen(AsyncOpenTaskHandle handle)
{
var onProgress = OnProgress;
if (onProgress == null)
if (onProgress is null)
{
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion Realm/Realm/Configurations/SyncConfigurationBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ internal virtual Native.SyncConfiguration CreateNativeSyncConfiguration()
{
SyncUserHandle = User.Handle,
session_stop_policy = SessionStopPolicy,
schema_mode = _schema == null ? SchemaMode.AdditiveDiscovered : SchemaMode.AdditiveExplicit,
schema_mode = _schema is null ? SchemaMode.AdditiveDiscovered : SchemaMode.AdditiveExplicit,
client_resync_mode = ClientResetHandler.ClientResetMode,
cancel_waits_on_nonfatal_error = CancelAsyncOperationsOnNonFatalErrors,
};
Expand Down
2 changes: 1 addition & 1 deletion Realm/Realm/DatabaseTypes/RealmValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ public Guid AsGuid()
private RealmInteger<T> AsRealmInteger<T>(T value)
where T : struct, IComparable<T>, IFormattable, IConvertible, IEquatable<T>
{
if (_objectHandle == null)
if (_objectHandle is null)
{
return new(value);
}
Expand Down
43 changes: 43 additions & 0 deletions Realm/Realm/Handles/AppHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,19 @@ public static extern IntPtr get_user_for_testing(
[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_clear_cached_apps", CallingConvention = CallingConvention.Cdecl)]
public static extern void clear_cached_apps(out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_base_file_path", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr get_base_file_path(AppHandle app, IntPtr buffer, IntPtr buffer_length, out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_base_uri", CallingConvention = CallingConvention.Cdecl)]
public static extern StringValue get_base_uri(AppHandle app, out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_get_id", CallingConvention = CallingConvention.Cdecl)]
public static extern StringValue get_id(AppHandle app, out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_is_same_instance", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.U1)]
public static extern bool is_same_instance(AppHandle lhs, AppHandle rhs, out NativeException ex);

public static class EmailPassword
{
[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_register_user", CallingConvention = CallingConvention.Cdecl)]
Expand Down Expand Up @@ -349,6 +362,36 @@ public SyncUserHandle GetUserForTesting(string id, string refreshToken, string a
return new SyncUserHandle(result);
}

public string GetBaseFilePath()
{
return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) =>
{
isNull = false;
return NativeMethods.get_base_file_path(this, buffer, length, out ex);
})!;
}

public Uri GetBaseUri()
{
var value = NativeMethods.get_base_uri(this, out var ex);
ex.ThrowIfNecessary();
return new Uri(value!);
}

public string GetId()
{
var value = NativeMethods.get_id(this, out var ex);
ex.ThrowIfNecessary();
return value!;
}

public bool IsSameInstance(AppHandle other)
{
var result = NativeMethods.is_same_instance(this, other, out var ex);
ex.ThrowIfNecessary();
return result;
}

protected override void Unbind() => NativeMethods.destroy(handle);

[MonoPInvokeCallback(typeof(NativeMethods.UserCallback))]
Expand Down
6 changes: 3 additions & 3 deletions Realm/Realm/Handles/SessionHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ private static IntPtr NotifyBeforeClientReset(IntPtr beforeFrozen, IntPtr manage
}
catch (Exception ex)
{
var handlerType = syncConfig == null ? "ClientResetHandler" : syncConfig.ClientResetHandler.GetType().Name;
var handlerType = syncConfig is null ? "ClientResetHandler" : syncConfig.ClientResetHandler.GetType().Name;
Logger.Default.Log(LogLevel.Error, $"An error has occurred while executing {handlerType}.OnBeforeReset during a client reset: {ex}");

var exHandle = GCHandle.Alloc(ex);
Expand Down Expand Up @@ -380,7 +380,7 @@ private static IntPtr NotifyAfterClientReset(IntPtr beforeFrozen, IntPtr after,
}
catch (Exception ex)
{
var handlerType = syncConfig == null ? "ClientResetHandler" : syncConfig.ClientResetHandler.GetType().Name;
var handlerType = syncConfig is null ? "ClientResetHandler" : syncConfig.ClientResetHandler.GetType().Name;
Logger.Default.Log(LogLevel.Error, $"An error has occurred while executing {handlerType}.OnAfterReset during a client reset: {ex}");

var exHandle = GCHandle.Alloc(ex);
Expand Down Expand Up @@ -429,7 +429,7 @@ private static void HandleSessionPropertyChangedCallback(IntPtr managedSessionHa
_ => throw new NotSupportedException($"Unexpected notifiable property value: {property}")
};
var session = (Session)GCHandle.FromIntPtr(managedSessionHandle).Target!;
if (session == null)
if (session is null)
{
// We're taking a weak handle to the session, so it's possible that it's been collected
return;
Expand Down
4 changes: 2 additions & 2 deletions Realm/Realm/Handles/SyncUserHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,9 @@ public async Task<ApiKey> CreateApiKeyAsync(AppHandle app, string name)

var result = await tcs.Task;

Debug.Assert(result == null || result.Length <= 1, "The result of the fetch operation should be either null, or an array of 0 or 1 elements.");
Debug.Assert(result is null || result.Length <= 1, "The result of the fetch operation should be either null, or an array of 0 or 1 elements.");

return result == null || result.Length == 0 ? null : result.Single();
return result is null || result.Length == 0 ? null : result.Single();
}
finally
{
Expand Down
3 changes: 3 additions & 0 deletions Realm/Realm/Native/AppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,8 @@ internal MetadataPersistenceMode? MetadataPersistence
internal UInt64 sync_pong_keep_alive_timeout_ms;

internal UInt64 sync_fast_reconnect_limit;

[MarshalAs(UnmanagedType.U1)]
internal bool use_cache;
}
}
2 changes: 1 addition & 1 deletion Realm/Realm/Native/PrimitiveValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public static PrimitiveValue Object(ObjectHandle handle)
{
return new PrimitiveValue
{
Type = handle == null ? RealmValueType.Null : RealmValueType.Object,
Type = handle is null ? RealmValueType.Null : RealmValueType.Object,
link_value = new LinkValue
{
object_ptr = handle?.DangerousGetHandle() ?? IntPtr.Zero
Expand Down
2 changes: 1 addition & 1 deletion Realm/Realm/Native/SynchronizationContextScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ internal void Invalidate()
private static IntPtr GetCurrentSynchronizationContext()
{
var context = SynchronizationContext.Current;
if (context == null)
if (context is null)
{
return IntPtr.Zero;
}
Expand Down
21 changes: 8 additions & 13 deletions Realm/Realm/Realm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,12 @@ public Session SyncSession
throw new NotSupportedException("Realm.SyncSession is only valid for synchronized Realms (i.e. ones that are opened with FlexibleSyncConfiguration or PartitionSyncConfiguration).");
}

if (_sessionRef == null || !_sessionRef.TryGetTarget(out var session) || session.IsClosed)
if (_sessionRef is null || !_sessionRef.TryGetTarget(out var session) || session.IsClosed)
{
var sessionHandle = SharedRealmHandle.GetSession();
session = new Session(sessionHandle);

if (_sessionRef == null)
if (_sessionRef is null)
{
_sessionRef = new WeakReference<Session>(session);
}
Expand Down Expand Up @@ -318,7 +318,7 @@ internal Realm(SharedRealmHandle sharedRealmHandle, RealmConfigurationBase confi
}
}

if (state == null)
if (state is null)
{
state = new State();
sharedRealmHandle.SetManagedStateHandle(state);
Expand All @@ -341,7 +341,7 @@ private Metadata CreateRealmObjectMetadata(ObjectSchema schema)
if (schema.Type != null && !Config.IsDynamic)
{
var wovenAtt = schema.Type.GetCustomAttribute<WovenAttribute>();
if (wovenAtt == null)
if (wovenAtt is null)
{
throw new RealmException($"Fody not properly installed. {schema.Type.FullName} is a RealmObjectBase but has not been woven.");
}
Expand Down Expand Up @@ -408,7 +408,7 @@ private void NotifyChanged(EventArgs e)

internal void NotifyError(Exception ex)
{
if (Error == null)
if (Error is null)
{
Logger.LogDefault(LogLevel.Error, "A realm-level exception has occurred. To handle and react to those, subscribe to the Realm.Error event.");
}
Expand Down Expand Up @@ -486,7 +486,7 @@ private void ThrowIfFrozen(string message)

private bool Equals(Realm? other)
{
if (other == null)
if (other is null)
{
return false;
}
Expand Down Expand Up @@ -758,7 +758,7 @@ public Transaction BeginWrite()

SharedRealmHandle.BeginTransaction();

Debug.Assert(_activeTransaction == null, "There should be no active transaction");
Debug.Assert(_activeTransaction is null, "There should be no active transaction");
return _activeTransaction = new Transaction(this);
}

Expand Down Expand Up @@ -878,7 +878,7 @@ public async Task<Transaction> BeginWriteAsync(CancellationToken cancellationTok

await SharedRealmHandle.BeginTransactionAsync(synchronizationContext, cancellationToken);

Debug.Assert(_activeTransaction == null, "There should be no active transaction");
Debug.Assert(_activeTransaction is null, "There should be no active transaction");
return _activeTransaction = new Transaction(this);
}

Expand Down Expand Up @@ -1344,11 +1344,6 @@ internal void DrainTransactionQueue()

internal void ExecuteOutsideTransaction(Action action)
{
if (action == null)
{
return;
}

if (IsInTransaction)
{
_state.AfterTransactionQueue.Enqueue(action);
Expand Down
10 changes: 5 additions & 5 deletions Realm/Realm/Schema/RealmSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ internal static RealmSchema CreateFromObjectStoreSchema(Native.Schema nativeSche
/// <c>null</c> if <paramref name="objects"/> is <c>null</c>; a <see cref="RealmSchema"/> containing the supplied <see cref="ObjectSchema"/>s otherwise.
/// </returns>
[return: NotNullIfNotNull("objects")]
public static implicit operator RealmSchema?(ObjectSchema[]? objects) => objects == null ? null : new Builder(objects).Build();
public static implicit operator RealmSchema?(ObjectSchema[]? objects) => objects is null ? null : new Builder(objects).Build();

/// <summary>
/// Constructs a <see cref="RealmSchema"/> from a list of <see cref="ObjectSchema"/> instances.
Expand All @@ -206,7 +206,7 @@ internal static RealmSchema CreateFromObjectStoreSchema(Native.Schema nativeSche
/// <c>null</c> if <paramref name="objects"/> is <c>null</c>; a <see cref="RealmSchema"/> containing the supplied <see cref="ObjectSchema"/>s otherwise.
/// </returns>
[return: NotNullIfNotNull("objects")]
public static implicit operator RealmSchema?(List<ObjectSchema>? objects) => objects == null ? null : new Builder(objects).Build();
public static implicit operator RealmSchema?(List<ObjectSchema>? objects) => objects is null ? null : new Builder(objects).Build();

/// <summary>
/// Constructs a <see cref="RealmSchema"/> from an array of <see cref="Type"/> instances.
Expand All @@ -217,7 +217,7 @@ internal static RealmSchema CreateFromObjectStoreSchema(Native.Schema nativeSche
/// </returns>
/// <seealso cref="Builder.Add(Type)"/>
[return: NotNullIfNotNull("objects")]
public static implicit operator RealmSchema?(Type[]? objects) => objects == null ? null : new Builder(objects).Build();
public static implicit operator RealmSchema?(Type[]? objects) => objects is null ? null : new Builder(objects).Build();

/// <summary>
/// Constructs a <see cref="RealmSchema"/> from a List of <see cref="Type"/> instances.
Expand All @@ -228,7 +228,7 @@ internal static RealmSchema CreateFromObjectStoreSchema(Native.Schema nativeSche
/// </returns>
/// <seealso cref="Builder.Add(Type)"/>
[return: NotNullIfNotNull("objects")]
public static implicit operator RealmSchema?(List<Type>? objects) => objects == null ? null : new Builder(objects).Build();
public static implicit operator RealmSchema?(List<Type>? objects) => objects is null ? null : new Builder(objects).Build();

/// <summary>
/// Constructs a <see cref="RealmSchema"/> from a HashSet of <see cref="Type"/> instances.
Expand All @@ -239,7 +239,7 @@ internal static RealmSchema CreateFromObjectStoreSchema(Native.Schema nativeSche
/// </returns>
/// <seealso cref="Builder.Add(Type)"/>
[return: NotNullIfNotNull("objects")]
public static implicit operator RealmSchema?(HashSet<Type>? objects) => objects == null ? null : new Builder(objects).Build();
public static implicit operator RealmSchema?(HashSet<Type>? objects) => objects is null ? null : new Builder(objects).Build();

/// <summary>
/// A convenience operator to construct a <see cref="RealmSchema"/> from a <see cref="Builder"/> by calling the
Expand Down
63 changes: 61 additions & 2 deletions Realm/Realm/Sync/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,27 @@ public class App
.Select(handle => new User(handle, this))
.ToArray();

/// <summary>
/// Gets the root folder relative to which all local data for this application will be stored. This data includes
/// metadata for users and synchronized Realms.
/// </summary>
/// <value>The app's base path.</value>
/// <seealso cref="AppConfiguration.BaseFilePath"/>
public string BaseFilePath => Handle.GetBaseFilePath();

/// <summary>
/// Gets the base url for this Realm application.
/// </summary>
/// <value>The app's base url.</value>
/// <seealso cref="AppConfiguration.BaseUri"/>
public Uri BaseUri => Handle.GetBaseUri();

/// <summary>
/// Gets the unique app id that identifies the Realm application.
/// </summary>
/// <value>The Atlas App Services App's id.</value>
public string Id => Handle.GetId();

internal App(AppHandle handle)
{
Handle = handle;
Expand All @@ -129,7 +150,6 @@ internal App(AppHandle handle)
/// </summary>
/// <param name="config">The <see cref="AppConfiguration"/>, specifying key parameters for the app behavior.</param>
/// <returns>An <see cref="App"/> instance can now be used to login users, call functions, or open synchronized Realms.</returns>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The http client is owned by the app and must be kept alive.")]
public static App Create(AppConfiguration config)
{
Argument.NotNull(config, nameof(config));
Expand All @@ -148,7 +168,7 @@ public static App Create(AppConfiguration config)
}
}

var httpClient = config.HttpClientHandler == null ? new HttpClient() : new HttpClient(config.HttpClientHandler);
var httpClient = config.HttpClientHandler is null ? new HttpClient() : new HttpClient(config.HttpClientHandler);
var clientHandle = GCHandle.Alloc(httpClient);
var nativeConfig = new Native.AppConfiguration
{
Expand All @@ -165,6 +185,7 @@ public static App Create(AppConfiguration config)
sync_fast_reconnect_limit = (ulong)syncTimeouts.FastReconnectLimit.TotalMilliseconds,
sync_ping_keep_alive_period_ms = (ulong)syncTimeouts.PingKeepAlivePeriod.TotalMilliseconds,
sync_pong_keep_alive_timeout_ms = (ulong)syncTimeouts.PongKeepAliveTimeout.TotalMilliseconds,
use_cache = config.UseAppCache,
};

var handle = AppHandle.CreateApp(nativeConfig, config.MetadataEncryptionKey);
Expand Down Expand Up @@ -246,6 +267,44 @@ public Task DeleteUserFromServerAsync(User user)
return Handle.DeleteUserAsync(user.Handle);
}

/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj is not App app)
{
return false;
}

return Handle.IsSameInstance(app.Handle);
}

/// <inheritdoc />
public override int GetHashCode() => Id.GetHashCode();

/// <summary>
/// Determines whether two <see cref="App"/> instances are equal.
/// </summary>
/// <param name="app1">The first app to compare.</param>
/// <param name="app2">The second app to compare.</param>
/// <returns><c>true</c> if the two instances are equal; <c>false</c> otherwise.</returns>
public static bool operator ==(App? app1, App? app2)
{
if (app1 is null || app2 is null)
{
return app1 is null && app2 is null;
}

return app1.Equals(app2);
}

/// <summary>
/// Determines whether two <see cref="App"/> instances are different.
/// </summary>
/// <param name="app1">The first app to compare.</param>
/// <param name="app2">The second app to compare.</param>
/// <returns><c>true</c> if the two instances are different; <c>false</c> otherwise.</returns>
public static bool operator !=(App? app1, App? app2) => !(app1 == app2);

/// <summary>
/// A sync manager, handling synchronization of local Realm with MongoDB Atlas. It is always scoped to a
/// particular app and can only be accessed via <see cref="Sync"/>.
Expand Down
Loading