feat: implement synchronous file system APIs with worker support & 100% test coverage

This commit is contained in:
2026-06-07 17:32:22 -07:00
parent fcc1a194c4
commit afb6302f0d
8 changed files with 1715 additions and 55 deletions
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:file/file.dart';
import 'package:web_file_system/src/backend/idb_inode_service.dart';
import '../web_file_system.dart';
@@ -57,7 +58,41 @@ class WebLink extends FileSystemEntity implements Link {
@override
void createSync(String target, {bool recursive = false}) {
throw UnsupportedError('Sync not supported');
if (existsSync()) {
throw FileSystemException(
'Link already exists',
path,
const OSError('EEXIST', 17),
);
}
final parentPath = _fs.path.dirname(path);
final parentType = _fs.typeSync(parentPath);
if (parentType == FileSystemEntityType.notFound) {
if (recursive) {
_fs.directory(parentPath).createSync(recursive: true);
} else {
throw FileSystemException(
'Cannot create link, path = \'$path\' (OS Error: No such file or directory, errno = 2)',
path,
);
}
} else if (parentType != FileSystemEntityType.directory) {
throw FileSystemException(
'Cannot create link, path = \'$path\' (OS Error: Not a directory, errno = 20)',
path,
);
}
final pathBytes = utf8.encode(path);
final targetBytes = utf8.encode(target);
final request = Uint8List(4 + pathBytes.length + targetBytes.length);
final bd = ByteData.sublistView(request);
bd.setUint32(0, pathBytes.length, Endian.little);
request.setRange(4, 4 + pathBytes.length, pathBytes);
request.setRange(4 + pathBytes.length, request.length, targetBytes);
_fs.makeSyncCall(7, request);
}
@override
@@ -85,7 +120,30 @@ class WebLink extends FileSystemEntity implements Link {
@override
void updateSync(String target) {
throw UnsupportedError('Sync not supported');
final type = _fs.typeSync(path, followLinks: false);
if (type == FileSystemEntityType.notFound) {
throw FileSystemException(
'Cannot update link, path = \'$path\' (OS Error: No such file or directory, errno = 2)',
path,
);
}
if (type != FileSystemEntityType.link) {
throw FileSystemException(
'Not a link',
path,
const OSError('EINVAL', 22),
);
}
final pathBytes = utf8.encode(path);
final targetBytes = utf8.encode(target);
final request = Uint8List(4 + pathBytes.length + targetBytes.length);
final bd = ByteData.sublistView(request);
bd.setUint32(0, pathBytes.length, Endian.little);
request.setRange(4, 4 + pathBytes.length, pathBytes);
request.setRange(4 + pathBytes.length, request.length, targetBytes);
_fs.makeSyncCall(13, request);
}
@override
@@ -107,7 +165,22 @@ class WebLink extends FileSystemEntity implements Link {
@override
String targetSync() {
throw UnsupportedError('Sync not supported');
final type = _fs.typeSync(path, followLinks: false);
if (type == FileSystemEntityType.notFound) {
throw FileSystemException(
'Cannot read link, path = \'$path\' (OS Error: No such file or directory, errno = 2)',
path,
);
}
if (type != FileSystemEntityType.link) {
throw FileSystemException(
'Not a link',
path,
const OSError('EINVAL', 22),
);
}
final respBytes = _fs.makeSyncCall(8, utf8.encode(path));
return utf8.decode(respBytes);
}
@override
@@ -132,8 +205,33 @@ class WebLink extends FileSystemEntity implements Link {
}
@override
Link renameSync(String newPath) =>
throw UnsupportedError('Sync not supported');
Link renameSync(String newPath) {
final type = _fs.typeSync(path, followLinks: false);
if (type == FileSystemEntityType.notFound) {
throw FileSystemException(
'Cannot rename link, path = \'$path\' (OS Error: No such file or directory, errno = 2)',
path,
);
}
final newParentDir = _fs.path.dirname(newPath);
if (_fs.typeSync(newParentDir) != FileSystemEntityType.directory) {
throw FileSystemException(
'Cannot rename link, path = \'$path\' (OS Error: No such file or directory, errno = 2)',
path,
);
}
final pathBytes = utf8.encode(path);
final newPathBytes = utf8.encode(newPath);
final request = Uint8List(4 + pathBytes.length + newPathBytes.length);
final bd = ByteData.sublistView(request);
bd.setUint32(0, pathBytes.length, Endian.little);
request.setRange(4, 4 + pathBytes.length, pathBytes);
request.setRange(4 + pathBytes.length, request.length, newPathBytes);
_fs.makeSyncCall(12, request);
return WebLink(_fs, newPath);
}
@override
Future<FileSystemEntity> delete({bool recursive = false}) async {
@@ -143,8 +241,16 @@ class WebLink extends FileSystemEntity implements Link {
}
@override
void deleteSync({bool recursive = false}) =>
throw UnsupportedError('Sync not supported');
void deleteSync({bool recursive = false}) {
final type = _fs.typeSync(path, followLinks: false);
if (type == FileSystemEntityType.notFound) {
throw FileSystemException(
'Cannot delete link, path = \'$path\' (OS Error: No such file or directory, errno = 2)',
path,
);
}
_fs.makeSyncCall(6, utf8.encode(path));
}
@override
Future<bool> exists() async {
@@ -157,13 +263,15 @@ class WebLink extends FileSystemEntity implements Link {
}
@override
bool existsSync() => throw UnsupportedError('Sync not supported');
bool existsSync() {
return _fs.typeSync(path, followLinks: false) == FileSystemEntityType.link;
}
@override
Future<FileStat> stat() async => (await _fs.stat(path));
@override
FileStat statSync() => throw UnsupportedError('Sync not supported');
FileStat statSync() => _fs.statSync(path);
@override
Uri get uri => Uri.parse(path);
@@ -187,8 +295,7 @@ class WebLink extends FileSystemEntity implements Link {
Future<String> resolveSymbolicLinks() => _fs.resolveSymbolicLinks(path);
@override
String resolveSymbolicLinksSync() =>
throw UnsupportedError('Sync not supported');
String resolveSymbolicLinksSync() => _fs.resolveSymbolicLinksSync(path);
@override
Stream<FileSystemEvent> watch({