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

Added ApplicationConfiguration #306

Merged
merged 13 commits into from
Apr 12, 2022
11 changes: 3 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,12 @@ x.x.x Release notes (yyyy-MM-dd)
* Made all `Configuration` fields final so they can only be initialized in the constructor. This better conveys the immutability of the configuration class. ([#455](https://github.com/realm/realm-dart/pull/455))

### Enhancements
* Added a new `Configuration` option: `disableFormatUpgrade`. When set to `true`, opening a Realm with an older file format will throw an exception to avoid automatically migrating it. ([#310](https://github.com/realm/realm-dart/pull/310))
```dart
var config = Configuration([Car.schema], disableFormatUpgrade: true);
await File('old-format.realm').copy(config.path);
var realm = Realm(config);
// Opening 'Realm' will throw a RealmException with message: "The Realm file format must be allowed to be upgraded in order to proceed."
```
* Added a property `Configuration.disableFormatUpgrade`. When set to `true`, opening a Realm with an older file format will throw an exception to avoid automatically upgrading it. ([#310](https://github.com/realm/realm-dart/pull/310))
* Support result value from write transaction callbacks ([#294](https://github.com/realm/realm-dart/pull/294/))
* Added a property `Realm.isInTransaction` that indicates whether the Realm instance has an open write transaction associated with it.
* Support anonymous application credentials ([#443](https://github.com/realm/realm-dart/pull/443/))
* Added support for `Configuration.initialDataCallback`. This is a callback executed when a Realm file is first created and allows you to populate some initial data necessary for your application. ([#298](https://github.com/realm/realm-dart/issues/298))
* Added a property `Configuration.initialDataCallback`. This is a callback executed when a Realm file is first created and allows you to populate some initial data necessary for your application. ([#298](https://github.com/realm/realm-dart/issues/298))
* Support application configuration ([#306](https://github.com/realm/realm-dart/pull/306/))

### Fixed
* Fixed an issue that would result in the wrong transaction being rolled back if you start a write transaction inside a write transaction. ([#442](https://github.com/realm/realm-dart/issues/442))
Expand Down
1 change: 1 addition & 0 deletions flutter/realm_flutter/ios/Classes/RealmPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ void dummy(void) {
realm_list_size(NULL, 0);
realm_results_snapshot(NULL);
realm_app_credentials_new_anonymous();
realm_app_config_new(NULL, NULL);
}

@end
100 changes: 100 additions & 0 deletions lib/src/application.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

import 'dart:io';
import 'package:meta/meta.dart';

/// Specify if and how to persists user objects.
enum MetadataPersistenceMode {
/// Persist [User] objects, but do not encrypt them.
unencrypted,

/// Persist [User] objects in an encrypted store.
encrypted,

/// Do not persist [User] objects.
disabled,
}

@immutable
class ApplicationConfiguration {
/// The [appId] is the unique id that identifies the Realm application.
final String appId;

/// The [baseFilePath] is the [Directory] relative to which all local data for this application will be stored.
///
/// This data includes metadata for users and synchronized Realms. If set, you must ensure that the [baseFilePath]
/// directory exists.
final Directory baseFilePath;

/// The [baseUrl] is the [Uri] used to reach the MongoDB Realm server.
///
/// [baseUrl] only needs to be set if for some reason your application isn't hosted on realm.mongodb.com.
/// This can be the case if you're testing locally or are using a pre-production environment.
final Uri baseUrl;

/// The [defaultRequestTimeout] for HTTP requests. Defaults to 60 seconds.
final Duration defaultRequestTimeout;

/// The [localAppName] is the friendly name identifying the current client application.
///
/// This is typically used to differentiate between client applications that use the same
/// MongoDB Realm app.
///
/// These can be the same conceptual app developed for different platforms, or
/// significantly different client side applications that operate on the same data - e.g. an event managing
/// service that has different clients apps for organizers and attendees.
final String? localAppName;

/// The [localAppVersion] can be specified, if you wish to distinguish different client versions of the
/// same application.
final String? localAppVersion;

/// Enumeration that specifies how and if logged-in User objects are persisted across application launches.
final MetadataPersistenceMode metadataPersistenceMode;

/// The encryption key to use for user metadata on this device, if [metadataPersistenceMode] is
/// [MetadataPersistenceMode.encrypted].
///
/// The [metadataEncryptionKey] must be exactly 64 bytes.
/// Setting this will not change the encryption key for individual Realms, which is set in the [Configuration].
final List<int>? metadataEncryptionKey;

/// The [HttpClient] that will be used for HTTP requests during authentication.
///
/// You can use this to override the default http client handler and configure settings like proxies,
/// client certificates, and cookies. While these are not required to connect to MongoDB Realm under
/// normal circumstances, they can be useful if client devices are behind corporate firewall or use
/// a more complex networking setup.
final HttpClient httpClient;

/// Instantiates a new [ApplicationConfiguration] with the specified appId.
ApplicationConfiguration(
this.appId, {
Uri? baseUrl,
Directory? baseFilePath,
this.defaultRequestTimeout = const Duration(seconds: 60),
this.localAppName,
this.localAppVersion,
this.metadataPersistenceMode = MetadataPersistenceMode.unencrypted,
this.metadataEncryptionKey,
HttpClient? httpClient,
}) : baseUrl = baseUrl ?? Uri.parse('https://realm.mongodb.com'),
baseFilePath = baseFilePath ?? Directory.current,
httpClient = httpClient ?? HttpClient();
}
4 changes: 2 additions & 2 deletions lib/src/cli/metrics/metrics_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,14 @@ Future<FlutterInfo?> getInfo(Options options) async {
// Sanity check full info, if we have it
if (info != null && (version == null || version == info.frameworkVersion) && flutterVersionConstraints.allows(info.frameworkVersion)) {
// The returned info match both the projects constraints and the
// flutter version of the lastest flutter command run on the project
// flutter version of the latest flutter command run on the project
return info;
}

// Fallback to simplified info build from the version read from .dart_tool/version,
// secondly the min constraint of the flutter SDK used
return FlutterInfo(
frameworkVersion: version ?? (await safe(() => (flutterVersionConstraints as VersionRange).min!)) ?? Version.none,
dartSdkVersion: Version.parse(Platform.version.toString().takeUntil(' ')),
dartSdkVersion: Version.parse(Platform.version.takeUntil(' ')),
);
}
33 changes: 33 additions & 0 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import 'dart:typed_data';

// Hide StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows silently allocating memory. Use toUtf8Ptr instead
import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer;
import 'package:pub_semver/pub_semver.dart';

import '../application.dart';
import '../collections.dart';
import '../init.dart';
import '../list.dart';
Expand Down Expand Up @@ -644,6 +646,33 @@ class _RealmCore {
return out_modified.asTypedList(count).toList();
});
}

AppConfigHandle createAppConfig(ApplicationConfiguration configuration, RealmHttpTransportHandle httpTransport) {
return using((arena) {
final app_id = configuration.appId.toUtf8Ptr(arena);
final handle = AppConfigHandle._(_realmLib.realm_app_config_new(app_id, httpTransport._pointer));

_realmLib.realm_app_config_set_base_url(handle._pointer, configuration.baseUrl.toString().toUtf8Ptr(arena));
_realmLib.realm_app_config_set_default_request_timeout(handle._pointer, configuration.defaultRequestTimeout!.inMilliseconds);

if (configuration.localAppName != null) {
_realmLib.realm_app_config_set_local_app_name(handle._pointer, configuration.localAppName!.toUtf8Ptr(arena));
}

if (configuration.localAppVersion != null) {
_realmLib.realm_app_config_set_local_app_version(handle._pointer, configuration.localAppVersion!.toUtf8Ptr(arena));
}

_realmLib.realm_app_config_set_platform(handle._pointer, Platform.operatingSystem.toUtf8Ptr(arena));
_realmLib.realm_app_config_set_platform_version(handle._pointer, Platform.operatingSystemVersion.toUtf8Ptr(arena));

//This sets the realm lib version instead of the SDK version.
//TODO: Read the SDK version from code generated version field
_realmLib.realm_app_config_set_sdk_version(handle._pointer, libraryVersion.toUtf8Ptr(arena));

return handle;
});
}

RealmAppCredentialsHandle createAppCredentialsAnonymous() {
return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_anonymous());
Expand Down Expand Up @@ -885,6 +914,10 @@ class RealmHttpTransportHandle extends Handle<realm_http_transport> {
RealmHttpTransportHandle._(Pointer<realm_http_transport> pointer) : super(pointer, 24);
}

class AppConfigHandle extends Handle<realm_app_config> {
AppConfigHandle._(Pointer<realm_app_config> pointer) : super(pointer, 256); // TODO: What should hint be?
}

extension on List<int> {
Pointer<Int8> toInt8Ptr(Allocator allocator) {
final nativeSize = length + 1;
Expand Down
1 change: 1 addition & 0 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import 'realm_object.dart';
import 'results.dart';

// always expose with `show` to explicitly control the public API surface
export 'application.dart' show ApplicationConfiguration, MetadataPersistenceMode;
export 'package:realm_common/realm_common.dart'
show Ignored, Indexed, MapTo, PrimaryKey, RealmError, RealmModel, RealmUnsupportedSetError, RealmStateError, RealmCollectionType, RealmPropertyType;
export "configuration.dart" show Configuration, RealmSchema, SchemaObject;
Expand Down
1 change: 1 addition & 0 deletions src/realm_dart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ void dummy(void) {
realm_list_size(nullptr, 0);
realm_results_add_notification_callback(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
realm_results_snapshot(nullptr);
realm_app_config_new(nullptr, nullptr);
realm_app_credentials_new_anonymous();
realm_http_transport_new(nullptr, nullptr, nullptr);
#if (ANDROID)
Expand Down
54 changes: 54 additions & 0 deletions test/application_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

import 'dart:io';

import 'package:test/expect.dart';

import '../lib/realm.dart';
import 'test.dart';

Future<void> main([List<String>? args]) async {
print("Current PID $pid");

setupTests(args);

test('ApplicationConfiguration can be created', () {
final a = ApplicationConfiguration('myapp');
expect(a.appId, 'myapp');
expect(a.baseFilePath.path, Directory.current.path);
expect(a.baseUrl, Uri.parse('https://realm.mongodb.com'));
expect(a.defaultRequestTimeout, const Duration(minutes: 1));

final httpClient = HttpClient(context: SecurityContext(withTrustedRoots: false));
final b = ApplicationConfiguration(
'myapp1',
baseFilePath: Directory.systemTemp,
baseUrl: Uri.parse('https://not_re.al'),
defaultRequestTimeout: const Duration(seconds: 2),
localAppName: 'bar',
localAppVersion: "1.0.0",
httpClient: httpClient,
);
expect(b.appId, 'myapp1');
expect(b.baseFilePath.path, Directory.systemTemp.path);
expect(b.baseUrl,Uri.parse('https://not_re.al'));
expect(b.defaultRequestTimeout, const Duration(seconds: 2));
expect(b.httpClient, httpClient);
});
}