Disabled obsolete CI
This commit is contained in:
87
docs/ARCHITECTURE.md
Normal file
87
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Furumi-ng: Distributed Virtual File System
|
||||
|
||||
## Project Overview
|
||||
This document describes the architectural vision and technical requirements for a modern, high-performance distributed virtual file system built in Rust. The system allows a remote client to connect to a server with authorization, list directories recursively, and mount the remote directory as a local virtual file system. It is designed as a faster, more reliable, and modern alternative to WebDAV.
|
||||
|
||||
## Core Requirements
|
||||
1. **Cross-Platform Support:** Initial support for Linux and macOS. The backend server and client mounting logic must be logically separated so that new OS support (e.g., Windows) can be added solely by writing a new mount layer without altering the core backend or client networking code.
|
||||
2. **Read-Only First:** The initial implementation will support read-only operations, with an architecture designed to easily accommodate write operations in subsequent phases.
|
||||
3. **Memory Safety & Reliability:** The entire stack (server, shared client core, and mount layers) will be implemented in **Rust** to leverage its strict compiler guarantees, memory safety, and high-performance asynchronous ecosystem.
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
The architecture is divided into three main components: Server, Shared Client Core, and OS-Specific Mount Layers.
|
||||
|
||||
### 1. Transport & Protocol (gRPC)
|
||||
- **Protocol:** gRPC over HTTP/2.
|
||||
- **Why gRPC:** Provides strong typing via Protobuf, multiplexing, and robust streaming capabilities which are essential for transferring large file chunks efficiently.
|
||||
- **Security:** Requires TLS (e.g., mTLS or JWT via metadata headers) to secure data in transit.
|
||||
|
||||
### 2. Server (Linux Backend)
|
||||
The server role is to expose local directories to authorized clients safely and asynchronously.
|
||||
- **Runtime:** `tokio` for non-blocking I/O.
|
||||
- **Security Validation:** Strict path sanitization (protection against Path Traversal). The server restricts clients strictly to their allowed document root.
|
||||
- **VFS Abstraction:** Backend logic will be abstracted behind a Rust trait. This allows future swapping of the storage backend (e.g., Local Disk -> AWS S3, or In-Memory for testing) without changing the gRPC transport layer.
|
||||
|
||||
### 3. Client Architecture
|
||||
To maximize code reuse and maintainability, the client is split into two layers:
|
||||
|
||||
#### A. Shared Client Core (Cross-Platform)
|
||||
A Rust library containing all OS-agnostic logic:
|
||||
- **Network Client:** Handles gRPC connections, request retries, backoff strategies, and error handling.
|
||||
- **VFS Cache:** An in-memory cache for metadata (TTL-based) to dramatically reduce network latency for high-frequency `stat` / `getattr` calls generated by file managers or terminals.
|
||||
- **VFS Translator:** Maps VFS operations into remote gRPC RPC calls.
|
||||
|
||||
#### B. OS-Specific Mount Layer
|
||||
Thin executable wrappers that consume the Shared Client Core and handle OS integration:
|
||||
- **Linux:** Uses the `fuser` crate (binds to `libfuse`) to mount and handle events from `/dev/fuse`.
|
||||
- **macOS:** Acts as a lightweight local NFSv3/v4 server (`nfssrv` or similar crate). The system natively mounts `localhost:/` via the built-in NFS client, avoiding any need for third-party kernel extensions (like `macFUSE`) or complex FileProvider bindings.
|
||||
- **Windows (Future):** Will wrap libraries like `WinFSP` or `dokany` to integrate with the Windows internal VFS.
|
||||
|
||||
## API Design (Read-Only Foundation)
|
||||
The initial Protobuf specification will involve core remote procedure calls (RPCs) to support read-only mode:
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
package virtualfs;
|
||||
|
||||
message PathRequest {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message AttrResponse {
|
||||
uint64 size = 1;
|
||||
uint32 mode = 2; // Permissions and file type
|
||||
uint64 mtime = 3; // Modification time
|
||||
// ... other standard stat attributes
|
||||
}
|
||||
|
||||
message DirEntry {
|
||||
string name = 1;
|
||||
uint32 type = 2; // File or Directory
|
||||
}
|
||||
|
||||
message ReadRequest {
|
||||
string path = 1;
|
||||
uint64 offset = 2;
|
||||
uint32 size = 3;
|
||||
}
|
||||
|
||||
message FileChunk {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
service RemoteFileSystem {
|
||||
// Get file or directory attributes (size, permissions, timestamps). Maps to stat/getattr.
|
||||
rpc GetAttr (PathRequest) returns (AttrResponse);
|
||||
|
||||
// List directory contents. Uses Server Streaming to handle massively large directories efficiently.
|
||||
rpc ReadDir (PathRequest) returns (stream DirEntry);
|
||||
|
||||
// Read chunks of a file. Uses Server Streaming for efficient chunk delivery based on offset/size.
|
||||
rpc ReadFile (ReadRequest) returns (stream FileChunk);
|
||||
}
|
||||
```
|
||||
|
||||
## Future Expansion: Write Operations
|
||||
The design ensures seamless expansion to a read-write file system. Future RPCs such as `CreateFile`, `MkDir`, `Remove`, `Rename`, and `WriteChunk` (utilizing Client Streaming or Bi-directional Streaming in gRPC) can be added without restructuring the foundational architecture.
|
||||
216
docs/CHIPTUNE.md
Normal file
216
docs/CHIPTUNE.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# Chiptune Support Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Add playback support for tracker/chiptune module formats (MOD, XM, S3M, IT, MPTM) to the
|
||||
Furumi web player. The implementation consists of two parts:
|
||||
|
||||
1. **Server-side** — lightweight metadata parser in pure Rust (zero external dependencies)
|
||||
2. **Client-side** — playback via libopenmpt WebAssembly using AudioWorklet API
|
||||
|
||||
## Supported Formats
|
||||
|
||||
| Format | Extension | Origin |
|
||||
|--------|-----------|--------|
|
||||
| MOD | `.mod` | Amiga ProTracker |
|
||||
| XM | `.xm` | FastTracker II |
|
||||
| S3M | `.s3m` | Scream Tracker 3 |
|
||||
| IT | `.it` | Impulse Tracker |
|
||||
| MPTM | `.mptm` | OpenMPT |
|
||||
|
||||
## Part 1: Server-Side Metadata Parser
|
||||
|
||||
### Rationale
|
||||
|
||||
libopenmpt must NOT be a server dependency. All tracker formats store metadata at fixed byte
|
||||
offsets in their headers, making manual parsing trivial. Reading the first ~400 bytes of a file
|
||||
is sufficient to extract all available metadata.
|
||||
|
||||
### Extracted Fields
|
||||
|
||||
- **Title** — song name embedded in the module header
|
||||
- **Channels** — number of active audio channels
|
||||
- **Patterns** — number of unique patterns in the module
|
||||
- **Message** — song message/comment (IT/MPTM only)
|
||||
|
||||
Note: none of these formats have a dedicated "artist" field. Author information, when present,
|
||||
is typically found in the IT/MPTM song message.
|
||||
|
||||
### Binary Format Reference
|
||||
|
||||
#### MOD
|
||||
|
||||
| Offset | Size | Field |
|
||||
|--------|------|-------|
|
||||
| 0 | 20 | Song title (space/null padded) |
|
||||
| 952 | 128 | Pattern order table |
|
||||
| 1080 | 4 | Signature (determines channel count) |
|
||||
|
||||
Channel count is derived from the 4-byte signature at offset 1080:
|
||||
|
||||
- `M.K.`, `M!K!`, `FLT4`, `4CHN` → 4 channels
|
||||
- `6CHN` → 6, `8CHN` / `OCTA` → 8
|
||||
- `xCHN` → x channels, `xxCH` → xx channels
|
||||
|
||||
Pattern count = max value in the order table (128 bytes at offset 952) + 1.
|
||||
|
||||
#### XM
|
||||
|
||||
All multi-byte values are little-endian.
|
||||
|
||||
| Offset | Size | Field |
|
||||
|--------|------|-------|
|
||||
| 0 | 17 | Magic: `"Extended Module: "` |
|
||||
| 17 | 20 | Module name |
|
||||
| 58 | 2 | Version number |
|
||||
| 68 | 2 | Number of channels |
|
||||
| 70 | 2 | Number of patterns |
|
||||
|
||||
#### S3M
|
||||
|
||||
| Offset | Size | Field |
|
||||
|--------|------|-------|
|
||||
| 0x00 | 28 | Song title (null-terminated) |
|
||||
| 0x1C | 1 | Signature byte (`0x1A`) |
|
||||
| 0x24 | 2 | Pattern count (LE u16) |
|
||||
| 0x2C | 4 | Magic: `"SCRM"` |
|
||||
| 0x40 | 32 | Channel settings |
|
||||
|
||||
Channel count = number of entries in channel settings (32 bytes) that are not `0xFF`.
|
||||
|
||||
#### IT
|
||||
|
||||
| Offset | Size | Field |
|
||||
|--------|------|-------|
|
||||
| 0x00 | 4 | Magic: `"IMPM"` |
|
||||
| 0x04 | 26 | Song title (null-terminated) |
|
||||
| 0x26 | 2 | Pattern count (LE u16) |
|
||||
| 0x2E | 2 | Special flags (bit 0 = message attached) |
|
||||
| 0x36 | 2 | Message length |
|
||||
| 0x38 | 4 | Message file offset |
|
||||
| 0x40 | 64 | Channel panning table |
|
||||
|
||||
Channel count = number of entries in channel panning (64 bytes) with value < 128.
|
||||
|
||||
Song message: if `special & 1`, read `message_length` bytes from `message_offset`. Uses `\r`
|
||||
(0x0D) as line separator.
|
||||
|
||||
#### MPTM
|
||||
|
||||
Parsed identically to IT. Detection:
|
||||
|
||||
- Legacy: magic `tpm.` instead of `IMPM`
|
||||
- Modern: magic `IMPM` with tracker version (offset 0x28) in range `0x0889..=0x0FFF`
|
||||
|
||||
### Integration Points
|
||||
|
||||
- **`browse.rs`** — add tracker extensions to the audio file whitelist
|
||||
- **`meta.rs`** — add a chiptune metadata branch that runs before Symphonia (which does not
|
||||
support tracker formats); return title, channel count, pattern count, and message
|
||||
- **`stream.rs`** — serve tracker files as-is (no server-side transcoding); these files are
|
||||
typically under 1 MB
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
- Zero external crate dependencies — only `std::io::Read` + `std::io::Seek`
|
||||
- Read at most the first 1084 bytes for header parsing (MOD needs offset 1080 + 4 byte sig)
|
||||
- For IT/MPTM messages, a second seek to `message_offset` is needed
|
||||
- All strings should be trimmed of null bytes and trailing whitespace
|
||||
- Expected code size: ~200–300 lines of Rust
|
||||
|
||||
## Part 2: Client-Side Playback via libopenmpt WASM
|
||||
|
||||
### Rationale
|
||||
|
||||
Browsers cannot decode tracker formats natively. libopenmpt compiled to WebAssembly decodes
|
||||
modules into PCM samples which are then rendered through the Web Audio API. Client-side
|
||||
decoding keeps the server dependency-free and enables interactive features (pattern display,
|
||||
channel visualization) in the future.
|
||||
|
||||
### libopenmpt WASM Source
|
||||
|
||||
Use the **chiptune3** library (npm: `chiptune3`, by DrSnuggles) which bundles libopenmpt as a
|
||||
self-contained AudioWorklet-compatible ES6 module.
|
||||
|
||||
Package contents:
|
||||
|
||||
| File | Size | Purpose |
|
||||
|------|------|---------|
|
||||
| `chiptune3.js` | ~4 KB | Main API (load, play, pause, seek) |
|
||||
| `chiptune3.worklet.js` | ~12 KB | AudioWorklet processor glue |
|
||||
| `libopenmpt.worklet.js` | ~1.7 MB | libopenmpt WASM + JS (single-file bundle) |
|
||||
|
||||
Available via jsDelivr CDN or can be vendored into the project.
|
||||
|
||||
If a newer libopenmpt version is needed, the official project provides source tarballs with an
|
||||
Emscripten build target:
|
||||
|
||||
```
|
||||
make CONFIG=emscripten EMSCRIPTEN_TARGET=audioworkletprocessor
|
||||
```
|
||||
|
||||
This produces a single ES6 module with WASM embedded inline (`SINGLE_FILE=1`), which is
|
||||
required because AudioWorklet contexts cannot fetch separate `.wasm` files.
|
||||
|
||||
### Playback Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ player.html │
|
||||
│ │
|
||||
│ Format detection (by file extension) │
|
||||
│ ┌─────────────────────┐ ┌────────────────────────────┐ │
|
||||
│ │ Standard audio │ │ Tracker module │ │
|
||||
│ │ (mp3/flac/ogg/...) │ │ (mod/xm/s3m/it/mptm) │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ <audio> element │ │ fetch() → ArrayBuffer │ │
|
||||
│ │ src=/api/stream/path │ │ ↓ │ │
|
||||
│ │ │ │ libopenmpt WASM decode │ │
|
||||
│ │ │ │ ↓ │ │
|
||||
│ │ │ │ AudioWorkletProcessor │ │
|
||||
│ │ │ │ ↓ │ │
|
||||
│ │ ↓ │ │ AudioContext.destination │ │
|
||||
│ └────────┼─────────────┘ └────────────┼───────────────┘ │
|
||||
│ └──────────┬──────────────────┘ │
|
||||
│ ↓ │
|
||||
│ Player controls │
|
||||
│ (play/pause/seek/volume) │
|
||||
│ MediaSession API │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Integration Points
|
||||
|
||||
- **`player.html`** — detect tracker format by extension; use chiptune3 API instead of
|
||||
`<audio>` element for tracker files; unify transport controls (play/pause/seek/volume)
|
||||
across both playback engines
|
||||
- **WASM assets** — serve `chiptune3.js`, `chiptune3.worklet.js`, and
|
||||
`libopenmpt.worklet.js` via a static file endpoint or embed them inline
|
||||
- **`mod.rs`** (routes) — add endpoint for serving WASM assets if not embedded
|
||||
|
||||
### Player Integration Details
|
||||
|
||||
The player must abstract over two playback backends behind a common interface:
|
||||
|
||||
```
|
||||
play(path) — start playback (auto-detect engine by extension)
|
||||
pause() — pause current playback
|
||||
resume() — resume current playback
|
||||
seek(seconds) — seek to position
|
||||
setVolume(v) — set volume (0.0–1.0)
|
||||
getDuration() — total duration in seconds
|
||||
getPosition() — current position in seconds
|
||||
isPlaying() — playback state
|
||||
onEnded(cb) — callback when track finishes
|
||||
```
|
||||
|
||||
For tracker modules, `getDuration()` and `getPosition()` are provided by libopenmpt's
|
||||
`get_duration_seconds()` and `get_position_seconds()` APIs.
|
||||
|
||||
### Considerations
|
||||
|
||||
- Tracker files are small (typically < 1 MB) — fetch the entire file before playback; no
|
||||
streaming/range-request needed
|
||||
- AudioWorklet requires a secure context (HTTPS or localhost)
|
||||
- The WASM bundle is ~1.7 MB — load it lazily on first tracker file playback
|
||||
- MediaSession API metadata should display module title from `/api/meta` response
|
||||
214
docs/PLAYER-API.md
Normal file
214
docs/PLAYER-API.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Furumi Web Player API
|
||||
|
||||
Base URL: `http://<host>:<port>/api`
|
||||
|
||||
All endpoints require authentication when `--token` is set (via cookie `furumi_token=<token>` or query param `?token=<token>`).
|
||||
|
||||
All entity references use **slugs** — 12-character hex identifiers (not sequential IDs).
|
||||
|
||||
## Artists
|
||||
|
||||
### `GET /api/artists`
|
||||
|
||||
List all artists that have at least one track.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"slug": "a1b2c3d4e5f6",
|
||||
"name": "Pink Floyd",
|
||||
"album_count": 5,
|
||||
"track_count": 42
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Sorted alphabetically by name.
|
||||
|
||||
### `GET /api/artists/:slug`
|
||||
|
||||
Get artist details.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"slug": "a1b2c3d4e5f6",
|
||||
"name": "Pink Floyd"
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:** `404` if not found.
|
||||
|
||||
### `GET /api/artists/:slug/albums`
|
||||
|
||||
List all albums by an artist.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"slug": "b2c3d4e5f6a7",
|
||||
"name": "Wish You Were Here",
|
||||
"year": 1975,
|
||||
"track_count": 5,
|
||||
"has_cover": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Sorted by year (nulls last), then name.
|
||||
|
||||
### `GET /api/artists/:slug/tracks`
|
||||
|
||||
List all tracks by an artist across all albums.
|
||||
|
||||
**Response:** same as album tracks (see below).
|
||||
|
||||
Sorted by album year, album name, track number, title.
|
||||
|
||||
## Albums
|
||||
|
||||
### `GET /api/albums/:slug`
|
||||
|
||||
List all tracks in an album.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"slug": "c3d4e5f6a7b8",
|
||||
"title": "Have a Cigar",
|
||||
"track_number": 3,
|
||||
"duration_secs": 312.5,
|
||||
"artist_name": "Pink Floyd",
|
||||
"album_name": "Wish You Were Here",
|
||||
"album_slug": "b2c3d4e5f6a7",
|
||||
"genre": "Progressive Rock"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Sorted by track number (nulls last), then title. Fields `album_name`, `album_slug` may be `null` for tracks without an album.
|
||||
|
||||
### `GET /api/albums/:slug/cover`
|
||||
|
||||
Serve the album cover image from the `album_images` table.
|
||||
|
||||
**Response:** Binary image data with appropriate `Content-Type` (`image/jpeg`, `image/png`, etc.) and `Cache-Control: public, max-age=86400`.
|
||||
|
||||
**Errors:** `404` if no cover exists.
|
||||
|
||||
## Tracks
|
||||
|
||||
### `GET /api/tracks/:slug`
|
||||
|
||||
Get full track details.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"slug": "c3d4e5f6a7b8",
|
||||
"title": "Have a Cigar",
|
||||
"track_number": 3,
|
||||
"duration_secs": 312.5,
|
||||
"genre": "Progressive Rock",
|
||||
"storage_path": "/music/storage/Pink Floyd/Wish You Were Here/03 - Have a Cigar.flac",
|
||||
"artist_name": "Pink Floyd",
|
||||
"artist_slug": "a1b2c3d4e5f6",
|
||||
"album_name": "Wish You Were Here",
|
||||
"album_slug": "b2c3d4e5f6a7",
|
||||
"album_year": 1975
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:** `404` if not found.
|
||||
|
||||
### `GET /api/tracks/:slug/cover`
|
||||
|
||||
Serve cover art for a specific track. Resolution order:
|
||||
|
||||
1. Album cover from `album_images` table (if the track belongs to an album with a cover)
|
||||
2. Embedded cover art extracted from the audio file metadata (ID3/Vorbis/etc. via Symphonia)
|
||||
3. `404` if no cover art is available
|
||||
|
||||
**Response:** Binary image data with `Content-Type` and `Cache-Control: public, max-age=86400`.
|
||||
|
||||
**Errors:** `404` if no cover art found.
|
||||
|
||||
## Streaming
|
||||
|
||||
### `GET /api/stream/:slug`
|
||||
|
||||
Stream the audio file for a track.
|
||||
|
||||
Supports HTTP **Range requests** for seeking:
|
||||
- Full response: `200 OK` with `Content-Length` and `Accept-Ranges: bytes`
|
||||
- Partial response: `206 Partial Content` with `Content-Range`
|
||||
- Invalid range: `416 Range Not Satisfiable`
|
||||
|
||||
`Content-Type` is determined by the file extension (e.g. `audio/flac`, `audio/mpeg`).
|
||||
|
||||
**Errors:** `404` if track or file not found.
|
||||
|
||||
## Search
|
||||
|
||||
### `GET /api/search?q=<query>&limit=<n>`
|
||||
|
||||
Search across artists, albums, and tracks by name (case-insensitive substring match).
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `q` | yes | — | Search query |
|
||||
| `limit` | no | 20 | Max results |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"result_type": "artist",
|
||||
"slug": "a1b2c3d4e5f6",
|
||||
"name": "Pink Floyd",
|
||||
"detail": null
|
||||
},
|
||||
{
|
||||
"result_type": "album",
|
||||
"slug": "b2c3d4e5f6a7",
|
||||
"name": "Wish You Were Here",
|
||||
"detail": "Pink Floyd"
|
||||
},
|
||||
{
|
||||
"result_type": "track",
|
||||
"slug": "c3d4e5f6a7b8",
|
||||
"name": "Have a Cigar",
|
||||
"detail": "Pink Floyd"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`detail` contains the artist name for albums and tracks, `null` for artists.
|
||||
|
||||
Sorted by result type (artist → album → track), then by name.
|
||||
|
||||
## Authentication
|
||||
|
||||
When `--token` / `FURUMI_PLAYER_TOKEN` is set:
|
||||
|
||||
- **Cookie:** `furumi_token=<token>` — set after login
|
||||
- **Query parameter:** `?token=<token>` — redirects to player and sets cookie
|
||||
|
||||
When token is empty, authentication is disabled and all endpoints are public.
|
||||
|
||||
Unauthenticated requests receive `401 Unauthorized` with a login form.
|
||||
|
||||
## Error format
|
||||
|
||||
All errors return JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "description of the error"
|
||||
}
|
||||
```
|
||||
|
||||
With appropriate HTTP status code (`400`, `404`, `500`, etc.).
|
||||
56
docs/windows-implementation-plan.md
Normal file
56
docs/windows-implementation-plan.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Implementation Plan for `furumi-mount-windows` Client
|
||||
|
||||
## Architectural Decision
|
||||
|
||||
- **VFS Driver:** `WinFSP` (Windows File System Proxy).
|
||||
- **Justification:** Excellent performance, perfect compatibility with the FUSE model, widely used in similar projects (e.g., rclone, sshfs-win).
|
||||
- **Installation:** A unified installer (bundle) will be created (for example, using Inno Setup or WiX Toolkit), which will:
|
||||
- Check if WinFSP is already installed.
|
||||
- Automatically install the official `winfsp.msi` silently (using `/qn` flags) if the driver is missing.
|
||||
- Install the `furumi-mount-windows.exe` client itself.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Application Scaffold
|
||||
- Create a new binary crate `furumi-mount-windows` within the workspace.
|
||||
- Add dependencies: `winfsp` (or `wfd`), `tokio`, `clap`, `tracing`, and an internal dependency on `furumi-client-core`.
|
||||
|
||||
### 2. Entry Point (CLI)
|
||||
- In `main.rs`, configure parsing for command-line arguments and environment variables (`--server`, `--token`, `--mount`), similar to `furumi-mount-macos`.
|
||||
- Initialize the gRPC connection to the server via `furumi-client-core`.
|
||||
- Configure directory mounting:
|
||||
- As a network drive (e.g., `Z:`).
|
||||
- Or as a transparent folder within an existing NTFS filesystem (depending on driver support/flags).
|
||||
|
||||
### 3. VFS Implementation
|
||||
- Create an `fs.rs` module.
|
||||
- Implement the trait or callback structure required by WinFSP (e.g., the `WinFspFileSystem` structure).
|
||||
- Action mapping:
|
||||
- `GetFileInfo` / `GetSecurityByName` → gRPC `GetAttr` call.
|
||||
- `ReadDirectory` → Streaming gRPC `ReadDir` call.
|
||||
- `ReadFile` → `ReadFile` gRPC call (with support for stream chunking).
|
||||
- **Crucial Part:** Translating Unix file attributes (from gRPC) into Windows File Attributes to ensure the system permits high-performance continuous stream reading (especially for media).
|
||||
|
||||
### 4. Installer Creation
|
||||
- Write a configuration script for a Windows installer builder (e.g., `windows/setup.iss` for Inno Setup).
|
||||
- Neatly bundle both `winfsp-x.y.z.msi` and `furumi-mount-windows.exe` together.
|
||||
- Add Custom Actions / Logic to:
|
||||
- Check the Windows Registry for an existing WinFSP installation.
|
||||
- Trigger the `winfsp.msi` installation conditionally.
|
||||
|
||||
### 5. CI/CD Integration
|
||||
- Update the GitHub Actions workflow (`docker-publish.yml` or create a dedicated release workflow).
|
||||
- Add the target toolchain: `x86_64-pc-windows-msvc`.
|
||||
- Add a step to compile: `cargo build --release --bin furumi-mount-windows`.
|
||||
- Add a step to build the installer (e.g., `iscc setup.iss` or via `cargo-wix`).
|
||||
- Output the final `setup.exe` as a GitHub Release artifact alongside other binaries.
|
||||
|
||||
### 6. Testing Strategy
|
||||
- Write unit tests in Rust covering attribute translation and path mapping (mapping slashes `/` to backslashes `\`).
|
||||
- Manual System Testing:
|
||||
- Start `furumi-server` locally.
|
||||
- Run the installer on a clean Windows machine (VM without pre-installed WinFSP).
|
||||
- Verify that the drive mounts correctly and seamlessly.
|
||||
- Launch media playback (e.g., via VLC/mpv) to ensure streaming stability over the VFS connection.
|
||||
Reference in New Issue
Block a user