use crate::cacher::Cacher;
use crate::crates::CratesApi;
use crate::opts::{AppCommand, Opts, UpdateCommand};
use crate::workbench::WorkbenchApi;
use anyhow::{Result, bail};
use dialoguer::Confirm;
use owo_colors::OwoColorize;
#[cfg(not(target_os = "windows"))]
use std::fs;
#[cfg(unix)]
use std::io;
use std::process::{Command, Stdio};
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
#[cfg(target_os = "windows")]
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
#[cfg(target_os = "windows")]
const DETACHED_PROCESS: u32 = 0x0000_0008;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
#[cfg(unix)]
unsafe extern "C" {
fn setsid() -> i32;
}
pub struct App {
opts: Opts,
cacher: Cacher,
crates_api: CratesApi,
workbench_api: WorkbenchApi,
}
impl App {
pub async fn entrypoint(opts: Opts) -> Result<()> {
let mut app = Self::init(opts).await?;
match &app.opts.command {
None => {
app.update_workbench(false, false).await?;
app.command_workbench().await?;
}
Some(AppCommand::Update(opts)) => {
let opts = Some(opts.clone());
app.command_update(true, false, opts).await?;
}
Some(AppCommand::Upgrade(opts)) => {
let opts = Some(opts.clone());
app.command_update(true, true, opts).await?;
}
Some(AppCommand::Workbench) => {
app.update_workbench(false, false).await?;
app.command_workbench().await?;
}
Some(AppCommand::Clean) => {
app.command_clean().await?;
}
}
Ok(())
}
async fn init(opts: Opts) -> Result<Self> {
let mut cacher = Cacher::create().await?;
cacher.initialize().await?;
Ok(Self {
opts,
cacher,
crates_api: CratesApi::new(),
workbench_api: WorkbenchApi::new(),
})
}
async fn update_launcher(&mut self, force_check: bool) -> Result<()> {
if self.cacher.launcher.is_update_required() || force_check {
eprintln!("Checking an update for the launcher...");
let version = self.crates_api.latest_version().await?;
if self.cacher.launcher.is_outdated(version) {
eprintln!(
"New version of launcher is available. To update run the following command"
);
eprintln!("{}", "cargo install knowledge".green());
}
self.cacher.launcher.update_check();
self.cacher.write_state().await?;
}
Ok(())
}
async fn update_workbench(&mut self, force_check: bool, force_download: bool) -> Result<()> {
if self.cacher.workbench.is_update_required() || force_check {
eprintln!("Checking for workbench updates...");
let latest = self.workbench_api.latest_release().await?;
let version = latest.version.clone();
if self.cacher.workbench.is_outdated(version.clone()) || force_download {
eprintln!("Downloading workbench {}...", version);
let url = latest.get_asset_url();
let bin_name = if cfg!(target_os = "windows") {
"workbench.exe"
} else {
"workbench"
};
let dest = self.cacher.bin_dir().join(bin_name);
self.workbench_api.download_binary(&url, &dest).await?;
#[cfg(not(target_os = "windows"))]
{
use std::os::unix::fs::PermissionsExt;
let mut perm = fs::metadata(&dest)?.permissions();
perm.set_mode(perm.mode() | 0o755);
fs::set_permissions(&dest, perm)?;
}
self.cacher.workbench.version = Some(version.clone());
eprintln!("workbench {} is ready.", version);
}
self.cacher.workbench.update_check();
self.cacher.write_state().await?;
}
Ok(())
}
pub async fn command_update(
&mut self,
force_check: bool,
force_download: bool,
_opts: Option<UpdateCommand>,
) -> Result<()> {
if let Err(err) = self.update_launcher(force_check).await {
eprintln!("Launcher updating failed: {}", err.to_string().red());
}
if let Err(err) = self.update_workbench(force_check, force_download).await {
eprintln!("Workbench updating failed: {}", err.to_string().red());
}
Ok(())
}
pub async fn command_workbench(&mut self) -> Result<()> {
let mut bin_path = self.cacher.bin_dir().clone();
let bin_name = if cfg!(target_os = "windows") {
"workbench.exe"
} else {
"workbench"
};
bin_path.push(bin_name);
if !bin_path.exists() {
bail!("Workbench binary not found. Run 'knowledge update' first.");
}
eprintln!("Starting workbench...");
let mut command = Command::new(&bin_path);
command
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
detach_command(&mut command);
command.spawn()?;
Ok(())
}
pub async fn command_clean(self) -> Result<()> {
if Confirm::new()
.with_prompt("Do you want to clean the cache?")
.interact()?
{
eprintln!("Cleaning the cache...");
self.cacher.remove_cache().await?;
}
Ok(())
}
}
#[cfg(unix)]
fn detach_command(command: &mut Command) {
unsafe {
command.pre_exec(|| {
let session_id = setsid();
if session_id == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
});
}
}
#[cfg(target_os = "windows")]
fn detach_command(command: &mut Command) {
command.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
}
#[cfg(not(any(unix, target_os = "windows")))]
fn detach_command(_command: &mut Command) {}