Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 95 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ import {
exists,
getMetadata,
getPlatformFsCapabilities,
openReadFileStream,
openWriteFileStream,
readDir,
readTextFile,
removeDirAll,
Expand All @@ -157,6 +159,8 @@ Portable functions include:

- `readFile`, `readTextFile`
- `writeFile`, `writeTextFile`
- `openReadFileStream`, `openWriteFileStream`
- `closeAllFileStreams`, `countAllFileStreams`
- `readDir`
- `createDir`, `createNewFile`, `createNewDir`
- `copyFile`
Expand Down Expand Up @@ -216,6 +220,83 @@ if (dir) {
}
```

Copy a large file without loading it into WebView memory:

```ts
import {
openReadFileStream,
openWriteFileStream,
showOpenFilePicker,
showSaveFilePicker,
} from '@vnidrop/tauri-plugin-fs'

const [source] = await showOpenFilePicker()
const destination = await showSaveFilePicker('backup.bin', 'application/octet-stream')

if (source && destination) {
const input = await openReadFileStream(source, {
// Larger chunks reduce IPC overhead. Tune this for your data pipeline.
bufferByteLength: 1024 * 1024,
})
const output = await openWriteFileStream(destination, {
create: true,
append: false,
bufferByteLength: 1024 * 1024,
})

await input.pipeTo(output)
}
```

Use `closeAllFileStreams()` as a cleanup escape hatch during app shutdown,
failed stream operations, or tests. Normal streams release their native handles when
they reach EOF, close, abort, or error.

## Rust Backend API

Frontend calls are not required for backend filesystem workflows. Register the
plugin, import `VnidropFsExt`, and use the Rust manager from an `AppHandle`,
`App`, or command state.

```rust
use std::io::{Read, Write};
use tauri_plugin_vnidrop_fs::{VnidropFsExt, VnidropOpenWriteOptions};

fn copy_in_rust<R: tauri::Runtime>(
app: &tauri::AppHandle<R>,
source: &std::path::Path,
destination: &std::path::Path,
) -> tauri_plugin_vnidrop_fs::Result<u64> {
let fs = app.vnidrop_fs();
let mut reader = fs.open_read(source)?;
let mut writer = fs.open_write(
destination,
VnidropOpenWriteOptions::default().create(true),
)?;

let mut buffer = vec![0; 1024 * 1024];
let mut copied = 0;

loop {
let read = reader.read(&mut buffer)?;
if read == 0 {
break;
}
writer.write_all(&buffer[..read])?;
copied += read as u64;
}

writer.flush()?;
Ok(copied)
}
```

The Rust API accepts normal paths, Android `FileUri` values, and iOS
`IosFsUri` values. Desktop and app-container paths use `std::fs`. Android URI
targets use the existing Android file descriptor bridge. iOS `IosFsUri` targets
use native stream resources so security-scoped access stays active until the
Rust reader or writer is closed or dropped.

Show different UI for platform-specific capabilities:

```ts
Expand Down Expand Up @@ -369,14 +450,15 @@ iOS supports the shared root API for:

- file, directory, and save pickers
- read/write bytes and text
- read/write byte streams
- directory listing
- create and unique create
- copy, rename, remove
- metadata and existence checks
- security-scoped bookmark list/resolve/release/persist helpers

Android public storage, thumbnails, streams, and share/view intents are not iOS
features and remain Android-only.
Android public storage, thumbnails, line streams, and share/view intents are not
iOS features and remain Android-only.

## Testing

Expand Down Expand Up @@ -406,6 +488,17 @@ npm install
npm run tauri dev
```

Run the Rust-side example app:

```sh
cd examples/tauri-rust-side-app
npm install
npm run tauri dev
```

This second example keeps filesystem logic in Rust commands with
`app.vnidrop_fs()`. The frontend only handles picker UI and command invocation.

iOS simulator:

```sh
Expand Down
18 changes: 18 additions & 0 deletions examples/tauri-rust-side-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

node_modules
dist
dist-ssr
*.local

.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.sw?
31 changes: 31 additions & 0 deletions examples/tauri-rust-side-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Vnidrop FS Rust-Side Example

This example keeps file transfer logic in Rust.

The frontend only selects files with Vnidrop FS pickers and calls Tauri
commands. The commands use `app.vnidrop_fs()` to read, write, and stream-copy
files from Rust without routing file bytes through frontend IPC.

## Desktop

```sh
npm install
npm run tauri dev
```

## Android

```sh
npm install
npm run tauri android dev
```

## iOS

```sh
npm install
npm run tauri ios dev
```

Use the app to pick a source file, choose a destination with the save picker,
then run the Rust streaming copy command.
12 changes: 12 additions & 0 deletions examples/tauri-rust-side-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vnidrop FS Rust-Side Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions examples/tauri-rust-side-app/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"checkJs": true,
"moduleResolution": "bundler"
},
"exclude": ["node_modules", "src-tauri"]
}
Loading
Loading