Skip to content

Commit ce1be73

Browse files
committed
Implement remote file system
1 parent b0513b8 commit ce1be73

File tree

4 files changed

+141
-9
lines changed

4 files changed

+141
-9
lines changed

sqlite3_web/lib/src/client.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:async';
22
import 'dart:js_interop';
33
import 'dart:js_interop_unsafe';
4+
import 'dart:typed_data';
45

56
import 'package:sqlite3/common.dart';
67
import 'package:web/web.dart'
@@ -142,6 +143,66 @@ final class RemoteDatabase implements Database {
142143
}
143144
}
144145

146+
final class RemoteFileSystem implements FileSystem {
147+
final RemoteDatabase database;
148+
149+
RemoteFileSystem(this.database);
150+
151+
@override
152+
Future<bool> exists(FileType type) async {
153+
final response = await database.connection.sendRequest(
154+
FileSystemExistsQuery(
155+
databaseId: database.databaseId,
156+
fsType: type,
157+
requestId: 0,
158+
),
159+
MessageType.simpleSuccessResponse,
160+
);
161+
162+
return (response.response as JSBoolean).toDart;
163+
}
164+
165+
@override
166+
Future<void> flush() async {
167+
await database.connection.sendRequest(
168+
FileSystemFlushRequest(databaseId: database.databaseId, requestId: 0),
169+
MessageType.simpleSuccessResponse,
170+
);
171+
}
172+
173+
@override
174+
Future<Uint8List> readFile(FileType type) async {
175+
final response = await database.connection.sendRequest(
176+
FileSystemAccess(
177+
databaseId: database.databaseId,
178+
requestId: 0,
179+
buffer: null,
180+
fsType: type,
181+
),
182+
MessageType.simpleSuccessResponse,
183+
);
184+
185+
final buffer = (response.response as JSArrayBuffer);
186+
return buffer.toDart.asUint8List();
187+
}
188+
189+
@override
190+
Future<void> writeFile(FileType type, Uint8List content) async {
191+
// We need to copy since we're about to transfer contents over
192+
final copy = Uint8List(content.length)..setAll(0, content);
193+
194+
await database.connection.sendRequest(
195+
FileSystemAccess(
196+
databaseId: database.databaseId,
197+
requestId: 0,
198+
buffer: copy.buffer.toJS,
199+
fsType: type,
200+
),
201+
MessageType.simpleSuccessResponse,
202+
);
203+
}
204+
}
205+
145206
final class WorkerConnection extends ProtocolChannel {
146207
final StreamController<Notification> notifications =
147208
StreamController.broadcast();

sqlite3_web/lib/src/protocol.dart

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ enum MessageType<T extends Message> {
2222
runQuery<RunQuery>(),
2323
fileSystemExists<FileSystemExistsQuery>(),
2424
fileSystemAccess<FileSystemAccess>(),
25+
fileSystemFlush<FileSystemFlushRequest>(),
2526
connect<ConnectRequest>(),
2627
startFileSystemServer<StartFileSystemServer>(),
2728
updateRequest<UpdateStreamRequest>(),
@@ -86,6 +87,7 @@ sealed class Message {
8687
MessageType.runQuery => RunQuery.deserialize(object),
8788
MessageType.fileSystemExists => FileSystemExistsQuery.deserialize(object),
8889
MessageType.fileSystemAccess => FileSystemAccess.deserialize(object),
90+
MessageType.fileSystemFlush => FileSystemFlushRequest.deserialize(object),
8991
MessageType.connect => ConnectRequest.deserialize(object),
9092
MessageType.closeDatabase => CloseDatabase.deserialize(object),
9193
MessageType.openAdditionalConnection =>
@@ -348,6 +350,24 @@ final class FileSystemExistsQuery extends Request {
348350
}
349351
}
350352

353+
/// Requests the worker to flush the file system for a database.
354+
final class FileSystemFlushRequest extends Request {
355+
@override
356+
MessageType<Message> get type => MessageType.fileSystemFlush;
357+
358+
FileSystemFlushRequest({
359+
required super.databaseId,
360+
required super.requestId,
361+
});
362+
363+
factory FileSystemFlushRequest.deserialize(JSObject object) {
364+
return FileSystemFlushRequest(
365+
databaseId: object.databaseId,
366+
requestId: object.requestId,
367+
);
368+
}
369+
}
370+
351371
/// Read or write to files of an opened database.
352372
///
353373
/// For reads, other side will respond with a [SimpleSuccessResponse] containing
@@ -385,8 +405,6 @@ final class FileSystemAccess extends Request {
385405
object[_UniqueFieldNames.buffer] = buffer;
386406
object[_UniqueFieldNames.fileType] = fsType.index.toJS;
387407

388-
// false positive? dart2js seems to emit a null check as it should
389-
// ignore: pattern_never_matches_value_type
390408
if (buffer case final buffer?) {
391409
transferred.add(buffer);
392410
}
@@ -462,6 +480,9 @@ final class OpenAdditonalConnection extends Request {
462480
MessageType<Message> get type => MessageType.openAdditionalConnection;
463481
}
464482

483+
@JS('ArrayBuffer')
484+
external JSFunction get _arrayBufferConstructor;
485+
465486
final class SimpleSuccessResponse extends Response {
466487
final JSAny? response;
467488

@@ -481,6 +502,10 @@ final class SimpleSuccessResponse extends Response {
481502
void serialize(JSObject object, List<JSObject> transferred) {
482503
super.serialize(object, transferred);
483504
object[_UniqueFieldNames.responseData] = response;
505+
506+
if (response.instanceof(_arrayBufferConstructor)) {
507+
transferred.add(response as JSObject);
508+
}
484509
}
485510
}
486511

sqlite3_web/lib/src/types.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,13 @@ final class RemoteException implements Exception {
7676
}
7777

7878
abstract class FileSystem {
79-
StorageMode get storage;
80-
String get databaseName;
81-
8279
Future<bool> exists(FileType type);
8380
Future<Uint8List> readFile(FileType type);
8481
Future<void> writeFile(FileType type, Uint8List content);
82+
83+
/// If the file system hosting the database in the worker is not synchronous,
84+
/// flushes pending writes.
85+
Future<void> flush();
8586
}
8687

8788
/// An enumeration of features not supported by the current browsers.

sqlite3_web/lib/src/worker.dart

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:async';
22
import 'dart:js_interop';
33
import 'dart:js_interop_unsafe';
4+
import 'dart:typed_data';
45
import 'package:sqlite3/wasm.dart';
56
import 'package:stream_channel/stream_channel.dart';
67
import 'package:web/web.dart'
@@ -21,6 +22,7 @@ import 'database.dart';
2122
import 'channel.dart';
2223
import 'protocol.dart';
2324
import 'shared.dart';
25+
import 'types.dart';
2426

2527
sealed class WorkerEnvironment {
2628
WorkerEnvironment._();
@@ -239,10 +241,46 @@ final class _ClientConnection extends ProtocolChannel
239241
await database.close();
240242
return SimpleSuccessResponse(
241243
response: null, requestId: request.requestId);
242-
case FileSystemExistsQuery():
243-
throw UnimplementedError();
244-
case FileSystemAccess():
245-
throw UnimplementedError();
244+
case FileSystemFlushRequest():
245+
if (database?.database.vfs case IndexedDbFileSystem idb) {
246+
await idb.flush();
247+
}
248+
249+
return SimpleSuccessResponse(
250+
response: null, requestId: request.requestId);
251+
case FileSystemExistsQuery(:final fsType):
252+
await database!.database.opened;
253+
final vfs = database.database.vfs!;
254+
final exists = vfs.xAccess(fsType.pathInVfs, 0) == 1;
255+
256+
return SimpleSuccessResponse(
257+
response: exists.toJS, requestId: request.requestId);
258+
case FileSystemAccess(:final buffer, :final fsType):
259+
await database!.database.opened;
260+
final vfs = database.database.vfs!;
261+
final file = vfs
262+
.xOpen(
263+
Sqlite3Filename(fsType.pathInVfs), SqlFlag.SQLITE_OPEN_CREATE)
264+
.file;
265+
266+
try {
267+
if (buffer != null) {
268+
final asDartBuffer = buffer.toDart;
269+
file.xTruncate(asDartBuffer.lengthInBytes);
270+
file.xWrite(asDartBuffer.asUint8List(), 0);
271+
272+
return SimpleSuccessResponse(
273+
response: null, requestId: request.requestId);
274+
} else {
275+
final buffer = Uint8List(file.xFileSize());
276+
file.xRead(buffer, 0);
277+
278+
return SimpleSuccessResponse(
279+
response: buffer.buffer.toJS, requestId: request.requestId);
280+
}
281+
} finally {
282+
file.xClose();
283+
}
246284
}
247285
}
248286

@@ -268,6 +306,13 @@ final class _ClientConnection extends ProtocolChannel
268306
}
269307
}
270308

309+
extension on FileType {
310+
String get pathInVfs => switch (this) {
311+
FileType.database => '/database',
312+
FileType.journal => '/database-journal',
313+
};
314+
}
315+
271316
final class DatabaseState {
272317
final WorkerRunner runner;
273318
final int id;

0 commit comments

Comments
 (0)