117 lines
3.9 KiB
Markdown
117 lines
3.9 KiB
Markdown
# web_file_system
|
|
|
|
A high-performance, fully asynchronous VFS (Virtual File System) for the web implementing Dart's standard `package:file` interfaces.
|
|
|
|
It uses a hybrid backend that separates metadata management from actual block storage for optimal latency and performance in the browser.
|
|
|
|
---
|
|
|
|
## Key Features
|
|
|
|
* **`package:file` Compliance**: Drop-in replacement for standard file system operations. Fully compatible with libraries expecting a `FileSystem` interface.
|
|
* **O(1) Directory Renames**: By utilizing an inode-based database structure, directory renames and moves are fast constant-time operations. Children don't require path prefix updates.
|
|
* **IndexedDB Metadata Store**: Keeps directory hierarchies, timestamps, file sizes, and symbolic links indexed using fast IndexedDB tables.
|
|
* **OPFS (Origin Private File System) Block Store**: Stores raw file bytes and stream chunks directly in the high-performance private browser storage space.
|
|
* **Symbolic Links (Symlinks)**: Full support for relative and absolute symbolic links, target updating, recursive listing, and canonical path resolution.
|
|
|
|
---
|
|
|
|
## Architectural Flow
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph WebFileSystem ["WebFileSystem (Dart API)"]
|
|
FS["WebFileSystem"]
|
|
WD["WebDirectory"]
|
|
WF["WebFile"]
|
|
WL["WebLink"]
|
|
end
|
|
|
|
subgraph Metadata ["IndexedDB (IdbInodeService)"]
|
|
DB[(WebFileSystemDB)]
|
|
Inodes[Inode Table: parentId index]
|
|
end
|
|
|
|
subgraph DataBlocks ["OPFS (OpfsBlockStore)"]
|
|
BlocksDir[/.blocks/ Directory]
|
|
RawData[[UUID Data Blobs]]
|
|
end
|
|
|
|
FS -->|Lookup / Resolve| Metadata
|
|
WD -->|Read / Write Children| Metadata
|
|
WF -->|Read / Write Metadata| Metadata
|
|
WF -->|Read / Write Data Blocks| DataBlocks
|
|
WL -->|Read / Write Link Target| DataBlocks
|
|
```
|
|
|
|
---
|
|
|
|
## Getting Started
|
|
|
|
### Installation
|
|
|
|
Add `web_file_system` to your `pubspec.yaml` (or reference the path if using as a path dependency):
|
|
|
|
```yaml
|
|
dependencies:
|
|
web_file_system:
|
|
path: path/to/web_file_system
|
|
```
|
|
|
|
---
|
|
|
|
## Usage Examples
|
|
|
|
### 1. Initializing the File System
|
|
|
|
```dart
|
|
import 'package:web_file_system/web_file_system.dart';
|
|
|
|
final fs = WebFileSystem();
|
|
```
|
|
|
|
### 2. Creating and Reading Files
|
|
|
|
```dart
|
|
final file = fs.file('/documents/report.txt');
|
|
|
|
// Standard write & read (creates parent directories if needed when recursive is true)
|
|
await file.create(recursive: true);
|
|
await file.writeAsString('Hello, Web private storage!');
|
|
|
|
final contents = await file.readAsString();
|
|
print(contents); // "Hello, Web private storage!"
|
|
```
|
|
|
|
### 3. Directory Listing & Traversal
|
|
|
|
```dart
|
|
final dir = fs.directory('/documents');
|
|
await for (final entity in dir.list(recursive: true, followLinks: false)) {
|
|
print('${entity.path} (${entity.runtimeType})');
|
|
}
|
|
```
|
|
|
|
### 4. Symbolic Links
|
|
|
|
```dart
|
|
// Create a symbolic link
|
|
final link = fs.link('/shortcut_to_report');
|
|
await link.create('/documents/report.txt');
|
|
|
|
// Reading content through the link
|
|
final data = await fs.file('/shortcut_to_report').readAsString();
|
|
|
|
// Resolve canonical absolute path
|
|
final canonicalPath = await link.resolveSymbolicLinks();
|
|
print(canonicalPath); // "/documents/report.txt"
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Notes: Inode vs Path-based Renames
|
|
|
|
Most browser file systems index files by their full path strings (e.g., keying a database table by `/documents/photos/holiday.png`). Renaming the directory `/documents` to `/archive` requires iterating over every nested path and rewriting their keys, which is an $O(N)$ operation where $N$ is the number of recursive items.
|
|
|
|
`web_file_system` implements an **inode index** model. Inodes reference parents by their database IDs rather than paths. Renaming a directory only updates the single directory node's `name` property. Its child files and directories remain untouched because they continue to reference the same unchanged parent inode ID. This makes directory renaming an **$O(1)$** operation.
|