Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::Context;
use anyhow::Result;
use std::path::Path;
use std::path::PathBuf;
use std::process::ExitCode;
use std::time::Duration;

mod cli;
Expand All @@ -20,6 +21,7 @@ mod watch;

use cli::BucketCommand;
use cli::Command;
use cli::DEFAULT_BUCKET_PADDING_BYTES;
use cli::parse_cli;
use cli::validate_profile_override;
use cli::validate_provider_override;
Expand All @@ -34,20 +36,32 @@ use output::next_steps_heading;
use output::no_launchd_plist_message;
use output::print_bucket_prepare_summary;
use output::print_install_service_summary;
use output::print_multi_sync_summary;
use output::print_status;
use output::print_sync_summary;
use output::run_status_next_step;
use output::sqlite_only_app_warning;
use output::sync_complete_title;
use output::uninstall_launchd_done;
use rollout::RolloutProgressConfig;
use rollout::RolloutScope;
use rollout::prepare_bucket_padding;
use sync::DEFAULT_BACKFILL_WAIT;
use sync::ReconcileStatus;
use sync::collect_status;
use sync::reconcile_once_with_backup_and_padding;
use sync::reconcile_once_with_backup_progress;
use sync::reconcile_all_stores_with_backup;
use watch::run_watch;

fn main() -> Result<()> {
fn main() -> ExitCode {
match run() {
Ok(code) => code,
Err(err) => {
eprintln!("{err:?}");
ExitCode::FAILURE
}
}
}

fn run() -> Result<ExitCode> {
let locale = detect_locale();
let cli = parse_cli(locale)?;
validate_provider_override(locale, cli.provider.as_deref())?;
Expand All @@ -59,6 +73,7 @@ fn main() -> Result<()> {
let summary =
collect_status(&codex_home, cli.provider.as_deref(), cli.profile.as_deref())?;
print_status(locale, &summary);
Ok(ExitCode::SUCCESS)
}
Command::Sync { sqlite_only } => {
let rollout_scope = if sqlite_only {
Expand All @@ -71,20 +86,27 @@ fn main() -> Result<()> {
} else {
Some(RolloutProgressConfig { locale })
};
let summary = reconcile_once_with_backup_progress(
let summary = reconcile_all_stores_with_backup(
&codex_home,
cli.provider.as_deref(),
cli.profile.as_deref(),
rollout_scope,
DEFAULT_BUCKET_PADDING_BYTES,
DEFAULT_BACKFILL_WAIT,
progress,
)?;
print_sync_summary(locale, sync_complete_title(locale), &summary);
print_multi_sync_summary(locale, sync_complete_title(locale), &summary);
if sqlite_only && summary.app_store_updated(&codex_home) {
eprintln!("{}", sqlite_only_app_warning(locale));
}
Ok(exit_code_for(summary.status()))
}
Command::Bucket { command } => match command {
BucketCommand::Prepare { padding_bytes } => {
let summary =
prepare_bucket_padding(&codex_home, cli.profile.as_deref(), padding_bytes)?;
print_bucket_prepare_summary(locale, &summary);
Ok(ExitCode::SUCCESS)
}
BucketCommand::Switch {
target_provider,
Expand All @@ -95,15 +117,17 @@ fn main() -> Result<()> {
Some(provider) => Some(provider),
None => cli.provider.clone(),
};
let summary = reconcile_once_with_backup_and_padding(
let summary = reconcile_all_stores_with_backup(
&codex_home,
provider.as_deref(),
cli.profile.as_deref(),
RolloutScope::AllRows,
padding_bytes,
DEFAULT_BACKFILL_WAIT,
Some(RolloutProgressConfig { locale }),
)?;
print_sync_summary(locale, bucket_switch_complete_title(locale), &summary);
print_multi_sync_summary(locale, bucket_switch_complete_title(locale), &summary);
Ok(exit_code_for(summary.status()))
}
},
Command::Watch {
Expand All @@ -122,6 +146,7 @@ fn main() -> Result<()> {
},
Duration::from_millis(poll_interval_ms),
)?;
Ok(ExitCode::SUCCESS)
}
Command::PrintServiceConfig { poll_interval_ms } => {
let exe_path = std::env::current_exe().context(current_exe_error(locale))?;
Expand All @@ -133,6 +158,7 @@ fn main() -> Result<()> {
Duration::from_millis(poll_interval_ms),
)?;
println!("{config}");
Ok(ExitCode::SUCCESS)
}
Command::InstallService { poll_interval_ms } => {
install_service(
Expand All @@ -142,13 +168,21 @@ fn main() -> Result<()> {
cli.profile.as_deref(),
Duration::from_millis(poll_interval_ms),
)?;
Ok(ExitCode::SUCCESS)
}
Command::UninstallService => {
uninstall_service(locale, &codex_home)?;
Ok(ExitCode::SUCCESS)
}
}
}

Ok(())
fn exit_code_for(status: ReconcileStatus) -> ExitCode {
match status {
ReconcileStatus::Full => ExitCode::SUCCESS,
ReconcileStatus::Partial => ExitCode::from(2),
ReconcileStatus::Failed => ExitCode::FAILURE,
}
}

fn resolve_codex_home(cli_codex_home: Option<PathBuf>) -> Result<PathBuf> {
Expand Down
147 changes: 147 additions & 0 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ use crate::rollout::BucketPrepareSummary;
use crate::service;
use crate::service::ServiceInstallSummary;
use crate::service::ServiceManager;
use crate::sync::MultiReconcileSummary;
use crate::sync::ReconcileStatus;
use crate::sync::ReconcileSummary;
use crate::sync::StatusSummary;
use crate::sync::StoreOutcome;

#[derive(Clone, Copy, Debug)]
pub(crate) struct RolloutProgressSnapshot {
Expand Down Expand Up @@ -185,6 +188,95 @@ pub(crate) fn print_sync_summary(locale: Locale, title: &str, summary: &Reconcil
}
}

pub(crate) fn print_multi_sync_summary(
locale: Locale,
title: &str,
summary: &MultiReconcileSummary,
) {
println!("{title}");
println!(
"{}: {}",
status_target_provider_label(locale),
summary.provider
);
if summary.checked_rollouts > 0 || summary.changed_rollouts > 0 {
println!(
"{}: {}",
sync_rollouts_checked_label(locale),
summary.checked_rollouts
);
println!(
"{}: {}",
sync_rollouts_updated_label(locale),
summary.changed_rollouts
);
if summary.prepared_rollouts > 0 {
println!(
"{}: {}",
sync_rollouts_prepared_label(locale),
summary.prepared_rollouts
);
}
if summary.skipped_rollouts > 0 {
println!(
"{}: {}",
sync_rollouts_skipped_label(locale),
summary.skipped_rollouts
);
}
}

println!();
println!("{}", sync_stores_heading(locale));
for store in &summary.stores {
println!();
println!(" [{}] {}", store.kind.slug(), store.kind.label(locale));
println!(
" {}: {}",
status_sqlite_file_label(locale),
store.db_path.display()
);
match &store.outcome {
StoreOutcome::Updated {
changed_rows,
total_rows,
backup_path,
} => {
println!(" {}: {}", sync_rows_updated_label(locale), changed_rows);
println!(" {}: {}", status_total_threads_label(locale), total_rows);
if let Some(backup_path) = backup_path {
println!(
" {}: {}",
sync_backup_label(locale),
backup_path.display()
);
}
}
StoreOutcome::Skipped => {
println!(" {}", sync_store_skipped_label(locale));
}
StoreOutcome::Failed { error } => {
println!(" {}: {}", sync_store_failed_label(locale), error);
}
}
}

println!();
if let Some(journal_path) = &summary.rollout_journal_path {
println!(
"{}: {}",
sync_rollout_journal_label(locale),
journal_path.display()
);
}
println!(
"{}: {} ms",
sync_elapsed_label(locale),
summary.elapsed.as_millis()
);
println!("{}", reconcile_status_line(locale, summary.status()));
}

pub(crate) fn print_bucket_prepare_summary(locale: Locale, summary: &BucketPrepareSummary) {
println!("{}", bucket_prepare_complete_title(locale));
println!(
Expand Down Expand Up @@ -838,6 +930,61 @@ pub(crate) fn status_split_note(locale: Locale) -> &'static str {
}
}

pub(crate) fn sync_stores_heading(locale: Locale) -> &'static str {
match locale {
Locale::En => "Stores:",
Locale::ZhHans => "存储面:",
}
}

pub(crate) fn sync_store_failed_label(locale: Locale) -> &'static str {
match locale {
Locale::En => "Failed",
Locale::ZhHans => "失败",
}
}

pub(crate) fn sync_store_skipped_label(locale: Locale) -> &'static str {
match locale {
Locale::En => {
"Skipped — a Codex backfill has not completed; threadripper avoids racing the rebuild. Re-run once Codex finishes (if it keeps skipping, check whether the backfill is stuck)."
}
Locale::ZhHans => {
"已跳过 —— Codex backfill 尚未完成;threadripper 不与重建竞态。待 Codex 完成后重跑(若持续跳过,请检查 backfill 是否卡住)。"
}
}
}

pub(crate) fn reconcile_status_line(locale: Locale, status: ReconcileStatus) -> String {
match (status, locale) {
(ReconcileStatus::Full, Locale::En) => "Result: all stores updated.".to_string(),
(ReconcileStatus::Full, Locale::ZhHans) => "结果:所有库均已更新。".to_string(),
(ReconcileStatus::Partial, Locale::En) => {
"Result: PARTIAL — some stores updated, at least one was skipped or failed (see above). Re-run after it is resolved.".to_string()
}
(ReconcileStatus::Partial, Locale::ZhHans) => {
"结果:部分成功 —— 部分库已更新,至少一个被跳过或失败(见上)。解决后请重跑。".to_string()
}
(ReconcileStatus::Failed, Locale::En) => {
"Result: FAILED — no store could be updated.".to_string()
}
(ReconcileStatus::Failed, Locale::ZhHans) => {
"结果:失败 —— 没有任何库被更新。".to_string()
}
}
}

pub(crate) fn sqlite_only_app_warning(locale: Locale) -> &'static str {
match locale {
Locale::En => {
"Warning: --sqlite-only edits to the Codex App store take effect immediately but may be reverted by Codex's startup backfill, because the rollout JSONL is the source of truth. Run a full sync (without --sqlite-only) to persist the change."
}
Locale::ZhHans => {
"警告:--sqlite-only 对 Codex App 库的改动会立即生效,但可能被 Codex 启动时的 backfill 从 rollout 还原(rollout 才是事实源)。要持久化请运行完整 sync(不加 --sqlite-only)。"
}
}
}

pub(crate) fn status_background_service_heading(locale: Locale) -> &'static str {
match locale {
Locale::En => "Background service:",
Expand Down
Loading
Loading