@@ -9,6 +9,7 @@ import 'dart:typed_data';
99
1010import 'package:ffi/ffi.dart' ;
1111import 'package:meta/meta.dart' ;
12+ import 'package:objectbox/src/native/version.dart' ;
1213import 'package:path/path.dart' as path;
1314
1415import '../common.dart' ;
@@ -27,6 +28,13 @@ part 'observable.dart';
2728/// Represents an ObjectBox database and works together with [Box] to allow
2829/// getting and putting.
2930class Store {
31+ /// Path of the default directory, currently 'objectbox'.
32+ static const String defaultDirectoryPath = 'objectbox' ;
33+
34+ /// Enables a couple of debug logs.
35+ /// This meant for tests only; do not enable for releases!
36+ static bool debugLogs = false ;
37+
3038 late final Pointer <OBX_store > _cStore;
3139 HashMap <int , Type >? _entityTypeById;
3240 final _boxes = HashMap <Type , Box >();
@@ -36,8 +44,8 @@ class Store {
3644 final _reader = ReaderWithCBuffer ();
3745 Transaction ? _tx;
3846
39- /// absolute path to the database directory
40- final String _dbDir ;
47+ /// Absolute path to the database directory, used for open check.
48+ final String _absoluteDirectoryPath ;
4149
4250 late final ByteData _reference;
4351
@@ -51,8 +59,12 @@ class Store {
5159 /// Default value for string query conditions [caseSensitive] argument.
5260 final bool _queriesCaseSensitiveDefault;
5361
62+ static String _safeDirectoryPath (String ? path) =>
63+ (path == null || path.isEmpty) ? defaultDirectoryPath : path;
64+
5465 /// Creates a BoxStore using the model definition from your
55- /// `objectbox.g.dart` file.
66+ /// `objectbox.g.dart` file in the given [directory] path
67+ /// (or if null the [defaultDirectoryPath] ).
5668 ///
5769 /// For example in a Flutter app:
5870 /// ```dart
@@ -76,10 +88,8 @@ class Store {
7688 String ? macosApplicationGroup})
7789 : _weak = false ,
7890 _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
79- _dbDir = path.context.canonicalize (
80- (directory == null || directory.isEmpty)
81- ? 'objectbox'
82- : directory) {
91+ _absoluteDirectoryPath =
92+ path.context.canonicalize (_safeDirectoryPath (directory)) {
8393 try {
8494 if (Platform .isMacOS && macosApplicationGroup != null ) {
8595 if (! macosApplicationGroup.endsWith ('/' )) {
@@ -96,12 +106,7 @@ class Store {
96106 malloc.free (cStr);
97107 }
98108 }
99- if (_openStoreDirectories.contains (_dbDir)) {
100- throw UnsupportedError (
101- 'Cannot create multiple Store instances for the same directory. '
102- 'Please use a single Store or close() the previous instance before '
103- 'opening another one.' );
104- }
109+ _checkStoreDirectoryNotOpen ();
105110 final model = Model (_defs.model);
106111
107112 final opt = C .opt ();
@@ -130,25 +135,14 @@ class Store {
130135 C .opt_free (opt);
131136 rethrow ;
132137 }
138+ if (debugLogs) {
139+ print ('Opening store (C lib V${libraryVersion ()})... path=$directory '
140+ ' isOpen=${isOpen (directory )}' );
141+ }
142+
133143 _cStore = C .store_open (opt);
134144
135- try {
136- checkObxPtr (_cStore, 'failed to create store' );
137- } on ObjectBoxException catch (e) {
138- // Recognize common problems when trying to open/create a database
139- // 10199 = OBX_ERROR_STORAGE_GENERAL
140- // 13 = permissions denied, 30 = read-only filesystem
141- if (e.message.contains (OBX_ERROR_STORAGE_GENERAL .toString ()) &&
142- e.message.contains ('Dir does not exist' ) &&
143- (e.message.endsWith (' (13)' ) || e.message.endsWith (' (30)' ))) {
144- throw ObjectBoxException (e.message +
145- ' - this usually indicates a problem with permissions; '
146- "if you're using Flutter you may need to use "
147- 'getApplicationDocumentsDirectory() from the path_provider '
148- 'package, see example/README.md' );
149- }
150- rethrow ;
151- }
145+ _checkStorePointer (_cStore);
152146
153147 // Always create _reference, so it can be non-nullable.
154148 // Ensure we only try to access the store created in the same process.
@@ -157,7 +151,7 @@ class Store {
157151 _reference.setUint64 (0 * _int64Size, pid);
158152 _reference.setUint64 (1 * _int64Size, _ptr.address);
159153
160- _openStoreDirectories.add (_dbDir );
154+ _openStoreDirectories.add (_absoluteDirectoryPath );
161155 } catch (e) {
162156 _reader.clear ();
163157 rethrow ;
@@ -205,7 +199,7 @@ class Store {
205199 {bool queriesCaseSensitiveDefault = true })
206200 // must not close the same native store twice so [_weak]=true
207201 : _weak = true ,
208- _dbDir = '' ,
202+ _absoluteDirectoryPath = '' ,
209203 _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault {
210204 // see [reference] for serialization order
211205 final readPid = _reference.getUint64 (0 * _int64Size);
@@ -221,6 +215,95 @@ class Store {
221215 }
222216 }
223217
218+ /// Attach to a store opened in the [directoryPath]
219+ /// (or if null the [defaultDirectoryPath] ).
220+ ///
221+ /// Use this to access an open store from other isolates.
222+ /// This results in each isolate having access to the same underlying native
223+ /// store.
224+ ///
225+ /// The returned store is a new instance (e.g. different pointer value) with
226+ /// its own lifetime and must also be closed (e.g. before an isolate exits).
227+ /// The actual underlying store is only closed when the last store instance
228+ /// is closed (e.g. when the app exits).
229+ Store .attach (this ._defs, String ? directoryPath,
230+ {bool queriesCaseSensitiveDefault = true })
231+ // _weak = false so store can be closed.
232+ : _weak = false ,
233+ _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
234+ _absoluteDirectoryPath =
235+ path.context.canonicalize (_safeDirectoryPath (directoryPath)) {
236+ try {
237+ // Do not allow attaching to a store that is already open in the current
238+ // isolate. While technically possible this is not the intended usage
239+ // and e.g. transactions would have to be carefully managed to not
240+ // overlap.
241+ _checkStoreDirectoryNotOpen ();
242+
243+ final path = _safeDirectoryPath (directoryPath);
244+ final pathCStr = path.toNativeUtf8 ();
245+ try {
246+ if (debugLogs) {
247+ final isOpen = C .store_is_open (pathCStr.cast ());
248+ print ('Attaching to store... path=$path isOpen=$isOpen ' );
249+ }
250+ _cStore = C .store_attach (pathCStr.cast ());
251+ } finally {
252+ malloc.free (pathCStr);
253+ }
254+
255+ checkObxPtr (_cStore,
256+ 'could not attach to the store at given path - please ensure it was opened before' );
257+
258+ // Not setting _reference as this is a replacement for obtaining a store
259+ // via reference.
260+ } catch (e) {
261+ _reader.clear ();
262+ rethrow ;
263+ }
264+ }
265+
266+ void _checkStoreDirectoryNotOpen () {
267+ if (_openStoreDirectories.contains (_absoluteDirectoryPath)) {
268+ throw UnsupportedError (
269+ 'Cannot create multiple Store instances for the same directory in the same isolate. '
270+ 'Please use a single Store, close() the previous instance before '
271+ 'opening another one or attach to it in another isolate.' );
272+ }
273+ }
274+
275+ void _checkStorePointer (Pointer cStore) {
276+ try {
277+ checkObxPtr (cStore, 'failed to create store' );
278+ } on ObjectBoxException catch (e) {
279+ // Recognize common problems when trying to open/create a database
280+ // 10199 = OBX_ERROR_STORAGE_GENERAL
281+ // 13 = permissions denied, 30 = read-only filesystem
282+ if (e.message.contains (OBX_ERROR_STORAGE_GENERAL .toString ()) &&
283+ e.message.contains ('Dir does not exist' ) &&
284+ (e.message.endsWith (' (13)' ) || e.message.endsWith (' (30)' ))) {
285+ throw ObjectBoxException (e.message +
286+ ' - this usually indicates a problem with permissions; '
287+ "if you're using Flutter you may need to use "
288+ 'getApplicationDocumentsDirectory() from the path_provider '
289+ 'package, see example/README.md' );
290+ }
291+ rethrow ;
292+ }
293+ }
294+
295+ /// Returns if an open store (i.e. opened before and not yet closed) was found
296+ /// for the given [directoryPath] (or if null the [defaultDirectoryPath] ).
297+ static bool isOpen (String ? directoryPath) {
298+ final path = _safeDirectoryPath (directoryPath);
299+ final cStr = path.toNativeUtf8 ();
300+ try {
301+ return C .store_is_open (cStr.cast ());
302+ } finally {
303+ malloc.free (cStr);
304+ }
305+ }
306+
224307 /// Returns a store reference you can use to create a new store instance with
225308 /// a single underlying native store. See [Store.fromReference] for more details.
226309 ByteData get reference => _reference;
@@ -243,7 +326,7 @@ class Store {
243326 _reader.clear ();
244327
245328 if (! _weak) {
246- _openStoreDirectories.remove (_dbDir );
329+ _openStoreDirectories.remove (_absoluteDirectoryPath );
247330 checkObx (C .store_close (_cStore));
248331 }
249332 }
0 commit comments