adding packages

This commit is contained in:
2026-01-15 14:38:46 -08:00
parent ef86e3ab6a
commit 6869cf47e5
5253 changed files with 726695 additions and 34 deletions
@@ -0,0 +1,260 @@
import 'dart:async';
import 'dart:convert';
import 'package:file/file.dart';
import 'package:path/path.dart' as p;
import 'package:uuid/uuid.dart';
import 'package:web_file_system/src/backend/idb_inode_service.dart';
import 'package:web_file_system/src/backend/opfs_block_store.dart';
import 'entities/web_directory.dart';
import 'entities/web_file.dart';
import 'entities/web_link.dart';
class WebFileSystem extends FileSystem {
final IdbInodeService _idb = IdbInodeService();
final OpfsBlockStore _opfs = OpfsBlockStore();
final Uuid _uuid = Uuid();
// Public matchers for internal use
IdbInodeService get idb => _idb;
OpfsBlockStore get opfs => _opfs;
Uuid get uuid => _uuid;
WebFileSystem();
@override
Directory directory(path) => WebDirectory(this, getPath(path));
@override
File file(path) => WebFile(this, getPath(path));
@override
Link link(path) => WebLink(this, getPath(path));
@override
p.Context get path => p.Context(style: p.Style.posix);
@override
Directory get currentDirectory => directory('/');
@override
set currentDirectory(dynamic path) {
throw UnsupportedError('Changing CWD not supported on web');
}
@override
Directory get systemTempDirectory => directory('/tmp');
@override
Future<FileSystemEntityType> type(
String path, {
bool followLinks = true,
}) async {
try {
final inode = await resolvepath(path, followLinks: followLinks);
if (inode.nodeType == 0) return FileSystemEntityType.file;
if (inode.nodeType == 1) return FileSystemEntityType.directory;
if (inode.nodeType == 2) return FileSystemEntityType.link;
return FileSystemEntityType.notFound;
} catch (e) {
return FileSystemEntityType.notFound;
}
}
@override
FileSystemEntityType typeSync(String path, {bool followLinks = true}) {
throw UnsupportedError('Sync type not supported');
}
// Internal Resolution Logic
Future<Inode> resolvepath(String pathStr, {bool followLinks = true}) async {
final normalized = path.normalize(pathStr);
final parts = path.split(normalized);
// Root is '/'
String currentId = IdbInodeService.rootId;
Inode currentInode = await _idb.getInode(currentId);
// Common recursion guard
int linkDepth = 0;
const maxLinkDepth = 20;
for (int i = 0; i < parts.length; i++) {
final part = parts[i];
if (part.isEmpty || part == '/' || part == '.') continue;
// Lookup child
final child = await _idb.getChild(currentId, part);
if (child == null) {
throw FileSystemException(
'No such file or directory',
pathStr,
const OSError('ENOENT', 2),
);
}
// If child is Link
if (child.nodeType == 2) {
// If we are at the last part, only follow if followLinks is true
if (i == parts.length - 1 && !followLinks) {
return child;
}
// Follow link
if (child.blobId != null) {
if (linkDepth++ > maxLinkDepth) {
throw FileSystemException(
'Too many levels of symbolic links',
pathStr,
const OSError('ELOOP', 40),
);
}
// Read target
final bytesList = await _opfs.readBlob(child.blobId!).toList();
final bytes = bytesList.expand((x) => x).toList();
final targetPath = utf8.decode(bytes);
// Resolve target path
// Standard: Relative to directory containing link if not absolute.
String resolvedTarget;
if (path.isAbsolute(targetPath)) {
resolvedTarget = targetPath;
} else {
// Parent path relative to root
final parentParts = parts.take(i).toList();
// join behaves weirdly with context parts, ensure root if needed
final parentStr = path.joinAll(['/', ...parentParts]);
resolvedTarget = path.normalize(path.join(parentStr, targetPath));
}
// Resolve the target inode (ALWAYS follow links when resolving intermediate link targets)
final targetInode = await resolvepath(
resolvedTarget,
followLinks: true,
);
// If we have more parts remaining in the original path, we must continue from this target
if (i < parts.length - 1) {
if (targetInode.nodeType != 1) {
throw FileSystemException(
'Not a directory',
pathStr,
const OSError('ENOTDIR', 20),
);
}
currentId = targetInode.id;
currentInode = targetInode;
continue;
} else {
// End of path, return target
return targetInode;
}
}
}
currentId = child.id;
currentInode = child;
}
return currentInode;
}
String getPath(dynamic path) {
if (path is String) return path;
if (path is FileSystemEntity) return path.path;
if (path is Uri) return path.toFilePath();
throw ArgumentError('Path must be a String, Uri, or FileSystemEntity');
}
@override
Future<FileStat> stat(String path) async {
try {
final inode = await resolvepath(path, followLinks: true);
return FileStatImpl(inode.modified, inode.size, _getType(inode.nodeType));
} catch (e) {
return FileStatImpl(0, 0, FileSystemEntityType.notFound);
}
}
@override
FileStat statSync(String path) {
throw UnsupportedError('Sync stat not supported');
}
FileSystemEntityType _getType(int nodeType) {
if (nodeType == 0) return FileSystemEntityType.file;
if (nodeType == 1) return FileSystemEntityType.directory;
if (nodeType == 2) return FileSystemEntityType.link;
return FileSystemEntityType.notFound;
}
@override
bool isFileSync(String path) =>
throw UnsupportedError('Sync isFile not supported');
@override
bool isDirectorySync(String path) =>
throw UnsupportedError('Sync isDirectory not supported');
@override
bool isLinkSync(String path) =>
throw UnsupportedError('Sync isLink not supported');
@override
Future<bool> isFile(String path) async =>
(await type(path)) == FileSystemEntityType.file;
@override
Future<bool> isDirectory(String path) async =>
(await type(path)) == FileSystemEntityType.directory;
@override
Future<bool> isLink(String path) async =>
(await type(path, followLinks: false)) == FileSystemEntityType.link;
bool get isWatchSupported => false;
@override
Future<bool> identical(String path1, String path2) async {
final s1 = await stat(path1);
final s2 = await stat(path2);
if (s1.type == FileSystemEntityType.notFound ||
s2.type == FileSystemEntityType.notFound)
return false;
final i1 = await resolvepath(path1);
final i2 = await resolvepath(path2);
return i1.id == i2.id;
}
@override
bool identicalSync(String path1, String path2) =>
throw UnsupportedError('Sync not supported');
}
class FileStatImpl implements FileStat {
final int _modified;
final int _size;
final FileSystemEntityType _type;
FileStatImpl(this._modified, this._size, this._type);
@override
DateTime get accessed => DateTime.fromMillisecondsSinceEpoch(_modified);
@override
DateTime get changed => DateTime.fromMillisecondsSinceEpoch(_modified);
@override
int get mode => 0;
@override
DateTime get modified => DateTime.fromMillisecondsSinceEpoch(_modified);
@override
int get size => _size;
@override
FileSystemEntityType get type => _type;
@override
String modeString() => 'rwxrwxrwx';
}