835a48fe9d
Build and Deploy / build-and-push (push) Failing after 58s
Adds a new `solitaire_web` crate that compiles the full `solitaire_engine` to `wasm32-unknown-unknown` and renders to a `<canvas id="bevy-canvas">` element in `play.html` — the same ECS code path as desktop and Android. Changes to enable the WASM target: - .cargo/config.toml: add wasm32-unknown-unknown rustflags for getrandom - Workspace Cargo.toml: add solitaire_web member - solitaire_data/Cargo.toml: gate tokio/reqwest/dirs/keyring to non-wasm - solitaire_data/src: add wasm32 branch to data_dir() (returns None); cfg-gate sync_client network types, auth_tokens, matomo_client - solitaire_engine/Cargo.toml: gate tokio/reqwest/kira/arboard/dirs/zip to non-wasm (mio/cpal/arboard don't compile for wasm32-unknown-unknown) - solitaire_engine/src/lib.rs: cfg-gate module declarations and re-exports for analytics, audio, sync, sync_setup, avatar, leaderboard plugins - solitaire_engine/src/core_game_plugin.rs: cfg-gate plugin registrations that require TokioRuntime (audio, sync, analytics, leaderboard, avatar) - solitaire_engine/src/resources.rs: cfg-gate TokioRuntimeResource - solitaire_engine/src/game_plugin.rs: cfg-gate std::fs::remove_file (x10) - solitaire_engine/src/theme/mod.rs: cfg-gate importer module (uses dirs+zip) - solitaire_engine/src/settings_plugin.rs: cfg-gate theme ZIP import UI - solitaire_engine/src/assets/sources.rs: cfg-gate FileAssetReader/user_theme_dir - solitaire_engine/src/auto_complete_plugin.rs: cfg-gate audio system - solitaire_engine/src/daily_challenge_plugin.rs: cfg-gate server fetch - solitaire_engine/src/hud_plugin.rs: cfg-gate AvatarResource import - solitaire_engine/src/profile_plugin.rs: cfg-gate AvatarResource import - solitaire_server/web/play.html: minimal HTML canvas shell - solitaire_web/: new crate (Cargo.toml + src/lib.rs) - build_wasm.sh: add Bevy WASM build step (cargo + wasm-bindgen + wasm-opt) All tests pass; clippy --workspace -- -D warnings clean; native build (solitaire_engine, solitaire_app) unaffected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
4.2 KiB
Rust
104 lines
4.2 KiB
Rust
//! Per-platform resolution of the per-user data directory.
|
|
//!
|
|
//! The rest of `solitaire_data` (settings, stats, achievements,
|
|
//! replays, progress, game state) and the engine's user-themes
|
|
//! discovery all need a base path under which to nest
|
|
//! `ferrous_solitaire/<file>`. On desktop the right answer is
|
|
//! `dirs::data_dir()` (which resolves to platform-appropriate
|
|
//! locations: `~/.local/share` on Linux, `~/Library/Application
|
|
//! Support` on macOS, `%APPDATA%` on Windows). On Android the
|
|
//! `dirs` crate returns `None`, which would silently disable
|
|
//! every persistence path — settings, stats, replays, the lot.
|
|
//!
|
|
//! [`data_dir`] is a thin shim that returns the right base path
|
|
//! per target. Callers continue to append
|
|
//! `ferrous_solitaire/<file>` themselves, so the on-disk layout is
|
|
//! identical across platforms (the per-app Android sandbox makes
|
|
//! the extra `ferrous_solitaire/` segment harmless, and a `tar`
|
|
//! export from one platform deserialises cleanly on another).
|
|
//!
|
|
//! # Why hardcode on Android?
|
|
//!
|
|
//! The "proper" Android answer is JNI: call back into Java to
|
|
//! invoke `Activity.getFilesDir()`. That requires plumbing an
|
|
//! `AndroidApp` context through Bevy's startup hooks and a
|
|
//! per-call JNI bridge — meaningfully more code than the
|
|
//! sandbox-guaranteed `/data/data/<package>/files` path. The
|
|
//! package name `com.ferrousapp.solitaire` is fixed at compile
|
|
//! time in `solitaire_app/Cargo.toml`'s
|
|
//! `[package.metadata.android]` block, so a hardcoded path is
|
|
//! safe until that ever changes (at which point this constant
|
|
//! moves with it).
|
|
|
|
use std::path::PathBuf;
|
|
|
|
/// Hardcoded per-app private files directory on Android.
|
|
///
|
|
/// Matches `[package.metadata.android]` in `solitaire_app/Cargo.toml`.
|
|
/// The Android sandbox guarantees this path exists, is writable,
|
|
/// and is private to the app — no JNI needed. Update both this
|
|
/// constant and the Cargo metadata together if the package id
|
|
/// ever changes.
|
|
#[cfg(target_os = "android")]
|
|
const ANDROID_APP_FILES_DIR: &str = "/data/data/com.ferrousapp.solitaire/files";
|
|
|
|
/// Returns the per-user data directory for the current target,
|
|
/// or `None` if the platform doesn't expose one (rare; usually
|
|
/// indicates a broken `$HOME` or `$XDG_*` configuration on a
|
|
/// minimal Linux container).
|
|
///
|
|
/// Callers append `ferrous_solitaire/<file>` themselves. See the
|
|
/// module-level doc comment for the per-platform behaviour and
|
|
/// why Android uses a hardcoded path.
|
|
pub fn data_dir() -> Option<PathBuf> {
|
|
#[cfg(target_os = "android")]
|
|
{
|
|
Some(PathBuf::from(ANDROID_APP_FILES_DIR))
|
|
}
|
|
#[cfg(target_arch = "wasm32")]
|
|
{
|
|
// No filesystem on the browser; all persistence goes through
|
|
// WasmStorage (localStorage-backed). Return None so every caller
|
|
// degrades gracefully (the same path they take on a
|
|
// misconfigured desktop environment).
|
|
None
|
|
}
|
|
#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
|
|
{
|
|
dirs::data_dir()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// On every supported desktop target the OS reports a usable
|
|
/// data directory. This test only runs on desktop because the
|
|
/// Android branch returns a fixed string regardless of host
|
|
/// state, and asserting on a fixed string is a tautology.
|
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
|
|
#[test]
|
|
fn data_dir_returns_some_on_desktop_targets() {
|
|
let dir = data_dir().expect("desktop targets must report a data dir");
|
|
assert!(
|
|
dir.is_absolute(),
|
|
"data_dir() must return an absolute path on desktop, got {dir:?}",
|
|
);
|
|
}
|
|
|
|
/// On Android the hardcoded path matches the package id pinned
|
|
/// in `solitaire_app/Cargo.toml`'s `[package.metadata.android]`.
|
|
/// If a future change rotates that id, this test fails loudly
|
|
/// so the path constant moves with it.
|
|
#[cfg(target_os = "android")]
|
|
#[test]
|
|
fn data_dir_returns_sandbox_path_on_android() {
|
|
let dir = data_dir().expect("android must report a data dir");
|
|
assert_eq!(
|
|
dir,
|
|
PathBuf::from("/data/data/com.ferrousapp.solitaire/files")
|
|
);
|
|
}
|
|
}
|