codex-mobile-bridge 0.3.10

Remote bridge and service manager for codex-mobile.
Documentation
use super::*;

pub(super) fn sha256_file(path: &Path) -> Result<String> {
    let bytes = fs::read(path).with_context(|| format!("读取文件失败: {}", path.display()))?;
    let mut hasher = Sha256::new();
    hasher.update(&bytes);
    Ok(hasher
        .finalize()
        .iter()
        .map(|byte| format!("{byte:02x}"))
        .collect())
}

pub(super) fn build_activate_record(
    existing_record: Option<&InstallRecord>,
    metadata: &ReleaseMetadata,
    operation: &str,
) -> InstallRecord {
    let now = now_millis();
    let promote_current = existing_record
        .and_then(|record| record.current_artifact_id.as_ref())
        .is_some_and(|artifact_id| artifact_id != &metadata.artifact_id);
    let promoted_record = existing_record.filter(|_| promote_current);

    InstallRecord {
        install_state: "installed".to_string(),
        current_artifact_id: Some(metadata.artifact_id.clone()),
        current_version: Some(metadata.version.clone()),
        current_build_hash: Some(metadata.build_hash.clone()),
        current_sha256: Some(metadata.sha256.clone()),
        current_protocol_version: Some(metadata.protocol_version),
        current_release_path: Some(metadata.release_root.clone()),
        previous_artifact_id: promoted_record
            .and_then(|record| record.current_artifact_id.clone())
            .or_else(|| existing_record.and_then(|record| record.previous_artifact_id.clone())),
        previous_version: promoted_record
            .and_then(|record| record.current_version.clone())
            .or_else(|| existing_record.and_then(|record| record.previous_version.clone())),
        previous_build_hash: promoted_record
            .and_then(|record| record.current_build_hash.clone())
            .or_else(|| existing_record.and_then(|record| record.previous_build_hash.clone())),
        previous_sha256: promoted_record
            .and_then(|record| record.current_sha256.clone())
            .or_else(|| existing_record.and_then(|record| record.previous_sha256.clone())),
        previous_protocol_version: promoted_record
            .and_then(|record| record.current_protocol_version)
            .or_else(|| existing_record.and_then(|record| record.previous_protocol_version)),
        previous_release_path: promoted_record
            .and_then(|record| record.current_release_path.clone())
            .or_else(|| existing_record.and_then(|record| record.previous_release_path.clone())),
        last_operation: Some(operation.to_string()),
        last_operation_status: Some("success".to_string()),
        last_operation_at_ms: now,
        installed_at_ms: existing_record
            .map(|record| record.installed_at_ms)
            .filter(|value| *value > 0)
            .unwrap_or(now),
        updated_at_ms: now,
    }
}

pub(super) fn build_rollback_record(
    existing_record: &InstallRecord,
    metadata: &ReleaseMetadata,
) -> InstallRecord {
    let now = now_millis();
    InstallRecord {
        install_state: "installed".to_string(),
        current_artifact_id: Some(metadata.artifact_id.clone()),
        current_version: Some(metadata.version.clone()),
        current_build_hash: Some(metadata.build_hash.clone()),
        current_sha256: Some(metadata.sha256.clone()),
        current_protocol_version: Some(metadata.protocol_version),
        current_release_path: Some(metadata.release_root.clone()),
        previous_artifact_id: existing_record.current_artifact_id.clone(),
        previous_version: existing_record.current_version.clone(),
        previous_build_hash: existing_record.current_build_hash.clone(),
        previous_sha256: existing_record.current_sha256.clone(),
        previous_protocol_version: existing_record.current_protocol_version,
        previous_release_path: existing_record.current_release_path.clone(),
        last_operation: Some("rollback".to_string()),
        last_operation_status: Some("success".to_string()),
        last_operation_at_ms: now,
        installed_at_ms: existing_record.installed_at_ms,
        updated_at_ms: now,
    }
}

pub(super) fn build_uninstall_record(existing_record: Option<&InstallRecord>) -> InstallRecord {
    let now = now_millis();
    InstallRecord {
        install_state: "uninstalled".to_string(),
        current_artifact_id: None,
        current_version: None,
        current_build_hash: None,
        current_sha256: None,
        current_protocol_version: None,
        current_release_path: None,
        previous_artifact_id: None,
        previous_version: None,
        previous_build_hash: None,
        previous_sha256: None,
        previous_protocol_version: None,
        previous_release_path: None,
        last_operation: Some("uninstall".to_string()),
        last_operation_status: Some("success".to_string()),
        last_operation_at_ms: now,
        installed_at_ms: existing_record
            .map(|record| record.installed_at_ms)
            .filter(|value| *value > 0)
            .unwrap_or(now),
        updated_at_ms: now,
    }
}

fn now_millis() -> i64 {
    use std::time::{SystemTime, UNIX_EPOCH};

    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as i64
}