use super::client::{Me, PlmClient};
use super::transport::EhttpTransport;
use super::PlmTransport;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Debug, PartialEq)]
pub enum Status {
Connected { url: String, username: String },
NotConnected { url: String, reason: Option<String> },
}
#[derive(Clone, Debug, PartialEq)]
pub enum Outcome {
Done(String),
Failed(String),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Session {
pub status: Status,
pub keep: Keep,
}
pub fn parse_label(label: &str) -> Option<(String, String)> {
let rest = label.strip_prefix("PLM ")?;
let (url, user) = rest.rsplit_once(" as ")?;
Some((url.to_string(), user.to_string()))
}
#[derive(Clone, Debug, PartialEq)]
pub enum Keep {
Files(std::path::PathBuf),
BrowserSession,
}
pub struct PlmConnection {
pub status: Status,
keep: Keep,
pub token_input: String,
pub username: String,
pub password: String,
pending: Option<Rc<RefCell<Option<Outcome>>>>,
pub last: Option<Outcome>,
reload: bool,
connectable: bool,
transport: Rc<dyn Fn(&str) -> Rc<dyn PlmTransport>>,
}
impl PlmConnection {
pub fn new(status: Status, keep: Keep) -> Self {
Self {
status,
keep,
token_input: String::new(),
username: String::new(),
password: String::new(),
pending: None,
last: None,
reload: false,
connectable: false,
transport: Rc::new(|url: &str| Rc::new(EhttpTransport::new(url.to_string())) as Rc<dyn PlmTransport>),
}
}
pub fn url(&self) -> &str {
match &self.status {
Status::Connected { url, .. } | Status::NotConnected { url, .. } => url,
}
}
pub fn keep(&self) -> &Keep {
&self.keep
}
pub fn busy(&self) -> bool {
self.pending.is_some()
}
pub fn poll(&mut self) -> bool {
let Some(slot) = &self.pending else { return false };
#[cfg(not(target_arch = "wasm32"))]
super::native::run_pending();
let Some(outcome) = slot.borrow_mut().take() else { return false };
self.pending = None;
if let (Keep::BrowserSession, Outcome::Done(_)) = (&self.keep, &outcome) {
self.reload = true;
}
self.connectable = matches!(self.keep, Keep::Files(_)) && matches!(outcome, Outcome::Done(_));
if let (Status::NotConnected { url, .. }, Outcome::Failed(why)) = (&self.status, &outcome) {
self.status = Status::NotConnected { url: url.clone(), reason: Some(why.clone()) };
}
self.last = Some(outcome);
true
}
pub fn connectable(&self) -> bool {
self.connectable
}
pub fn reconnected(&mut self, outcome: Result<String, String>) {
self.connectable = outcome.is_err() && self.connectable;
self.last = Some(match outcome {
Ok(text) => Outcome::Done(text),
Err(why) => Outcome::Failed(why),
});
}
pub fn set_status(&mut self, status: Status) {
if std::mem::discriminant(&status) != std::mem::discriminant(&self.status) {
self.status = status;
self.connectable = false;
}
}
pub fn take_reload(&mut self) -> bool {
std::mem::take(&mut self.reload)
}
fn client(&self) -> Rc<PlmClient> {
Rc::new(PlmClient::new((self.transport)(self.url())))
}
fn start(&mut self, work: impl std::future::Future<Output = Outcome> + 'static) {
let slot: Rc<RefCell<Option<Outcome>>> = Rc::default();
let into = slot.clone();
self.pending = Some(slot);
self.last = None;
spawn(async move {
let outcome = work.await;
*into.borrow_mut() = Some(outcome);
});
}
pub fn use_token(&mut self) {
let token = self.token_input.trim().to_string();
let (client, keep) = (self.client(), self.keep.clone());
self.token_input.clear();
self.start(async move {
match client.sign_in_with_token(&token).await {
Ok(me) => keep_token(&keep, &token, &me),
Err(e) => Outcome::Failed(e.to_string()),
}
});
}
pub fn use_password(&mut self) {
let (username, password) = (self.username.trim().to_string(), std::mem::take(&mut self.password));
let (client, keep) = (self.client(), self.keep.clone());
self.start(async move {
match keep {
Keep::Files(_) => match client.sign_in_for_token(&username, &password, &token_name()).await {
Ok((me, token)) => keep_token(&keep, &token, &me),
Err(e) => Outcome::Failed(e.to_string()),
},
Keep::BrowserSession => match client.sign_in_with_password(&username, &password).await {
Ok(me) => Outcome::Done(format!("signed in as {}; reloading onto the PLM", me.username)),
Err(e) => Outcome::Failed(e.to_string()),
},
}
});
}
pub fn check(&mut self) {
let (client, keep) = (self.client(), self.keep.clone());
self.start(async move {
let signed_in = match &keep {
Keep::Files(dir) => match held_token(dir) {
Ok(Some(token)) => client.sign_in_with_token(&token).await,
Ok(None) => return Outcome::Failed("no token on this machine: paste one, or sign in with a password".into()),
Err(e) => return Outcome::Failed(e),
},
Keep::BrowserSession => client.sign_in_with_browser_session().await,
};
match signed_in {
Ok(me) => Outcome::Done(format!("the server accepts this machine as {} and serves this app", me.username)),
Err(e) => Outcome::Failed(e.to_string()),
}
});
}
pub fn forget(&mut self) {
match self.keep.clone() {
Keep::Files(dir) => {
self.connectable = false;
self.last = Some(match forget_files(&dir) {
Ok(()) => Outcome::Done("this machine's PLM sign-in is forgotten; the next start is the file app".into()),
Err(e) => Outcome::Failed(e),
});
}
Keep::BrowserSession => {
let client = self.client();
self.start(async move {
match client.raw("POST", "/api/logout", Some(b"{}".to_vec())).await {
Ok(_) => Outcome::Done("signed out; reloading".into()),
Err(e) => Outcome::Failed(e.to_string()),
}
});
}
}
}
}
fn token_name() -> String {
#[cfg(not(target_arch = "wasm32"))]
{
let host = std::env::var("HOSTNAME").ok().filter(|h| !h.is_empty());
format!("brep-app {}", host.unwrap_or_else(|| "desktop".into()))
}
#[cfg(target_arch = "wasm32")]
{
"brep-app web".into()
}
}
fn keep_token(keep: &Keep, token: &str, me: &Me) -> Outcome {
match keep {
Keep::Files(dir) => match write_token(dir, token) {
Ok(()) => Outcome::Done(format!(
"signed in as {}; the token is kept on this machine, and the next start connects",
me.username
)),
Err(e) => Outcome::Failed(format!("signed in, but the token could not be kept: {e}")),
},
Keep::BrowserSession => Outcome::Done(format!("signed in as {}; reloading onto the PLM", me.username)),
}
}
#[cfg(not(target_arch = "wasm32"))]
fn write_token(dir: &std::path::Path, token: &str) -> Result<(), String> {
super::config::write_token(dir, token)
}
#[cfg(target_arch = "wasm32")]
fn write_token(_: &std::path::Path, _: &str) -> Result<(), String> {
Err("a browser keeps no token file".into())
}
#[cfg(not(target_arch = "wasm32"))]
fn held_token(dir: &std::path::Path) -> Result<Option<String>, String> {
Ok(super::config::resolve(dir, &|_| None, &Default::default())?.and_then(|c| c.token))
}
#[cfg(target_arch = "wasm32")]
fn held_token(_: &std::path::Path) -> Result<Option<String>, String> {
Ok(None)
}
#[cfg(not(target_arch = "wasm32"))]
fn forget_files(dir: &std::path::Path) -> Result<(), String> {
super::config::forget(dir).map(|_| ())
}
#[cfg(target_arch = "wasm32")]
fn forget_files(_: &std::path::Path) -> Result<(), String> {
Err("a browser keeps no PLM files".into())
}
fn spawn(task: impl std::future::Future<Output = ()> + 'static) {
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(task);
#[cfg(not(target_arch = "wasm32"))]
super::native::spawn(task);
}