use std::rc::Rc;
use perspective_client::clone;
use perspective_client::utils::PerspectiveResultExt;
use crate::config::*;
use crate::presentation::Presentation;
use crate::renderer::Renderer;
use crate::session::{BindPlan, MissingTable, OpCtx, OpKind, Session, probe_table};
use crate::tasks::transactional_restore::{Outcome, commit_and_render, prepare};
use crate::tasks::*;
use crate::workspace::{PanelId, Workspace};
use crate::*;
pub(crate) enum RestoreMode {
Existing { active: bool },
Fresh,
}
#[derive(Clone, Copy)]
pub(crate) enum RestoreErrors {
Publish,
Suppress,
}
type Probed = (
perspective_client::Client,
Option<perspective_client::Table>,
);
async fn probe(
session: &Session,
workspace: &Workspace,
name: &str,
missing: MissingTable,
) -> ApiResult<Probed> {
let current = session.get_client();
let resolved = workspace
.resolve_client_for_table(name, current.as_ref())
.await;
let client = resolved.or(current).into_apierror()?;
let table = probe_table(&client, name, missing).await?;
Ok((client, table))
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Rebind {
Replace,
Complete,
}
#[allow(clippy::too_many_arguments)]
async fn plan_binding(
ctx: &OpCtx,
session: &Session,
renderer: &Renderer,
workspace: &Workspace,
table: &TableUpdate,
fresh: bool,
rebind: Rebind,
missing: MissingTable,
) -> ApiResult<Option<BindPlan>> {
let OptionalUpdate::Update(table) = table else {
return Ok(Some(BindPlan::Keep));
};
let name = table.name()?;
let same = session.get_table().is_some_and(|t| t.get_name() == name);
if same && !session.is_errored() && renderer.failure().is_none() {
return Ok(Some(BindPlan::Keep));
}
let (client, table) = probe(session, workspace, name, missing).await?;
if !fresh && ctx.is_superseded() {
return Ok(None);
}
if table.is_none()
&& rebind == Rebind::Complete
&& session.pending_table().as_deref() == Some(name)
{
return Ok(None);
}
let reset = !fresh && rebind == Rebind::Replace;
Ok(Some(match table {
Some(table) => BindPlan::Bind {
client,
table: Box::new(table),
reset,
},
None => BindPlan::Pend {
client,
name: name.to_owned(),
reset,
},
}))
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn restore_panel(
session: &Session,
renderer: &Renderer,
presentation: &Presentation,
workspace: &Workspace,
mode: RestoreMode,
update: ViewerConfigUpdate,
errors: RestoreErrors,
missing: MissingTable,
preamble: Option<futures::future::LocalBoxFuture<'static, ApiResult<()>>>,
) -> ApiResult<()> {
let update = Rc::new(update);
let kind = OpKind::Restore {
update: (!matches!(mode, RestoreMode::Fresh)).then(|| update.clone()),
};
let ticket = session.submit(kind, {
clone!(session, renderer, presentation, workspace);
move |ctx| {
Box::pin(async move {
if let Some(preamble) = preamble {
preamble.await?;
}
restore_panel_step(
&ctx,
&session,
&renderer,
&presentation,
&workspace,
mode,
Rebind::Replace,
RunOrigin::Public,
Rc::unwrap_or_clone(update),
errors,
missing,
)
.await?;
Ok(None)
})
}
});
ticket.settle().await
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn restore_panel_step(
ctx: &OpCtx,
session: &Session,
renderer: &Renderer,
presentation: &Presentation,
workspace: &Workspace,
mode: RestoreMode,
rebind: Rebind,
origin: RunOrigin,
mut update: ViewerConfigUpdate,
errors: RestoreErrors,
missing: MissingTable,
) -> ApiResult<()> {
let active = matches!(mode, RestoreMode::Existing { active: true });
let fresh = matches!(mode, RestoreMode::Fresh);
crate::tasks::resolve_table_ref(workspace, &mut update, missing).await?;
let missing = match &update.table {
OptionalUpdate::Update(table)
if table
.name()
.is_ok_and(|name| workspace.is_derived_pending(name)) =>
{
MissingTable::Pend
},
_ => missing,
};
renderer.check_plugin_update(&update.plugin)?;
let Some(plan) = plan_binding(
ctx,
session,
renderer,
workspace,
&update.table,
fresh,
rebind,
missing,
)
.await?
else {
return Ok(());
};
if !fresh {
tracing::info!("Restoring {update}");
}
let overlay = renderer
.slot_name()
.map(|id| overlay_for(workspace, &PanelId::from(id)));
let rebound = !matches!(plan, BindPlan::Keep);
let prepared = prepare(
session,
renderer,
presentation,
active,
plan,
overlay,
update,
)
.await?;
let Outcome { committed, result } =
commit_and_render(session, renderer, presentation, origin, prepared).await;
if let Err(e) = &result
&& committed
&& matches!(errors, RestoreErrors::Publish)
{
let _ = renderer.fail(e.clone());
}
result?;
if rebound {
workspace.bindings_changed().emit(());
}
if fresh {
renderer.resize().await.unwrap_or_log();
}
Ok(())
}