Skip to content
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
1 change: 1 addition & 0 deletions objectbox/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## latest

* Add an option to change code-generator's `output_dir` in `pubspec.yaml`. #341
* Support ObjectBox Admin for Android apps to browse the database, see our [docs](https://docs.objectbox.io/data-browser) to get started. #148
* Update: [objectbox-c 0.15.2](https://github.com/objectbox/objectbox-c/releases/tag/v0.15.0).
* Update: [objectbox-android 3.1.2](https://github.com/objectbox/objectbox-java/releases/tag/V3.1.0).
* Update: [objectbox-swift 1.7.0](https://github.com/objectbox/objectbox-swift/releases/tag/v1.7.0).
Expand Down
1 change: 1 addition & 0 deletions objectbox/lib/objectbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ library objectbox;

export 'src/annotations.dart';
export 'src/box.dart' show Box, PutMode;
export 'src/admin.dart' show Admin;
export 'src/common.dart';
export 'src/query.dart'
show
Expand Down
1 change: 1 addition & 0 deletions objectbox/lib/src/admin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'native/admin.dart' if (dart.library.html) 'web/admin.dart';
89 changes: 89 additions & 0 deletions objectbox/lib/src/native/admin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'dart:ffi';

import 'bindings/bindings.dart';
import 'bindings/helpers.dart';
import 'store.dart';

/// ObjectBox Admin allows you to explore the database in a regular web browser.
///
/// ```dart
/// if (Admin.isAvailable()) {
/// // Keep a reference until no longer needed or manually closed.
/// admin = Admin(store);
/// }
/// ```
///
/// Admin runs directly on your device or on your development machine.
/// Behind the scenes this works by bundling a simple HTTP server into ObjectBox
/// when building your app. If triggered, it will then provide a basic web
/// interface to the data and schema.
///
/// Note: ObjectBox Admin is currently supported for Android apps only.
/// [Additional configuration](https://docs.objectbox.io/data-browser) is
/// required.
class Admin {
late Pointer<OBX_admin> _cAdmin;
late final Pointer<OBX_dart_finalizer> _cFinalizer;

@pragma('vm:prefer-inline')
Pointer<OBX_admin> get _ptr =>
isClosed() ? throw StateError('Admin already closed') : _cAdmin;

/// Whether the loaded ObjectBox native library supports Admin.
static bool isAvailable() => C.has_feature(OBXFeature.Admin);

/// Creates an ObjectBox Admin associated with the given store and options.
Admin(Store store, {String bindUri = 'http://127.0.0.1:8090'}) {
if (!isAvailable()) {
throw UnsupportedError(
'Admin is not available in the loaded ObjectBox runtime library.');
}
initializeDartAPI();

final opt = checkObxPtr(C.admin_opt());
try {
checkObx(C.admin_opt_store(opt, InternalStoreAccess.ptr(store)));
checkObx(C.admin_opt_user_management(opt, false));
withNativeString(bindUri,
(Pointer<Int8> cStr) => checkObx(C.admin_opt_bind(opt, cStr)));
} catch (_) {
C.admin_opt_free(opt);
rethrow;
}

_cAdmin = C.admin(opt);

// Keep the finalizer so we can detach it when close() is called manually.
_cFinalizer = C.dartc_attach_finalizer(
this, native_admin_close, _cAdmin.cast(), 1024 * 1024);
if (_cFinalizer == nullptr) {
close();
throwLatestNativeError();
}
}

/// Closes and cleans up all resources used by this Admin.
void close() {
if (!isClosed()) {
final errors = List.filled(2, 0);
if (_cFinalizer != nullptr) {
errors[0] = C.dartc_detach_finalizer(_cFinalizer, this);
}
errors[1] = C.admin_close(_cAdmin);
_cAdmin = nullptr;
errors.forEach(checkObx);
}
}

/// Returns if the admin is already closed and can no longer be used.
bool isClosed() => _cAdmin.address == 0;

/// Port the admin listens on. This is especially useful if the port was
/// assigned automatically (a "0" port was used in the [bindUri]).
late final int port = () {
final result = C.admin_port(_ptr);
reachabilityFence(this);
if (result == 0) throwLatestNativeError();
return result;
}();
}
2 changes: 2 additions & 0 deletions objectbox/lib/src/native/bindings/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ late final native_query_close =
_lib!.lookup<NativeFunction<_NativeClose>>('obx_query_close');
late final native_query_prop_close =
_lib!.lookup<NativeFunction<_NativeClose>>('obx_query_prop_close');
late final native_admin_close =
_lib!.lookup<NativeFunction<_NativeClose>>('obx_admin_close');

/// Keeps `this` alive until this call, preventing finalizers to run.
/// Necessary for objects with a finalizer attached because the optimizer may
Expand Down
1 change: 1 addition & 0 deletions objectbox/lib/src/web/admin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

42 changes: 42 additions & 0 deletions objectbox/test/admin_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'dart:convert';
import 'dart:io';

import 'package:objectbox/objectbox.dart';
import 'package:test/test.dart';

import 'entity.dart';
import 'test_env.dart';

void main() {
late TestEnv env;

setUp(() {
env = TestEnv('admin');
});

tearDown(() => env.closeAndDelete());

// Note: this test currently requires a C library with Sync server,
// so it is not run on public CI and must be run manually.
test('admin', () async {
env.box.put(TestEntity.filled());

final admin = Admin(env.store);

// Check that it serves requests and has correct permissions configured.
final response = await HttpClient()
.get('127.0.0.1', admin.port, '/api/v2/auth-info')
.then((request) => request.close());
expect(response.statusCode, 200);
expect(await response.transform(utf8.decoder).join(''),
'{"auth":false,"permissions":{"modelRead":true,"modelWrite":true,"objectsRead":true,"objectsWrite":true,"runtimeRead":true,"runtimeWrite":true}}');

expect(admin.isClosed(), isFalse);
admin.close();
expect(admin.isClosed(), isTrue);
admin.close(); // does nothing
},
skip: Admin.isAvailable()
? null
: 'Admin is not available in the loaded library');
}