Files
1jehuang--jcode/crates/jcode-build-support/src/storage_helpers.rs
T
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

218 lines
6.4 KiB
Rust

use super::{MigrationContext, binary_name};
use anyhow::Result;
use jcode_storage as storage;
use std::path::PathBuf;
/// Get path to builds directory
pub fn builds_dir() -> Result<PathBuf> {
let base = storage::jcode_dir()?;
let dir = base.join("builds");
storage::ensure_dir(&dir)?;
Ok(dir)
}
/// Get path to build manifest
pub fn manifest_path() -> Result<PathBuf> {
Ok(builds_dir()?.join("manifest.json"))
}
/// Get path to a specific version's binary
pub fn version_binary_path(hash: &str) -> Result<PathBuf> {
Ok(builds_dir()?
.join("versions")
.join(hash)
.join(binary_name()))
}
/// Get path to stable symlink
pub fn stable_binary_path() -> Result<PathBuf> {
Ok(builds_dir()?.join("stable").join(binary_name()))
}
/// Get path to current symlink (active local build channel)
pub fn current_binary_path() -> Result<PathBuf> {
Ok(builds_dir()?.join("current").join(binary_name()))
}
/// Get path to the shared server symlink (approved daemon channel).
pub fn shared_server_binary_path() -> Result<PathBuf> {
Ok(builds_dir()?.join("shared-server").join(binary_name()))
}
/// Get path to canary binary
pub fn canary_binary_path() -> Result<PathBuf> {
Ok(builds_dir()?.join("canary").join(binary_name()))
}
/// Get path to migration context file
pub fn migration_context_path(session_id: &str) -> Result<PathBuf> {
Ok(builds_dir()?
.join("migrations")
.join(format!("{}.json", session_id)))
}
/// Get path to stable version file (watched by other sessions)
pub fn stable_version_file() -> Result<PathBuf> {
Ok(builds_dir()?.join("stable-version"))
}
/// Get path to current version file (active local build marker).
pub fn current_version_file() -> Result<PathBuf> {
Ok(builds_dir()?.join("current-version"))
}
/// Get path to the shared server version file (approved daemon marker).
pub fn shared_server_version_file() -> Result<PathBuf> {
Ok(builds_dir()?.join("shared-server-version"))
}
/// Save migration context before switching to canary
pub fn save_migration_context(ctx: &MigrationContext) -> Result<()> {
let path = migration_context_path(&ctx.session_id)?;
storage::write_json(&path, ctx)
}
/// Load migration context
pub fn load_migration_context(session_id: &str) -> Result<Option<MigrationContext>> {
let path = migration_context_path(session_id)?;
if path.exists() {
Ok(Some(storage::read_json(&path)?))
} else {
Ok(None)
}
}
/// Clear migration context after successful migration
pub fn clear_migration_context(session_id: &str) -> Result<()> {
let path = migration_context_path(session_id)?;
if path.exists() {
std::fs::remove_file(path)?;
}
Ok(())
}
/// Read the current stable version
pub fn read_stable_version() -> Result<Option<String>> {
let path = stable_version_file()?;
if path.exists() {
let content = std::fs::read_to_string(path)?;
let hash = content.trim();
if hash.is_empty() {
Ok(None)
} else {
Ok(Some(hash.to_string()))
}
} else {
Ok(None)
}
}
/// Read the current active version.
pub fn read_current_version() -> Result<Option<String>> {
let path = current_version_file()?;
if path.exists() {
let content = std::fs::read_to_string(path)?;
let hash = content.trim();
if hash.is_empty() {
Ok(None)
} else {
Ok(Some(hash.to_string()))
}
} else {
Ok(None)
}
}
/// Read the current shared-server version.
pub fn read_shared_server_version() -> Result<Option<String>> {
let path = shared_server_version_file()?;
if path.exists() {
let content = std::fs::read_to_string(path)?;
let hash = content.trim();
if hash.is_empty() {
Ok(None)
} else {
Ok(Some(hash.to_string()))
}
} else {
Ok(None)
}
}
/// Get path to build log file
pub fn build_log_path() -> Result<PathBuf> {
Ok(storage::jcode_dir()?.join("build.log"))
}
/// Get path to build progress file (for TUI to watch)
pub fn build_progress_path() -> Result<PathBuf> {
Ok(storage::jcode_dir()?.join("build-progress"))
}
/// Write current build progress (for TUI to display)
pub fn write_build_progress(status: &str) -> Result<()> {
let path = build_progress_path()?;
std::fs::write(&path, status)?;
invalidate_build_progress_cache();
Ok(())
}
/// Process-local cache for `read_build_progress`. Stores the last-read value
/// alongside the time it was read so per-frame TUI calls can be served without
/// a disk hit.
static BUILD_PROGRESS_CACHE: std::sync::Mutex<Option<(std::time::Instant, Option<String>)>> =
std::sync::Mutex::new(None);
const BUILD_PROGRESS_TTL: std::time::Duration = std::time::Duration::from_millis(100);
fn invalidate_build_progress_cache() {
if let Ok(mut guard) = BUILD_PROGRESS_CACHE.lock() {
*guard = None;
}
}
/// Read current build progress.
///
/// The TUI calls this from its per-frame redraw scheduler (several times per
/// frame, across every connected client), so a naive implementation performs a
/// synchronous disk read on every render tick even when no build is running.
/// Build progress is a purely cosmetic status string, so we cache the result
/// for a short window. The cache is invalidated immediately on
/// `write_build_progress`/`clear_build_progress` so progress still updates
/// promptly when a build is driven from the same process; cross-process updates
/// become visible within the TTL.
pub fn read_build_progress() -> Option<String> {
if let Ok(guard) = BUILD_PROGRESS_CACHE.lock()
&& let Some((at, ref value)) = *guard
&& at.elapsed() < BUILD_PROGRESS_TTL
{
return value.clone();
}
let value = read_build_progress_uncached();
if let Ok(mut guard) = BUILD_PROGRESS_CACHE.lock() {
*guard = Some((std::time::Instant::now(), value.clone()));
}
value
}
fn read_build_progress_uncached() -> Option<String> {
build_progress_path()
.ok()
.and_then(|p| std::fs::read_to_string(p).ok())
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}
/// Clear build progress
pub fn clear_build_progress() -> Result<()> {
let path = build_progress_path()?;
if path.exists() {
std::fs::remove_file(&path)?;
}
invalidate_build_progress_cache();
Ok(())
}