Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc1767289d | |||
| 1e71601594 | |||
| 3064ce48e2 | |||
| 042da8c7e3 | |||
| 981779120a | |||
| 00d5ced000 | |||
| ad649263d5 | |||
| 2a88573734 | |||
| a627abc813 | |||
| afb6302f0d | |||
| fcc1a194c4 | |||
| e1e8d8ded0 | |||
| 9ec5986d6e | |||
| cc32380977 | |||
| e2a470abfe | |||
| 1164b50cc2 | |||
| ee78f04d33 | |||
| 3f8c930d48 | |||
| 6d3ff3dcd5 | |||
| 289b3dd4fa | |||
| 5d2fb37973 | |||
| 29116fa919 | |||
| 36cb839dd8 | |||
| a6f4b4aed6 | |||
| 21224bffcf | |||
| ab8d57c400 | |||
| 2debeaa79d |
@@ -0,0 +1,60 @@
|
||||
# Generated and managed by Antigravity
|
||||
**/.dart_tool
|
||||
**/build
|
||||
.agents
|
||||
.dart_tool
|
||||
.git
|
||||
.gitea
|
||||
.github
|
||||
.vscode
|
||||
build
|
||||
deprecated/cupertino_controllers/.github
|
||||
deprecated/dart_firebase/.github
|
||||
deprecated/dart_firebase/.vscode
|
||||
deprecated/data_tables/.github
|
||||
deprecated/data_tables/.vscode
|
||||
deprecated/dynamic_tabs/.github
|
||||
deprecated/dynamic_tabs/.vscode
|
||||
deprecated/easy_google_maps/.vscode
|
||||
deprecated/easy_web_view/.github
|
||||
deprecated/easy_web_view/.vscode
|
||||
deprecated/fb_auth/.vscode
|
||||
deprecated/fb_storage/.vscode
|
||||
deprecated/file_access/.vscode
|
||||
deprecated/floating_search_bar/.vscode
|
||||
deprecated/flutter_ast/.vscode
|
||||
deprecated/flutter_cli/.vscode
|
||||
deprecated/flutter_data_view/.github
|
||||
deprecated/flutter_data_view/.vscode
|
||||
deprecated/flutter_dynamic_widget/.vscode
|
||||
deprecated/flutter_midi/.github
|
||||
deprecated/flutter_midi/.vscode
|
||||
deprecated/get_version/.vscode
|
||||
deprecated/golden_layout/.vscode
|
||||
deprecated/image_resizer/.vscode
|
||||
deprecated/mobile_popup/.vscode
|
||||
deprecated/mobile_sidebar/.vscode
|
||||
deprecated/native_color_picker/.vscode
|
||||
deprecated/navigation_rail/.vscode
|
||||
deprecated/responsive_scaffold/.github
|
||||
deprecated/responsive_scaffold/.vscode
|
||||
deprecated/scaffold_tab_bar/.vscode
|
||||
deprecated/sheet_music/.vscode
|
||||
deprecated/storyboard/.github
|
||||
deprecated/storyboard/.vscode
|
||||
deprecated/widget_gen/.vscode
|
||||
experimental/http_get_cache/.vscode
|
||||
experimental/web_file_system/.dart_tool
|
||||
experimental/web_file_system/example/.dart_tool
|
||||
experimental/web_file_system/example/build
|
||||
node_modules
|
||||
packages/app_review/.github
|
||||
packages/open_xml/.github
|
||||
packages/open_xml/third_party/validator/.github
|
||||
packages/open_xml/third_party/validator/.vscode
|
||||
packages/signals/.github
|
||||
packages/signals/.vscode
|
||||
packages/signals/third_party/preactjs_signals/.github
|
||||
packages/signals/website/.vscode
|
||||
packages/undo/.github
|
||||
packages/undo/.vscode
|
||||
@@ -0,0 +1,11 @@
|
||||
# --- Remote Caching ---
|
||||
build:http-cache --remote_cache=https://bazel-cache.rodydavis.dev
|
||||
build:http-cache --remote_timeout=5
|
||||
build:http-cache --remote_local_fallback=true
|
||||
build:http-cache --remote_header=Authorization="Bearer ${BAZEL_CACHE_TOKEN}"
|
||||
|
||||
build:grpc-cache --remote_cache=grpcs://bazel-grpc-cache.rodydavis.dev
|
||||
build:grpc-cache --remote_upload_local_results=true
|
||||
build:grpc-cache --remote_timeout=5
|
||||
build:grpc-cache --remote_local_fallback=true
|
||||
build:grpc-cache --remote_header=Authorization="Bearer ${BAZEL_CACHE_TOKEN}"
|
||||
@@ -1,31 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/packages/breakpoint"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/packages/flutter_sms"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/packages/flutter_vibrate"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/packages/flutter_whatsnew"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
+45
-12
@@ -1,22 +1,55 @@
|
||||
# ==========================================
|
||||
# 🌌 Federated Workspaces - Ultimate Ignores
|
||||
# ==========================================
|
||||
|
||||
# --- System & Editor Files ---
|
||||
.DS_Store
|
||||
.dart_tool/
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
.packages
|
||||
.pub/
|
||||
pubspec.lock
|
||||
# --- Bazel Convenience Symlinks & Outputs ---
|
||||
bazel-*
|
||||
bazel_out/
|
||||
|
||||
build/
|
||||
*.iml
|
||||
# --- C/C++ Outputs ---
|
||||
*.o
|
||||
*.obj
|
||||
*.a
|
||||
*.lib
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.out
|
||||
*.exe
|
||||
|
||||
DerivedData/
|
||||
# --- Go Outputs ---
|
||||
*.test
|
||||
*.prof
|
||||
__debug_bin
|
||||
MODULE.bazel.lock
|
||||
|
||||
# --- Node / JavaScript / pnpm ---
|
||||
node_modules/
|
||||
.npm/
|
||||
.pnpm-store/
|
||||
.pnpm-debug.log*
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
|
||||
# Files and directories created by pub
|
||||
# --- Dart / Flutter ---
|
||||
.dart_tool/
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
build/
|
||||
|
||||
*.SF2
|
||||
/build/
|
||||
*.flutter-plugins
|
||||
*.flutter-plugins-dependencies
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Root BUILD file for packages_dart workspace
|
||||
@@ -0,0 +1,25 @@
|
||||
module(
|
||||
name = "packages_dart",
|
||||
version = "1.0.0",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_flutter", version = "0.0.0")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||
bazel_dep(name = "rules_go", version = "0.60.0")
|
||||
bazel_dep(name = "gazelle", version = "0.42.0")
|
||||
|
||||
git_override(
|
||||
module_name = "rules_flutter",
|
||||
remote = "https://github.com/SpencerC/rules_flutter.git",
|
||||
commit = "ca70cb77c09d4522fea090c42d81beed597bdacf",
|
||||
)
|
||||
|
||||
flutter = use_extension("@rules_flutter//flutter:extensions.bzl", "flutter")
|
||||
flutter.toolchain(flutter_version = "3.24.0")
|
||||
use_repo(
|
||||
flutter,
|
||||
"flutter_sdk",
|
||||
"flutter_toolchains",
|
||||
)
|
||||
|
||||
register_toolchains("@flutter_toolchains//:all")
|
||||
@@ -26,6 +26,7 @@ A collection of Flutter packages maintained by @rodydavis.
|
||||
| Package | Description | Version |
|
||||
| :--- | :--- | :--- |
|
||||
| [diff_algorithims](./experimental/diff_algorithims) | A set of diffing algorithims for Dart |  |
|
||||
| [http_get_cache](./experimental/http_get_cache) | HTTP Compliant cache for Flutter |  |
|
||||
| [pocketbase_auth](./experimental/pocketbase_auth) | Pocketbase auth client with built in UI screens and token refresh |  |
|
||||
| [pocketbase_sync](./experimental/pocketbase_sync) | A Flutter package for offline-first syncing with PocketBase. |  |
|
||||
| [web_file_system](./experimental/web_file_system) | A high-performance, asynchronous file system for the web using IDB and OPFS. |  |
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
# web_file_system
|
||||
|
||||
A high-performance, fully asynchronous VFS (Virtual File System) for the web implementing Dart's standard `package:file` interfaces.
|
||||
|
||||
It uses a hybrid backend that separates metadata management from actual block storage for optimal latency and performance in the browser.
|
||||
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
* **`package:file` Compliance**: Drop-in replacement for standard file system operations. Fully compatible with libraries expecting a `FileSystem` interface.
|
||||
* **O(1) Directory Renames**: By utilizing an inode-based database structure, directory renames and moves are fast constant-time operations. Children don't require path prefix updates.
|
||||
* **IndexedDB Metadata Store**: Keeps directory hierarchies, timestamps, file sizes, and symbolic links indexed using fast IndexedDB tables.
|
||||
* **OPFS (Origin Private File System) Block Store**: Stores raw file bytes and stream chunks directly in the high-performance private browser storage space.
|
||||
* **Symbolic Links (Symlinks)**: Full support for relative and absolute symbolic links, target updating, recursive listing, and canonical path resolution.
|
||||
|
||||
---
|
||||
|
||||
## Architectural Flow
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph WebFileSystem ["WebFileSystem (Dart API)"]
|
||||
FS["WebFileSystem"]
|
||||
WD["WebDirectory"]
|
||||
WF["WebFile"]
|
||||
WL["WebLink"]
|
||||
end
|
||||
|
||||
subgraph Metadata ["IndexedDB (IdbInodeService)"]
|
||||
DB["WebFileSystemDB"]
|
||||
Inodes["Inode Table: parentId index"]
|
||||
end
|
||||
|
||||
subgraph DataBlocks ["OPFS (OpfsBlockStore)"]
|
||||
BlocksDir["/.blocks/ Directory"]
|
||||
RawData["UUID Data Blobs"]
|
||||
end
|
||||
|
||||
FS -->|Lookup / Resolve| Metadata
|
||||
WD -->|Read / Write Children| Metadata
|
||||
WF -->|Read / Write Metadata| Metadata
|
||||
WF -->|Read / Write Data Blocks| DataBlocks
|
||||
WL -->|Read / Write Link Target| DataBlocks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
Add `web_file_system` to your `pubspec.yaml` (or reference the path if using as a path dependency):
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
web_file_system:
|
||||
path: path/to/web_file_system
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 1. Initializing the File System
|
||||
|
||||
```dart
|
||||
import 'package:web_file_system/web_file_system.dart';
|
||||
|
||||
final fs = WebFileSystem();
|
||||
```
|
||||
|
||||
### 2. Creating and Reading Files
|
||||
|
||||
```dart
|
||||
final file = fs.file('/documents/report.txt');
|
||||
|
||||
// Standard write & read (creates parent directories if needed when recursive is true)
|
||||
await file.create(recursive: true);
|
||||
await file.writeAsString('Hello, Web private storage!');
|
||||
|
||||
final contents = await file.readAsString();
|
||||
print(contents); // "Hello, Web private storage!"
|
||||
```
|
||||
|
||||
### 3. Directory Listing & Traversal
|
||||
|
||||
```dart
|
||||
final dir = fs.directory('/documents');
|
||||
await for (final entity in dir.list(recursive: true, followLinks: false)) {
|
||||
print('${entity.path} (${entity.runtimeType})');
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Symbolic Links
|
||||
|
||||
```dart
|
||||
// Create a symbolic link
|
||||
final link = fs.link('/shortcut_to_report');
|
||||
await link.create('/documents/report.txt');
|
||||
|
||||
// Reading content through the link
|
||||
final data = await fs.file('/shortcut_to_report').readAsString();
|
||||
|
||||
// Resolve canonical absolute path
|
||||
final canonicalPath = await link.resolveSymbolicLinks();
|
||||
print(canonicalPath); // "/documents/report.txt"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
`web_file_system` implements an **inode index** model. Inodes reference parents by their database IDs rather than paths. Renaming a directory only updates the single directory node's `name` property. Its child files and directories remain untouched because they continue to reference the same unchanged parent inode ID. This makes directory renaming an **$O(1)$** operation.
|
||||
|
||||
---
|
||||
|
||||
## Synchronous API Support (Web Workers Only)
|
||||
|
||||
Because JavaScript and Dart run on a single-threaded event loop, blocking the main browser thread is not permitted. Therefore, standard synchronous APIs (such as `readAsBytesSync`, `writeAsBytesSync`, `existsSync`, `createSync`, `deleteSync`, `listSync`, `renameSync`, and `statSync`) will throw an `UnsupportedError` if invoked on the main application thread.
|
||||
|
||||
However, synchronous operations are **fully supported within Web Workers**. By delegating asynchronous work (IndexedDB metadata updates and Origin Private File System operations) to the main thread via a `SharedArrayBuffer` and blocking the Web Worker's execution loop using `Atomics.wait()`, you can safely use synchronous file system calls inside your workers.
|
||||
|
||||
### Prerequisites & Security Headers
|
||||
|
||||
To use the synchronous APIs, the browser requires cross-origin isolation. You must serve your web application with the following HTTP headers:
|
||||
|
||||
```http
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
```
|
||||
|
||||
If these headers are not present, `SharedArrayBuffer` is disabled by the browser, and synchronous operations will throw a `StateError`.
|
||||
|
||||
### Setup Example
|
||||
|
||||
#### 1. Main Application Thread
|
||||
|
||||
Initialize the file system and register the worker proxy to listen to RPC requests from your worker:
|
||||
|
||||
```dart
|
||||
import 'dart:html'; // or standard JS interop / package:web
|
||||
import 'package:web_file_system/web_file_system.dart';
|
||||
|
||||
void main() {
|
||||
final fs = WebFileSystem();
|
||||
final worker = Worker('worker.js');
|
||||
|
||||
// Register the proxy to handle synchronous RPC requests
|
||||
WebFileSystem.registerWorkerProxy(worker, fs);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Web Worker Context (`worker.js` / Dart Worker)
|
||||
|
||||
In your worker code, once initialized with the `SharedArrayBuffer`, you can access the file system synchronously:
|
||||
|
||||
```dart
|
||||
import 'package:web_file_system/web_file_system.dart';
|
||||
|
||||
void main() {
|
||||
final fs = WebFileSystem();
|
||||
|
||||
// These operations block synchronous execution inside the Web Worker
|
||||
final file = fs.file('/data.bin');
|
||||
file.createSync();
|
||||
file.writeAsBytesSync([1, 2, 3, 4]);
|
||||
|
||||
final bytes = file.readAsBytesSync();
|
||||
print(bytes); // [1, 2, 3, 4]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/dist/
|
||||
/coverage/
|
||||
/scripts/temp_bin/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# Xcode data
|
||||
/macos/DerivedData/
|
||||
@@ -153,9 +153,13 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
||||
spacing: 8,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: _createFile, child: const Text('New File')),
|
||||
onPressed: _createFile,
|
||||
child: const Text('New File'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _createDir, child: const Text('New Directory')),
|
||||
onPressed: _createDir,
|
||||
child: const Text('New Directory'),
|
||||
),
|
||||
if (_currentPath != '/')
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
@@ -164,7 +168,8 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
||||
});
|
||||
_refreshFiles();
|
||||
},
|
||||
child: const Text('Go Up')),
|
||||
child: const Text('Go Up'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
@@ -187,7 +192,8 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
||||
return Text(_formatBytes(snapshot.data!));
|
||||
}
|
||||
return const Text('Loading...');
|
||||
}),
|
||||
},
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () => _delete(entity),
|
||||
@@ -205,13 +211,16 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(entity.basename),
|
||||
content: SingleChildScrollView(
|
||||
child: Text(content)),
|
||||
child: Text(content),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Close'))
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
));
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -229,11 +238,13 @@ class _FileSystemDemoState extends State<FileSystemDemo> {
|
||||
itemCount: _logs.length,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Text(_logs[_logs.length - 1 - index],
|
||||
child: Text(
|
||||
_logs[_logs.length - 1 - index],
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: lints
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.20"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.3"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.12"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "1d774bbdf6b72a0b12122fc1560c9c2d2a67db5a4a4cc2bd8a5c990ab20e3188"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_file_system:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
sdks:
|
||||
dart: ">=3.10.0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
@@ -0,0 +1,123 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop';
|
||||
import 'dart:typed_data';
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class IdbBlockStore {
|
||||
static const String _dbName = 'WebFileSystemBlobs';
|
||||
static const int _version = 1;
|
||||
static const String _storeName = 'blobs';
|
||||
|
||||
web.IDBDatabase? _db;
|
||||
final Completer<void> _initCompleter = Completer<void>();
|
||||
final Uuid _uuid = Uuid();
|
||||
|
||||
Future<void> _ensureReady() async {
|
||||
if (_db != null) return;
|
||||
if (_initCompleter.isCompleted) return _initCompleter.future;
|
||||
|
||||
final request = web.window.indexedDB.open(_dbName, _version);
|
||||
|
||||
request.onupgradeneeded = (web.IDBVersionChangeEvent event) {
|
||||
final db =
|
||||
(event.target as web.IDBOpenDBRequest).result as web.IDBDatabase;
|
||||
if (!db.objectStoreNames.contains(_storeName)) {
|
||||
db.createObjectStore(_storeName);
|
||||
}
|
||||
}.toJS;
|
||||
|
||||
final completer = Completer<void>();
|
||||
|
||||
request.onsuccess = (web.Event event) {
|
||||
_db = (event.target as web.IDBOpenDBRequest).result as web.IDBDatabase;
|
||||
if (!_initCompleter.isCompleted) completer.complete();
|
||||
}.toJS;
|
||||
|
||||
request.onerror = (web.Event event) {
|
||||
if (!_initCompleter.isCompleted) {
|
||||
completer.completeError(Exception('Failed to open Blob IDB'));
|
||||
}
|
||||
}.toJS;
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<String> writeBlob(Stream<List<int>> stream) async {
|
||||
await _ensureReady();
|
||||
final blockId = _uuid.v4();
|
||||
|
||||
// Read all bytes into memory to store in IDB (IDB requires complete blob/buffer usually)
|
||||
// For streams, we must materialize them.
|
||||
final chunks = await stream.toList();
|
||||
final allBytes = chunks.expand((x) => x).toList();
|
||||
final uint8Array = Uint8List.fromList(allBytes).toJS;
|
||||
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS,
|
||||
'readwrite',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
|
||||
// Using put(value, key) since we didn't specify keyPath/autoIncrement
|
||||
final request = store.put(uint8Array, blockId.toJS);
|
||||
await _requestToFuture(request);
|
||||
|
||||
return blockId;
|
||||
}
|
||||
|
||||
Stream<List<int>> readBlob(String blockId) async* {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS,
|
||||
'readonly',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.get(blockId.toJS);
|
||||
|
||||
final result = await _requestToFuture(request);
|
||||
if (result == null) throw Exception('Blob $blockId not found');
|
||||
|
||||
// result is JSUint8Array or ArrayBuffer
|
||||
final uint8Array = result as JSUint8Array;
|
||||
yield uint8Array.toDart;
|
||||
}
|
||||
|
||||
Future<void> deleteBlob(String blockId) async {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS,
|
||||
'readwrite',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.delete(blockId.toJS);
|
||||
await _requestToFuture(request);
|
||||
}
|
||||
|
||||
Future<web.Blob> getBlob(String blockId) async {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS,
|
||||
'readonly',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.get(blockId.toJS);
|
||||
|
||||
final result = await _requestToFuture(request);
|
||||
if (result == null) throw Exception('Blob $blockId not found');
|
||||
|
||||
final uint8Array = result as JSUint8Array;
|
||||
return web.Blob([uint8Array].toJS);
|
||||
}
|
||||
|
||||
Future<dynamic> _requestToFuture(web.IDBRequest request) {
|
||||
final completer = Completer<dynamic>();
|
||||
request.onsuccess = (web.Event e) {
|
||||
completer.complete((e.target as web.IDBRequest).result);
|
||||
}.toJS;
|
||||
request.onerror = (web.Event e) {
|
||||
completer.completeError(Exception('IDB Blob Error'));
|
||||
}.toJS;
|
||||
return completer.future;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop';
|
||||
import 'dart:js_interop_unsafe';
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
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({
|
||||
required String id,
|
||||
required String parentId,
|
||||
@@ -32,16 +12,18 @@ extension type InodeJS._(JSObject _) implements JSObject {
|
||||
String? blobId,
|
||||
int size = 0,
|
||||
required int modified,
|
||||
int storageType = 0,
|
||||
}) {
|
||||
final obj = JSObject() as InodeJS;
|
||||
obj.id = id;
|
||||
obj.parentId = parentId;
|
||||
obj.name = name;
|
||||
obj.nodeType = nodeType;
|
||||
obj.blobId = blobId;
|
||||
obj.size = size;
|
||||
obj.modified = modified;
|
||||
return obj;
|
||||
final obj = JSObject();
|
||||
obj.setProperty('id'.toJS, id.toJS);
|
||||
obj.setProperty('parentId'.toJS, parentId.toJS);
|
||||
obj.setProperty('name'.toJS, name.toJS);
|
||||
obj.setProperty('nodeType'.toJS, nodeType.toJS);
|
||||
if (blobId != null) obj.setProperty('blobId'.toJS, blobId.toJS);
|
||||
obj.setProperty('size'.toJS, size.toJS);
|
||||
obj.setProperty('modified'.toJS, modified.toJS);
|
||||
obj.setProperty('storageType'.toJS, storageType.toJS);
|
||||
return obj as InodeJS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +35,7 @@ class Inode {
|
||||
final String? blobId;
|
||||
final int size;
|
||||
final int modified;
|
||||
final int storageType; // 0: OPFS, 1: IDB
|
||||
|
||||
Inode({
|
||||
required this.id,
|
||||
@@ -62,6 +45,7 @@ class Inode {
|
||||
this.blobId,
|
||||
this.size = 0,
|
||||
required this.modified,
|
||||
this.storageType = 0,
|
||||
});
|
||||
|
||||
InodeJS toJS() {
|
||||
@@ -73,18 +57,30 @@ class Inode {
|
||||
blobId: blobId,
|
||||
size: size,
|
||||
modified: modified,
|
||||
storageType: storageType,
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
id: js.id,
|
||||
parentId: js.parentId,
|
||||
name: js.name,
|
||||
nodeType: js.nodeType,
|
||||
blobId: js.blobId,
|
||||
size: js.size,
|
||||
modified: js.modified,
|
||||
id: id != null && !id.isUndefinedOrNull ? (id as JSString).toDart : '',
|
||||
parentId: parentId != null && !parentId.isUndefinedOrNull ? (parentId as JSString).toDart : '',
|
||||
name: name != null && !name.isUndefinedOrNull ? (name as JSString).toDart : '',
|
||||
nodeType: nodeType != null && !nodeType.isUndefinedOrNull ? (nodeType as JSNumber).toDartInt : 0,
|
||||
blobId: blobId != null && !blobId.isUndefinedOrNull ? (blobId as JSString).toDart : null,
|
||||
size: size != null && !size.isUndefinedOrNull ? (size as JSNumber).toDartInt : 0,
|
||||
modified: modified != null && !modified.isUndefinedOrNull ? (modified as JSNumber).toDartInt : 0,
|
||||
storageType: storageType != null && !storageType.isUndefinedOrNull ? (storageType as JSNumber).toDartInt : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -113,9 +109,15 @@ class IdbInodeService {
|
||||
web.IDBObjectStoreParameters(keyPath: 'id'.toJS),
|
||||
);
|
||||
store.createIndex(
|
||||
'parentId', 'parentId'.toJS, web.IDBIndexParameters(unique: false));
|
||||
store.createIndex('parent_name', ['parentId'.toJS, 'name'.toJS].toJS,
|
||||
web.IDBIndexParameters(unique: true));
|
||||
'parentId',
|
||||
'parentId'.toJS,
|
||||
web.IDBIndexParameters(unique: false),
|
||||
);
|
||||
store.createIndex(
|
||||
'parent_name',
|
||||
['parentId'.toJS, 'name'.toJS].toJS,
|
||||
web.IDBIndexParameters(unique: true),
|
||||
);
|
||||
}
|
||||
}.toJS;
|
||||
|
||||
@@ -123,16 +125,19 @@ class IdbInodeService {
|
||||
|
||||
request.onsuccess = (web.Event event) {
|
||||
_db = (event.target as web.IDBOpenDBRequest).result as web.IDBDatabase;
|
||||
_ensureRootExists().then((_) {
|
||||
_ensureRootExists()
|
||||
.then((_) {
|
||||
if (!_initCompleter.isCompleted) completer.complete();
|
||||
}).catchError((e) {
|
||||
})
|
||||
.catchError((e) {
|
||||
if (!_initCompleter.isCompleted) completer.completeError(e);
|
||||
});
|
||||
}.toJS;
|
||||
|
||||
request.onerror = (web.Event event) {
|
||||
if (!_initCompleter.isCompleted)
|
||||
if (!_initCompleter.isCompleted) {
|
||||
completer.completeError(Exception('Failed to open IDB'));
|
||||
}
|
||||
}.toJS;
|
||||
|
||||
return completer.future;
|
||||
@@ -142,20 +147,24 @@ class IdbInodeService {
|
||||
try {
|
||||
await getInode(rootId);
|
||||
} catch (_) {
|
||||
await createInode(Inode(
|
||||
await createInode(
|
||||
Inode(
|
||||
id: rootId,
|
||||
parentId: 'null',
|
||||
name: '',
|
||||
nodeType: 1,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createInode(Inode inode) async {
|
||||
if (_db == null) await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readwrite'.toJS as web.IDBTransactionMode);
|
||||
_storeName.toJS,
|
||||
'readwrite',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.put(inode.toJS());
|
||||
await _requestToFuture(request);
|
||||
@@ -168,7 +177,9 @@ class IdbInodeService {
|
||||
Future<void> deleteInode(String id) async {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readwrite'.toJS as web.IDBTransactionMode);
|
||||
_storeName.toJS,
|
||||
'readwrite',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.delete(id.toJS);
|
||||
await _requestToFuture(request);
|
||||
@@ -178,7 +189,9 @@ class IdbInodeService {
|
||||
if (_db == null) await _ensureReady();
|
||||
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
||||
_storeName.toJS,
|
||||
'readonly',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final request = store.get(id.toJS);
|
||||
final result = await _requestToFuture(request);
|
||||
@@ -190,7 +203,9 @@ class IdbInodeService {
|
||||
Future<Inode?> getChild(String parentId, String name) async {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
||||
_storeName.toJS,
|
||||
'readonly',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final index = store.index('parent_name');
|
||||
final key = JSArray();
|
||||
@@ -211,7 +226,9 @@ class IdbInodeService {
|
||||
Future<List<Inode>> listChildren(String parentId) async {
|
||||
await _ensureReady();
|
||||
final transaction = _db!.transaction(
|
||||
_storeName.toJS, 'readonly'.toJS as web.IDBTransactionMode);
|
||||
_storeName.toJS,
|
||||
'readonly',
|
||||
);
|
||||
final store = transaction.objectStore(_storeName);
|
||||
final index = store.index('parentId');
|
||||
final request = index.getAll(parentId.toJS);
|
||||
|
||||
@@ -12,10 +12,7 @@ class OpfsBlockStore {
|
||||
Future<void> _ensureReady() async {
|
||||
if (_blocksDir != null) return;
|
||||
|
||||
final web.StorageManager? storage = web.window.navigator.storage;
|
||||
if (storage == null) {
|
||||
throw UnsupportedError('StorageManager not supported');
|
||||
}
|
||||
final web.StorageManager storage = web.window.navigator.storage;
|
||||
|
||||
final root = await storage.getDirectory().toDart;
|
||||
_blocksDir = await root
|
||||
@@ -31,10 +28,7 @@ class OpfsBlockStore {
|
||||
final blockId = _uuid.v4();
|
||||
|
||||
final fileHandle = await _blocksDir!
|
||||
.getFileHandle(
|
||||
blockId,
|
||||
web.FileSystemGetFileOptions(create: true),
|
||||
)
|
||||
.getFileHandle(blockId, web.FileSystemGetFileOptions(create: true))
|
||||
.toDart;
|
||||
|
||||
final writable = await fileHandle.createWritable().toDart;
|
||||
@@ -50,6 +44,7 @@ class OpfsBlockStore {
|
||||
await writable.abort().toDart;
|
||||
await _blocksDir!.removeEntry(blockId).toDart;
|
||||
} catch (_) {}
|
||||
print('OpfsBlockStore: writeBlob failed for $blockId: $e');
|
||||
rethrow;
|
||||
}
|
||||
|
||||
@@ -59,11 +54,7 @@ class OpfsBlockStore {
|
||||
Stream<List<int>> readBlob(String blockId) async* {
|
||||
await _ensureReady();
|
||||
try {
|
||||
final fileHandle = await _blocksDir!
|
||||
.getFileHandle(
|
||||
blockId,
|
||||
)
|
||||
.toDart;
|
||||
final fileHandle = await _blocksDir!.getFileHandle(blockId).toDart;
|
||||
|
||||
final file = await fileHandle.getFile().toDart;
|
||||
final web.Blob blob = file;
|
||||
@@ -78,6 +69,7 @@ class OpfsBlockStore {
|
||||
yield chunk.toDart;
|
||||
}
|
||||
} catch (e) {
|
||||
print('OpfsBlockStore: readBlob failed for $blockId: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -90,4 +82,11 @@ class OpfsBlockStore {
|
||||
// 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 'package:file/file.dart';
|
||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
||||
import '../backend/idb_inode_service.dart';
|
||||
import '../web_file_system.dart';
|
||||
import 'web_file.dart';
|
||||
|
||||
@@ -33,13 +33,19 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
||||
// We assume parent must exist if not recursive.
|
||||
final parentInode = await _fs.resolvepath(parentPath); // throws if missing
|
||||
|
||||
await _fs.idb.createInode(Inode(
|
||||
try {
|
||||
await _fs.idb.createInode(
|
||||
Inode(
|
||||
id: _fs.uuid.v4(),
|
||||
parentId: parentInode.id,
|
||||
name: name,
|
||||
nodeType: 1, // Directory
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
));
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
if (!await exists()) rethrow;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -50,12 +56,19 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
||||
|
||||
await _createRecursiveSafe(_fs.path.dirname(p));
|
||||
final parentVal = await _fs.resolvepath(_fs.path.dirname(p));
|
||||
await _fs.idb.createInode(Inode(
|
||||
try {
|
||||
await _fs.idb.createInode(
|
||||
Inode(
|
||||
id: _fs.uuid.v4(),
|
||||
parentId: parentVal.id,
|
||||
name: _fs.path.basename(p),
|
||||
nodeType: 1,
|
||||
modified: DateTime.now().millisecondsSinceEpoch));
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
if (await _fs.type(p) == FileSystemEntityType.notFound) rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -69,8 +82,15 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
||||
final tempDir = _fs.path.join(path, name);
|
||||
// Ensure path exists
|
||||
if (!await exists()) {
|
||||
if (path == '/tmp') {
|
||||
await create();
|
||||
} else {
|
||||
throw FileSystemException(
|
||||
'Directory does not exist', path, const OSError('ENOENT', 2));
|
||||
'Directory does not exist',
|
||||
path,
|
||||
const OSError('ENOENT', 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
final dir = WebDirectory(_fs, tempDir);
|
||||
await dir.create();
|
||||
@@ -89,7 +109,10 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
||||
final children = await _fs.idb.listChildren(inode.id);
|
||||
if (children.isNotEmpty && !recursive) {
|
||||
throw FileSystemException(
|
||||
'Directory not empty', path, const OSError('ENOTEMPTY', 39));
|
||||
'Directory not empty',
|
||||
path,
|
||||
const OSError('ENOTEMPTY', 39),
|
||||
);
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
@@ -125,11 +148,16 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
||||
bool existsSync() => throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Stream<FileSystemEntity> list(
|
||||
{bool recursive = false, bool followLinks = true}) async* {
|
||||
Stream<FileSystemEntity> list({
|
||||
bool recursive = false,
|
||||
bool followLinks = true,
|
||||
}) async* {
|
||||
if (!await exists()) {
|
||||
throw FileSystemException(
|
||||
'Directory not found', path, const OSError('ENOENT', 2));
|
||||
'Directory not found',
|
||||
path,
|
||||
const OSError('ENOENT', 2),
|
||||
);
|
||||
}
|
||||
|
||||
final inode = await _fs.resolvepath(path);
|
||||
@@ -150,8 +178,10 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
||||
}
|
||||
|
||||
@override
|
||||
List<FileSystemEntity> listSync(
|
||||
{bool recursive = false, bool followLinks = true}) {
|
||||
List<FileSystemEntity> listSync({
|
||||
bool recursive = false,
|
||||
bool followLinks = true,
|
||||
}) {
|
||||
throw UnsupportedError('Sync list not supported');
|
||||
}
|
||||
|
||||
@@ -170,7 +200,8 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
||||
nodeType: inode.nodeType,
|
||||
blobId: inode.blobId,
|
||||
size: inode.size,
|
||||
modified: DateTime.now().millisecondsSinceEpoch);
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
|
||||
await _fs.idb.updateInode(updated);
|
||||
return WebDirectory(_fs, newPath);
|
||||
@@ -209,8 +240,10 @@ class WebDirectory extends FileSystemEntity implements Directory {
|
||||
throw UnsupportedError('Sync not supported');
|
||||
|
||||
@override
|
||||
Stream<FileSystemEvent> watch(
|
||||
{int events = FileSystemEvent.all, bool recursive = false}) {
|
||||
Stream<FileSystemEvent> watch({
|
||||
int events = FileSystemEvent.all,
|
||||
bool recursive = false,
|
||||
}) {
|
||||
return const Stream.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
||||
import '../backend/idb_inode_service.dart';
|
||||
import '../web_file_system.dart';
|
||||
|
||||
class WebFile extends FileSystemEntity implements File {
|
||||
@@ -57,22 +57,19 @@ class WebFile extends FileSystemEntity implements File {
|
||||
|
||||
@override
|
||||
Future<File> copy(String newPath) async {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
final newParent = await _fs.resolvepath(_fs.path.dirname(newPath));
|
||||
// Deep copy to ensure independence and correct storage type
|
||||
|
||||
await _fs.idb.createInode(
|
||||
Inode(
|
||||
id: _fs.uuid.v4(),
|
||||
parentId: newParent.id,
|
||||
name: _fs.path.basename(newPath),
|
||||
nodeType: 0,
|
||||
blobId: inode.blobId,
|
||||
size: inode.size,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
final newFile = WebFile(_fs, newPath);
|
||||
// Use writeAsBytes to handle creation and storage logic
|
||||
// We need to consume the stream to a list for now as writeAsBytes takes List<int>
|
||||
// Or we could use a stream API if available, but writeAsBytes expects List<int>.
|
||||
// Given these are images/slides, memory is likely okay.
|
||||
// To be safer with memory, we should really stream it, but writeAsBytes takes List<int>.
|
||||
// Let's use readAsBytes() helper.
|
||||
final bytes = await readAsBytes();
|
||||
await newFile.writeAsBytes(bytes);
|
||||
|
||||
return WebFile(_fs, newPath);
|
||||
return newFile;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -149,7 +146,24 @@ class WebFile extends FileSystemEntity implements File {
|
||||
bool flush = false,
|
||||
}) async {
|
||||
final stream = Stream.value(bytes);
|
||||
final newBlobId = await _fs.opfs.writeBlob(stream);
|
||||
String newBlobId;
|
||||
int usedStorageType = 0; // 0 = OPFS, 1 = IDB
|
||||
|
||||
try {
|
||||
// Try OPFS first
|
||||
newBlobId = await _fs.opfs.writeBlob(stream);
|
||||
} catch (e) {
|
||||
// Fallback to IDB on ANY error (TypeError, NotFoundError, etc)
|
||||
try {
|
||||
newBlobId = await _fs.idbStore.writeBlob(Stream.value(bytes));
|
||||
usedStorageType = 1;
|
||||
} catch (e2) {
|
||||
throw FileSystemException(
|
||||
'Write failed on both OPFS ($e) and IDB: $e2',
|
||||
path,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Inode inode;
|
||||
try {
|
||||
@@ -171,6 +185,7 @@ class WebFile extends FileSystemEntity implements File {
|
||||
blobId: newBlobId,
|
||||
size: bytes.length,
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
storageType: usedStorageType,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -211,7 +226,37 @@ class WebFile extends FileSystemEntity implements File {
|
||||
final inode = await _fs.resolvepath(path);
|
||||
if (inode.blobId == null) return;
|
||||
|
||||
if (inode.storageType == 1) {
|
||||
try {
|
||||
yield* _fs.idbStore.readBlob(inode.blobId!);
|
||||
} catch (e) {
|
||||
// Fallback to OPFS if IDB fails (maybe metadata is wrong)
|
||||
print(
|
||||
'Using fallback to OPFS for reading ${inode.name} (blob: ${inode.blobId}). Error: $e',
|
||||
);
|
||||
try {
|
||||
yield* _fs.opfs.readBlob(inode.blobId!);
|
||||
} 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
|
||||
@@ -238,7 +283,16 @@ class WebFile extends FileSystemEntity implements File {
|
||||
FileMode mode,
|
||||
) async {
|
||||
try {
|
||||
final newId = await _fs.opfs.writeBlob(stream);
|
||||
String newId;
|
||||
int usedStorageType = 0; // 0 = OPFS, 1 = IDB
|
||||
|
||||
try {
|
||||
newId = await _fs.opfs.writeBlob(stream);
|
||||
} catch (e) {
|
||||
// Fallback to IDB on ANY error
|
||||
newId = await _fs.idbStore.writeBlob(stream);
|
||||
usedStorageType = 1;
|
||||
}
|
||||
|
||||
Inode inode;
|
||||
try {
|
||||
@@ -259,6 +313,7 @@ class WebFile extends FileSystemEntity implements File {
|
||||
size:
|
||||
0, // TODO: Size not returned by OPFS yet, so 0 for streamed content
|
||||
modified: DateTime.now().millisecondsSinceEpoch,
|
||||
storageType: usedStorageType,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -336,6 +391,13 @@ class WebFile extends FileSystemEntity implements File {
|
||||
@override
|
||||
Future<FileSystemEntity> delete({bool recursive = false}) async {
|
||||
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);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
||||
import '../backend/idb_inode_service.dart';
|
||||
import '../web_file_system.dart';
|
||||
|
||||
class WebLink extends FileSystemEntity implements Link {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:js_interop';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:web_file_system/src/backend/idb_inode_service.dart';
|
||||
import 'package:web_file_system/src/backend/opfs_block_store.dart';
|
||||
|
||||
import 'backend/idb_inode_service.dart';
|
||||
import 'backend/opfs_block_store.dart';
|
||||
import 'backend/idb_block_store.dart';
|
||||
import 'entities/web_directory.dart';
|
||||
import 'entities/web_file.dart';
|
||||
import 'entities/web_link.dart';
|
||||
@@ -12,11 +16,13 @@ import 'entities/web_link.dart';
|
||||
class WebFileSystem extends FileSystem {
|
||||
final IdbInodeService _idb = IdbInodeService();
|
||||
final OpfsBlockStore _opfs = OpfsBlockStore();
|
||||
final IdbBlockStore _idbStore = IdbBlockStore();
|
||||
final Uuid _uuid = Uuid();
|
||||
|
||||
// Public matchers for internal use
|
||||
IdbInodeService get idb => _idb;
|
||||
OpfsBlockStore get opfs => _opfs;
|
||||
IdbBlockStore get idbStore => _idbStore;
|
||||
Uuid get uuid => _uuid;
|
||||
|
||||
WebFileSystem();
|
||||
@@ -157,6 +163,7 @@ class WebFileSystem extends FileSystem {
|
||||
return currentInode;
|
||||
}
|
||||
|
||||
@override
|
||||
String getPath(dynamic path) {
|
||||
if (path is String) return path;
|
||||
if (path is FileSystemEntity) return path.path;
|
||||
@@ -210,6 +217,7 @@ class WebFileSystem extends FileSystem {
|
||||
Future<bool> isLink(String path) async =>
|
||||
(await type(path, followLinks: false)) == FileSystemEntityType.link;
|
||||
|
||||
@override
|
||||
bool get isWatchSupported => false;
|
||||
|
||||
@override
|
||||
@@ -217,17 +225,31 @@ class WebFileSystem extends FileSystem {
|
||||
final s1 = await stat(path1);
|
||||
final s2 = await stat(path2);
|
||||
if (s1.type == FileSystemEntityType.notFound ||
|
||||
s2.type == FileSystemEntityType.notFound)
|
||||
s2.type == FileSystemEntityType.notFound) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final i1 = await resolvepath(path1);
|
||||
final i2 = await resolvepath(path2);
|
||||
return i1.id == i2.id;
|
||||
}
|
||||
|
||||
@override
|
||||
@override
|
||||
bool identicalSync(String path1, String path2) =>
|
||||
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 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/// A high-performance, asynchronous file system for the web.
|
||||
library web_file_system;
|
||||
library;
|
||||
|
||||
export 'package:file/file.dart';
|
||||
export 'src/web_file_system.dart';
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "563c6992eaeda8625f45b87f6a6a0c547df16565d1c93d8271c7c11057710ca7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "101.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: aa6a9365901532864cae51208f2a6bb18dd01972ebead19c431efc848f60080b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.1.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
file:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: lints
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.20"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.3"
|
||||
mime:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mime
|
||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
sha256: ca578dc12bb8b2f40b67b7d3bd2fac4f31c01a6ff7130a14e2597b919934507f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.31.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.12"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: d2e98ec12998368dc59ddd47ab709f2cd55acd6b66dc7db764455a44082f4bc5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.18"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.3"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
web:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.11.0 <4.0.0"
|
||||
@@ -1,13 +1,8 @@
|
||||
name: web_file_system
|
||||
description: A high-performance, asynchronous file system for the web using IDB and OPFS.
|
||||
version: 0.0.1
|
||||
publish_to: 'none'
|
||||
homepage: https://github.com/fluttercommunity/flutter_whatsnew
|
||||
maintainer: Rody Davis (@rodydavis)
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
flutter: ^3.38.5
|
||||
sdk: ^3.10.0
|
||||
|
||||
dependencies:
|
||||
file: ^7.0.0
|
||||
@@ -17,5 +12,5 @@ dependencies:
|
||||
mime: ^1.0.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^6.0.0
|
||||
lints: ^3.0.0
|
||||
test: ^1.25.0
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
@TestOn('browser')
|
||||
import 'dart:convert';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:web_file_system/web_file_system.dart';
|
||||
|
||||
void main() {
|
||||
late WebFileSystem fs;
|
||||
|
||||
setUp(() async {
|
||||
// Tests run in parallel in browser often sharing context, so use unique paths.
|
||||
fs = WebFileSystem();
|
||||
});
|
||||
|
||||
group('Troubleshooting NotFoundError', () {
|
||||
test('Temp directory creation and persistence', () async {
|
||||
print('DEBUG: creating temp dir');
|
||||
final tempDir = await fs.systemTempDirectory.createTemp('debug_test_');
|
||||
print('DEBUG: Created temp dir at ${tempDir.path}');
|
||||
|
||||
expect(await tempDir.exists(), isTrue);
|
||||
|
||||
final file = tempDir.childFile('test.txt');
|
||||
await file.writeAsString('Persistence Check');
|
||||
|
||||
print('DEBUG: checking existence immediately');
|
||||
expect(await file.exists(), isTrue);
|
||||
expect(await file.readAsString(), equals('Persistence Check'));
|
||||
|
||||
// Wait a bit to simulate pipeline delays
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
print('DEBUG: checking existence after delay');
|
||||
expect(await file.readAsString(), equals('Persistence Check'));
|
||||
|
||||
// Cleanup
|
||||
await tempDir.delete(recursive: true);
|
||||
print('DEBUG: deleted temp dir');
|
||||
expect(await file.exists(), isFalse);
|
||||
});
|
||||
|
||||
test('Deep copy behavior verification', () async {
|
||||
final srcDir = await fs.systemTempDirectory.createTemp('src_');
|
||||
final destDir = await fs.systemTempDirectory.createTemp('dest_');
|
||||
|
||||
final srcFile = srcDir.childFile('source.dat');
|
||||
// Write some substantial data
|
||||
final data = utf8.encode('Essential Data for Export');
|
||||
await srcFile.writeAsBytes(data);
|
||||
|
||||
print('DEBUG: Source file written to ${srcFile.path}');
|
||||
|
||||
final destPath = destDir.childFile('copy.dat').path;
|
||||
print('DEBUG: Copying to $destPath');
|
||||
|
||||
final copiedFile = await srcFile.copy(destPath);
|
||||
|
||||
print('DEBUG: Copy complete');
|
||||
expect(await copiedFile.exists(), isTrue);
|
||||
expect(await copiedFile.readAsBytes(), equals(data));
|
||||
|
||||
// Now delete source
|
||||
print('DEBUG: Deleting source file');
|
||||
await srcFile.delete();
|
||||
expect(await srcFile.exists(), isFalse);
|
||||
|
||||
// Verify copy still exists and is readable (Deep Copy Check)
|
||||
print('DEBUG: Verifying copy after source deletion');
|
||||
expect(await copiedFile.exists(), isTrue);
|
||||
try {
|
||||
final copyData = await copiedFile.readAsBytes();
|
||||
expect(copyData, equals(data));
|
||||
print('DEBUG: Deep copy verified!');
|
||||
} catch (e) {
|
||||
print('ERROR: Deep copy failed! Reading copy caused error: $e');
|
||||
rethrow;
|
||||
}
|
||||
});
|
||||
|
||||
test('Concurrent Write/Read stress', () async {
|
||||
final dir = await fs.systemTempDirectory.createTemp('stress_');
|
||||
final file = dir.childFile('stress.txt');
|
||||
|
||||
// Write
|
||||
await file.writeAsString('Initial');
|
||||
|
||||
// Rapidly update
|
||||
for (int i = 0; i < 10; i++) {
|
||||
await file.writeAsString('Update $i');
|
||||
final content = await file.readAsString();
|
||||
expect(content, equals('Update $i'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
@TestOn('browser')
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:web_file_system/web_file_system.dart';
|
||||
|
||||
void main() {
|
||||
late WebFileSystem fs;
|
||||
|
||||
setUp(() async {
|
||||
fs = WebFileSystem();
|
||||
});
|
||||
|
||||
group('Robust Correctness Tests', () {
|
||||
test('Multiple files in a directory stress test', () async {
|
||||
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
||||
final dirPath = '/stress_$uniqueId';
|
||||
final dir = fs.directory(dirPath);
|
||||
await dir.create();
|
||||
|
||||
final fileCount = 50;
|
||||
final futures = <Future>[];
|
||||
|
||||
for (var i = 0; i < fileCount; i++) {
|
||||
futures.add(
|
||||
fs.file('$dirPath/file_$i.txt').writeAsString('Content of file $i'),
|
||||
);
|
||||
}
|
||||
await Future.wait(futures);
|
||||
|
||||
final entities = await dir.list().toList();
|
||||
expect(entities.length, equals(fileCount));
|
||||
|
||||
final names = entities.map((e) => fs.path.basename(e.path)).toSet();
|
||||
for (var i = 0; i < fileCount; i++) {
|
||||
expect(names, contains('file_$i.txt'));
|
||||
final content = await fs.file('$dirPath/file_$i.txt').readAsString();
|
||||
expect(content, equals('Content of file $i'));
|
||||
}
|
||||
});
|
||||
|
||||
test('Temporary directory usage', () async {
|
||||
final tempDir = await fs.systemTempDirectory.createTemp('my_prefix_');
|
||||
expect(tempDir.path, startsWith('/tmp/my_prefix_'));
|
||||
expect(await tempDir.exists(), isTrue);
|
||||
|
||||
final file = fs.file(fs.path.join(tempDir.path, 'test.txt'));
|
||||
await file.writeAsString('temp content');
|
||||
expect(await file.readAsString(), equals('temp content'));
|
||||
|
||||
await tempDir.delete(recursive: true);
|
||||
expect(await tempDir.exists(), isFalse);
|
||||
expect(await file.exists(), isFalse);
|
||||
});
|
||||
|
||||
test('Different files with the same bytes (deduplication/collision check)', () async {
|
||||
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
||||
final data = Uint8List.fromList([1, 2, 3, 4, 5]);
|
||||
|
||||
final file1 = fs.file('/file1_$uniqueId.bin');
|
||||
final file2 = fs.file('/file2_$uniqueId.bin');
|
||||
|
||||
await file1.writeAsBytes(data);
|
||||
await file2.writeAsBytes(data);
|
||||
|
||||
expect(await file1.readAsBytes(), equals(data));
|
||||
expect(await file2.readAsBytes(), equals(data));
|
||||
|
||||
// Update one, ensure other is unchanged
|
||||
final newData = Uint8List.fromList([6, 7, 8]);
|
||||
await file1.writeAsBytes(newData);
|
||||
|
||||
expect(await file1.readAsBytes(), equals(newData));
|
||||
expect(await file2.readAsBytes(), equals(data));
|
||||
|
||||
// Cleanup
|
||||
await file1.delete();
|
||||
await file2.delete();
|
||||
|
||||
expect(await file1.exists(), isFalse);
|
||||
expect(await file2.exists(), isFalse);
|
||||
});
|
||||
|
||||
test('Resource cleanup verification (Blob leak check)', () async {
|
||||
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
||||
final filePath = '/leak_test_$uniqueId.bin';
|
||||
final data = Uint8List.fromList(List.generate(100, (i) => i));
|
||||
|
||||
final file = fs.file(filePath);
|
||||
await file.writeAsBytes(data);
|
||||
|
||||
final inode = await fs.resolvepath(filePath);
|
||||
final blobId = inode.blobId;
|
||||
expect(blobId, isNotNull);
|
||||
|
||||
// Verify blob exists in store
|
||||
if (inode.storageType == 1) {
|
||||
expect(await fs.idbStore.getBlob(blobId!), isNotNull);
|
||||
} else {
|
||||
expect(await fs.opfs.getBlob(blobId!), isNotNull);
|
||||
}
|
||||
|
||||
await file.delete();
|
||||
|
||||
// Verify blob is gone
|
||||
if (inode.storageType == 1) {
|
||||
expect(() => fs.idbStore.getBlob(blobId!), throwsA(anything));
|
||||
} else {
|
||||
expect(() => fs.opfs.getBlob(blobId!), throwsA(anything));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -139,7 +139,9 @@ void main() {
|
||||
final uniqueId = DateTime.now().millisecondsSinceEpoch;
|
||||
final chunkSize = 1024 * 1024;
|
||||
final chunk = Uint8List(chunkSize);
|
||||
for (int i = 0; i < chunkSize; i++) chunk[i] = i % 256;
|
||||
for (int i = 0; i < chunkSize; i++) {
|
||||
chunk[i] = i % 256;
|
||||
}
|
||||
|
||||
final file = fs.file('/video_$uniqueId.mp4');
|
||||
final sink = file.openWrite();
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||
|
||||
dart_library(
|
||||
name = "app_review",
|
||||
srcs = glob(["lib/**/*.dart"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -24,5 +24,5 @@ flutter:
|
||||
macos:
|
||||
pluginClass: AppReviewPlugin
|
||||
dev_dependencies:
|
||||
pigeon: ^26.1.5
|
||||
pigeon: ^26.1.7
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||
|
||||
dart_library(
|
||||
name = "breakpoint",
|
||||
srcs = glob(["lib/**/*.dart"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.3.2
|
||||
|
||||
* Fix by @josealba to use .sizeOf(context): https://github.com/rodydavis/packages.dart/pull/14
|
||||
|
||||
## 1.3.0
|
||||
|
||||
* Update Dart SDK constraint to >=3.0.0.
|
||||
|
||||
@@ -51,16 +51,14 @@ class Breakpoint {
|
||||
///
|
||||
/// Use [Breakpoint.fromConstraints] when the widget does not take up the full screen
|
||||
factory Breakpoint.fromMediaQuery(BuildContext context) {
|
||||
final _media = MediaQuery.of(context);
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
|
||||
double _width = 359;
|
||||
double width = size.width;
|
||||
|
||||
Orientation orientation = Orientation.portrait;
|
||||
final orientation =
|
||||
size.width > size.height ? Orientation.landscape : Orientation.portrait;
|
||||
|
||||
_width = _media.size.width;
|
||||
orientation = _media.orientation;
|
||||
|
||||
return _calcBreakpoint(orientation, _width);
|
||||
return _calcBreakpoint(orientation, width);
|
||||
}
|
||||
|
||||
static Breakpoint _calcBreakpoint(Orientation orientation, double _width) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
name: breakpoint
|
||||
description: A Flutter plugin to calculate the material design breakpoints.
|
||||
version: 1.3.0
|
||||
version: 1.3.2
|
||||
|
||||
maintainer: Rody Davis (@rodydavis)
|
||||
homepage: https://github.com/rodydavis/plugins
|
||||
repository: https://github.com/fluttercommunity/breakpoint
|
||||
repository: https://github.com/rodydavis/packages.dart/tree/main/packages/breakpoint
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
@@ -19,4 +19,3 @@ dev_dependencies:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||
|
||||
dart_library(
|
||||
name = "flutter_sms",
|
||||
srcs = glob(["lib/**/*.dart"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -1,3 +1,7 @@
|
||||
# 3.0.1
|
||||
|
||||
- Fix iOS build error: https://github.com/rodydavis/packages.dart/pull/11
|
||||
|
||||
# 3.0.0
|
||||
|
||||
* **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) {
|
||||
#if targetEnvironment(simulator)
|
||||
completion(.failure(FlutterError(
|
||||
completion(.failure(PigeonError(
|
||||
code: "message_not_sent",
|
||||
message: "Cannot send message on this 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
|
||||
UIApplication.shared.keyWindow?.rootViewController?.present(controller, animated: true, completion: nil)
|
||||
} else {
|
||||
completion(.failure(FlutterError(
|
||||
completion(.failure(PigeonError(
|
||||
code: "device_not_capable",
|
||||
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."
|
||||
@@ -36,14 +36,14 @@ public class SwiftFlutterSmsPlugin: NSObject, FlutterPlugin, SmsHostApi, UINavig
|
||||
#endif
|
||||
}
|
||||
|
||||
public func canSendSms() -> Bool {
|
||||
public func canSendSms(completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
#if targetEnvironment(simulator)
|
||||
return false
|
||||
completion(.success(false))
|
||||
#else
|
||||
if (MFMessageComposeViewController.canSendText()) {
|
||||
return true
|
||||
completion(.success(true))
|
||||
} else {
|
||||
return false
|
||||
completion(.success(false))
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: flutter_sms
|
||||
description: A Flutter plugin to send SMS and MMS on iOS and Android.
|
||||
version: 3.0.0
|
||||
version: 3.0.1
|
||||
homepage: https://github.com/rodydavis/packages.dart
|
||||
repository: https://github.com/rodydavis/packages.dart
|
||||
maintainer: Rody Davis (@rodydavis)
|
||||
@@ -21,7 +21,7 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
pigeon: ^26.1.5
|
||||
pigeon: ^26.1.7
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||
|
||||
dart_library(
|
||||
name = "flutter_vibrate",
|
||||
srcs = glob(["lib/**/*.dart"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -3,7 +3,13 @@ import UIKit
|
||||
import AudioToolbox
|
||||
|
||||
public class SwiftVibratePlugin: NSObject, FlutterPlugin, VibrateApi {
|
||||
private let isDevice = TARGET_OS_SIMULATOR == 0
|
||||
private var isDevice: Bool {
|
||||
#if targetEnvironment(simulator)
|
||||
return false
|
||||
#else
|
||||
return true
|
||||
#endif
|
||||
}
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let messenger : FlutterBinaryMessenger = registrar.messenger()
|
||||
|
||||
@@ -15,7 +15,7 @@ dependencies:
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^6.0.0
|
||||
pigeon: ^26.1.5
|
||||
pigeon: ^26.1.7
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||
|
||||
dart_library(
|
||||
name = "flutter_whatsnew",
|
||||
srcs = glob(["lib/**/*.dart"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -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.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}}
|
||||
{"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}}
|
||||
+1
-1
Submodule packages/open_xml updated: 1c8774a605...f5a2b493c4
+1
-1
Submodule packages/signals updated: 157794c620...57a1ace989
@@ -0,0 +1,7 @@
|
||||
load("@rules_flutter//flutter:defs.bzl", "dart_library")
|
||||
|
||||
dart_library(
|
||||
name = "undo",
|
||||
srcs = glob(["lib/**/*.dart"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -10,5 +10,6 @@ environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^6.1.0
|
||||
test: ^1.24.9
|
||||
lints: ^6.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:undo/undo.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
+5
-181
@@ -65,14 +65,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -105,14 +97,6 @@ packages:
|
||||
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:
|
||||
@@ -167,7 +151,7 @@ packages:
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: transitive
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
@@ -192,14 +176,6 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -216,38 +192,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -276,18 +220,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "6.0.0"
|
||||
markdown:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -320,22 +256,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -380,10 +300,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pigeon
|
||||
sha256: fcfee18cf18dae434a149879e6876214c1b57c8d2d5d0cc55a5ba0c85278b886
|
||||
sha256: b12f739047654cd65338dc7e9c6c03cbd4a1de179364a45c9e8bbeccf23729c6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "26.1.5"
|
||||
version: "26.1.7"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -400,14 +320,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -472,59 +384,11 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -565,14 +429,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.26.3"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -581,14 +437,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -693,30 +541,6 @@ packages:
|
||||
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"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user