adding packages
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop';
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
extension type InodeJS._(JSObject _) implements JSObject {
|
||||
external String get id;
|
||||
external set id(String value);
|
||||
|
||||
external String get parentId;
|
||||
external set parentId(String value);
|
||||
|
||||
external String get name;
|
||||
external set name(String value);
|
||||
|
||||
external int get nodeType;
|
||||
external set nodeType(int value);
|
||||
|
||||
external String? get blobId;
|
||||
external set blobId(String? value);
|
||||
|
||||
external int get size;
|
||||
external set size(int value);
|
||||
|
||||
external int get modified;
|
||||
external set modified(int value);
|
||||
|
||||
factory InodeJS({
|
||||
required String id,
|
||||
required String parentId,
|
||||
required String name,
|
||||
required int nodeType,
|
||||
String? blobId,
|
||||
int size = 0,
|
||||
required int modified,
|
||||
}) {
|
||||
final obj = JSObject() as InodeJS;
|
||||
obj.id = id;
|
||||
obj.parentId = parentId;
|
||||
obj.name = name;
|
||||
obj.nodeType = nodeType;
|
||||
obj.blobId = blobId;
|
||||
obj.size = size;
|
||||
obj.modified = modified;
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
class Inode {
|
||||
final String id;
|
||||
final String parentId;
|
||||
final String name;
|
||||
final int nodeType;
|
||||
final String? blobId;
|
||||
final int size;
|
||||
final int modified;
|
||||
|
||||
Inode({
|
||||
required this.id,
|
||||
required this.parentId,
|
||||
required this.name,
|
||||
required this.nodeType,
|
||||
this.blobId,
|
||||
this.size = 0,
|
||||
required this.modified,
|
||||
});
|
||||
|
||||
InodeJS toJS() {
|
||||
return InodeJS(
|
||||
id: id,
|
||||
parentId: parentId,
|
||||
name: name,
|
||||
nodeType: nodeType,
|
||||
blobId: blobId,
|
||||
size: size,
|
||||
modified: modified,
|
||||
);
|
||||
}
|
||||
|
||||
static Inode fromJS(InodeJS js) {
|
||||
return Inode(
|
||||
id: js.id,
|
||||
parentId: js.parentId,
|
||||
name: js.name,
|
||||
nodeType: js.nodeType,
|
||||
blobId: js.blobId,
|
||||
size: js.size,
|
||||
modified: js.modified,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IdbInodeService {
|
||||
static const String _dbName = 'WebFileSystemDB';
|
||||
static const int _version = 1;
|
||||
static const String _storeName = 'inodes';
|
||||
|
||||
web.IDBDatabase? _db;
|
||||
final Completer<void> _initCompleter = Completer<void>();
|
||||
static const String rootId = '00000000-0000-0000-0000-000000000000';
|
||||
|
||||
Future<void> _ensureReady() async {
|
||||
if (_db != null) return;
|
||||
if (_initCompleter.isCompleted) return _initCompleter.future;
|
||||
|
||||
final request = web.window.indexedDB.open(_dbName, _version);
|
||||
|
||||
request.onupgradeneeded = (web.IDBVersionChangeEvent event) {
|
||||
final db =
|
||||
(event.target as web.IDBOpenDBRequest).result as web.IDBDatabase;
|
||||
if (!db.objectStoreNames.contains(_storeName)) {
|
||||
final store = db.createObjectStore(
|
||||
_storeName,
|
||||
web.IDBObjectStoreParameters(keyPath: 'id'.toJS),
|
||||
);
|
||||
store.createIndex(
|
||||
'parentId', 'parentId'.toJS, web.IDBIndexParameters(unique: false));
|
||||
store.createIndex('parent_name', ['parentId'.toJS, 'name'.toJS].toJS,
|
||||
web.IDBIndexParameters(unique: true));
|
||||
}
|
||||
}.toJS;
|
||||
|
||||
final completer = Completer<void>();
|
||||
|
||||
request.onsuccess = (web.Event event) {
|
||||
_db = (event.target as web.IDBOpenDBRequest).result as web.IDBDatabase;
|
||||
_ensureRootExists().then((_) {
|
||||
if (!_initCompleter.isCompleted) completer.complete();
|
||||
}).catchError((e) {
|
||||
if (!_initCompleter.isCompleted) completer.completeError(e);
|
||||
});
|
||||
}.toJS;
|
||||
|
||||
request.onerror = (web.Event event) {
|
||||
if (!_initCompleter.isCompleted)
|
||||
completer.completeError(Exception('Failed to open IDB'));
|
||||
}.toJS;
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<void> _ensureRootExists() async {
|
||||
try {
|
||||
await getInode(rootId);
|
||||
} catch (_) {
|
||||
await createInode(Inode(
|
||||
id: rootId,
|
||||
parentId: 'null',
|
||||
name: '',
|
||||
nodeType: 1,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createInode(Inode inode) async {
|
||||
if (_db == null) await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readwrite'.toJS as web.IDBTransactionMode);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.put(inode.toJS());
|
||||
await _requestToFuture(request);
|
||||
}
|
||||
|
||||
Future<void> updateInode(Inode inode) async {
|
||||
await createInode(inode);
|
||||
}
|
||||
|
||||
Future<void> deleteInode(String id) async {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readwrite'.toJS as web.IDBTransactionMode);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.delete(id.toJS);
|
||||
await _requestToFuture(request);
|
||||
}
|
||||
|
||||
Future<Inode> getInode(String id) async {
|
||||
if (_db == null) await _ensureReady();
|
||||
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.get(id.toJS);
|
||||
final result = await _requestToFuture(request);
|
||||
if (result == null) throw Exception('Inode $id not found');
|
||||
|
||||
return Inode.fromJS(result as InodeJS);
|
||||
}
|
||||
|
||||
Future<Inode?> getChild(String parentId, String name) async {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final index = store.index('parent_name');
|
||||
final key = JSArray();
|
||||
key.add(parentId.toJS);
|
||||
key.add(name.toJS);
|
||||
|
||||
final request = index.get(key);
|
||||
|
||||
try {
|
||||
final result = await _requestToFuture(request);
|
||||
if (result == null) return null;
|
||||
return Inode.fromJS(result as InodeJS);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Inode>> listChildren(String parentId) async {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final index = store.index('parentId');
|
||||
final request = index.getAll(parentId.toJS);
|
||||
|
||||
final result = await _requestToFuture(request);
|
||||
final list = (result as JSArray).toDart;
|
||||
return list.map((item) => Inode.fromJS(item as InodeJS)).toList();
|
||||
}
|
||||
|
||||
Future<dynamic> _requestToFuture(web.IDBRequest request) {
|
||||
final completer = Completer<dynamic>();
|
||||
request.onsuccess = (web.Event e) {
|
||||
completer.complete((e.target as web.IDBRequest).result);
|
||||
}.toJS;
|
||||
request.onerror = (web.Event e) {
|
||||
completer.completeError(Exception('IDB Error'));
|
||||
}.toJS;
|
||||
return completer.future;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop';
|
||||
import 'dart:typed_data';
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class OpfsBlockStore {
|
||||
static const String _blocksDirName = '.blocks';
|
||||
web.FileSystemDirectoryHandle? _blocksDir;
|
||||
final Uuid _uuid = Uuid();
|
||||
|
||||
Future<void> _ensureReady() async {
|
||||
if (_blocksDir != null) return;
|
||||
|
||||
final web.StorageManager? storage = web.window.navigator.storage;
|
||||
if (storage == null) {
|
||||
throw UnsupportedError('StorageManager not supported');
|
||||
}
|
||||
|
||||
final root = await storage.getDirectory().toDart;
|
||||
_blocksDir = await root
|
||||
.getDirectoryHandle(
|
||||
_blocksDirName,
|
||||
web.FileSystemGetDirectoryOptions(create: true),
|
||||
)
|
||||
.toDart;
|
||||
}
|
||||
|
||||
Future<String> writeBlob(Stream<List<int>> stream) async {
|
||||
await _ensureReady();
|
||||
final blockId = _uuid.v4();
|
||||
|
||||
final fileHandle = await _blocksDir!
|
||||
.getFileHandle(
|
||||
blockId,
|
||||
web.FileSystemGetFileOptions(create: true),
|
||||
)
|
||||
.toDart;
|
||||
|
||||
final writable = await fileHandle.createWritable().toDart;
|
||||
|
||||
try {
|
||||
await for (final chunk in stream) {
|
||||
final uint8 = Uint8List.fromList(chunk);
|
||||
await writable.write(uint8.toJS).toDart;
|
||||
}
|
||||
await writable.close().toDart;
|
||||
} catch (e) {
|
||||
try {
|
||||
await writable.abort().toDart;
|
||||
await _blocksDir!.removeEntry(blockId).toDart;
|
||||
} catch (_) {}
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return blockId;
|
||||
}
|
||||
|
||||
Stream<List<int>> readBlob(String blockId) async* {
|
||||
await _ensureReady();
|
||||
try {
|
||||
final fileHandle = await _blocksDir!
|
||||
.getFileHandle(
|
||||
blockId,
|
||||
)
|
||||
.toDart;
|
||||
|
||||
final file = await fileHandle.getFile().toDart;
|
||||
final web.Blob blob = file;
|
||||
final reader =
|
||||
blob.stream().getReader() as web.ReadableStreamDefaultReader;
|
||||
|
||||
while (true) {
|
||||
final result = await reader.read().toDart;
|
||||
if (result.done) break;
|
||||
// Cast to JSUint8Array (via package:web assumption or direct JSObject)
|
||||
final chunk = result.value as JSUint8Array;
|
||||
yield chunk.toDart;
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteBlob(String blockId) async {
|
||||
await _ensureReady();
|
||||
try {
|
||||
await _blocksDir!.removeEntry(blockId).toDart;
|
||||
} catch (e) {
|
||||
// Ignore if not found
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user