@@ -9,6 +9,7 @@ import 'dart:typed_data';
9
9
10
10
import 'package:ffi/ffi.dart' ;
11
11
import 'package:meta/meta.dart' ;
12
+ import 'package:objectbox/src/native/version.dart' ;
12
13
import 'package:path/path.dart' as path;
13
14
14
15
import '../common.dart' ;
@@ -27,6 +28,13 @@ part 'observable.dart';
27
28
/// Represents an ObjectBox database and works together with [Box] to allow
28
29
/// getting and putting.
29
30
class 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
+
30
38
late final Pointer <OBX_store > _cStore;
31
39
HashMap <int , Type >? _entityTypeById;
32
40
final _boxes = HashMap <Type , Box >();
@@ -36,8 +44,8 @@ class Store {
36
44
final _reader = ReaderWithCBuffer ();
37
45
Transaction ? _tx;
38
46
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 ;
41
49
42
50
late final ByteData _reference;
43
51
@@ -51,8 +59,12 @@ class Store {
51
59
/// Default value for string query conditions [caseSensitive] argument.
52
60
final bool _queriesCaseSensitiveDefault;
53
61
62
+ static String _safeDirectoryPath (String ? path) =>
63
+ (path == null || path.isEmpty) ? defaultDirectoryPath : path;
64
+
54
65
/// 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] ).
56
68
///
57
69
/// For example in a Flutter app:
58
70
/// ```dart
@@ -76,10 +88,8 @@ class Store {
76
88
String ? macosApplicationGroup})
77
89
: _weak = false ,
78
90
_queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
79
- _dbDir = path.context.canonicalize (
80
- (directory == null || directory.isEmpty)
81
- ? 'objectbox'
82
- : directory) {
91
+ _absoluteDirectoryPath =
92
+ path.context.canonicalize (_safeDirectoryPath (directory)) {
83
93
try {
84
94
if (Platform .isMacOS && macosApplicationGroup != null ) {
85
95
if (! macosApplicationGroup.endsWith ('/' )) {
@@ -96,12 +106,7 @@ class Store {
96
106
malloc.free (cStr);
97
107
}
98
108
}
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 ();
105
110
final model = Model (_defs.model);
106
111
107
112
final opt = C .opt ();
@@ -130,25 +135,14 @@ class Store {
130
135
C .opt_free (opt);
131
136
rethrow ;
132
137
}
138
+ if (debugLogs) {
139
+ print ('Opening store (C lib V${libraryVersion ()})... path=$directory '
140
+ ' isOpen=${isOpen (directory )}' );
141
+ }
142
+
133
143
_cStore = C .store_open (opt);
134
144
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);
152
146
153
147
// Always create _reference, so it can be non-nullable.
154
148
// Ensure we only try to access the store created in the same process.
@@ -157,7 +151,7 @@ class Store {
157
151
_reference.setUint64 (0 * _int64Size, pid);
158
152
_reference.setUint64 (1 * _int64Size, _ptr.address);
159
153
160
- _openStoreDirectories.add (_dbDir );
154
+ _openStoreDirectories.add (_absoluteDirectoryPath );
161
155
} catch (e) {
162
156
_reader.clear ();
163
157
rethrow ;
@@ -205,7 +199,7 @@ class Store {
205
199
{bool queriesCaseSensitiveDefault = true })
206
200
// must not close the same native store twice so [_weak]=true
207
201
: _weak = true ,
208
- _dbDir = '' ,
202
+ _absoluteDirectoryPath = '' ,
209
203
_queriesCaseSensitiveDefault = queriesCaseSensitiveDefault {
210
204
// see [reference] for serialization order
211
205
final readPid = _reference.getUint64 (0 * _int64Size);
@@ -221,6 +215,95 @@ class Store {
221
215
}
222
216
}
223
217
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
+
224
307
/// Returns a store reference you can use to create a new store instance with
225
308
/// a single underlying native store. See [Store.fromReference] for more details.
226
309
ByteData get reference => _reference;
@@ -243,7 +326,7 @@ class Store {
243
326
_reader.clear ();
244
327
245
328
if (! _weak) {
246
- _openStoreDirectories.remove (_dbDir );
329
+ _openStoreDirectories.remove (_absoluteDirectoryPath );
247
330
checkObx (C .store_close (_cStore));
248
331
}
249
332
}
0 commit comments