Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 839a57ea6b | |||
| 042da8c7e3 | |||
| 981779120a | |||
| 00d5ced000 | |||
| ad649263d5 | |||
| 2a88573734 | |||
| a627abc813 | |||
| afb6302f0d | |||
| fcc1a194c4 | |||
| e1e8d8ded0 | |||
| 9ec5986d6e | |||
| cc32380977 |
@@ -0,0 +1,60 @@
|
|||||||
|
# Generated and managed by Antigravity
|
||||||
|
**/.dart_tool
|
||||||
|
**/build
|
||||||
|
.agents
|
||||||
|
.dart_tool
|
||||||
|
.git
|
||||||
|
.gitea
|
||||||
|
.github
|
||||||
|
.vscode
|
||||||
|
build
|
||||||
|
deprecated/cupertino_controllers/.github
|
||||||
|
deprecated/dart_firebase/.github
|
||||||
|
deprecated/dart_firebase/.vscode
|
||||||
|
deprecated/data_tables/.github
|
||||||
|
deprecated/data_tables/.vscode
|
||||||
|
deprecated/dynamic_tabs/.github
|
||||||
|
deprecated/dynamic_tabs/.vscode
|
||||||
|
deprecated/easy_google_maps/.vscode
|
||||||
|
deprecated/easy_web_view/.github
|
||||||
|
deprecated/easy_web_view/.vscode
|
||||||
|
deprecated/fb_auth/.vscode
|
||||||
|
deprecated/fb_storage/.vscode
|
||||||
|
deprecated/file_access/.vscode
|
||||||
|
deprecated/floating_search_bar/.vscode
|
||||||
|
deprecated/flutter_ast/.vscode
|
||||||
|
deprecated/flutter_cli/.vscode
|
||||||
|
deprecated/flutter_data_view/.github
|
||||||
|
deprecated/flutter_data_view/.vscode
|
||||||
|
deprecated/flutter_dynamic_widget/.vscode
|
||||||
|
deprecated/flutter_midi/.github
|
||||||
|
deprecated/flutter_midi/.vscode
|
||||||
|
deprecated/get_version/.vscode
|
||||||
|
deprecated/golden_layout/.vscode
|
||||||
|
deprecated/image_resizer/.vscode
|
||||||
|
deprecated/mobile_popup/.vscode
|
||||||
|
deprecated/mobile_sidebar/.vscode
|
||||||
|
deprecated/native_color_picker/.vscode
|
||||||
|
deprecated/navigation_rail/.vscode
|
||||||
|
deprecated/responsive_scaffold/.github
|
||||||
|
deprecated/responsive_scaffold/.vscode
|
||||||
|
deprecated/scaffold_tab_bar/.vscode
|
||||||
|
deprecated/sheet_music/.vscode
|
||||||
|
deprecated/storyboard/.github
|
||||||
|
deprecated/storyboard/.vscode
|
||||||
|
deprecated/widget_gen/.vscode
|
||||||
|
experimental/http_get_cache/.vscode
|
||||||
|
experimental/web_file_system/.dart_tool
|
||||||
|
experimental/web_file_system/example/.dart_tool
|
||||||
|
experimental/web_file_system/example/build
|
||||||
|
node_modules
|
||||||
|
packages/app_review/.github
|
||||||
|
packages/open_xml/.github
|
||||||
|
packages/open_xml/third_party/validator/.github
|
||||||
|
packages/open_xml/third_party/validator/.vscode
|
||||||
|
packages/signals/.github
|
||||||
|
packages/signals/.vscode
|
||||||
|
packages/signals/third_party/preactjs_signals/.github
|
||||||
|
packages/signals/website/.vscode
|
||||||
|
packages/undo/.github
|
||||||
|
packages/undo/.vscode
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# --- Remote Caching ---
|
||||||
|
build:http-cache --remote_cache=https://bazel-cache.rodydavis.dev
|
||||||
|
build:http-cache --remote_timeout=5
|
||||||
|
build:http-cache --remote_local_fallback=true
|
||||||
|
build:http-cache --remote_header=Authorization="Bearer ${BAZEL_CACHE_TOKEN}"
|
||||||
|
|
||||||
|
build:grpc-cache --remote_cache=grpcs://bazel-grpc-cache.rodydavis.dev
|
||||||
|
build:grpc-cache --remote_upload_local_results=true
|
||||||
|
build:grpc-cache --remote_timeout=5
|
||||||
|
build:grpc-cache --remote_local_fallback=true
|
||||||
|
build:grpc-cache --remote_header=Authorization="Bearer ${BAZEL_CACHE_TOKEN}"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: Renovate
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 * * * *" # Triggers hourly
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch: # Allows manual triggering from the Gitea UI
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
renovate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ghcr.io/renovatebot/renovate:latest
|
||||||
|
env:
|
||||||
|
RENOVATE_PLATFORM: gitea
|
||||||
|
RENOVATE_ENDPOINT: https://git.rodydavis.dev/api/v1
|
||||||
|
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
|
RENOVATE_AUTODISCOVER: false
|
||||||
|
RENOVATE_REPOSITORIES: ${{ github.repository }}
|
||||||
|
steps:
|
||||||
|
- name: Run Renovate
|
||||||
|
run: renovate
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "pub"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "pub"
|
|
||||||
directory: "/packages/breakpoint"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "pub"
|
|
||||||
directory: "/packages/flutter_sms"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "pub"
|
|
||||||
directory: "/packages/flutter_vibrate"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "pub"
|
|
||||||
directory: "/packages/flutter_whatsnew"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
+45
-12
@@ -1,22 +1,55 @@
|
|||||||
|
# ==========================================
|
||||||
|
# 🌌 Federated Workspaces - Ultimate Ignores
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
# --- System & Editor Files ---
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.dart_tool/
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
.packages
|
# --- Bazel Convenience Symlinks & Outputs ---
|
||||||
.pub/
|
bazel-*
|
||||||
pubspec.lock
|
bazel_out/
|
||||||
|
|
||||||
build/
|
# --- C/C++ Outputs ---
|
||||||
*.iml
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
*.out
|
||||||
|
*.exe
|
||||||
|
|
||||||
DerivedData/
|
# --- Go Outputs ---
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
__debug_bin
|
||||||
|
MODULE.bazel.lock
|
||||||
|
|
||||||
|
# --- Node / JavaScript / pnpm ---
|
||||||
|
node_modules/
|
||||||
|
.npm/
|
||||||
|
.pnpm-store/
|
||||||
|
.pnpm-debug.log*
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# --- Dart / Flutter ---
|
||||||
# Files and directories created by pub
|
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
.packages
|
.packages
|
||||||
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
build/
|
/build/
|
||||||
|
*.flutter-plugins
|
||||||
*.SF2
|
*.flutter-plugins-dependencies
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
# Root BUILD file for packages_dart workspace
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
module(
|
||||||
|
name = "packages_dart",
|
||||||
|
version = "1.0.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
bazel_dep(name = "rules_flutter", version = "0.0.0")
|
||||||
|
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||||
|
bazel_dep(name = "rules_go", version = "0.60.0")
|
||||||
|
bazel_dep(name = "gazelle", version = "0.42.0")
|
||||||
|
|
||||||
|
git_override(
|
||||||
|
module_name = "rules_flutter",
|
||||||
|
remote = "https://github.com/SpencerC/rules_flutter.git",
|
||||||
|
commit = "ca70cb77c09d4522fea090c42d81beed597bdacf",
|
||||||
|
)
|
||||||
|
|
||||||
|
flutter = use_extension("@rules_flutter//flutter:extensions.bzl", "flutter")
|
||||||
|
flutter.toolchain(flutter_version = "3.24.0")
|
||||||
|
use_repo(
|
||||||
|
flutter,
|
||||||
|
"flutter_sdk",
|
||||||
|
"flutter_toolchains",
|
||||||
|
)
|
||||||
|
|
||||||
|
register_toolchains("@flutter_toolchains//:all")
|
||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
# 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"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Synchronous API Support (Web Workers Only)
|
||||||
|
|
||||||
|
Because JavaScript and Dart run on a single-threaded event loop, blocking the main browser thread is not permitted. Therefore, standard synchronous APIs (such as `readAsBytesSync`, `writeAsBytesSync`, `existsSync`, `createSync`, `deleteSync`, `listSync`, `renameSync`, and `statSync`) will throw an `UnsupportedError` if invoked on the main application thread.
|
||||||
|
|
||||||
|
However, synchronous operations are **fully supported within Web Workers**. By delegating asynchronous work (IndexedDB metadata updates and Origin Private File System operations) to the main thread via a `SharedArrayBuffer` and blocking the Web Worker's execution loop using `Atomics.wait()`, you can safely use synchronous file system calls inside your workers.
|
||||||
|
|
||||||
|
### Prerequisites & Security Headers
|
||||||
|
|
||||||
|
To use the synchronous APIs, the browser requires cross-origin isolation. You must serve your web application with the following HTTP headers:
|
||||||
|
|
||||||
|
```http
|
||||||
|
Cross-Origin-Opener-Policy: same-origin
|
||||||
|
Cross-Origin-Embedder-Policy: require-corp
|
||||||
|
```
|
||||||
|
|
||||||
|
If these headers are not present, `SharedArrayBuffer` is disabled by the browser, and synchronous operations will throw a `StateError`.
|
||||||
|
|
||||||
|
### Setup Example
|
||||||
|
|
||||||
|
#### 1. Main Application Thread
|
||||||
|
|
||||||
|
Initialize the file system and register the worker proxy to listen to RPC requests from your worker:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'dart:html'; // or standard JS interop / package:web
|
||||||
|
import 'package:web_file_system/web_file_system.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final fs = WebFileSystem();
|
||||||
|
final worker = Worker('worker.js');
|
||||||
|
|
||||||
|
// Register the proxy to handle synchronous RPC requests
|
||||||
|
WebFileSystem.registerWorkerProxy(worker, fs);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Web Worker Context (`worker.js` / Dart Worker)
|
||||||
|
|
||||||
|
In your worker code, once initialized with the `SharedArrayBuffer`, you can access the file system synchronously:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:web_file_system/web_file_system.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final fs = WebFileSystem();
|
||||||
|
|
||||||
|
// These operations block synchronous execution inside the Web Worker
|
||||||
|
final file = fs.file('/data.bin');
|
||||||
|
file.createSync();
|
||||||
|
file.writeAsBytesSync([1, 2, 3, 4]);
|
||||||
|
|
||||||
|
final bytes = file.readAsBytesSync();
|
||||||
|
print(bytes); // [1, 2, 3, 4]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
/dist/
|
||||||
|
/coverage/
|
||||||
|
/scripts/temp_bin/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
|
|
||||||
|
# Xcode data
|
||||||
|
/macos/DerivedData/
|
||||||
@@ -153,9 +153,13 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _createFile, child: const Text('New File')),
|
onPressed: _createFile,
|
||||||
|
child: const Text('New File'),
|
||||||
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _createDir, child: const Text('New Directory')),
|
onPressed: _createDir,
|
||||||
|
child: const Text('New Directory'),
|
||||||
|
),
|
||||||
if (_currentPath != '/')
|
if (_currentPath != '/')
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -164,7 +168,8 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
|||||||
});
|
});
|
||||||
_refreshFiles();
|
_refreshFiles();
|
||||||
},
|
},
|
||||||
child: const Text('Go Up')),
|
child: const Text('Go Up'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@@ -187,7 +192,8 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
|||||||
return Text(_formatBytes(snapshot.data!));
|
return Text(_formatBytes(snapshot.data!));
|
||||||
}
|
}
|
||||||
return const Text('Loading...');
|
return const Text('Loading...');
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.delete, color: Colors.red),
|
icon: const Icon(Icons.delete, color: Colors.red),
|
||||||
onPressed: () => _delete(entity),
|
onPressed: () => _delete(entity),
|
||||||
@@ -205,13 +211,16 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: Text(entity.basename),
|
title: Text(entity.basename),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Text(content)),
|
child: Text(content),
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Close'))
|
child: const Text('Close'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
));
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -229,11 +238,13 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
|||||||
itemCount: _logs.length,
|
itemCount: _logs.length,
|
||||||
itemBuilder: (context, index) => Padding(
|
itemBuilder: (context, index) => Padding(
|
||||||
padding: const EdgeInsets.all(4.0),
|
padding: const EdgeInsets.all(4.0),
|
||||||
child: Text(_logs[_logs.length - 1 - index],
|
child: Text(
|
||||||
|
_logs[_logs.length - 1 - index],
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.2"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.20"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.0"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.3"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.6"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.12"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.2"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: "1d774bbdf6b72a0b12122fc1560c9c2d2a67db5a4a4cc2bd8a5c990ab20e3188"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.2"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
web_file_system:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: ".."
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.10.0 <4.0.0"
|
||||||
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:js_interop';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:web/web.dart' as web;
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class IdbBlockStore {
|
||||||
|
static const String _dbName = 'WebFileSystemBlobs';
|
||||||
|
static const int _version = 1;
|
||||||
|
static const String _storeName = 'blobs';
|
||||||
|
|
||||||
|
web.IDBDatabase? _db;
|
||||||
|
final Completer<void> _initCompleter = Completer<void>();
|
||||||
|
final Uuid _uuid = Uuid();
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
db.createObjectStore(_storeName);
|
||||||
|
}
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
|
final completer = Completer<void>();
|
||||||
|
|
||||||
|
request.onsuccess = (web.Event event) {
|
||||||
|
_db = (event.target as web.IDBOpenDBRequest).result as web.IDBDatabase;
|
||||||
|
if (!_initCompleter.isCompleted) completer.complete();
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
|
request.onerror = (web.Event event) {
|
||||||
|
if (!_initCompleter.isCompleted) {
|
||||||
|
completer.completeError(Exception('Failed to open Blob IDB'));
|
||||||
|
}
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> writeBlob(Stream<List<int>> stream) async {
|
||||||
|
await _ensureReady();
|
||||||
|
final blockId = _uuid.v4();
|
||||||
|
|
||||||
|
// Read all bytes into memory to store in IDB (IDB requires complete blob/buffer usually)
|
||||||
|
// For streams, we must materialize them.
|
||||||
|
final chunks = await stream.toList();
|
||||||
|
final allBytes = chunks.expand((x) => x).toList();
|
||||||
|
final uint8Array = Uint8List.fromList(allBytes).toJS;
|
||||||
|
|
||||||
|
final transaction = _db!.transaction(
|
||||||
|
_storeName.toJS,
|
||||||
|
'readwrite',
|
||||||
|
);
|
||||||
|
final store = transaction.objectStore(_storeName);
|
||||||
|
|
||||||
|
// Using put(value, key) since we didn't specify keyPath/autoIncrement
|
||||||
|
final request = store.put(uint8Array, blockId.toJS);
|
||||||
|
await _requestToFuture(request);
|
||||||
|
|
||||||
|
return blockId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<int>> readBlob(String blockId) async* {
|
||||||
|
await _ensureReady();
|
||||||
|
final transaction = _db!.transaction(
|
||||||
|
_storeName.toJS,
|
||||||
|
'readonly',
|
||||||
|
);
|
||||||
|
final store = transaction.objectStore(_storeName);
|
||||||
|
final request = store.get(blockId.toJS);
|
||||||
|
|
||||||
|
final result = await _requestToFuture(request);
|
||||||
|
if (result == null) throw Exception('Blob $blockId not found');
|
||||||
|
|
||||||
|
// result is JSUint8Array or ArrayBuffer
|
||||||
|
final uint8Array = result as JSUint8Array;
|
||||||
|
yield uint8Array.toDart;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteBlob(String blockId) async {
|
||||||
|
await _ensureReady();
|
||||||
|
final transaction = _db!.transaction(
|
||||||
|
_storeName.toJS,
|
||||||
|
'readwrite',
|
||||||
|
);
|
||||||
|
final store = transaction.objectStore(_storeName);
|
||||||
|
final request = store.delete(blockId.toJS);
|
||||||
|
await _requestToFuture(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<web.Blob> getBlob(String blockId) async {
|
||||||
|
await _ensureReady();
|
||||||
|
final transaction = _db!.transaction(
|
||||||
|
_storeName.toJS,
|
||||||
|
'readonly',
|
||||||
|
);
|
||||||
|
final store = transaction.objectStore(_storeName);
|
||||||
|
final request = store.get(blockId.toJS);
|
||||||
|
|
||||||
|
final result = await _requestToFuture(request);
|
||||||
|
if (result == null) throw Exception('Blob $blockId not found');
|
||||||
|
|
||||||
|
final uint8Array = result as JSUint8Array;
|
||||||
|
return web.Blob([uint8Array].toJS);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Blob Error'));
|
||||||
|
}.toJS;
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:js_interop';
|
import 'dart:js_interop';
|
||||||
|
import 'dart:js_interop_unsafe';
|
||||||
import 'package:web/web.dart' as web;
|
import 'package:web/web.dart' as web;
|
||||||
|
|
||||||
extension type InodeJS._(JSObject _) implements JSObject {
|
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({
|
factory InodeJS({
|
||||||
required String id,
|
required String id,
|
||||||
required String parentId,
|
required String parentId,
|
||||||
@@ -32,16 +12,18 @@ extension type InodeJS._(JSObject _) implements JSObject {
|
|||||||
String? blobId,
|
String? blobId,
|
||||||
int size = 0,
|
int size = 0,
|
||||||
required int modified,
|
required int modified,
|
||||||
|
int storageType = 0,
|
||||||
}) {
|
}) {
|
||||||
final obj = JSObject() as InodeJS;
|
final obj = JSObject();
|
||||||
obj.id = id;
|
obj.setProperty('id'.toJS, id.toJS);
|
||||||
obj.parentId = parentId;
|
obj.setProperty('parentId'.toJS, parentId.toJS);
|
||||||
obj.name = name;
|
obj.setProperty('name'.toJS, name.toJS);
|
||||||
obj.nodeType = nodeType;
|
obj.setProperty('nodeType'.toJS, nodeType.toJS);
|
||||||
obj.blobId = blobId;
|
if (blobId != null) obj.setProperty('blobId'.toJS, blobId.toJS);
|
||||||
obj.size = size;
|
obj.setProperty('size'.toJS, size.toJS);
|
||||||
obj.modified = modified;
|
obj.setProperty('modified'.toJS, modified.toJS);
|
||||||
return obj;
|
obj.setProperty('storageType'.toJS, storageType.toJS);
|
||||||
|
return obj as InodeJS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +35,7 @@ class Inode {
|
|||||||
final String? blobId;
|
final String? blobId;
|
||||||
final int size;
|
final int size;
|
||||||
final int modified;
|
final int modified;
|
||||||
|
final int storageType; // 0: OPFS, 1: IDB
|
||||||
|
|
||||||
Inode({
|
Inode({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -62,6 +45,7 @@ class Inode {
|
|||||||
this.blobId,
|
this.blobId,
|
||||||
this.size = 0,
|
this.size = 0,
|
||||||
required this.modified,
|
required this.modified,
|
||||||
|
this.storageType = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
InodeJS toJS() {
|
InodeJS toJS() {
|
||||||
@@ -73,18 +57,30 @@ class Inode {
|
|||||||
blobId: blobId,
|
blobId: blobId,
|
||||||
size: size,
|
size: size,
|
||||||
modified: modified,
|
modified: modified,
|
||||||
|
storageType: storageType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Inode fromJS(InodeJS js) {
|
static Inode fromJS(InodeJS js) {
|
||||||
|
final obj = js as JSObject;
|
||||||
|
final id = obj.getProperty('id'.toJS);
|
||||||
|
final parentId = obj.getProperty('parentId'.toJS);
|
||||||
|
final name = obj.getProperty('name'.toJS);
|
||||||
|
final nodeType = obj.getProperty('nodeType'.toJS);
|
||||||
|
final blobId = obj.getProperty('blobId'.toJS);
|
||||||
|
final size = obj.getProperty('size'.toJS);
|
||||||
|
final modified = obj.getProperty('modified'.toJS);
|
||||||
|
final storageType = obj.getProperty('storageType'.toJS);
|
||||||
|
|
||||||
return Inode(
|
return Inode(
|
||||||
id: js.id,
|
id: id != null && !id.isUndefinedOrNull ? (id as JSString).toDart : '',
|
||||||
parentId: js.parentId,
|
parentId: parentId != null && !parentId.isUndefinedOrNull ? (parentId as JSString).toDart : '',
|
||||||
name: js.name,
|
name: name != null && !name.isUndefinedOrNull ? (name as JSString).toDart : '',
|
||||||
nodeType: js.nodeType,
|
nodeType: nodeType != null && !nodeType.isUndefinedOrNull ? (nodeType as JSNumber).toDartInt : 0,
|
||||||
blobId: js.blobId,
|
blobId: blobId != null && !blobId.isUndefinedOrNull ? (blobId as JSString).toDart : null,
|
||||||
size: js.size,
|
size: size != null && !size.isUndefinedOrNull ? (size as JSNumber).toDartInt : 0,
|
||||||
modified: js.modified,
|
modified: modified != null && !modified.isUndefinedOrNull ? (modified as JSNumber).toDartInt : 0,
|
||||||
|
storageType: storageType != null && !storageType.isUndefinedOrNull ? (storageType as JSNumber).toDartInt : 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,9 +109,15 @@ class IdbInodeService {
|
|||||||
web.IDBObjectStoreParameters(keyPath: 'id'.toJS),
|
web.IDBObjectStoreParameters(keyPath: 'id'.toJS),
|
||||||
);
|
);
|
||||||
store.createIndex(
|
store.createIndex(
|
||||||
'parentId', 'parentId'.toJS, web.IDBIndexParameters(unique: false));
|
'parentId',
|
||||||
store.createIndex('parent_name', ['parentId'.toJS, 'name'.toJS].toJS,
|
'parentId'.toJS,
|
||||||
web.IDBIndexParameters(unique: true));
|
web.IDBIndexParameters(unique: false),
|
||||||
|
);
|
||||||
|
store.createIndex(
|
||||||
|
'parent_name',
|
||||||
|
['parentId'.toJS, 'name'.toJS].toJS,
|
||||||
|
web.IDBIndexParameters(unique: true),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}.toJS;
|
}.toJS;
|
||||||
|
|
||||||
@@ -123,16 +125,19 @@ class IdbInodeService {
|
|||||||
|
|
||||||
request.onsuccess = (web.Event event) {
|
request.onsuccess = (web.Event event) {
|
||||||
_db = (event.target as web.IDBOpenDBRequest).result as web.IDBDatabase;
|
_db = (event.target as web.IDBOpenDBRequest).result as web.IDBDatabase;
|
||||||
_ensureRootExists().then((_) {
|
_ensureRootExists()
|
||||||
|
.then((_) {
|
||||||
if (!_initCompleter.isCompleted) completer.complete();
|
if (!_initCompleter.isCompleted) completer.complete();
|
||||||
}).catchError((e) {
|
})
|
||||||
|
.catchError((e) {
|
||||||
if (!_initCompleter.isCompleted) completer.completeError(e);
|
if (!_initCompleter.isCompleted) completer.completeError(e);
|
||||||
});
|
});
|
||||||
}.toJS;
|
}.toJS;
|
||||||
|
|
||||||
request.onerror = (web.Event event) {
|
request.onerror = (web.Event event) {
|
||||||
if (!_initCompleter.isCompleted)
|
if (!_initCompleter.isCompleted) {
|
||||||
completer.completeError(Exception('Failed to open IDB'));
|
completer.completeError(Exception('Failed to open IDB'));
|
||||||
|
}
|
||||||
}.toJS;
|
}.toJS;
|
||||||
|
|
||||||
return completer.future;
|
return completer.future;
|
||||||
@@ -142,20 +147,24 @@ class IdbInodeService {
|
|||||||
try {
|
try {
|
||||||
await getInode(rootId);
|
await getInode(rootId);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
await createInode(Inode(
|
await createInode(
|
||||||
|
Inode(
|
||||||
id: rootId,
|
id: rootId,
|
||||||
parentId: 'null',
|
parentId: 'null',
|
||||||
name: '',
|
name: '',
|
||||||
nodeType: 1,
|
nodeType: 1,
|
||||||
modified: DateTime.now().millisecondsSinceEpoch,
|
modified: DateTime.now().millisecondsSinceEpoch,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createInode(Inode inode) async {
|
Future<void> createInode(Inode inode) async {
|
||||||
if (_db == null) await _ensureReady();
|
if (_db == null) await _ensureReady();
|
||||||
final transaction = _db!.transaction(
|
final transaction = _db!.transaction(
|
||||||
_storeName.toJS, 'readwrite'.toJS as web.IDBTransactionMode);
|
_storeName.toJS,
|
||||||
|
'readwrite',
|
||||||
|
);
|
||||||
final store = transaction.objectStore(_storeName);
|
final store = transaction.objectStore(_storeName);
|
||||||
final request = store.put(inode.toJS());
|
final request = store.put(inode.toJS());
|
||||||
await _requestToFuture(request);
|
await _requestToFuture(request);
|
||||||
@@ -168,7 +177,9 @@ class IdbInodeService {
|
|||||||
Future<void> deleteInode(String id) async {
|
Future<void> deleteInode(String id) async {
|
||||||
await _ensureReady();
|
await _ensureReady();
|
||||||
final transaction = _db!.transaction(
|
final transaction = _db!.transaction(
|
||||||
_storeName.toJS, 'readwrite'.toJS as web.IDBTransactionMode);
|
_storeName.toJS,
|
||||||
|
'readwrite',
|
||||||
|
);
|
||||||
final store = transaction.objectStore(_storeName);
|
final store = transaction.objectStore(_storeName);
|
||||||
final request = store.delete(id.toJS);
|
final request = store.delete(id.toJS);
|
||||||
await _requestToFuture(request);
|
await _requestToFuture(request);
|
||||||
@@ -178,7 +189,9 @@ class IdbInodeService {
|
|||||||
if (_db == null) await _ensureReady();
|
if (_db == null) await _ensureReady();
|
||||||
|
|
||||||
final transaction = _db!.transaction(
|
final transaction = _db!.transaction(
|
||||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
_storeName.toJS,
|
||||||
|
'readonly',
|
||||||
|
);
|
||||||
final store = transaction.objectStore(_storeName);
|
final store = transaction.objectStore(_storeName);
|
||||||
final request = store.get(id.toJS);
|
final request = store.get(id.toJS);
|
||||||
final result = await _requestToFuture(request);
|
final result = await _requestToFuture(request);
|
||||||
@@ -190,7 +203,9 @@ class IdbInodeService {
|
|||||||
Future<Inode?> getChild(String parentId, String name) async {
|
Future<Inode?> getChild(String parentId, String name) async {
|
||||||
await _ensureReady();
|
await _ensureReady();
|
||||||
final transaction = _db!.transaction(
|
final transaction = _db!.transaction(
|
||||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
_storeName.toJS,
|
||||||
|
'readonly',
|
||||||
|
);
|
||||||
final store = transaction.objectStore(_storeName);
|
final store = transaction.objectStore(_storeName);
|
||||||
final index = store.index('parent_name');
|
final index = store.index('parent_name');
|
||||||
final key = JSArray();
|
final key = JSArray();
|
||||||
@@ -211,7 +226,9 @@ class IdbInodeService {
|
|||||||
Future<List<Inode>> listChildren(String parentId) async {
|
Future<List<Inode>> listChildren(String parentId) async {
|
||||||
await _ensureReady();
|
await _ensureReady();
|
||||||
final transaction = _db!.transaction(
|
final transaction = _db!.transaction(
|
||||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
_storeName.toJS,
|
||||||
|
'readonly',
|
||||||
|
);
|
||||||
final store = transaction.objectStore(_storeName);
|
final store = transaction.objectStore(_storeName);
|
||||||
final index = store.index('parentId');
|
final index = store.index('parentId');
|
||||||
final request = index.getAll(parentId.toJS);
|
final request = index.getAll(parentId.toJS);
|
||||||
|
|||||||
@@ -12,10 +12,7 @@ class OpfsBlockStore {
|
|||||||
Future<void> _ensureReady() async {
|
Future<void> _ensureReady() async {
|
||||||
if (_blocksDir != null) return;
|
if (_blocksDir != null) return;
|
||||||
|
|
||||||
final web.StorageManager? storage = web.window.navigator.storage;
|
final web.StorageManager storage = web.window.navigator.storage;
|
||||||
if (storage == null) {
|
|
||||||
throw UnsupportedError('StorageManager not supported');
|
|
||||||
}
|
|
||||||
|
|
||||||
final root = await storage.getDirectory().toDart;
|
final root = await storage.getDirectory().toDart;
|
||||||
_blocksDir = await root
|
_blocksDir = await root
|
||||||
@@ -31,10 +28,7 @@ class OpfsBlockStore {
|
|||||||
final blockId = _uuid.v4();
|
final blockId = _uuid.v4();
|
||||||
|
|
||||||
final fileHandle = await _blocksDir!
|
final fileHandle = await _blocksDir!
|
||||||
.getFileHandle(
|
.getFileHandle(blockId, web.FileSystemGetFileOptions(create: true))
|
||||||
blockId,
|
|
||||||
web.FileSystemGetFileOptions(create: true),
|
|
||||||
)
|
|
||||||
.toDart;
|
.toDart;
|
||||||
|
|
||||||
final writable = await fileHandle.createWritable().toDart;
|
final writable = await fileHandle.createWritable().toDart;
|
||||||
@@ -50,6 +44,7 @@ class OpfsBlockStore {
|
|||||||
await writable.abort().toDart;
|
await writable.abort().toDart;
|
||||||
await _blocksDir!.removeEntry(blockId).toDart;
|
await _blocksDir!.removeEntry(blockId).toDart;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
print('OpfsBlockStore: writeBlob failed for $blockId: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,11 +54,7 @@ class OpfsBlockStore {
|
|||||||
Stream<List<int>> readBlob(String blockId) async* {
|
Stream<List<int>> readBlob(String blockId) async* {
|
||||||
await _ensureReady();
|
await _ensureReady();
|
||||||
try {
|
try {
|
||||||
final fileHandle = await _blocksDir!
|
final fileHandle = await _blocksDir!.getFileHandle(blockId).toDart;
|
||||||
.getFileHandle(
|
|
||||||
blockId,
|
|
||||||
)
|
|
||||||
.toDart;
|
|
||||||
|
|
||||||
final file = await fileHandle.getFile().toDart;
|
final file = await fileHandle.getFile().toDart;
|
||||||
final web.Blob blob = file;
|
final web.Blob blob = file;
|
||||||
@@ -78,6 +69,7 @@ class OpfsBlockStore {
|
|||||||
yield chunk.toDart;
|
yield chunk.toDart;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print('OpfsBlockStore: readBlob failed for $blockId: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,4 +82,11 @@ class OpfsBlockStore {
|
|||||||
// Ignore if not found
|
// Ignore if not found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<web.Blob> getBlob(String blockId) async {
|
||||||
|
await _ensureReady();
|
||||||
|
final fileHandle = await _blocksDir!.getFileHandle(blockId).toDart;
|
||||||
|
final file = await fileHandle.getFile().toDart;
|
||||||
|
return file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
import '../backend/idb_inode_service.dart';
|
||||||
import '../web_file_system.dart';
|
import '../web_file_system.dart';
|
||||||
import 'web_file.dart';
|
import 'web_file.dart';
|
||||||
|
|
||||||
@@ -33,13 +33,19 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
|||||||
// We assume parent must exist if not recursive.
|
// We assume parent must exist if not recursive.
|
||||||
final parentInode = await _fs.resolvepath(parentPath); // throws if missing
|
final parentInode = await _fs.resolvepath(parentPath); // throws if missing
|
||||||
|
|
||||||
await _fs.idb.createInode(Inode(
|
try {
|
||||||
|
await _fs.idb.createInode(
|
||||||
|
Inode(
|
||||||
id: _fs.uuid.v4(),
|
id: _fs.uuid.v4(),
|
||||||
parentId: parentInode.id,
|
parentId: parentInode.id,
|
||||||
name: name,
|
name: name,
|
||||||
nodeType: 1, // Directory
|
nodeType: 1, // Directory
|
||||||
modified: DateTime.now().millisecondsSinceEpoch,
|
modified: DateTime.now().millisecondsSinceEpoch,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
if (!await exists()) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -50,12 +56,19 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
|||||||
|
|
||||||
await _createRecursiveSafe(_fs.path.dirname(p));
|
await _createRecursiveSafe(_fs.path.dirname(p));
|
||||||
final parentVal = await _fs.resolvepath(_fs.path.dirname(p));
|
final parentVal = await _fs.resolvepath(_fs.path.dirname(p));
|
||||||
await _fs.idb.createInode(Inode(
|
try {
|
||||||
|
await _fs.idb.createInode(
|
||||||
|
Inode(
|
||||||
id: _fs.uuid.v4(),
|
id: _fs.uuid.v4(),
|
||||||
parentId: parentVal.id,
|
parentId: parentVal.id,
|
||||||
name: _fs.path.basename(p),
|
name: _fs.path.basename(p),
|
||||||
nodeType: 1,
|
nodeType: 1,
|
||||||
modified: DateTime.now().millisecondsSinceEpoch));
|
modified: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
if (await _fs.type(p) == FileSystemEntityType.notFound) rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -69,8 +82,15 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
|||||||
final tempDir = _fs.path.join(path, name);
|
final tempDir = _fs.path.join(path, name);
|
||||||
// Ensure path exists
|
// Ensure path exists
|
||||||
if (!await exists()) {
|
if (!await exists()) {
|
||||||
|
if (path == '/tmp') {
|
||||||
|
await create();
|
||||||
|
} else {
|
||||||
throw FileSystemException(
|
throw FileSystemException(
|
||||||
'Directory does not exist', path, const OSError('ENOENT', 2));
|
'Directory does not exist',
|
||||||
|
path,
|
||||||
|
const OSError('ENOENT', 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final dir = WebDirectory(_fs, tempDir);
|
final dir = WebDirectory(_fs, tempDir);
|
||||||
await dir.create();
|
await dir.create();
|
||||||
@@ -89,7 +109,10 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
|||||||
final children = await _fs.idb.listChildren(inode.id);
|
final children = await _fs.idb.listChildren(inode.id);
|
||||||
if (children.isNotEmpty && !recursive) {
|
if (children.isNotEmpty && !recursive) {
|
||||||
throw FileSystemException(
|
throw FileSystemException(
|
||||||
'Directory not empty', path, const OSError('ENOTEMPTY', 39));
|
'Directory not empty',
|
||||||
|
path,
|
||||||
|
const OSError('ENOTEMPTY', 39),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
@@ -125,11 +148,16 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
|||||||
bool existsSync() => throw UnsupportedError('Sync not supported');
|
bool existsSync() => throw UnsupportedError('Sync not supported');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<FileSystemEntity> list(
|
Stream<FileSystemEntity> list({
|
||||||
{bool recursive = false, bool followLinks = true}) async* {
|
bool recursive = false,
|
||||||
|
bool followLinks = true,
|
||||||
|
}) async* {
|
||||||
if (!await exists()) {
|
if (!await exists()) {
|
||||||
throw FileSystemException(
|
throw FileSystemException(
|
||||||
'Directory not found', path, const OSError('ENOENT', 2));
|
'Directory not found',
|
||||||
|
path,
|
||||||
|
const OSError('ENOENT', 2),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final inode = await _fs.resolvepath(path);
|
final inode = await _fs.resolvepath(path);
|
||||||
@@ -150,8 +178,10 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<FileSystemEntity> listSync(
|
List<FileSystemEntity> listSync({
|
||||||
{bool recursive = false, bool followLinks = true}) {
|
bool recursive = false,
|
||||||
|
bool followLinks = true,
|
||||||
|
}) {
|
||||||
throw UnsupportedError('Sync list not supported');
|
throw UnsupportedError('Sync list not supported');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +200,8 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
|||||||
nodeType: inode.nodeType,
|
nodeType: inode.nodeType,
|
||||||
blobId: inode.blobId,
|
blobId: inode.blobId,
|
||||||
size: inode.size,
|
size: inode.size,
|
||||||
modified: DateTime.now().millisecondsSinceEpoch);
|
modified: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
);
|
||||||
|
|
||||||
await _fs.idb.updateInode(updated);
|
await _fs.idb.updateInode(updated);
|
||||||
return WebDirectory(_fs, newPath);
|
return WebDirectory(_fs, newPath);
|
||||||
@@ -209,8 +240,10 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
|||||||
throw UnsupportedError('Sync not supported');
|
throw UnsupportedError('Sync not supported');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<FileSystemEvent> watch(
|
Stream<FileSystemEvent> watch({
|
||||||
{int events = FileSystemEvent.all, bool recursive = false}) {
|
int events = FileSystemEvent.all,
|
||||||
|
bool recursive = false,
|
||||||
|
}) {
|
||||||
return const Stream.empty();
|
return const Stream.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
import '../backend/idb_inode_service.dart';
|
||||||
import '../web_file_system.dart';
|
import '../web_file_system.dart';
|
||||||
|
|
||||||
class WebFile extends FileSystemEntity implements File {
|
class WebFile extends FileSystemEntity implements File {
|
||||||
@@ -57,22 +57,19 @@ class WebFile extends FileSystemEntity implements File {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<File> copy(String newPath) async {
|
Future<File> copy(String newPath) async {
|
||||||
final inode = await _fs.resolvepath(path);
|
// Deep copy to ensure independence and correct storage type
|
||||||
final newParent = await _fs.resolvepath(_fs.path.dirname(newPath));
|
|
||||||
|
|
||||||
await _fs.idb.createInode(
|
final newFile = WebFile(_fs, newPath);
|
||||||
Inode(
|
// Use writeAsBytes to handle creation and storage logic
|
||||||
id: _fs.uuid.v4(),
|
// We need to consume the stream to a list for now as writeAsBytes takes List<int>
|
||||||
parentId: newParent.id,
|
// Or we could use a stream API if available, but writeAsBytes expects List<int>.
|
||||||
name: _fs.path.basename(newPath),
|
// Given these are images/slides, memory is likely okay.
|
||||||
nodeType: 0,
|
// To be safer with memory, we should really stream it, but writeAsBytes takes List<int>.
|
||||||
blobId: inode.blobId,
|
// Let's use readAsBytes() helper.
|
||||||
size: inode.size,
|
final bytes = await readAsBytes();
|
||||||
modified: DateTime.now().millisecondsSinceEpoch,
|
await newFile.writeAsBytes(bytes);
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return WebFile(_fs, newPath);
|
return newFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -149,7 +146,24 @@ class WebFile extends FileSystemEntity implements File {
|
|||||||
bool flush = false,
|
bool flush = false,
|
||||||
}) async {
|
}) async {
|
||||||
final stream = Stream.value(bytes);
|
final stream = Stream.value(bytes);
|
||||||
final newBlobId = await _fs.opfs.writeBlob(stream);
|
String newBlobId;
|
||||||
|
int usedStorageType = 0; // 0 = OPFS, 1 = IDB
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try OPFS first
|
||||||
|
newBlobId = await _fs.opfs.writeBlob(stream);
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to IDB on ANY error (TypeError, NotFoundError, etc)
|
||||||
|
try {
|
||||||
|
newBlobId = await _fs.idbStore.writeBlob(Stream.value(bytes));
|
||||||
|
usedStorageType = 1;
|
||||||
|
} catch (e2) {
|
||||||
|
throw FileSystemException(
|
||||||
|
'Write failed on both OPFS ($e) and IDB: $e2',
|
||||||
|
path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Inode inode;
|
Inode inode;
|
||||||
try {
|
try {
|
||||||
@@ -171,6 +185,7 @@ class WebFile extends FileSystemEntity implements File {
|
|||||||
blobId: newBlobId,
|
blobId: newBlobId,
|
||||||
size: bytes.length,
|
size: bytes.length,
|
||||||
modified: DateTime.now().millisecondsSinceEpoch,
|
modified: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
storageType: usedStorageType,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -211,7 +226,37 @@ class WebFile extends FileSystemEntity implements File {
|
|||||||
final inode = await _fs.resolvepath(path);
|
final inode = await _fs.resolvepath(path);
|
||||||
if (inode.blobId == null) return;
|
if (inode.blobId == null) return;
|
||||||
|
|
||||||
|
if (inode.storageType == 1) {
|
||||||
|
try {
|
||||||
|
yield* _fs.idbStore.readBlob(inode.blobId!);
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to OPFS if IDB fails (maybe metadata is wrong)
|
||||||
|
print(
|
||||||
|
'Using fallback to OPFS for reading ${inode.name} (blob: ${inode.blobId}). Error: $e',
|
||||||
|
);
|
||||||
|
try {
|
||||||
yield* _fs.opfs.readBlob(inode.blobId!);
|
yield* _fs.opfs.readBlob(inode.blobId!);
|
||||||
|
} catch (e2) {
|
||||||
|
print('OPFS fallback ALSO failed for ${inode.name}: $e2');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
yield* _fs.opfs.readBlob(inode.blobId!);
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to IDB if OPFS fails
|
||||||
|
print(
|
||||||
|
'Using fallback to IDB for reading ${inode.name} (blob: ${inode.blobId}). Error: $e',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
yield* _fs.idbStore.readBlob(inode.blobId!);
|
||||||
|
} catch (e2) {
|
||||||
|
print('IDB fallback ALSO failed for ${inode.name}: $e2');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -238,7 +283,16 @@ class WebFile extends FileSystemEntity implements File {
|
|||||||
FileMode mode,
|
FileMode mode,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final newId = await _fs.opfs.writeBlob(stream);
|
String newId;
|
||||||
|
int usedStorageType = 0; // 0 = OPFS, 1 = IDB
|
||||||
|
|
||||||
|
try {
|
||||||
|
newId = await _fs.opfs.writeBlob(stream);
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to IDB on ANY error
|
||||||
|
newId = await _fs.idbStore.writeBlob(stream);
|
||||||
|
usedStorageType = 1;
|
||||||
|
}
|
||||||
|
|
||||||
Inode inode;
|
Inode inode;
|
||||||
try {
|
try {
|
||||||
@@ -259,6 +313,7 @@ class WebFile extends FileSystemEntity implements File {
|
|||||||
size:
|
size:
|
||||||
0, // TODO: Size not returned by OPFS yet, so 0 for streamed content
|
0, // TODO: Size not returned by OPFS yet, so 0 for streamed content
|
||||||
modified: DateTime.now().millisecondsSinceEpoch,
|
modified: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
storageType: usedStorageType,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -336,6 +391,13 @@ class WebFile extends FileSystemEntity implements File {
|
|||||||
@override
|
@override
|
||||||
Future<FileSystemEntity> delete({bool recursive = false}) async {
|
Future<FileSystemEntity> delete({bool recursive = false}) async {
|
||||||
final inode = await _fs.resolvepath(path);
|
final inode = await _fs.resolvepath(path);
|
||||||
|
if (inode.blobId != null) {
|
||||||
|
if (inode.storageType == 1) {
|
||||||
|
await _fs.idbStore.deleteBlob(inode.blobId!);
|
||||||
|
} else {
|
||||||
|
await _fs.opfs.deleteBlob(inode.blobId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
await _fs.idb.deleteInode(inode.id);
|
await _fs.idb.deleteInode(inode.id);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
import '../backend/idb_inode_service.dart';
|
||||||
import '../web_file_system.dart';
|
import '../web_file_system.dart';
|
||||||
|
|
||||||
class WebLink extends FileSystemEntity implements Link {
|
class WebLink extends FileSystemEntity implements Link {
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:js_interop';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
|
import 'package:web/web.dart' as web;
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:uuid/uuid.dart';
|
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 'backend/idb_inode_service.dart';
|
||||||
|
import 'backend/opfs_block_store.dart';
|
||||||
|
import 'backend/idb_block_store.dart';
|
||||||
import 'entities/web_directory.dart';
|
import 'entities/web_directory.dart';
|
||||||
import 'entities/web_file.dart';
|
import 'entities/web_file.dart';
|
||||||
import 'entities/web_link.dart';
|
import 'entities/web_link.dart';
|
||||||
@@ -12,11 +16,13 @@ import 'entities/web_link.dart';
|
|||||||
class WebFileSystem extends FileSystem {
|
class WebFileSystem extends FileSystem {
|
||||||
final IdbInodeService _idb = IdbInodeService();
|
final IdbInodeService _idb = IdbInodeService();
|
||||||
final OpfsBlockStore _opfs = OpfsBlockStore();
|
final OpfsBlockStore _opfs = OpfsBlockStore();
|
||||||
|
final IdbBlockStore _idbStore = IdbBlockStore();
|
||||||
final Uuid _uuid = Uuid();
|
final Uuid _uuid = Uuid();
|
||||||
|
|
||||||
// Public matchers for internal use
|
// Public matchers for internal use
|
||||||
IdbInodeService get idb => _idb;
|
IdbInodeService get idb => _idb;
|
||||||
OpfsBlockStore get opfs => _opfs;
|
OpfsBlockStore get opfs => _opfs;
|
||||||
|
IdbBlockStore get idbStore => _idbStore;
|
||||||
Uuid get uuid => _uuid;
|
Uuid get uuid => _uuid;
|
||||||
|
|
||||||
WebFileSystem();
|
WebFileSystem();
|
||||||
@@ -157,6 +163,7 @@ class WebFileSystem extends FileSystem {
|
|||||||
return currentInode;
|
return currentInode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
String getPath(dynamic path) {
|
String getPath(dynamic path) {
|
||||||
if (path is String) return path;
|
if (path is String) return path;
|
||||||
if (path is FileSystemEntity) return path.path;
|
if (path is FileSystemEntity) return path.path;
|
||||||
@@ -210,6 +217,7 @@ class WebFileSystem extends FileSystem {
|
|||||||
Future<bool> isLink(String path) async =>
|
Future<bool> isLink(String path) async =>
|
||||||
(await type(path, followLinks: false)) == FileSystemEntityType.link;
|
(await type(path, followLinks: false)) == FileSystemEntityType.link;
|
||||||
|
|
||||||
|
@override
|
||||||
bool get isWatchSupported => false;
|
bool get isWatchSupported => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -217,17 +225,31 @@ class WebFileSystem extends FileSystem {
|
|||||||
final s1 = await stat(path1);
|
final s1 = await stat(path1);
|
||||||
final s2 = await stat(path2);
|
final s2 = await stat(path2);
|
||||||
if (s1.type == FileSystemEntityType.notFound ||
|
if (s1.type == FileSystemEntityType.notFound ||
|
||||||
s2.type == FileSystemEntityType.notFound)
|
s2.type == FileSystemEntityType.notFound) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
final i1 = await resolvepath(path1);
|
final i1 = await resolvepath(path1);
|
||||||
final i2 = await resolvepath(path2);
|
final i2 = await resolvepath(path2);
|
||||||
return i1.id == i2.id;
|
return i1.id == i2.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
@override
|
@override
|
||||||
bool identicalSync(String path1, String path2) =>
|
bool identicalSync(String path1, String path2) =>
|
||||||
throw UnsupportedError('Sync not supported');
|
throw UnsupportedError('Sync not supported');
|
||||||
|
|
||||||
|
Future<web.Blob> getAsBlob(String path) async {
|
||||||
|
final inode = await resolvepath(path);
|
||||||
|
if (inode.nodeType != 0) throw FileSystemException('Not a file', path);
|
||||||
|
if (inode.blobId == null) return web.Blob(<JSAny>[].toJS);
|
||||||
|
|
||||||
|
if (inode.storageType == 1) {
|
||||||
|
return _idbStore.getBlob(inode.blobId!);
|
||||||
|
} else {
|
||||||
|
return _opfs.getBlob(inode.blobId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileStatImpl implements FileStat {
|
class FileStatImpl implements FileStat {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// A high-performance, asynchronous file system for the web.
|
/// A high-performance, asynchronous file system for the web.
|
||||||
library web_file_system;
|
library;
|
||||||
|
|
||||||
export 'package:file/file.dart';
|
export 'package:file/file.dart';
|
||||||
export 'src/web_file_system.dart';
|
export 'src/web_file_system.dart';
|
||||||
|
|||||||
@@ -0,0 +1,405 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "563c6992eaeda8625f45b87f6a6a0c547df16565d1c93d8271c7c11057710ca7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "101.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: aa6a9365901532864cae51208f2a6bb18dd01972ebead19c431efc848f60080b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "13.1.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.1"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
cli_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_config
|
||||||
|
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.15.0"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
|
file:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.20"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.3"
|
||||||
|
mime:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.6"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
path:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: ca578dc12bb8b2f40b67b7d3bd2fac4f31c01a6ff7130a14e2597b919934507f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.31.1"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.12"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: d2e98ec12998368dc59ddd47ab709f2cd55acd6b66dc7db764455a44082f4bc5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.18"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
uuid:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.3"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.2.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
web:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.11.0 <4.0.0"
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
name: web_file_system
|
name: web_file_system
|
||||||
description: A high-performance, asynchronous file system for the web using IDB and OPFS.
|
description: A high-performance, asynchronous file system for the web using IDB and OPFS.
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
publish_to: 'none'
|
|
||||||
homepage: https://github.com/fluttercommunity/flutter_whatsnew
|
|
||||||
maintainer: Rody Davis (@rodydavis)
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.0
|
sdk: ^3.10.0
|
||||||
flutter: ^3.38.5
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
file: ^7.0.0
|
file: ^7.0.0
|
||||||
@@ -17,5 +12,5 @@ dependencies:
|
|||||||
mime: ^1.0.0
|
mime: ^1.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^6.0.0
|
lints: ^3.0.0
|
||||||
test: ^1.25.0
|
test: ^1.25.0
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
@TestOn('browser')
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:web_file_system/web_file_system.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late WebFileSystem fs;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
// Tests run in parallel in browser often sharing context, so use unique paths.
|
||||||
|
fs = WebFileSystem();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Troubleshooting NotFoundError', () {
|
||||||
|
test('Temp directory creation and persistence', () async {
|
||||||
|
print('DEBUG: creating temp dir');
|
||||||
|
final tempDir = await fs.systemTempDirectory.createTemp('debug_test_');
|
||||||
|
print('DEBUG: Created temp dir at ${tempDir.path}');
|
||||||
|
|
||||||
|
expect(await tempDir.exists(), isTrue);
|
||||||
|
|
||||||
|
final file = tempDir.childFile('test.txt');
|
||||||
|
await file.writeAsString('Persistence Check');
|
||||||
|
|
||||||
|
print('DEBUG: checking existence immediately');
|
||||||
|
expect(await file.exists(), isTrue);
|
||||||
|
expect(await file.readAsString(), equals('Persistence Check'));
|
||||||
|
|
||||||
|
// Wait a bit to simulate pipeline delays
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
print('DEBUG: checking existence after delay');
|
||||||
|
expect(await file.readAsString(), equals('Persistence Check'));
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await tempDir.delete(recursive: true);
|
||||||
|
print('DEBUG: deleted temp dir');
|
||||||
|
expect(await file.exists(), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Deep copy behavior verification', () async {
|
||||||
|
final srcDir = await fs.systemTempDirectory.createTemp('src_');
|
||||||
|
final destDir = await fs.systemTempDirectory.createTemp('dest_');
|
||||||
|
|
||||||
|
final srcFile = srcDir.childFile('source.dat');
|
||||||
|
// Write some substantial data
|
||||||
|
final data = utf8.encode('Essential Data for Export');
|
||||||
|
await srcFile.writeAsBytes(data);
|
||||||
|
|
||||||
|
print('DEBUG: Source file written to ${srcFile.path}');
|
||||||
|
|
||||||
|
final destPath = destDir.childFile('copy.dat').path;
|
||||||
|
print('DEBUG: Copying to $destPath');
|
||||||
|
|
||||||
|
final copiedFile = await srcFile.copy(destPath);
|
||||||
|
|
||||||
|
print('DEBUG: Copy complete');
|
||||||
|
expect(await copiedFile.exists(), isTrue);
|
||||||
|
expect(await copiedFile.readAsBytes(), equals(data));
|
||||||
|
|
||||||
|
// Now delete source
|
||||||
|
print('DEBUG: Deleting source file');
|
||||||
|
await srcFile.delete();
|
||||||
|
expect(await srcFile.exists(), isFalse);
|
||||||
|
|
||||||
|
// Verify copy still exists and is readable (Deep Copy Check)
|
||||||
|
print('DEBUG: Verifying copy after source deletion');
|
||||||
|
expect(await copiedFile.exists(), isTrue);
|
||||||
|
try {
|
||||||
|
final copyData = await copiedFile.readAsBytes();
|
||||||
|
expect(copyData, equals(data));
|
||||||
|
print('DEBUG: Deep copy verified!');
|
||||||
|
} catch (e) {
|
||||||
|
print('ERROR: Deep copy failed! Reading copy caused error: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Concurrent Write/Read stress', () async {
|
||||||
|
final dir = await fs.systemTempDirectory.createTemp('stress_');
|
||||||
|
final file = dir.childFile('stress.txt');
|
||||||
|
|
||||||
|
// Write
|
||||||
|
await file.writeAsString('Initial');
|
||||||
|
|
||||||
|
// Rapidly update
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
await file.writeAsString('Update $i');
|
||||||
|
final content = await file.readAsString();
|
||||||
|
expect(content, equals('Update $i'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
@TestOn('browser')
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:web_file_system/web_file_system.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late WebFileSystem fs;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
fs = WebFileSystem();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Robust Correctness Tests', () {
|
||||||
|
test('Multiple files in a directory stress test', () async {
|
||||||
|
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final dirPath = '/stress_$uniqueId';
|
||||||
|
final dir = fs.directory(dirPath);
|
||||||
|
await dir.create();
|
||||||
|
|
||||||
|
final fileCount = 50;
|
||||||
|
final futures = <Future>[];
|
||||||
|
|
||||||
|
for (var i = 0; i < fileCount; i++) {
|
||||||
|
futures.add(
|
||||||
|
fs.file('$dirPath/file_$i.txt').writeAsString('Content of file $i'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await Future.wait(futures);
|
||||||
|
|
||||||
|
final entities = await dir.list().toList();
|
||||||
|
expect(entities.length, equals(fileCount));
|
||||||
|
|
||||||
|
final names = entities.map((e) => fs.path.basename(e.path)).toSet();
|
||||||
|
for (var i = 0; i < fileCount; i++) {
|
||||||
|
expect(names, contains('file_$i.txt'));
|
||||||
|
final content = await fs.file('$dirPath/file_$i.txt').readAsString();
|
||||||
|
expect(content, equals('Content of file $i'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Temporary directory usage', () async {
|
||||||
|
final tempDir = await fs.systemTempDirectory.createTemp('my_prefix_');
|
||||||
|
expect(tempDir.path, startsWith('/tmp/my_prefix_'));
|
||||||
|
expect(await tempDir.exists(), isTrue);
|
||||||
|
|
||||||
|
final file = fs.file(fs.path.join(tempDir.path, 'test.txt'));
|
||||||
|
await file.writeAsString('temp content');
|
||||||
|
expect(await file.readAsString(), equals('temp content'));
|
||||||
|
|
||||||
|
await tempDir.delete(recursive: true);
|
||||||
|
expect(await tempDir.exists(), isFalse);
|
||||||
|
expect(await file.exists(), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Different files with the same bytes (deduplication/collision check)', () async {
|
||||||
|
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final data = Uint8List.fromList([1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
|
final file1 = fs.file('/file1_$uniqueId.bin');
|
||||||
|
final file2 = fs.file('/file2_$uniqueId.bin');
|
||||||
|
|
||||||
|
await file1.writeAsBytes(data);
|
||||||
|
await file2.writeAsBytes(data);
|
||||||
|
|
||||||
|
expect(await file1.readAsBytes(), equals(data));
|
||||||
|
expect(await file2.readAsBytes(), equals(data));
|
||||||
|
|
||||||
|
// Update one, ensure other is unchanged
|
||||||
|
final newData = Uint8List.fromList([6, 7, 8]);
|
||||||
|
await file1.writeAsBytes(newData);
|
||||||
|
|
||||||
|
expect(await file1.readAsBytes(), equals(newData));
|
||||||
|
expect(await file2.readAsBytes(), equals(data));
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await file1.delete();
|
||||||
|
await file2.delete();
|
||||||
|
|
||||||
|
expect(await file1.exists(), isFalse);
|
||||||
|
expect(await file2.exists(), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Resource cleanup verification (Blob leak check)', () async {
|
||||||
|
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final filePath = '/leak_test_$uniqueId.bin';
|
||||||
|
final data = Uint8List.fromList(List.generate(100, (i) => i));
|
||||||
|
|
||||||
|
final file = fs.file(filePath);
|
||||||
|
await file.writeAsBytes(data);
|
||||||
|
|
||||||
|
final inode = await fs.resolvepath(filePath);
|
||||||
|
final blobId = inode.blobId;
|
||||||
|
expect(blobId, isNotNull);
|
||||||
|
|
||||||
|
// Verify blob exists in store
|
||||||
|
if (inode.storageType == 1) {
|
||||||
|
expect(await fs.idbStore.getBlob(blobId!), isNotNull);
|
||||||
|
} else {
|
||||||
|
expect(await fs.opfs.getBlob(blobId!), isNotNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
await file.delete();
|
||||||
|
|
||||||
|
// Verify blob is gone
|
||||||
|
if (inode.storageType == 1) {
|
||||||
|
expect(() => fs.idbStore.getBlob(blobId!), throwsA(anything));
|
||||||
|
} else {
|
||||||
|
expect(() => fs.opfs.getBlob(blobId!), throwsA(anything));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -139,7 +139,9 @@ void main() {
|
|||||||
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
||||||
final chunkSize = 1024 * 1024;
|
final chunkSize = 1024 * 1024;
|
||||||
final chunk = Uint8List(chunkSize);
|
final chunk = Uint8List(chunkSize);
|
||||||
for (int i = 0; i < chunkSize; i++) chunk[i] = i % 256;
|
for (int i = 0; i < chunkSize; i++) {
|
||||||
|
chunk[i] = i % 256;
|
||||||
|
}
|
||||||
|
|
||||||
final file = fs.file('/video_$uniqueId.mp4');
|
final file = fs.file('/video_$uniqueId.mp4');
|
||||||
final sink = file.openWrite();
|
final sink = file.openWrite();
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||||
|
|
||||||
|
dart_library(
|
||||||
|
name = "app_review",
|
||||||
|
srcs = glob(["lib/**/*.dart"]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -24,5 +24,5 @@ flutter:
|
|||||||
macos:
|
macos:
|
||||||
pluginClass: AppReviewPlugin
|
pluginClass: AppReviewPlugin
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
pigeon: ^26.3.4
|
pigeon: ^26.1.7
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||||
|
|
||||||
|
dart_library(
|
||||||
|
name = "breakpoint",
|
||||||
|
srcs = glob(["lib/**/*.dart"]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||||
|
|
||||||
|
dart_library(
|
||||||
|
name = "flutter_sms",
|
||||||
|
srcs = glob(["lib/**/*.dart"]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -21,7 +21,7 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
pigeon: ^26.3.4
|
pigeon: ^26.1.7
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
plugin:
|
plugin:
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||||
|
|
||||||
|
dart_library(
|
||||||
|
name = "flutter_vibrate",
|
||||||
|
srcs = glob(["lib/**/*.dart"]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -15,7 +15,7 @@ dependencies:
|
|||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
pigeon: ^26.3.4
|
pigeon: ^26.1.7
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
plugin:
|
plugin:
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||||
|
|
||||||
|
dart_library(
|
||||||
|
name = "flutter_whatsnew",
|
||||||
|
srcs = glob(["lib/**/*.dart"]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||||
|
|
||||||
|
dart_library(
|
||||||
|
name = "undo",
|
||||||
|
srcs = glob(["lib/**/*.dart"]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
+16
-16
@@ -5,18 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "93.0.0"
|
version: "91.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.1"
|
version: "8.4.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -61,10 +61,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -109,10 +109,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.7"
|
version: "3.1.3"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -236,18 +236,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.19"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -300,10 +300,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pigeon
|
name: pigeon
|
||||||
sha256: "04cfefc8add8b47ddf9ccac8b92bb4edeb67c87f185c623ba0db118ac99334ad"
|
sha256: b12f739047654cd65338dc7e9c6c03cbd4a1de179364a45c9e8bbeccf23729c6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "26.3.4"
|
version: "26.1.7"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -433,10 +433,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.10"
|
version: "0.7.7"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user