Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] c0016e8bfb Bump lints from 6.0.0 to 6.1.0
Bumps [lints](https://github.com/dart-lang/core/tree/main/pkgs) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/dart-lang/core/releases)
- [Commits](https://github.com/dart-lang/core/commits/lints-v6.1.0/pkgs)

---
updated-dependencies:
- dependency-name: lints
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 04:23:06 +00:00
48 changed files with 593 additions and 1937 deletions
-60
View File
@@ -1,60 +0,0 @@
# 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
-11
View File
@@ -1,11 +0,0 @@
# --- 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}"
+31
View File
@@ -0,0 +1,31 @@
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"
+12 -45
View File
@@ -1,55 +1,22 @@
# ==========================================
# 🌌 Federated Workspaces - Ultimate Ignores
# ==========================================
# --- System & Editor Files ---
.DS_Store .DS_Store
.DS_Store? .dart_tool/
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
.vscode/
.idea/ .idea/
*.swp
*.swo
# --- Bazel Convenience Symlinks & Outputs --- .packages
bazel-* .pub/
bazel_out/ pubspec.lock
# --- C/C++ Outputs --- build/
*.o *.iml
*.obj
*.a
*.lib
*.so
*.dylib
*.dll
*.out
*.exe
# --- Go Outputs --- DerivedData/
*.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
*.flutter-plugins-dependencies *.SF2
-1
View File
@@ -1 +0,0 @@
# Root BUILD file for packages_dart workspace
-25
View File
@@ -1,25 +0,0 @@
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 -7
View File
@@ -8,33 +8,27 @@ A collection of Flutter packages maintained by @rodydavis.
| :--- | :--- | :--- | | :--- | :--- | :--- |
| [app_review](./packages/app_review) | Request and Write Reviews and Open Store Listing for Android and iOS/MacOS in Flutter. | [![Pub](https://img.shields.io/pub/v/app_review.svg?style=popout)](https://pub.dartlang.org/packages/app_review) | | [app_review](./packages/app_review) | Request and Write Reviews and Open Store Listing for Android and iOS/MacOS in Flutter. | [![Pub](https://img.shields.io/pub/v/app_review.svg?style=popout)](https://pub.dartlang.org/packages/app_review) |
| [breakpoint](./packages/breakpoint) | A Flutter plugin to calculate the material design breakpoints. | [![Pub](https://img.shields.io/pub/v/breakpoint.svg?style=popout)](https://pub.dartlang.org/packages/breakpoint) | | [breakpoint](./packages/breakpoint) | A Flutter plugin to calculate the material design breakpoints. | [![Pub](https://img.shields.io/pub/v/breakpoint.svg?style=popout)](https://pub.dartlang.org/packages/breakpoint) |
| [doc_site](https://github.com/rodydavis/open_xml/tree/main/doc_site) | A documentation site built with Jaspr | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) |
| [docs](https://github.com/rodydavis/signals.dart/tree/main/docs) | A documentation site built with Jaspr | [![Pub](https://img.shields.io/pub/v/docs.svg?style=popout)](https://pub.dartlang.org/packages/docs) |
| [flutter_sms](./packages/flutter_sms) | A Flutter plugin to send SMS and MMS on iOS and Android. | [![Pub](https://img.shields.io/pub/v/flutter_sms.svg?style=popout)](https://pub.dartlang.org/packages/flutter_sms) | | [flutter_sms](./packages/flutter_sms) | A Flutter plugin to send SMS and MMS on iOS and Android. | [![Pub](https://img.shields.io/pub/v/flutter_sms.svg?style=popout)](https://pub.dartlang.org/packages/flutter_sms) |
| [flutter_vibrate](./packages/flutter_vibrate) | A Haptic Feedback and Vibration plugin. | [![Pub](https://img.shields.io/pub/v/flutter_vibrate.svg?style=popout)](https://pub.dartlang.org/packages/flutter_vibrate) | | [flutter_vibrate](./packages/flutter_vibrate) | A Haptic Feedback and Vibration plugin. | [![Pub](https://img.shields.io/pub/v/flutter_vibrate.svg?style=popout)](https://pub.dartlang.org/packages/flutter_vibrate) |
| [flutter_whatsnew](./packages/flutter_whatsnew) | A new Flutter package to show updates to users. | [![Pub](https://img.shields.io/pub/v/flutter_whatsnew.svg?style=popout)](https://pub.dartlang.org/packages/flutter_whatsnew) | | [flutter_whatsnew](./packages/flutter_whatsnew) | A new Flutter package to show updates to users. | [![Pub](https://img.shields.io/pub/v/flutter_whatsnew.svg?style=popout)](https://pub.dartlang.org/packages/flutter_whatsnew) |
| [open_xml](https://github.com/rodydavis/open_xml) | Open XML (OOXML) formatted documents. | [![Pub](https://img.shields.io/pub/v/open_xml.svg?style=popout)](https://pub.dartlang.org/packages/open_xml) | | [open_xml](https://github.com/rodydavis/open_xml) | Open XML (OOXML) formatted documents. | [![Pub](https://img.shields.io/pub/v/open_xml.svg?style=popout)](https://pub.dartlang.org/packages/open_xml) |
| [preact_signals](https://github.com/rodydavis/signals.dart/tree/main/packages/preact_signals) | Dart port of Preact.js Signals | [![Pub](https://img.shields.io/pub/v/preact_signals.svg?style=popout)](https://pub.dartlang.org/packages/preact_signals) | | [preact_signals](https://github.com/rodydavis/signals.dart/tree/main/packages/preact_signals) | Dart port of Preact.js Signals | [![Pub](https://img.shields.io/pub/v/preact_signals.svg?style=popout)](https://pub.dartlang.org/packages/preact_signals) |
| [scripts](https://github.com/rodydavis/signals.dart/tree/main/scripts) | Standalone Dart scripts for documentation and skills generation | [![Pub](https://img.shields.io/pub/v/scripts.svg?style=popout)](https://pub.dartlang.org/packages/scripts) |
| [signals](https://github.com/rodydavis/signals.dart/tree/main/packages/signals) | Reactivity made simple. Do more by doing less. Supports Flutter and any Dart project including HTML/JS, CLI, Shelf Server, VM and more. | [![Pub](https://img.shields.io/pub/v/signals.svg?style=popout)](https://pub.dartlang.org/packages/signals) | | [signals](https://github.com/rodydavis/signals.dart/tree/main/packages/signals) | Reactivity made simple. Do more by doing less. Supports Flutter and any Dart project including HTML/JS, CLI, Shelf Server, VM and more. | [![Pub](https://img.shields.io/pub/v/signals.svg?style=popout)](https://pub.dartlang.org/packages/signals) |
| [signals_core](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_core) | The signals library exposes four core functions which are the building blocks to model any business logic you can think of. | [![Pub](https://img.shields.io/pub/v/signals_core.svg?style=popout)](https://pub.dartlang.org/packages/signals_core) | | [signals_core](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_core) | The signals library exposes four core functions which are the building blocks to model any business logic you can think of. | [![Pub](https://img.shields.io/pub/v/signals_core.svg?style=popout)](https://pub.dartlang.org/packages/signals_core) |
| [signals_devtools_extension](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_devtools_extension) | Signals DevTools extension. | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) | | [signals_devtools_extension](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_devtools_extension) | Signals DevTools extension. | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) |
| [signals_flutter](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_flutter) | The signals library exposes four core functions which are the building blocks to model any business logic you can think of. | [![Pub](https://img.shields.io/pub/v/signals_flutter.svg?style=popout)](https://pub.dartlang.org/packages/signals_flutter) | | [signals_flutter](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_flutter) | The signals library exposes four core functions which are the building blocks to model any business logic you can think of. | [![Pub](https://img.shields.io/pub/v/signals_flutter.svg?style=popout)](https://pub.dartlang.org/packages/signals_flutter) |
| [signals_hooks](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_hooks) | flutter_hooks bindings for signals | [![Pub](https://img.shields.io/pub/v/signals_hooks.svg?style=popout)](https://pub.dartlang.org/packages/signals_hooks) | | [signals_hooks](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_hooks) | flutter_hooks bindings for signals | [![Pub](https://img.shields.io/pub/v/signals_hooks.svg?style=popout)](https://pub.dartlang.org/packages/signals_hooks) |
| [signals_lint](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_lint) | linter and developer tool for signals | [![Pub](https://img.shields.io/pub/v/signals_lint.svg?style=popout)](https://pub.dartlang.org/packages/signals_lint) | | [signals_lint](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_lint) | linter and developer tool for signals | [![Pub](https://img.shields.io/pub/v/signals_lint.svg?style=popout)](https://pub.dartlang.org/packages/signals_lint) |
| [test_fixtures](https://github.com/rodydavis/signals.dart/tree/main/packages/signals_lint/test_fixtures) | | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) |
| [undo](./packages/undo) | A Undo/Redo Library for Flutter | [![Pub](https://img.shields.io/pub/v/undo.svg?style=popout)](https://pub.dartlang.org/packages/undo) | | [undo](./packages/undo) | A Undo/Redo Library for Flutter | [![Pub](https://img.shields.io/pub/v/undo.svg?style=popout)](https://pub.dartlang.org/packages/undo) |
| [web_file_system](https://github.com/rodydavis/open_xml/tree/main/third_party/web_file_system) | A high-performance, asynchronous file system for the web using IDB and OPFS. | [![Pub](https://img.shields.io/pub/v/web_file_system.svg?style=popout)](https://pub.dartlang.org/packages/web_file_system) |
## Experimental ## Experimental
| Package | Description | Version | | Package | Description | Version |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| [diff_algorithims](./experimental/diff_algorithims) | A set of diffing algorithims for Dart | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) | | [diff_algorithims](./experimental/diff_algorithims) | A set of diffing algorithims for Dart | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) |
| [http_get_cache](./experimental/http_get_cache) | HTTP Compliant cache for Flutter | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) |
| [pocketbase_auth](./experimental/pocketbase_auth) | Pocketbase auth client with built in UI screens and token refresh | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) | | [pocketbase_auth](./experimental/pocketbase_auth) | Pocketbase auth client with built in UI screens and token refresh | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) |
| [pocketbase_sync](./experimental/pocketbase_sync) | A Flutter package for offline-first syncing with PocketBase. | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) | | [pocketbase_sync](./experimental/pocketbase_sync) | A Flutter package for offline-first syncing with PocketBase. | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) |
| [web_file_system](./experimental/web_file_system) | A high-performance, asynchronous file system for the web using IDB and OPFS. | [![Pub](https://img.shields.io/pub/v/web_file_system.svg?style=popout)](https://pub.dartlang.org/packages/web_file_system) | | [web_file_system](./experimental/web_file_system) | A high-performance, asynchronous file system for the web using IDB and OPFS. | ![Unpublished](https://img.shields.io/badge/pub-unpublished-inactive) |
## Deprecated ## Deprecated
-171
View File
@@ -1,171 +0,0 @@
# 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]
}
```
@@ -1,50 +0,0 @@
# 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,23 +153,18 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
spacing: 8, spacing: 8,
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: _createFile, onPressed: _createFile, child: const Text('New File')),
child: const Text('New File'),
),
ElevatedButton( ElevatedButton(
onPressed: _createDir, onPressed: _createDir, child: const Text('New Directory')),
child: const Text('New Directory'),
),
if (_currentPath != '/') if (_currentPath != '/')
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
_currentPath = _fs.path.dirname(_currentPath); _currentPath = _fs.path.dirname(_currentPath);
}); });
_refreshFiles(); _refreshFiles();
}, },
child: const Text('Go Up'), child: const Text('Go Up')),
),
], ],
), ),
const Divider(), const Divider(),
@@ -186,14 +181,13 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
), ),
title: Text(entity.basename), title: Text(entity.basename),
subtitle: FutureBuilder<int>( subtitle: FutureBuilder<int>(
future: _getSize(entity), future: _getSize(entity),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
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),
@@ -207,20 +201,17 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
} else if (entity is File) { } else if (entity is File) {
entity.readAsString().then((content) { entity.readAsString().then((content) {
showDialog( showDialog(
context: context, context: context,
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'), ],
), ));
],
),
);
}); });
} }
}, },
@@ -238,13 +229,11 @@ 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( child: Text(_logs[_logs.length - 1 - index],
_logs[_logs.length - 1 - index], style: const TextStyle(
style: const TextStyle( fontSize: 12,
fontSize: 12, fontFamily: 'monospace',
fontFamily: 'monospace', )),
),
),
), ),
), ),
), ),
@@ -1,260 +0,0 @@
# 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"
@@ -1,123 +0,0 @@
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,9 +1,29 @@
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,
@@ -12,18 +32,16 @@ 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(); final obj = JSObject() as InodeJS;
obj.setProperty('id'.toJS, id.toJS); obj.id = id;
obj.setProperty('parentId'.toJS, parentId.toJS); obj.parentId = parentId;
obj.setProperty('name'.toJS, name.toJS); obj.name = name;
obj.setProperty('nodeType'.toJS, nodeType.toJS); obj.nodeType = nodeType;
if (blobId != null) obj.setProperty('blobId'.toJS, blobId.toJS); obj.blobId = blobId;
obj.setProperty('size'.toJS, size.toJS); obj.size = size;
obj.setProperty('modified'.toJS, modified.toJS); obj.modified = modified;
obj.setProperty('storageType'.toJS, storageType.toJS); return obj;
return obj as InodeJS;
} }
} }
@@ -35,7 +53,6 @@ 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,
@@ -45,7 +62,6 @@ 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() {
@@ -57,30 +73,18 @@ 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: id != null && !id.isUndefinedOrNull ? (id as JSString).toDart : '', id: js.id,
parentId: parentId != null && !parentId.isUndefinedOrNull ? (parentId as JSString).toDart : '', parentId: js.parentId,
name: name != null && !name.isUndefinedOrNull ? (name as JSString).toDart : '', name: js.name,
nodeType: nodeType != null && !nodeType.isUndefinedOrNull ? (nodeType as JSNumber).toDartInt : 0, nodeType: js.nodeType,
blobId: blobId != null && !blobId.isUndefinedOrNull ? (blobId as JSString).toDart : null, blobId: js.blobId,
size: size != null && !size.isUndefinedOrNull ? (size as JSNumber).toDartInt : 0, size: js.size,
modified: modified != null && !modified.isUndefinedOrNull ? (modified as JSNumber).toDartInt : 0, modified: js.modified,
storageType: storageType != null && !storageType.isUndefinedOrNull ? (storageType as JSNumber).toDartInt : 0,
); );
} }
} }
@@ -109,15 +113,9 @@ class IdbInodeService {
web.IDBObjectStoreParameters(keyPath: 'id'.toJS), web.IDBObjectStoreParameters(keyPath: 'id'.toJS),
); );
store.createIndex( store.createIndex(
'parentId', 'parentId', 'parentId'.toJS, web.IDBIndexParameters(unique: false));
'parentId'.toJS, store.createIndex('parent_name', ['parentId'.toJS, 'name'.toJS].toJS,
web.IDBIndexParameters(unique: false), web.IDBIndexParameters(unique: true));
);
store.createIndex(
'parent_name',
['parentId'.toJS, 'name'.toJS].toJS,
web.IDBIndexParameters(unique: true),
);
} }
}.toJS; }.toJS;
@@ -125,19 +123,16 @@ 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() _ensureRootExists().then((_) {
.then((_) { if (!_initCompleter.isCompleted) completer.complete();
if (!_initCompleter.isCompleted) completer.complete(); }).catchError((e) {
}) if (!_initCompleter.isCompleted) completer.completeError(e);
.catchError((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;
@@ -147,24 +142,20 @@ class IdbInodeService {
try { try {
await getInode(rootId); await getInode(rootId);
} catch (_) { } catch (_) {
await createInode( await createInode(Inode(
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, _storeName.toJS, 'readwrite'.toJS as web.IDBTransactionMode);
'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);
@@ -177,9 +168,7 @@ 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, _storeName.toJS, 'readwrite'.toJS as web.IDBTransactionMode);
'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);
@@ -189,9 +178,7 @@ class IdbInodeService {
if (_db == null) await _ensureReady(); if (_db == null) await _ensureReady();
final transaction = _db!.transaction( final transaction = _db!.transaction(
_storeName.toJS, _storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
'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);
@@ -203,9 +190,7 @@ 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, _storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
'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();
@@ -226,9 +211,7 @@ 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, _storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
'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,7 +12,10 @@ 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
@@ -28,7 +31,10 @@ class OpfsBlockStore {
final blockId = _uuid.v4(); final blockId = _uuid.v4();
final fileHandle = await _blocksDir! final fileHandle = await _blocksDir!
.getFileHandle(blockId, web.FileSystemGetFileOptions(create: true)) .getFileHandle(
blockId,
web.FileSystemGetFileOptions(create: true),
)
.toDart; .toDart;
final writable = await fileHandle.createWritable().toDart; final writable = await fileHandle.createWritable().toDart;
@@ -44,7 +50,6 @@ 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;
} }
@@ -54,7 +59,11 @@ 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!.getFileHandle(blockId).toDart; final fileHandle = await _blocksDir!
.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;
@@ -69,7 +78,6 @@ class OpfsBlockStore {
yield chunk.toDart; yield chunk.toDart;
} }
} catch (e) { } catch (e) {
print('OpfsBlockStore: readBlob failed for $blockId: $e');
rethrow; rethrow;
} }
} }
@@ -82,11 +90,4 @@ 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 '../backend/idb_inode_service.dart'; import 'package:web_file_system/src/backend/idb_inode_service.dart';
import '../web_file_system.dart'; import '../web_file_system.dart';
import 'web_file.dart'; import 'web_file.dart';
@@ -33,19 +33,13 @@ 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
try { await _fs.idb.createInode(Inode(
await _fs.idb.createInode( id: _fs.uuid.v4(),
Inode( parentId: parentInode.id,
id: _fs.uuid.v4(), name: name,
parentId: parentInode.id, nodeType: 1, // Directory
name: name, modified: DateTime.now().millisecondsSinceEpoch,
nodeType: 1, // Directory ));
modified: DateTime.now().millisecondsSinceEpoch,
),
);
} catch (_) {
if (!await exists()) rethrow;
}
return this; return this;
} }
@@ -56,19 +50,12 @@ 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));
try { await _fs.idb.createInode(Inode(
await _fs.idb.createInode( id: _fs.uuid.v4(),
Inode( parentId: parentVal.id,
id: _fs.uuid.v4(), name: _fs.path.basename(p),
parentId: parentVal.id, nodeType: 1,
name: _fs.path.basename(p), modified: DateTime.now().millisecondsSinceEpoch));
nodeType: 1,
modified: DateTime.now().millisecondsSinceEpoch,
),
);
} catch (_) {
if (await _fs.type(p) == FileSystemEntityType.notFound) rethrow;
}
} }
@override @override
@@ -82,15 +69,8 @@ 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') { throw FileSystemException(
await create(); 'Directory does not exist', path, const OSError('ENOENT', 2));
} else {
throw FileSystemException(
'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();
@@ -109,10 +89,7 @@ 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', 'Directory not empty', path, const OSError('ENOTEMPTY', 39));
path,
const OSError('ENOTEMPTY', 39),
);
} }
if (recursive) { if (recursive) {
@@ -148,16 +125,11 @@ 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 recursive = false, bool followLinks = true}) async* {
bool followLinks = true,
}) async* {
if (!await exists()) { if (!await exists()) {
throw FileSystemException( throw FileSystemException(
'Directory not found', 'Directory not found', path, const OSError('ENOENT', 2));
path,
const OSError('ENOENT', 2),
);
} }
final inode = await _fs.resolvepath(path); final inode = await _fs.resolvepath(path);
@@ -178,10 +150,8 @@ class WebDirectory extends FileSystemEntity implements Directory {
} }
@override @override
List<FileSystemEntity> listSync({ List<FileSystemEntity> listSync(
bool recursive = false, {bool recursive = false, bool followLinks = true}) {
bool followLinks = true,
}) {
throw UnsupportedError('Sync list not supported'); throw UnsupportedError('Sync list not supported');
} }
@@ -194,14 +164,13 @@ class WebDirectory extends FileSystemEntity implements Directory {
final newParentInode = await _fs.resolvepath(newParentDir); final newParentInode = await _fs.resolvepath(newParentDir);
final updated = Inode( final updated = Inode(
id: inode.id, id: inode.id,
parentId: newParentInode.id, parentId: newParentInode.id,
name: newName, name: newName,
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);
@@ -240,10 +209,8 @@ 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, {int events = FileSystemEvent.all, bool recursive = false}) {
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 '../backend/idb_inode_service.dart'; import 'package:web_file_system/src/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,19 +57,22 @@ class WebFile extends FileSystemEntity implements File {
@override @override
Future<File> copy(String newPath) async { Future<File> copy(String newPath) async {
// Deep copy to ensure independence and correct storage type final inode = await _fs.resolvepath(path);
final newParent = await _fs.resolvepath(_fs.path.dirname(newPath));
final newFile = WebFile(_fs, newPath); await _fs.idb.createInode(
// Use writeAsBytes to handle creation and storage logic Inode(
// We need to consume the stream to a list for now as writeAsBytes takes List<int> id: _fs.uuid.v4(),
// Or we could use a stream API if available, but writeAsBytes expects List<int>. parentId: newParent.id,
// Given these are images/slides, memory is likely okay. name: _fs.path.basename(newPath),
// To be safer with memory, we should really stream it, but writeAsBytes takes List<int>. nodeType: 0,
// Let's use readAsBytes() helper. blobId: inode.blobId,
final bytes = await readAsBytes(); size: inode.size,
await newFile.writeAsBytes(bytes); modified: DateTime.now().millisecondsSinceEpoch,
),
);
return newFile; return WebFile(_fs, newPath);
} }
@override @override
@@ -146,24 +149,7 @@ class WebFile extends FileSystemEntity implements File {
bool flush = false, bool flush = false,
}) async { }) async {
final stream = Stream.value(bytes); final stream = Stream.value(bytes);
String newBlobId; final newBlobId = await _fs.opfs.writeBlob(stream);
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 {
@@ -185,7 +171,6 @@ 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,
), ),
); );
@@ -226,37 +211,7 @@ 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) { yield* _fs.opfs.readBlob(inode.blobId!);
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!);
} 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
@@ -283,16 +238,7 @@ class WebFile extends FileSystemEntity implements File {
FileMode mode, FileMode mode,
) async { ) async {
try { try {
String newId; final newId = await _fs.opfs.writeBlob(stream);
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 {
@@ -313,7 +259,6 @@ 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) {
@@ -391,13 +336,6 @@ 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 '../backend/idb_inode_service.dart'; import 'package:web_file_system/src/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,14 +1,10 @@
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 'backend/idb_inode_service.dart'; import 'package:web_file_system/src/backend/opfs_block_store.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';
@@ -16,13 +12,11 @@ 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();
@@ -163,7 +157,6 @@ 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;
@@ -217,7 +210,6 @@ 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
@@ -225,31 +217,17 @@ 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; library web_file_system;
export 'package:file/file.dart'; export 'package:file/file.dart';
export 'src/web_file_system.dart'; export 'src/web_file_system.dart';
-405
View File
@@ -1,405 +0,0 @@
# 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"
+7 -2
View File
@@ -1,8 +1,13 @@
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.10.0 sdk: ^3.5.0
flutter: ^3.38.5
dependencies: dependencies:
file: ^7.0.0 file: ^7.0.0
@@ -12,5 +17,5 @@ dependencies:
mime: ^1.0.0 mime: ^1.0.0
dev_dependencies: dev_dependencies:
lints: ^3.0.0 lints: ^6.0.0
test: ^1.25.0 test: ^1.25.0
@@ -1,94 +0,0 @@
@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'));
}
});
});
}
@@ -1,113 +0,0 @@
@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,9 +139,7 @@ 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++) { for (int i = 0; i < chunkSize; i++) chunk[i] = i % 256;
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();
-7
View File
@@ -1,7 +0,0 @@
load("@rules_flutter//flutter:defs.bzl", "dart_library")
dart_library(
name = "app_review",
srcs = glob(["lib/**/*.dart"]),
visibility = ["//visibility:public"],
)
+1 -1
View File
@@ -24,5 +24,5 @@ flutter:
macos: macos:
pluginClass: AppReviewPlugin pluginClass: AppReviewPlugin
dev_dependencies: dev_dependencies:
pigeon: ^26.1.7 pigeon: ^26.1.5
-7
View File
@@ -1,7 +0,0 @@
load("@rules_flutter//flutter:defs.bzl", "dart_library")
dart_library(
name = "breakpoint",
srcs = glob(["lib/**/*.dart"]),
visibility = ["//visibility:public"],
)
-4
View File
@@ -1,7 +1,3 @@
## 1.3.2
* Fix by @josealba to use .sizeOf(context): https://github.com/rodydavis/packages.dart/pull/14
## 1.3.0 ## 1.3.0
* Update Dart SDK constraint to >=3.0.0. * Update Dart SDK constraint to >=3.0.0.
+7 -5
View File
@@ -51,14 +51,16 @@ class Breakpoint {
/// ///
/// Use [Breakpoint.fromConstraints] when the widget does not take up the full screen /// Use [Breakpoint.fromConstraints] when the widget does not take up the full screen
factory Breakpoint.fromMediaQuery(BuildContext context) { factory Breakpoint.fromMediaQuery(BuildContext context) {
final size = MediaQuery.sizeOf(context); final _media = MediaQuery.of(context);
double width = size.width; double _width = 359;
final orientation = Orientation orientation = Orientation.portrait;
size.width > size.height ? Orientation.landscape : Orientation.portrait;
return _calcBreakpoint(orientation, width); _width = _media.size.width;
orientation = _media.orientation;
return _calcBreakpoint(orientation, _width);
} }
static Breakpoint _calcBreakpoint(Orientation orientation, double _width) { static Breakpoint _calcBreakpoint(Orientation orientation, double _width) {
+3 -2
View File
@@ -1,10 +1,10 @@
name: breakpoint name: breakpoint
description: A Flutter plugin to calculate the material design breakpoints. description: A Flutter plugin to calculate the material design breakpoints.
version: 1.3.2 version: 1.3.0
maintainer: Rody Davis (@rodydavis) maintainer: Rody Davis (@rodydavis)
homepage: https://github.com/rodydavis/plugins homepage: https://github.com/rodydavis/plugins
repository: https://github.com/rodydavis/packages.dart/tree/main/packages/breakpoint repository: https://github.com/fluttercommunity/breakpoint
resolution: workspace resolution: workspace
environment: environment:
@@ -19,3 +19,4 @@ dev_dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
-7
View File
@@ -1,7 +0,0 @@
load("@rules_flutter//flutter:defs.bzl", "dart_library")
dart_library(
name = "flutter_sms",
srcs = glob(["lib/**/*.dart"]),
visibility = ["//visibility:public"],
)
-4
View File
@@ -1,7 +1,3 @@
# 3.0.1
- Fix iOS build error: https://github.com/rodydavis/packages.dart/pull/11
# 3.0.0 # 3.0.0
* **Breaking Change**: Migrated to Pigeon for type-safe platform channels. * **Breaking Change**: Migrated to Pigeon for type-safe platform channels.
@@ -13,7 +13,7 @@ public class SwiftFlutterSmsPlugin: NSObject, FlutterPlugin, SmsHostApi, UINavig
public func sendSms(message: String, recipients: [String], completion: @escaping (Result<String, Error>) -> Void) { public func sendSms(message: String, recipients: [String], completion: @escaping (Result<String, Error>) -> Void) {
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
completion(.failure(PigeonError( completion(.failure(FlutterError(
code: "message_not_sent", code: "message_not_sent",
message: "Cannot send message on this device!", message: "Cannot send message on this device!",
details: "Cannot send SMS and MMS on a Simulator. Test on a real device." details: "Cannot send SMS and MMS on a Simulator. Test on a real device."
@@ -27,7 +27,7 @@ public class SwiftFlutterSmsPlugin: NSObject, FlutterPlugin, SmsHostApi, UINavig
controller.messageComposeDelegate = self controller.messageComposeDelegate = self
UIApplication.shared.keyWindow?.rootViewController?.present(controller, animated: true, completion: nil) UIApplication.shared.keyWindow?.rootViewController?.present(controller, animated: true, completion: nil)
} else { } else {
completion(.failure(PigeonError( completion(.failure(FlutterError(
code: "device_not_capable", code: "device_not_capable",
message: "The current device is not capable of sending text messages.", message: "The current device is not capable of sending text messages.",
details: "A device may be unable to send messages if it does not support messaging or if it is not currently configured to send messages. This only applies to the ability to send text messages via iMessage, SMS, and MMS." details: "A device may be unable to send messages if it does not support messaging or if it is not currently configured to send messages. This only applies to the ability to send text messages via iMessage, SMS, and MMS."
@@ -36,14 +36,14 @@ public class SwiftFlutterSmsPlugin: NSObject, FlutterPlugin, SmsHostApi, UINavig
#endif #endif
} }
public func canSendSms(completion: @escaping (Result<Bool, Error>) -> Void) { public func canSendSms() -> Bool {
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
completion(.success(false)) return false
#else #else
if (MFMessageComposeViewController.canSendText()) { if (MFMessageComposeViewController.canSendText()) {
completion(.success(true)) return true
} else { } else {
completion(.success(false)) return false
} }
#endif #endif
} }
+2 -2
View File
@@ -1,6 +1,6 @@
name: flutter_sms name: flutter_sms
description: A Flutter plugin to send SMS and MMS on iOS and Android. description: A Flutter plugin to send SMS and MMS on iOS and Android.
version: 3.0.1 version: 3.0.0
homepage: https://github.com/rodydavis/packages.dart homepage: https://github.com/rodydavis/packages.dart
repository: https://github.com/rodydavis/packages.dart repository: https://github.com/rodydavis/packages.dart
maintainer: Rody Davis (@rodydavis) maintainer: Rody Davis (@rodydavis)
@@ -21,7 +21,7 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
pigeon: ^26.1.7 pigeon: ^26.1.5
flutter: flutter:
plugin: plugin:
-7
View File
@@ -1,7 +0,0 @@
load("@rules_flutter//flutter:defs.bzl", "dart_library")
dart_library(
name = "flutter_vibrate",
srcs = glob(["lib/**/*.dart"]),
visibility = ["//visibility:public"],
)
@@ -3,13 +3,7 @@ import UIKit
import AudioToolbox import AudioToolbox
public class SwiftVibratePlugin: NSObject, FlutterPlugin, VibrateApi { public class SwiftVibratePlugin: NSObject, FlutterPlugin, VibrateApi {
private var isDevice: Bool { private let isDevice = TARGET_OS_SIMULATOR == 0
#if targetEnvironment(simulator)
return false
#else
return true
#endif
}
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let messenger : FlutterBinaryMessenger = registrar.messenger() let messenger : FlutterBinaryMessenger = registrar.messenger()
+1 -1
View File
@@ -15,7 +15,7 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0
pigeon: ^26.1.7 pigeon: ^26.1.5
flutter: flutter:
plugin: plugin:
-7
View File
@@ -1,7 +0,0 @@
load("@rules_flutter//flutter:defs.bzl", "dart_library")
dart_library(
name = "flutter_whatsnew",
srcs = glob(["lib/**/*.dart"]),
visibility = ["//visibility:public"],
)
@@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"shared_preferences_foundation","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"shared_preferences_android","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"shared_preferences_foundation","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"shared_preferences_web","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2026-02-16 10:38:50.657587","version":"3.38.5","swift_package_manager_enabled":{"ios":false,"macos":false}} {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"shared_preferences_foundation","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"shared_preferences_android","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.18/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"shared_preferences_foundation","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"shared_preferences_web","path":"/Users/rodydavis/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2026-01-15 17:05:21.433136","version":"3.38.5","swift_package_manager_enabled":{"ios":false,"macos":false}}
-7
View File
@@ -1,7 +0,0 @@
load("@rules_flutter//flutter:defs.bzl", "dart_library")
dart_library(
name = "undo",
srcs = glob(["lib/**/*.dart"]),
visibility = ["//visibility:public"],
)
+2 -3
View File
@@ -10,6 +10,5 @@ environment:
sdk: ^3.5.0 sdk: ^3.5.0
dev_dependencies: dev_dependencies:
lints: ^6.0.0 lints: ^6.1.0
flutter_test: test: ^1.24.9
sdk: flutter
+176 -174
View File
@@ -1,195 +1,197 @@
import 'package:flutter_test/flutter_test.dart'; import 'dart:async';
import 'package:test/test.dart';
import 'package:undo/undo.dart'; import 'package:undo/undo.dart';
void main() { void main() {
group('initial state', () { group('initial state', () {
test('is correct', () { test('is correct', () {
final states = <int>[]; final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add); final testObject = SimpleStack(0, onUpdate: states.add);
expect(testObject.state, 0); expect(testObject.state, 0);
}); });
});
group('canUndo', () {
test('is false when no state changes have occurred', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
expect(testObject.canUndo, isFalse);
}); });
test('is true when a single state change has occurred', () async { group('canUndo', () {
final states = <int>[]; test('is false when no state changes have occurred', () async {
final testObject = SimpleStack(0, onUpdate: states.add); final states = <int>[];
await Future<void>.delayed( final testObject = SimpleStack(0, onUpdate: states.add);
Duration.zero, () => testObject.modify(testObject.state + 1));
expect(testObject.canUndo, isTrue); expect(testObject.canUndo, isFalse);
});
test('is true when a single state change has occurred', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
expect(testObject.canUndo, isTrue);
});
test('is false when undos have been exhausted', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(testObject.canUndo, isFalse);
});
}); });
test('is false when undos have been exhausted', () async { group('canRedo', () {
final states = <int>[]; test('is false when no state changes have occurred', () async {
final testObject = SimpleStack(0, onUpdate: states.add); final states = <int>[];
await Future<void>.delayed( final testObject = SimpleStack(0, onUpdate: states.add);
Duration.zero, () => testObject.modify(testObject.state + 1)); expect(testObject.canRedo, isFalse);
await Future<void>.delayed(Duration.zero, testObject.undo); });
expect(testObject.canUndo, isFalse); test('is true when a single undo has occurred', () async {
}); final states = <int>[];
}); final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(testObject.canRedo, isTrue);
});
group('canRedo', () { test('is false when redos have been exhausted', () async {
test('is false when no state changes have occurred', () async { final states = <int>[];
final states = <int>[]; final testObject = SimpleStack(0, onUpdate: states.add);
final testObject = SimpleStack(0, onUpdate: states.add); await Future<void>.delayed(
expect(testObject.canRedo, isFalse); Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(testObject.canRedo, isFalse);
});
}); });
test('is true when a single undo has occurred', () async { group('clearHistory', () {
final states = <int>[]; test('clears history and redos on new testObject', () async {
final testObject = SimpleStack(0, onUpdate: states.add); final testObject = SimpleStack(0)..clearHistory();
await Future<void>.delayed( expect(testObject.canRedo, isFalse);
Duration.zero, () => testObject.modify(testObject.state + 1)); expect(testObject.canUndo, isFalse);
await Future<void>.delayed(Duration.zero, testObject.undo); });
expect(testObject.canRedo, isTrue);
}); });
test('is false when redos have been exhausted', () async { group('undo', () {
final states = <int>[]; test('does nothing when no state changes have occurred', () async {
final testObject = SimpleStack(0, onUpdate: states.add); final states = <int>[];
await Future<void>.delayed( final testObject = SimpleStack(0, onUpdate: states.add);
Duration.zero, () => testObject.modify(testObject.state + 1)); await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(testObject.canRedo, isFalse);
});
});
group('clearHistory', () { expect(states, [0]);
test('clears history and redos on new testObject', () async { });
final testObject = SimpleStack(0)..clearHistory();
expect(testObject.canRedo, isFalse);
expect(testObject.canUndo, isFalse);
});
});
group('undo', () { test('does nothing when limit is 0', () async {
test('does nothing when no state changes have occurred', () async { final states = <int>[];
final states = <int>[]; final testObject = SimpleStack(0, limit: 0, onUpdate: states.add);
final testObject = SimpleStack(0, onUpdate: states.add); await Future<void>.delayed(
await Future<void>.delayed(Duration.zero, testObject.undo); Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(states, [0]); expect(states, [0, 1, 0]);
});
test('loses history outside of limit', () async {
final states = <int>[];
final testObject = SimpleStack(0, limit: 1, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(states, [0, 1, 2, 1, 0]);
});
test('reverts to initial state', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(states, [0, 1, 0]);
});
test('reverts to previous state with multiple state changes ', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(states, [0, 1, 2, 1]);
});
}); });
test('does nothing when limit is 0', () async { group('redo', () {
final states = <int>[]; test('does nothing when no state changes have occurred', () async {
final testObject = SimpleStack(0, limit: 0, onUpdate: states.add); final states = <int>[];
await Future<void>.delayed( final testObject = SimpleStack(0, onUpdate: states.add);
Duration.zero, () => testObject.modify(testObject.state + 1)); await Future<void>.delayed(Duration.zero, testObject.redo);
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(states, [0, 1, 0]); expect(states, [0]);
});
test('does nothing when no undos have occurred', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0, 1, 2]);
});
test('works when one undo has occurred', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0, 1, 2, 1, 2]);
});
test('does nothing when undos have been exhausted', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.redo);
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0, 1, 2, 1, 2]);
});
test(
'does nothing when undos has occurred '
'followed by a new state change', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state - 1));
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0, 1, 2, 1, 0]);
});
}); });
test('loses history outside of limit', () async {
final states = <int>[];
final testObject = SimpleStack(0, limit: 1, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(states, [0, 1, 2, 1, 0]);
});
test('reverts to initial state', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(states, [0, 1, 0]);
});
test('reverts to previous state with multiple state changes ', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
expect(states, [0, 1, 2, 1]);
});
});
group('redo', () {
test('does nothing when no state changes have occurred', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0]);
});
test('does nothing when no undos have occurred', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0, 1, 2]);
});
test('works when one undo has occurred', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0, 1, 2, 1, 2]);
});
test('does nothing when undos have been exhausted', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(Duration.zero, testObject.redo);
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0, 1, 2, 1, 2]);
});
test(
'does nothing when undos has occurred '
'followed by a new state change', () async {
final states = <int>[];
final testObject = SimpleStack(0, onUpdate: states.add);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state + 1));
await Future<void>.delayed(Duration.zero, testObject.undo);
await Future<void>.delayed(
Duration.zero, () => testObject.modify(testObject.state - 1));
await Future<void>.delayed(Duration.zero, testObject.redo);
expect(states, [0, 1, 2, 1, 0]);
});
});
} }
+181 -5
View File
@@ -65,6 +65,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
cli_config:
dependency: transitive
description:
name: cli_config
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
url: "https://pub.dev"
source: hosted
version: "0.2.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -97,6 +105,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
coverage:
dependency: transitive
description:
name: coverage
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
url: "https://pub.dev"
source: hosted
version: "1.15.0"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@@ -151,7 +167,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: transitive
description: description:
name: flutter_lints name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
@@ -176,6 +192,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@@ -192,6 +216,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.2"
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"
js:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -220,10 +276,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.1.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
markdown: markdown:
dependency: transitive dependency: transitive
description: description:
@@ -256,6 +320,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -300,10 +380,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pigeon name: pigeon
sha256: b12f739047654cd65338dc7e9c6c03cbd4a1de179364a45c9e8bbeccf23729c6 sha256: fcfee18cf18dae434a149879e6876214c1b57c8d2d5d0cc55a5ba0c85278b886
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "26.1.7" version: "26.1.5"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -320,6 +400,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
pool:
dependency: transitive
description:
name: pool
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@@ -384,11 +472,59 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.1"
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"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.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: source_span:
dependency: transitive dependency: transitive
description: description:
@@ -429,6 +565,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.2" version: "1.2.2"
test:
dependency: transitive
description:
name: test
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
url: "https://pub.dev"
source: hosted
version: "1.26.3"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
@@ -437,6 +581,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.7" version: "0.7.7"
test_core:
dependency: transitive
description:
name: test_core
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
url: "https://pub.dev"
source: hosted
version: "0.6.12"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -541,6 +693,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" 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"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
-6
View File
@@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}