adding packages
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
import 'dart:async';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
||||
import '../web_file_system.dart';
|
||||
import 'web_file.dart';
|
||||
|
||||
class WebDirectory extends FileSystemEntity implements Directory {
|
||||
final WebFileSystem _fs;
|
||||
@override
|
||||
final String path;
|
||||
|
||||
WebDirectory(this._fs, this.path);
|
||||
|
||||
@override
|
||||
FileSystem get fileSystem => _fs;
|
||||
|
||||
@override
|
||||
Uri get uri => Uri.parse(path);
|
||||
|
||||
@override
|
||||
Future<Directory> create({bool recursive = false}) async {
|
||||
if (await exists()) return this;
|
||||
|
||||
final parentPath = _fs.path.dirname(path);
|
||||
final name = _fs.path.basename(path);
|
||||
|
||||
if (recursive) {
|
||||
await _createRecursiveSafe(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Validate parent exists (handled by resolvepath usually throwing, or we must check)
|
||||
// We assume parent must exist if not recursive.
|
||||
final parentInode = await _fs.resolvepath(parentPath); // throws if missing
|
||||
|
||||
await _fs.idb.createInode(Inode(
|
||||
id: _fs.uuid.v4(),
|
||||
parentId: parentInode.id,
|
||||
name: name,
|
||||
nodeType: 1, // Directory
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
Future<void> _createRecursiveSafe(String p) async {
|
||||
if (p == '/' || p == '.') return;
|
||||
if (await _fs.type(p) != FileSystemEntityType.notFound) return;
|
||||
|
||||
await _createRecursiveSafe(_fs.path.dirname(p));
|
||||
final parentVal = await _fs.resolvepath(_fs.path.dirname(p));
|
||||
await _fs.idb.createInode(Inode(
|
||||
id: _fs.uuid.v4(),
|
||||
parentId: parentVal.id,
|
||||
name: _fs.path.basename(p),
|
||||
nodeType: 1,
|
||||
modified: DateTime.now().millisecondsSinceEpoch));
|
||||
}
|
||||
|
||||
@override
|
||||
void createSync({bool recursive = false}) {
|
||||
throw UnsupportedError('Sync create not supported');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Directory> createTemp([String? prefix]) async {
|
||||
final name = (prefix ?? 'temp') + _fs.uuid.v4();
|
||||
final tempDir = _fs.path.join(path, name);
|
||||
// Ensure path exists
|
||||
if (!await exists()) {
|
||||
throw FileSystemException(
|
||||
'Directory does not exist', path, const OSError('ENOENT', 2));
|
||||
}
|
||||
final dir = WebDirectory(_fs, tempDir);
|
||||
await dir.create();
|
||||
return dir;
|
||||
}
|
||||
|
||||
@override
|
||||
Directory createTempSync([String? prefix]) {
|
||||
throw UnsupportedError('Sync not supported');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FileSystemEntity> delete({bool recursive = false}) async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
|
||||
final children = await _fs.idb.listChildren(inode.id);
|
||||
if (children.isNotEmpty && !recursive) {
|
||||
throw FileSystemException(
|
||||
'Directory not empty', path, const OSError('ENOTEMPTY', 39));
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
for (final child in children) {
|
||||
final childPath = _fs.path.join(path, child.name);
|
||||
if (child.nodeType == 1) {
|
||||
await _fs.directory(childPath).delete(recursive: true);
|
||||
} else {
|
||||
await _fs.file(childPath).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _fs.idb.deleteInode(inode.id);
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
void deleteSync({bool recursive = false}) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<bool> exists() async {
|
||||
try {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
return inode.nodeType == 1;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool existsSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Stream<FileSystemEntity> list(
|
||||
{bool recursive = false, bool followLinks = true}) async* {
|
||||
if (!await exists()) {
|
||||
throw FileSystemException(
|
||||
'Directory not found', path, const OSError('ENOENT', 2));
|
||||
}
|
||||
|
||||
final inode = await _fs.resolvepath(path);
|
||||
final children = await _fs.idb.listChildren(inode.id);
|
||||
|
||||
for (final child in children) {
|
||||
final childPath = _fs.path.join(path, child.name);
|
||||
if (child.nodeType == 1) {
|
||||
final dir = WebDirectory(_fs, childPath);
|
||||
yield dir;
|
||||
if (recursive) {
|
||||
yield* dir.list(recursive: true, followLinks: followLinks);
|
||||
}
|
||||
} else {
|
||||
yield WebFile(_fs, childPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<FileSystemEntity> listSync(
|
||||
{bool recursive = false, bool followLinks = true}) {
|
||||
throw UnsupportedError('Sync list not supported');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Directory> rename(String newPath) async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
final newParentDir = _fs.path.dirname(newPath);
|
||||
final newName = _fs.path.basename(newPath);
|
||||
|
||||
final newParentInode = await _fs.resolvepath(newParentDir);
|
||||
|
||||
final updated = Inode(
|
||||
id: inode.id,
|
||||
parentId: newParentInode.id,
|
||||
name: newName,
|
||||
nodeType: inode.nodeType,
|
||||
blobId: inode.blobId,
|
||||
size: inode.size,
|
||||
modified: DateTime.now().millisecondsSinceEpoch);
|
||||
|
||||
await _fs.idb.updateInode(updated);
|
||||
return WebDirectory(_fs, newPath);
|
||||
}
|
||||
|
||||
@override
|
||||
Directory renameSync(String newPath) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
String get basename => _fs.path.basename(path);
|
||||
|
||||
@override
|
||||
String get dirname => _fs.path.dirname(path);
|
||||
|
||||
@override
|
||||
Directory get parent => _fs.directory(dirname);
|
||||
|
||||
@override
|
||||
bool get isAbsolute => _fs.path.isAbsolute(path);
|
||||
|
||||
@override
|
||||
Directory get absolute => WebDirectory(_fs, _fs.path.absolute(path));
|
||||
|
||||
@override
|
||||
Future<FileStat> stat() => _fs.stat(path);
|
||||
|
||||
@override
|
||||
FileStat statSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<String> resolveSymbolicLinks() async => path;
|
||||
|
||||
@override
|
||||
String resolveSymbolicLinksSync() =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Stream<FileSystemEvent> watch(
|
||||
{int events = FileSystemEvent.all, bool recursive = false}) {
|
||||
return const Stream.empty();
|
||||
}
|
||||
|
||||
@override
|
||||
Directory childDirectory(String basename) =>
|
||||
_fs.directory(_fs.path.join(path, basename));
|
||||
|
||||
@override
|
||||
File childFile(String basename) => _fs.file(_fs.path.join(path, basename));
|
||||
|
||||
@override
|
||||
Link childLink(String basename) => _fs.link(_fs.path.join(path, basename));
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
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';
|
||||
|
||||
class WebFile extends FileSystemEntity implements File {
|
||||
final WebFileSystem _fs;
|
||||
@override
|
||||
final String path;
|
||||
|
||||
WebFile(this._fs, this.path);
|
||||
|
||||
@override
|
||||
FileSystem get fileSystem => _fs;
|
||||
|
||||
@override
|
||||
Future<File> create({bool recursive = false, bool exclusive = false}) async {
|
||||
if (await exists()) {
|
||||
if (exclusive) {
|
||||
throw FileSystemException(
|
||||
'File already exists',
|
||||
path,
|
||||
const OSError('EEXIST', 17),
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
final parentDir = _fs.path.dirname(path);
|
||||
if (await _fs.type(parentDir) == FileSystemEntityType.notFound) {
|
||||
await _fs.directory(parentDir).create(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
final parentPath = _fs.path.dirname(path);
|
||||
final parentInode = await _fs.resolvepath(parentPath);
|
||||
|
||||
await _fs.idb.createInode(
|
||||
Inode(
|
||||
id: _fs.uuid.v4(),
|
||||
parentId: parentInode.id,
|
||||
name: _fs.path.basename(path),
|
||||
nodeType: 0,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
void createSync({bool recursive = false, bool exclusive = false}) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<File> copy(String newPath) async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
final newParent = await _fs.resolvepath(_fs.path.dirname(newPath));
|
||||
|
||||
await _fs.idb.createInode(
|
||||
Inode(
|
||||
id: _fs.uuid.v4(),
|
||||
parentId: newParent.id,
|
||||
name: _fs.path.basename(newPath),
|
||||
nodeType: 0,
|
||||
blobId: inode.blobId,
|
||||
size: inode.size,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
|
||||
return WebFile(_fs, newPath);
|
||||
}
|
||||
|
||||
@override
|
||||
File copySync(String newPath) => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<int> length() async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
return inode.size;
|
||||
}
|
||||
|
||||
@override
|
||||
int lengthSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<DateTime> lastModified() async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
return DateTime.fromMillisecondsSinceEpoch(inode.modified);
|
||||
}
|
||||
|
||||
@override
|
||||
DateTime lastModifiedSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<DateTime> lastAccessed() async {
|
||||
return lastModified();
|
||||
}
|
||||
|
||||
@override
|
||||
DateTime lastAccessedSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<dynamic> setLastAccessed(DateTime time) async {}
|
||||
|
||||
@override
|
||||
void setLastAccessedSync(DateTime time) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<dynamic> setLastModified(DateTime time) async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
await _fs.idb.updateInode(
|
||||
Inode(
|
||||
id: inode.id,
|
||||
parentId: inode.parentId,
|
||||
name: inode.name,
|
||||
nodeType: inode.nodeType,
|
||||
blobId: inode.blobId,
|
||||
size: inode.size,
|
||||
modified: time.millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void setLastModifiedSync(DateTime time) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<RandomAccessFile> open({FileMode mode = FileMode.read}) async {
|
||||
throw UnsupportedError(
|
||||
'RandomAccessFile not supported on web (use streams)',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
RandomAccessFile openSync({FileMode mode = FileMode.read}) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<File> writeAsBytes(
|
||||
List<int> bytes, {
|
||||
FileMode mode = FileMode.write,
|
||||
bool flush = false,
|
||||
}) async {
|
||||
final stream = Stream.value(bytes);
|
||||
final newBlobId = await _fs.opfs.writeBlob(stream);
|
||||
|
||||
Inode inode;
|
||||
try {
|
||||
inode = await _fs.resolvepath(path);
|
||||
if (mode == FileMode.append) {
|
||||
throw UnsupportedError('Append not yet optimized');
|
||||
}
|
||||
} catch (_) {
|
||||
await create(recursive: true);
|
||||
inode = await _fs.resolvepath(path);
|
||||
}
|
||||
|
||||
await _fs.idb.updateInode(
|
||||
Inode(
|
||||
id: inode.id,
|
||||
parentId: inode.parentId,
|
||||
name: inode.name,
|
||||
nodeType: inode.nodeType,
|
||||
blobId: newBlobId,
|
||||
size: bytes.length,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
void writeAsBytesSync(
|
||||
List<int> bytes, {
|
||||
FileMode mode = FileMode.write,
|
||||
bool flush = false,
|
||||
}) {
|
||||
throw UnsupportedError('Sync not supported');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<File> writeAsString(
|
||||
String contents, {
|
||||
FileMode mode = FileMode.write,
|
||||
Encoding encoding = utf8,
|
||||
bool flush = false,
|
||||
}) async {
|
||||
return writeAsBytes(encoding.encode(contents), mode: mode, flush: flush);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeAsStringSync(
|
||||
String contents, {
|
||||
FileMode mode = FileMode.write,
|
||||
Encoding encoding = utf8,
|
||||
bool flush = false,
|
||||
}) {
|
||||
throw UnsupportedError('Sync not supported');
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<int>> openRead([int? start, int? end]) async* {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
if (inode.blobId == null) return;
|
||||
|
||||
yield* _fs.opfs.readBlob(inode.blobId!);
|
||||
}
|
||||
|
||||
@override
|
||||
IOSink openWrite({FileMode mode = FileMode.write, Encoding encoding = utf8}) {
|
||||
final controller = StreamController<List<int>>();
|
||||
|
||||
// Start background write but keep future to await in close()
|
||||
final writeFuture = _handleWrite(controller.stream, encoding, mode);
|
||||
|
||||
final sink = _WebIOSink(
|
||||
controller,
|
||||
encoding,
|
||||
onDone: () async {
|
||||
await writeFuture;
|
||||
},
|
||||
);
|
||||
|
||||
return sink;
|
||||
}
|
||||
|
||||
Future<void> _handleWrite(
|
||||
Stream<List<int>> stream,
|
||||
Encoding encoding,
|
||||
FileMode mode,
|
||||
) async {
|
||||
try {
|
||||
final newId = await _fs.opfs.writeBlob(stream);
|
||||
|
||||
Inode inode;
|
||||
try {
|
||||
inode = await _fs.resolvepath(path);
|
||||
} catch (_) {
|
||||
// Create if missing
|
||||
await create(recursive: true);
|
||||
inode = await _fs.resolvepath(path);
|
||||
}
|
||||
|
||||
await _fs.idb.updateInode(
|
||||
Inode(
|
||||
id: inode.id,
|
||||
parentId: inode.parentId,
|
||||
name: inode.name,
|
||||
nodeType: 0,
|
||||
blobId: newId,
|
||||
size:
|
||||
0, // TODO: Size not returned by OPFS yet, so 0 for streamed content
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
throw FileSystemException('Write failed: $e', path);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> readAsBytes() async {
|
||||
final chunks = await openRead().toList();
|
||||
return Uint8List.fromList(chunks.expand((x) => x).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Uint8List readAsBytesSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<String> readAsString({Encoding encoding = utf8}) async {
|
||||
final bytes = await readAsBytes();
|
||||
return encoding.decode(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
String readAsStringSync({Encoding encoding = utf8}) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<List<String>> readAsLines({Encoding encoding = utf8}) async {
|
||||
final str = await readAsString(encoding: encoding);
|
||||
return str.split('\n');
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> readAsLinesSync({Encoding encoding = utf8}) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<bool> exists() async {
|
||||
try {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
return inode.nodeType == 0;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool existsSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<File> rename(String newPath) async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
final newParentDir = _fs.path.dirname(newPath);
|
||||
final newName = _fs.path.basename(newPath);
|
||||
final newParentInode = await _fs.resolvepath(newParentDir);
|
||||
|
||||
final updated = Inode(
|
||||
id: inode.id,
|
||||
parentId: newParentInode.id,
|
||||
name: newName,
|
||||
nodeType: inode.nodeType,
|
||||
blobId: inode.blobId,
|
||||
size: inode.size,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
|
||||
await _fs.idb.updateInode(updated);
|
||||
return WebFile(_fs, newPath);
|
||||
}
|
||||
|
||||
@override
|
||||
File renameSync(String newPath) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<FileSystemEntity> delete({bool recursive = false}) async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
await _fs.idb.deleteInode(inode.id);
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
void deleteSync({bool recursive = false}) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<FileStat> stat() => _fs.stat(path);
|
||||
|
||||
@override
|
||||
FileStat statSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Uri get uri => Uri.parse(path);
|
||||
@override
|
||||
String get basename => _fs.path.basename(path);
|
||||
@override
|
||||
String get dirname => _fs.path.dirname(path);
|
||||
@override
|
||||
Directory get parent => _fs.directory(dirname);
|
||||
@override
|
||||
bool get isAbsolute => _fs.path.isAbsolute(path);
|
||||
@override
|
||||
File get absolute => WebFile(_fs, _fs.path.absolute(path));
|
||||
|
||||
@override
|
||||
Future<String> resolveSymbolicLinks() async => path;
|
||||
|
||||
@override
|
||||
String resolveSymbolicLinksSync() =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Stream<FileSystemEvent> watch({
|
||||
int events = FileSystemEvent.all,
|
||||
bool recursive = false,
|
||||
}) {
|
||||
return const Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
class _WebIOSink implements IOSink {
|
||||
final StreamController<List<int>> _controller;
|
||||
final Future<void> Function()? onDone;
|
||||
Encoding _encoding;
|
||||
|
||||
_WebIOSink(this._controller, this._encoding, {this.onDone});
|
||||
|
||||
@override
|
||||
Encoding get encoding => _encoding;
|
||||
|
||||
@override
|
||||
set encoding(Encoding value) => _encoding = value;
|
||||
|
||||
@override
|
||||
void add(List<int> data) {
|
||||
_controller.add(data);
|
||||
}
|
||||
|
||||
@override
|
||||
void addError(Object error, [StackTrace? stackTrace]) {
|
||||
_controller.addError(error, stackTrace);
|
||||
}
|
||||
|
||||
@override
|
||||
Future addStream(Stream<List<int>> stream) {
|
||||
return _controller.addStream(stream);
|
||||
}
|
||||
|
||||
@override
|
||||
Future close() async {
|
||||
await _controller.close();
|
||||
if (onDone != null) await onDone!();
|
||||
}
|
||||
|
||||
@override
|
||||
Future get done => _controller.done;
|
||||
|
||||
@override
|
||||
Future flush() async {}
|
||||
|
||||
@override
|
||||
void write(Object? object) {
|
||||
add(encoding.encode(object.toString()));
|
||||
}
|
||||
|
||||
@override
|
||||
void writeAll(Iterable objects, [String separator = ""]) {
|
||||
write(objects.join(separator));
|
||||
}
|
||||
|
||||
@override
|
||||
void writeCharCode(int charCode) {
|
||||
add([charCode]);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeln([Object? object = ""]) {
|
||||
write(object);
|
||||
write('\n');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
||||
import '../web_file_system.dart';
|
||||
|
||||
class WebLink extends FileSystemEntity implements Link {
|
||||
final WebFileSystem _fs;
|
||||
@override
|
||||
final String path;
|
||||
|
||||
WebLink(this._fs, this.path);
|
||||
|
||||
@override
|
||||
FileSystem get fileSystem => _fs;
|
||||
|
||||
@override
|
||||
Future<Link> create(String target, {bool recursive = false}) async {
|
||||
if (await exists()) {
|
||||
// Should throw if exists? Standard create throws if already exists usually unless overwrite logic applied?
|
||||
// create(recursive) usually implies ensuring parent exists.
|
||||
throw FileSystemException(
|
||||
'Link already exists',
|
||||
path,
|
||||
const OSError('EEXIST', 17),
|
||||
);
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
final parentDir = _fs.path.dirname(path);
|
||||
if (await _fs.type(parentDir) == FileSystemEntityType.notFound) {
|
||||
await _fs.directory(parentDir).create(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
// Write target path string to OPFS blob
|
||||
final stream = Stream.value(utf8.encode(target));
|
||||
final blobId = await _fs.opfs.writeBlob(stream);
|
||||
|
||||
final parentPath = _fs.path.dirname(path);
|
||||
final parentInode = await _fs.resolvepath(parentPath);
|
||||
|
||||
await _fs.idb.createInode(
|
||||
Inode(
|
||||
id: _fs.uuid.v4(),
|
||||
parentId: parentInode.id,
|
||||
name: _fs.path.basename(path),
|
||||
nodeType: 2, // Link
|
||||
blobId: blobId,
|
||||
size: target.length,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
void createSync(String target, {bool recursive = false}) {
|
||||
throw UnsupportedError('Sync not supported');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Link> update(String target) async {
|
||||
final inode = await _fs.resolvepath(path, followLinks: false);
|
||||
|
||||
// Write new blob
|
||||
final stream = Stream.value(utf8.encode(target));
|
||||
final blobId = await _fs.opfs.writeBlob(stream);
|
||||
|
||||
await _fs.idb.updateInode(
|
||||
Inode(
|
||||
id: inode.id,
|
||||
parentId: inode.parentId,
|
||||
name: inode.name,
|
||||
nodeType: 2,
|
||||
blobId: blobId,
|
||||
size: target.length,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
void updateSync(String target) {
|
||||
throw UnsupportedError('Sync not supported');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> target() async {
|
||||
final inode = await _fs.resolvepath(path, followLinks: false);
|
||||
if (inode.nodeType != 2) {
|
||||
throw FileSystemException(
|
||||
'Not a link',
|
||||
path,
|
||||
const OSError('EINVAL', 22),
|
||||
);
|
||||
}
|
||||
if (inode.blobId == null) return '';
|
||||
|
||||
final bytesList = await _fs.opfs.readBlob(inode.blobId!).toList();
|
||||
final bytes = bytesList.expand((x) => x).toList();
|
||||
return utf8.decode(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
String targetSync() {
|
||||
throw UnsupportedError('Sync not supported');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Link> rename(String newPath) async {
|
||||
final inode = await _fs.resolvepath(path, followLinks: false);
|
||||
final newParentDir = _fs.path.dirname(newPath);
|
||||
final newName = _fs.path.basename(newPath);
|
||||
final newParentInode = await _fs.resolvepath(newParentDir);
|
||||
|
||||
final updated = Inode(
|
||||
id: inode.id,
|
||||
parentId: newParentInode.id,
|
||||
name: newName,
|
||||
nodeType: inode.nodeType,
|
||||
blobId: inode.blobId,
|
||||
size: inode.size,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
|
||||
await _fs.idb.updateInode(updated);
|
||||
return WebLink(_fs, newPath);
|
||||
}
|
||||
|
||||
@override
|
||||
Link renameSync(String newPath) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<FileSystemEntity> delete({bool recursive = false}) async {
|
||||
final inode = await _fs.resolvepath(path, followLinks: false);
|
||||
await _fs.idb.deleteInode(inode.id);
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
void deleteSync({bool recursive = false}) =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<bool> exists() async {
|
||||
try {
|
||||
final inode = await _fs.resolvepath(path, followLinks: false);
|
||||
return inode.nodeType == 2;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool existsSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Future<FileStat> stat() async => (await _fs.stat(path));
|
||||
|
||||
@override
|
||||
FileStat statSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Uri get uri => Uri.parse(path);
|
||||
|
||||
@override
|
||||
String get basename => _fs.path.basename(path);
|
||||
|
||||
@override
|
||||
String get dirname => _fs.path.dirname(path);
|
||||
|
||||
@override
|
||||
Directory get parent => _fs.directory(dirname);
|
||||
|
||||
@override
|
||||
bool get isAbsolute => _fs.path.isAbsolute(path);
|
||||
|
||||
@override
|
||||
Link get absolute => WebLink(_fs, _fs.path.absolute(path));
|
||||
|
||||
@override
|
||||
Future<String> resolveSymbolicLinks() async {
|
||||
// If we are a link, return target? No, resolveSymbolicLinks follows all the way to canonical path.
|
||||
// For now, simpler: resolve path logic.
|
||||
final targetPath = await target();
|
||||
// If target is relative, resolve against directory. This gets complex.
|
||||
// MVP: Return target path raw? No, contract says "path with all symbolic links resolved".
|
||||
// This requires full traversal logic.
|
||||
// For MVP just return the path as we stored it if it's absolute, or join if relative.
|
||||
if (_fs.path.isAbsolute(targetPath)) return targetPath;
|
||||
return _fs.path.normalize(_fs.path.join(dirname, targetPath));
|
||||
}
|
||||
|
||||
@override
|
||||
String resolveSymbolicLinksSync() =>
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Stream<FileSystemEvent> watch({
|
||||
int events = FileSystemEvent.all,
|
||||
bool recursive = false,
|
||||
}) {
|
||||
return const Stream.empty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user