use super::super::enroll::{Enrolled, SignIn, Steps, WatchedSignIn};
use super::super::{Error, Result, Settled, enrolled, exclusive, identify, shape};
use super::moves::{self, Leaving, Mac, Step};
use super::verdict::{Unsure, Verdict, look};
use super::{Begun, Open, begin, midway, open, refusal, saying, there};
use crate::context::Context;
use crate::error::Then;
use crate::fault;
use crate::provider::folder::{Folder, Keys};
use crate::service::{Permit, Warning};
use crate::state::{self, Aside, Awaited, Key, SetAside, State};
use crate::words::{self, WaitLine as Line};
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Sender;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WaitStep {
Waiting,
Open,
QuitSignedOut,
QuitUnfinished,
}
pub(crate) struct Wait {
stop: Arc<AtomicBool>,
thread: Option<std::thread::JoinHandle<Result<()>>>,
}
impl Wait {
pub(crate) fn finish(&mut self) -> Result<()> {
match self.thread.take().map(std::thread::JoinHandle::join) {
Some(Ok(ended)) => ended,
Some(Err(_)) | None => Err(Error::SignInIncomplete),
}
}
pub(crate) fn cancel(&mut self) {
self.stop.store(true, Ordering::SeqCst);
let _ = self.finish();
}
}
fn set_keys_aside(
ctx: &Context,
permit: Permit,
state: &mut State,
app: &'static dyn Folder,
root: &Path,
) -> Result<Option<Warning>> {
let tool = app.id();
let refused = |detail: String| Error::LoginFolderUnknown {
tool,
what: format!("could not be made ready for a sign-in ({detail})"),
then: Then::Nothing,
};
let held = moves::settings(app, root)
.map_err(refused)?
.account_keys(app);
if held.is_empty() {
return Ok(None);
}
let mac = Mac::here(ctx)?;
crate::store::folder::ready(ctx, permit, root)?;
let aside = crate::park::reserve_aside(ctx, permit, state)?;
let kept = || -> Step {
moves::keep_aside(ctx, permit, app, &aside, None, Vec::new(), &held, &mac)?;
fault::point("start.aside_kept");
moves::put_keys(app, permit, root, &Keys::none())?;
fault::point("start.keys_cleared");
Ok(())
};
kept().map_err(refused)?;
midway(ctx, app, None)?;
state.aside.push(Aside {
name: aside.clone(),
tool,
at: ctx.now(),
why: SetAside::InTheWay,
account: None,
login: None,
});
Ok(Some(Warning::SetAside {
tool,
path: crate::store::folder::at(ctx, &aside),
label: None,
why: SetAside::InTheWay,
}))
}
pub(crate) fn start(
settled: Settled,
key: &Key,
app: &'static dyn Folder,
one_at_a_time: std::fs::File,
first: Vec<String>,
) -> Result<(WatchedSignIn, Vec<Warning>)> {
let Settled {
_exclusive,
mut state,
ctx,
permit,
} = settled;
let tool = key.provider;
let label = state.typed(key);
let again = state.get(key).map(|a| a.id.clone());
let Begun {
closed: _closed,
warnings,
} = begin(&ctx, permit, &mut state, app)?;
let ((put_back, parked), warnings) = saying(warnings, |warnings| {
let root = app.root(&ctx).map_err(|e| shape(tool, e))?;
let mut parked = None;
let put_back = match look(&ctx, &state, app)? {
Verdict::Account {
key: holder,
session,
as_named,
} => {
let mac = moves::may_start(&ctx, permit, app, &root)?;
let awaited = Awaited {
since: ctx.now(),
label: key.label.clone(),
account: again.clone(),
put_back: enrolled(&state, &holder)?.id,
};
let leaving = Leaving {
key: &holder,
session: &session,
awaited: Some(awaited),
};
let (_, said) = moves::leave(&ctx, permit, &mut state, app, &root, leaving, &mac)?;
if as_named {
warnings.push(Warning::TakenAsNamed {
tool,
label: state.typed(&holder),
recorded: None,
});
}
warnings.extend(said);
parked = Some(state.typed(&holder));
Some(holder)
}
Verdict::Nobody { .. } => {
let mut changed = false;
if there(tool, &root)? {
let set_aside = set_keys_aside(&ctx, permit, &mut state, app, &root)?;
changed |= set_aside.is_some();
warnings.extend(set_aside);
}
let now = ctx.now();
let (recorded, noticed) =
identify::record(&mut state, tool, moves::nobody(now), now);
changed |= recorded;
let standing = state.awaiting(tool).cloned();
let held = match standing {
Some(standing) => {
let held = state
.by_id(tool, &standing.put_back)
.map(state::Account::key);
let taken_up = Awaited {
since: now,
label: key.label.clone(),
account: again.clone(),
put_back: standing.put_back,
};
state.awaiting.insert(tool.code().to_string(), taken_up);
changed = true;
held
}
None => None,
};
if changed {
state::save(&ctx, permit, &state)?;
fault::point("start.saved");
}
identify::write_down(&ctx, permit, ¬iced);
held
}
Verdict::Unsure(unsure) => return Err(refusal(&state, tool, &unsure)),
};
Ok((put_back, parked))
})?;
let typed_back = put_back.as_ref().map(|key| state.typed(key));
let (say, said) = std::sync::mpsc::channel();
let steps = Steps::default();
for line in first {
let _ = say.send(line);
}
for warning in &warnings {
let _ = say.send(format!("{warning}\n"));
}
if let Some(parked) = &parked {
let _ = say.send(words::awaited(Line::Parked { label: parked }));
}
steps.set(WaitStep::Waiting);
let _ = say.send(words::awaited(match again {
Some(_) => Line::Again { label: &label },
None => Line::Add { label: &label },
}));
let _ = say.send(words::awaited(Line::NoLogOut));
let _ = say.send(words::awaited(Line::Waiting {
put_back: typed_back.as_deref(),
}));
let stop = Arc::new(AtomicBool::new(false));
let waiting = Waiting {
ctx: ctx.clone(),
permit,
app,
label: key.label.clone(),
recorded: put_back.is_some(),
stop: Arc::clone(&stop),
say,
steps: steps.clone(),
};
let thread = std::thread::spawn(move || waiting.run());
let wait = Wait {
stop,
thread: Some(thread),
};
let pending = SignIn::in_its_app(&ctx, permit, one_at_a_time, app, put_back);
Ok((WatchedSignIn::awaited(pending, wait, said, steps), warnings))
}
struct Waiting {
ctx: Context,
permit: Permit,
app: &'static dyn Folder,
label: String,
recorded: bool,
stop: Arc<AtomicBool>,
say: Sender<String>,
steps: Steps,
}
enum Looked {
SignedIn,
Nobody,
Unfinished,
OpenAgain,
Busy,
}
impl Waiting {
fn says(&self, step: WaitStep, line: Line<'_>) {
self.steps.set(step);
let _ = self.say.send(words::awaited(line));
}
fn run(self) -> Result<()> {
let (mut was_open, mut look_again) = (false, false);
loop {
if self.stop.load(Ordering::SeqCst) {
return Err(Error::SignInIncomplete);
}
match open(&self.ctx, self.app) {
Open::Yes(_) => {
if !was_open {
self.says(WaitStep::Open, Line::Open);
}
(was_open, look_again) = (true, false);
}
Open::No if was_open || look_again => {
(was_open, look_again) = (false, false);
match self.look()? {
Looked::SignedIn => return Ok(()),
Looked::Nobody => self.says(WaitStep::QuitSignedOut, Line::QuitSignedOut),
Looked::Unfinished => {
self.says(WaitStep::QuitUnfinished, Line::QuitUnfinished);
}
Looked::OpenAgain => was_open = true,
Looked::Busy => look_again = true,
}
}
Open::No | Open::Unknown(_) => {}
}
std::thread::sleep(self.ctx.awaited_every());
}
}
fn look(&self) -> Result<Looked> {
let tool = self.app.id();
let elsewhere = || Error::SignInSettledElsewhere {
label: self.label.clone(),
};
if !crate::home::dir(&self.ctx).is_dir() {
return Err(elsewhere());
}
let _exclusive = exclusive(&self.ctx, self.permit)?;
let mut state = state::load(&self.ctx)?;
if self.recorded
&& state
.awaiting(tool)
.is_none_or(|awaited| awaited.label != self.label)
{
return Err(elsewhere());
}
let _closed = match begin(&self.ctx, self.permit, &mut state, self.app) {
Ok(Begun { closed, warnings }) => {
for warning in warnings {
let _ = self.say.send(format!("{warning}\n"));
}
closed
}
Err(Error::AppOpen { .. } | Error::AppStateUnknown { .. }) => {
return Ok(Looked::OpenAgain);
}
Err(Error::LoginFolderBusy { .. }) => return Ok(Looked::Busy),
Err(other) => return Err(other),
};
Ok(match look(&self.ctx, &state, self.app)? {
Verdict::Nobody { .. } => Looked::Nobody,
Verdict::Unsure(Unsure::Unfinished) => Looked::Unfinished,
Verdict::Account { .. } | Verdict::Unsure(_) => Looked::SignedIn,
})
}
}
pub(crate) fn enroll(
settled: Settled,
key: &Key,
app: &'static dyn Folder,
) -> Result<(Enrolled, Vec<Warning>)> {
super::enroll_as(settled, key, app, true)
}