@@ -27,6 +27,9 @@ part 'observable.dart';
2727/// Represents an ObjectBox database and works together with [Box] to allow
2828/// getting and putting.
2929class Store {
30+ /// Path of the default directory, currently 'objectbox'.
31+ static const String defaultDirectoryPath = 'objectbox' ;
32+
3033 late final Pointer <OBX_store > _cStore;
3134 HashMap <int , Type >? _entityTypeById;
3235 final _boxes = HashMap <Type , Box >();
@@ -36,8 +39,8 @@ class Store {
3639 final _reader = ReaderWithCBuffer ();
3740 Transaction ? _tx;
3841
39- /// absolute path to the database directory
40- final String _dbDir ;
42+ /// Absolute path to the database directory, used for open check.
43+ final String _absoluteDirectoryPath ;
4144
4245 late final ByteData _reference;
4346
@@ -51,8 +54,12 @@ class Store {
5154 /// Default value for string query conditions [caseSensitive] argument.
5255 final bool _queriesCaseSensitiveDefault;
5356
57+ static String _safeDirectoryPath (String ? path) =>
58+ (path == null || path.isEmpty) ? defaultDirectoryPath : path;
59+
5460 /// Creates a BoxStore using the model definition from your
55- /// `objectbox.g.dart` file.
61+ /// `objectbox.g.dart` file in the given [directory] path
62+ /// (or if null the [defaultDirectoryPath] ).
5663 ///
5764 /// For example in a Flutter app:
5865 /// ```dart
@@ -76,10 +83,8 @@ class Store {
7683 String ? macosApplicationGroup})
7784 : _weak = false ,
7885 _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
79- _dbDir = path.context.canonicalize (
80- (directory == null || directory.isEmpty)
81- ? 'objectbox'
82- : directory) {
86+ _absoluteDirectoryPath =
87+ path.context.canonicalize (_safeDirectoryPath (directory)) {
8388 try {
8489 if (Platform .isMacOS && macosApplicationGroup != null ) {
8590 if (! macosApplicationGroup.endsWith ('/' )) {
@@ -96,12 +101,7 @@ class Store {
96101 malloc.free (cStr);
97102 }
98103 }
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- }
104+ _checkStoreDirectoryNotOpen ();
105105 final model = Model (_defs.model);
106106
107107 final opt = C .opt ();
@@ -132,23 +132,7 @@ class Store {
132132 }
133133 _cStore = C .store_open (opt);
134134
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- }
135+ _checkStorePointer (_cStore);
152136
153137 // Always create _reference, so it can be non-nullable.
154138 // Ensure we only try to access the store created in the same process.
@@ -157,7 +141,7 @@ class Store {
157141 _reference.setUint64 (0 * _int64Size, pid);
158142 _reference.setUint64 (1 * _int64Size, _ptr.address);
159143
160- _openStoreDirectories.add (_dbDir );
144+ _openStoreDirectories.add (_absoluteDirectoryPath );
161145 } catch (e) {
162146 _reader.clear ();
163147 rethrow ;
@@ -205,7 +189,7 @@ class Store {
205189 {bool queriesCaseSensitiveDefault = true })
206190 // must not close the same native store twice so [_weak]=true
207191 : _weak = true ,
208- _dbDir = '' ,
192+ _absoluteDirectoryPath = '' ,
209193 _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault {
210194 // see [reference] for serialization order
211195 final readPid = _reference.getUint64 (0 * _int64Size);
@@ -221,6 +205,90 @@ class Store {
221205 }
222206 }
223207
208+ /// Attach to a store opened in the [directoryPath]
209+ /// (or if null the [defaultDirectoryPath] ).
210+ ///
211+ /// Use this to access an open store from other isolates.
212+ /// This results in each isolate having access to the same underlying native
213+ /// store.
214+ ///
215+ /// The returned store is a new instance (e.g. different pointer value) with
216+ /// its own lifetime and must also be closed (e.g. before an isolate exits).
217+ /// The actual underlying store is only closed when the last store instance
218+ /// is closed (e.g. when the app exits).
219+ Store .attach (this ._defs, String ? directoryPath,
220+ {bool queriesCaseSensitiveDefault = true })
221+ // _weak = false so store can be closed.
222+ : _weak = false ,
223+ _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
224+ _absoluteDirectoryPath =
225+ path.context.canonicalize (_safeDirectoryPath (directoryPath)) {
226+ try {
227+ // Do not allow attaching to a store that is already open in the current
228+ // isolate. While technically possible this is not the intended usage
229+ // and e.g. transactions would have to be carefully managed to not
230+ // overlap.
231+ _checkStoreDirectoryNotOpen ();
232+
233+ final path = _safeDirectoryPath (directoryPath);
234+ final pathCStr = path.toNativeUtf8 ();
235+ try {
236+ _cStore = C .store_attach (pathCStr.cast ());
237+ } finally {
238+ malloc.free (pathCStr);
239+ }
240+
241+ _checkStorePointer (_cStore);
242+
243+ // Not setting reference as this is a replacement for obtaining a store
244+ // via reference.
245+ } catch (e) {
246+ _reader.clear ();
247+ rethrow ;
248+ }
249+ }
250+
251+ void _checkStoreDirectoryNotOpen () {
252+ if (_openStoreDirectories.contains (_absoluteDirectoryPath)) {
253+ throw UnsupportedError (
254+ 'Cannot create multiple Store instances for the same directory in the same isolate. '
255+ 'Please use a single Store, close() the previous instance before '
256+ 'opening another one or attach to it in another isolate.' );
257+ }
258+ }
259+
260+ void _checkStorePointer (Pointer cStore) {
261+ try {
262+ checkObxPtr (cStore, 'failed to create store' );
263+ } on ObjectBoxException catch (e) {
264+ // Recognize common problems when trying to open/create a database
265+ // 10199 = OBX_ERROR_STORAGE_GENERAL
266+ // 13 = permissions denied, 30 = read-only filesystem
267+ if (e.message.contains (OBX_ERROR_STORAGE_GENERAL .toString ()) &&
268+ e.message.contains ('Dir does not exist' ) &&
269+ (e.message.endsWith (' (13)' ) || e.message.endsWith (' (30)' ))) {
270+ throw ObjectBoxException (e.message +
271+ ' - this usually indicates a problem with permissions; '
272+ "if you're using Flutter you may need to use "
273+ 'getApplicationDocumentsDirectory() from the path_provider '
274+ 'package, see example/README.md' );
275+ }
276+ rethrow ;
277+ }
278+ }
279+
280+ /// Returns if an open store (i.e. opened before and not yet closed) was found
281+ /// for the given [directoryPath] (or if null the [defaultDirectoryPath] ).
282+ static bool isOpen (String ? directoryPath) {
283+ final path = _safeDirectoryPath (directoryPath);
284+ final cStr = path.toNativeUtf8 ();
285+ try {
286+ return C .store_is_open (cStr.cast ());
287+ } finally {
288+ malloc.free (cStr);
289+ }
290+ }
291+
224292 /// Returns a store reference you can use to create a new store instance with
225293 /// a single underlying native store. See [Store.fromReference] for more details.
226294 ByteData get reference => _reference;
@@ -243,7 +311,7 @@ class Store {
243311 _reader.clear ();
244312
245313 if (! _weak) {
246- _openStoreDirectories.remove (_dbDir );
314+ _openStoreDirectories.remove (_absoluteDirectoryPath );
247315 checkObx (C .store_close (_cStore));
248316 }
249317 }
0 commit comments