use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::time::Duration;
use super::{Flow, LoginRun, LoginView};
use crate::cli_outbound::{Binary, Cli};
use crate::config_edit::brazen::{BzRunner, ProviderRow, RealBzRunner};
use crate::state::{LoginCell, lock_logins as lock_runs};
const READ_TICK: Duration = Duration::from_millis(16);
const TTL_SECONDS: i64 = 3600;
type Key = (PathBuf, String);
struct Slot {
run: LoginRun,
serial: u64,
at: i64,
}
#[derive(Default)]
pub(crate) struct Board {
live: BTreeMap<Key, Slot>,
seq: u64,
}
#[derive(Clone)]
pub struct Runs {
cell: LoginCell,
bz: Cli,
tick: Duration,
}
impl Default for Runs {
fn default() -> Self {
Self::of(Cli::resolve(Binary::Bz))
}
}
impl Runs {
pub fn of(bz: Cli) -> Self {
Self {
cell: LoginCell::default(),
bz,
tick: READ_TICK,
}
}
pub fn start(
&self,
world: &crate::xdg::Env,
workspace: &Path,
provider: &str,
state_root: &Path,
ts: &str,
) -> Result<LoginView, String> {
let now: i64 = ts.parse().unwrap_or(0);
let key = (workspace.to_path_buf(), provider.to_owned());
let wall = crate::world::wall::env(world, workspace);
let flow = flow_of(&wall, provider);
let bz = self.bz.and_env(crate::world::wall::pairs_of(&wall));
let mut board = lock_runs(&self.cell);
board.live.retain(|_, slot| now - slot.at <= TTL_SECONDS);
board.live.remove(&key);
let run = super::start(&bz, provider, state_root, ts, Some(workspace), flow)
.map_err(|e| e.to_string())?;
board.seq += 1;
let serial = board.seq;
let view = run.view();
board.live.insert(
key.clone(),
Slot {
run,
serial,
at: now,
},
);
drop(board);
let (runs, tick) = (self.clone(), self.tick);
std::thread::spawn(move || {
loop {
std::thread::sleep(tick);
if !runs.drain(&key, serial) {
return;
}
}
});
Ok(view)
}
fn drain(&self, key: &Key, serial: u64) -> bool {
let mut board = lock_runs(&self.cell);
match board.live.get_mut(key) {
Some(slot) if slot.serial == serial => slot.run.poll(),
_ => false,
}
}
pub fn standing(&self, workspace: &Path, provider: &str) -> LoginView {
self.frame(workspace, provider, 0).unwrap_or_default()
}
pub(crate) fn frame(&self, workspace: &Path, provider: &str, sent: usize) -> Option<LoginView> {
let key = (workspace.to_path_buf(), provider.to_owned());
let board = lock_runs(&self.cell);
let view = board.live.get(&key)?.run.view();
Some(LoginView {
lines: view.lines.get(sent..).unwrap_or_default().to_vec(),
outcome: view.outcome,
fallback: view.fallback,
})
}
#[cfg(test)]
pub(crate) fn seat(&self, workspace: &Path, provider: &str, run: LoginRun, at: i64) -> u64 {
let mut board = lock_runs(&self.cell);
board.seq += 1;
let serial = board.seq;
board.live.insert(
(workspace.to_path_buf(), provider.to_owned()),
Slot { run, serial, at },
);
serial
}
#[cfg(test)]
pub(crate) fn read_once(&self, workspace: &Path, provider: &str, serial: u64) -> bool {
self.drain(&(workspace.to_path_buf(), provider.to_owned()), serial)
}
}
fn flow_of(wall: &crate::xdg::Env, provider: &str) -> Flow {
let headless = RealBzRunner::resolve(wall)
.providers()
.iter()
.any(|row| row.name == provider && ProviderRow::headless_login(row));
if headless {
Flow::Device
} else {
Flow::Browser
}
}
#[cfg(test)]
mod tests;