Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] 980c32c8b7 Bump shared_preferences from 2.5.4 to 2.5.5
Bumps [shared_preferences](https://github.com/flutter/packages/tree/main/packages/shared_preferences) from 2.5.4 to 2.5.5.
- [Commits](https://github.com/flutter/packages/commits/shared_preferences-v2.5.5/packages/shared_preferences)

---
updated-dependencies:
- dependency-name: shared_preferences
  dependency-version: 2.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-26 04:11:13 +00:00
40 changed files with 224 additions and 1751 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}"
-23
View File
@@ -1,23 +0,0 @@
name: Renovate
on:
schedule:
- cron: "0 * * * *" # Triggers hourly
push:
branches:
- main
workflow_dispatch: # Allows manual triggering from the Gitea UI
jobs:
renovate:
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:latest
env:
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: https://git.rodydavis.dev/api/v1
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_AUTODISCOVER: false
RENOVATE_REPOSITORIES: ${{ github.repository }}
steps:
- name: Run Renovate
run: renovate
+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")
@@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.2' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.2' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.2' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.2' classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.2' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
-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"],
)
-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"],
)
-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"],
)
-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"],
)
-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
View File
@@ -14,7 +14,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_markdown: ^0.7.0 flutter_markdown: ^0.7.0
shared_preferences: ^2.2.0 shared_preferences: ^2.5.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
-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"],
)
+10 -10
View File
@@ -61,10 +61,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -236,18 +236,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.17" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.13.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -332,10 +332,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4" version: "2.5.5"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
@@ -433,10 +433,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.7" version: "0.7.10"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
-6
View File
@@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}