Files
Edison Jwa aa796d7395 feat: file transfer system (avatar/icon download with cacache) (#40)
* docs(architecture): add file transfer design, research, and implementation plan

* feat(cache): add chanora_cache crate with cacache-backed blob cache

- New chanora_cache crate: content-addressed blob store wrapping cacache
- BlobCache API: async put/get/remove/clear/total_size/evict
- Key validation: av_ prefix (32 hex chars), ic_ prefix (decimal digits)
- Cacache provides crash safety, SSRI integrity, content dedup
- Mtime-based eviction via cacache::list_sync + sort by timestamp
- 7 unit tests all passing
- Added to workspace members

* feat(protocol): add file download support for avatars and icons

- Add Request::DownloadFile variant with oneshot reply
- Add ProtocolClient::download_avatar(client_uid) and download_icon(icon_id)
- Track pending file downloads by FiletransferHandle
- Handle StreamItem::FileDownload: read bytes from TCP stream
- Handle StreamItem::FiletransferFailed: map to ProtocolError
- Add ProtocolError::FileTransfer(String) variant
- Add path helper tests for avatar/icon download paths
- No tsclientlib types leak across the adapter boundary

* feat(core): add blob cache wiring and avatar download orchestration

- Add chanora_cache dependency to Cargo.toml
- Add blob_cache field to ChanoraSession (Arc<Mutex<Option<BlobCache>>>)
- Add init_cache() method: creates BlobCache, runs eviction
- Add get_avatar() method: cache-first, download on miss, store in cache
- Add clear_cache() and cache_size() methods for cache management
- Add CoreError::Cache variant for BlobCacheError conversion
- Add avatar_cache integration test

* feat(bridge): add init_cache, download_avatar, and cache management functions

- Add init_cache(dir) bridge function
- Add download_avatar(avatar_hash, client_uid) bridge function
- Add clear_file_cache() and file_cache_size() bridge functions
- Map CoreError::Cache and ProtocolError::FileTransfer in BridgeError

* feat(flutter): add cache initialization wiring and avatar download shims

- Add wireCache() to app_bootstrap using getApplicationCacheDirectory()
- Call wireCache() after wireStorage() in main bootstrap flow
- Add Dart-side initCache and downloadAvatar wrapper shims in api.dart
- Update Cargo.lock for new chanora_cache dependency

* feat(core): FileTransferService with coalescing, throttling, negative cache

- New file_transfer module with FileTransferService struct
- Semaphore(2) throttles concurrent downloads
- In-flight HashMap coalesces duplicate avatar requests
- 5-min negative cache short-circuits ServerRejected misses
- ChanoraSession delegates get_avatar through the service
- connect/disconnect update shared protocol handle
- clear_cache/cache_size delegate to service
- 2 new unit tests (cached hit, negative cache)

* feat(core,bridge): add get_icon with coalescing and negative cache

- FileTransferService::get_icon() mirrors get_avatar pattern
- ChanoraSession::get_icon() delegates through FileTransferService
- Bridge download_icon() exposed for Flutter
- Dart downloadIcon() shim added
- Uses PREFIX_ICON (ic_<crc32u>) cache key format
- 1 new unit test (cached icon hit)

* fix(core,protocol): simplify store_protocol and add download size cap

- store_protocol: always write to shared Arc<Mutex<Option<ProtocolClient>>>;
  the FileTransferService holds the same Arc so it sees updates automatically
- read_download_bytes: reject downloads exceeding 10 MB to prevent
  malicious servers from causing OOM
2026-06-10 11:45:14 +09:00

101 lines
4.3 KiB
TOML

# Chanora — top-level Cargo workspace.
#
# Layout authority: DEC-022 in
# docs/governance/product-decision-register.md, which adopts the
# README's sketch as canonical:
#
# apps/chanora_flutter/ — Flutter application (separate toolchain)
# core/chanora_core/ — top-level Rust API + orchestration
# crates/chanora_protocol/ — tsclientlib isolation
# crates/chanora_resolver/ — TeamSpeak address resolution
# crates/chanora_state/ — snapshot, deltas, reducers
# crates/chanora_audio/ — capture, DSP, Opus, jitter, mixer
# crates/chanora_cache/ — avatar/icon blob cache (cacache-backed)
# crates/chanora_storage/ — bookmarks, settings, identity refs
# crates/chanora_diagnostics/ — logs, redaction, export
# crates/chanora_prefetch — server-resolution prefetch cache/policy
# crates/chanora_bridge/ — Flutter/Rust typed DTOs + glue
#
# The Flutter app is not part of this Cargo workspace; it is owned by
# Gradle / Xcode / Flutter tooling under apps/chanora_flutter/.
#
# Proof-of-concept spikes are archived separately in the private
# chanoraapp/chanora-poc repository. Promotion of PoC findings into
# product code happens crate-by-crate with explicit audit-trail commits.
[workspace]
resolver = "2"
members = [
"core/chanora_core",
"crates/chanora_protocol",
"crates/chanora_state",
"crates/chanora_audio",
"crates/chanora_storage",
"crates/chanora_cache",
"crates/chanora_diagnostics",
"crates/chanora_prefetch",
"crates/chanora_bridge",
"crates/chanora_resolver",
]
exclude = [
"apps/chanora_flutter",
]
[workspace.package]
version = "0.2.0-beta.1"
edition = "2021"
rust-version = "1.95"
authors = ["The Chanora Project Contributors"]
license = "MIT OR Apache-2.0" # DEC-020
repository = "https://github.com/anomalyco/opencode"
publish = false # not on crates.io
readme = "README.md"
# Shared dependency pinnings. Members refer to these via
# `package.dependencies.<x>.workspace = true` so versions stay aligned.
[workspace.dependencies]
thiserror = "2"
tracing = "0.1"
serde = { version = "1", features = ["derive"] }
# DEC-032 exit step: override the `cmake` crate with the fork carrying
# cmake-rs PR #257 (https://github.com/rust-lang/cmake-rs/pull/257).
# The patch forwards `ANDROID_ABI` and `ANDROID_PLATFORM` environment
# variables (set per-invocation by cargo-ndk >= 4.x and reinforced by
# our SDD-118 item 5 cleanEnv map) to the child `cmake` invocation as
# `-D` variables, so audiopus_sys's libopus CMake build picks up the
# correct per-ABI target on multi-ABI Android builds. Without it the
# child cmake falls through to the NDK toolchain file's default and
# armeabi-v7a / x86_64 fail to compile.
#
# Pinned by exact commit SHA (the `android-build` branch tip on
# pr2502/cmake-rs as of 2026-05-18) so the patch is reproducible.
# Re-evaluate and remove once PR #257 merges and a fresh `cmake`
# release lands on crates.io (current upstream is 0.7.2).
# Patch tsproto-types to left-pad P-256 coordinates that are shorter than
# 32 bytes. BigInt::to_bytes_be() strips leading zero bytes; when a
# coordinate happens to start with 0x00 (~0.8 % probability per coordinate)
# the upstream code rejects it with WrongPublicKeyLength, breaking the
# init-server handshake. The fix zero-pads to the field size instead.
# Fork: https://github.com/EdisonJwa/tsclientlib/tree/fix/p256-short-coordinate-pad
[patch."https://github.com/ReSpeak/tsclientlib.git"]
tsproto-types = { git = "https://github.com/EdisonJwa/tsclientlib.git", branch = "fix/p256-short-coordinate-pad" }
[patch.crates-io]
cmake = { git = "https://github.com/pr2502/cmake-rs", rev = "bdad5edc569d82151922c5c6c4685b1563f12aa1" }
# Apple-only DWARF emission for archive validation lives in the iOS and
# macOS chanora_bridge podspecs (apps/chanora_flutter/{ios,macos}/
# chanora_bridge.podspec) as per-build environment overrides:
#
# CARGO_PROFILE_RELEASE_DEBUG=true
# CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off
# CARGO_PROFILE_RELEASE_STRIP=false
#
# This keeps Android, Linux, and Windows release binaries on the cargo
# default release profile (no DWARF, no extra ~10MB symbol payload).
# Apple builds need the DWARF so dsymutil can emit a usable
# chanora_bridge.framework.dSYM that the archive validator accepts.