@@ -27,6 +27,9 @@ part 'observable.dart';
27
27
/// Represents an ObjectBox database and works together with [Box] to allow
28
28
/// getting and putting.
29
29
class Store {
30
+ /// Path of the default directory, currently 'objectbox'.
31
+ static const String defaultDirectoryPath = 'objectbox' ;
32
+
30
33
late final Pointer <OBX_store > _cStore;
31
34
HashMap <int , Type >? _entityTypeById;
32
35
final _boxes = HashMap <Type , Box >();
@@ -36,8 +39,8 @@ class Store {
36
39
final _reader = ReaderWithCBuffer ();
37
40
Transaction ? _tx;
38
41
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 ;
41
44
42
45
late final ByteData _reference;
43
46
@@ -51,8 +54,12 @@ class Store {
51
54
/// Default value for string query conditions [caseSensitive] argument.
52
55
final bool _queriesCaseSensitiveDefault;
53
56
57
+ static String _safeDirectoryPath (String ? path) =>
58
+ (path == null || path.isEmpty) ? defaultDirectoryPath : path;
59
+
54
60
/// 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] ).
56
63
///
57
64
/// For example in a Flutter app:
58
65
/// ```dart
@@ -76,10 +83,8 @@ class Store {
76
83
String ? macosApplicationGroup})
77
84
: _weak = false ,
78
85
_queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
79
- _dbDir = path.context.canonicalize (
80
- (directory == null || directory.isEmpty)
81
- ? 'objectbox'
82
- : directory) {
86
+ _absoluteDirectoryPath =
87
+ path.context.canonicalize (_safeDirectoryPath (directory)) {
83
88
try {
84
89
if (Platform .isMacOS && macosApplicationGroup != null ) {
85
90
if (! macosApplicationGroup.endsWith ('/' )) {
@@ -96,12 +101,7 @@ class Store {
96
101
malloc.free (cStr);
97
102
}
98
103
}
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 ();
105
105
final model = Model (_defs.model);
106
106
107
107
final opt = C .opt ();
@@ -132,23 +132,7 @@ class Store {
132
132
}
133
133
_cStore = C .store_open (opt);
134
134
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);
152
136
153
137
// Always create _reference, so it can be non-nullable.
154
138
// Ensure we only try to access the store created in the same process.
@@ -157,7 +141,7 @@ class Store {
157
141
_reference.setUint64 (0 * _int64Size, pid);
158
142
_reference.setUint64 (1 * _int64Size, _ptr.address);
159
143
160
- _openStoreDirectories.add (_dbDir );
144
+ _openStoreDirectories.add (_absoluteDirectoryPath );
161
145
} catch (e) {
162
146
_reader.clear ();
163
147
rethrow ;
@@ -205,7 +189,7 @@ class Store {
205
189
{bool queriesCaseSensitiveDefault = true })
206
190
// must not close the same native store twice so [_weak]=true
207
191
: _weak = true ,
208
- _dbDir = '' ,
192
+ _absoluteDirectoryPath = '' ,
209
193
_queriesCaseSensitiveDefault = queriesCaseSensitiveDefault {
210
194
// see [reference] for serialization order
211
195
final readPid = _reference.getUint64 (0 * _int64Size);
@@ -221,6 +205,90 @@ class Store {
221
205
}
222
206
}
223
207
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
+
224
292
/// Returns a store reference you can use to create a new store instance with
225
293
/// a single underlying native store. See [Store.fromReference] for more details.
226
294
ByteData get reference => _reference;
@@ -243,7 +311,7 @@ class Store {
243
311
_reader.clear ();
244
312
245
313
if (! _weak) {
246
- _openStoreDirectories.remove (_dbDir );
314
+ _openStoreDirectories.remove (_absoluteDirectoryPath );
247
315
checkObx (C .store_close (_cStore));
248
316
}
249
317
}
0 commit comments