fixed browser creating " directory, added watcher for chromedriver process, cleanup
This commit is contained in:
parent
60f8f3ed87
commit
058442ac53
|
@ -11,10 +11,14 @@ const DATA_DIR_CHROMIUM: &str = "/.config/chromium";
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
const DATA_DIR_CHROME: &str = "~/.config/google-chrome";
|
const DATA_DIR_CHROME: &str = "~/.config/google-chrome";
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
const DATA_DIR_CHROME_BETA: &str = "~/.config/google-chrome-beta";
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
const HOME_VARIABLE: &str = "HOME";
|
const HOME_VARIABLE: &str = "HOME";
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
const DATA_DIR_CHROME: &str = "\\Google\\Chrome\\User Data";
|
const DATA_DIR_CHROME: &str = "\\Google\\Chrome\\User Data";
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
const DATA_DIR_CHROME_BETA: &str = "\\Google\\Chrome Beta\\User Data";
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
const DATA_DIR_CHROMIUM: &str = "\\Google\\Chromium\\User Data";
|
const DATA_DIR_CHROMIUM: &str = "\\Google\\Chromium\\User Data";
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
const HOME_VARIABLE: &str = "LOCALAPPDATA";
|
const HOME_VARIABLE: &str = "LOCALAPPDATA";
|
||||||
|
@ -34,11 +38,13 @@ fn get_data_dir() -> String {
|
||||||
if home.is_empty() {
|
if home.is_empty() {
|
||||||
panic!("cannot find home env variable");
|
panic!("cannot find home env variable");
|
||||||
}
|
}
|
||||||
if Path::new(format!("{}{}", home, DATA_DIR_CHROMIUM).as_str()).exists() {
|
let by_preference = vec![DATA_DIR_CHROMIUM, DATA_DIR_CHROME, DATA_DIR_CHROME_BETA];
|
||||||
println!("{}{}", home, DATA_DIR_CHROMIUM);
|
for data_dir in by_preference {
|
||||||
return format!("{}{}", home, DATA_DIR_CHROMIUM);
|
if Path::new(format!("{}{}", home, data_dir).as_str()).exists() {
|
||||||
|
return format!("{}{}", home, data_dir);
|
||||||
}
|
}
|
||||||
println!("{}{}", home, DATA_DIR_CHROME);
|
}
|
||||||
|
// Default to chrome anyway I guess
|
||||||
return format!("{}{}", home, DATA_DIR_CHROME);
|
return format!("{}{}", home, DATA_DIR_CHROME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +53,13 @@ impl BrowserSession {
|
||||||
// TODO: error if chrome is already running
|
// TODO: error if chrome is already running
|
||||||
let mut caps = DesiredCapabilities::chrome();
|
let mut caps = DesiredCapabilities::chrome();
|
||||||
// Use the normal data directory (for extensions and logins)
|
// Use the normal data directory (for extensions and logins)
|
||||||
caps.add_chrome_arg(format!("{}={}", DATA_DIR_ARG, get_data_dir()).as_str())?;
|
let data_dir = get_data_dir();
|
||||||
|
println!("starting chrome using data directory: {}", data_dir);
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let data_dir = format!("\"{}\"", data_dir);
|
||||||
|
|
||||||
|
caps.add_chrome_arg(format!("{}={}", DATA_DIR_ARG, data_dir).as_str())?;
|
||||||
let driver = WebDriver::new("http://localhost:6444", caps).await?;
|
let driver = WebDriver::new("http://localhost:6444", caps).await?;
|
||||||
|
|
||||||
println!("started browser");
|
println!("started browser");
|
||||||
|
@ -69,12 +81,10 @@ impl BrowserSession {
|
||||||
impl BrowserHandle {
|
impl BrowserHandle {
|
||||||
pub async fn start(&self, start_url: &str) -> Result<(), anyhow::Error> {
|
pub async fn start(&self, start_url: &str) -> Result<(), anyhow::Error> {
|
||||||
self.handle.fullscreen_window().await?;
|
self.handle.fullscreen_window().await?;
|
||||||
println!("!");
|
|
||||||
self.handle
|
self.handle
|
||||||
.get(format!("http://{}", start_url))
|
.get(format!("http://{}", start_url))
|
||||||
.await
|
.await
|
||||||
.unwrap_or(()); // Ignore errors ig? idfk it errors out in some weirdo way but it works
|
.unwrap_or(()); // Ignore errors ig? idfk it errors out in some weirdo way but it works
|
||||||
println!("?");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
|
use futures::channel::oneshot::Sender;
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
io::ErrorKind,
|
||||||
io::{BufRead, BufReader},
|
process::{Child, Command, Output, Stdio},
|
||||||
process::{Child, Command, Stdio},
|
thread::{self, JoinHandle},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use sysinfo::{Pid, ProcessRefreshKind, RefreshKind, System, SystemExt};
|
use sysinfo::{Pid, ProcessRefreshKind, RefreshKind, System, SystemExt};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct AddrInUse;
|
|
||||||
|
|
||||||
impl fmt::Display for AddrInUse {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "address is in use")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Driver {
|
pub struct Driver {
|
||||||
child: Child,
|
child: Child,
|
||||||
}
|
}
|
||||||
|
@ -22,27 +15,17 @@ impl Driver {
|
||||||
pub fn new() -> Result<Self, anyhow::Error> {
|
pub fn new() -> Result<Self, anyhow::Error> {
|
||||||
let mut child = Command::new("chromedriver")
|
let mut child = Command::new("chromedriver")
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
.arg("--port=6444")
|
.arg("--port=6444")
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
let child_id = child.id();
|
let child_id = child.id();
|
||||||
let mut child_out = BufReader::new(child.stdout.as_mut().unwrap());
|
|
||||||
let mut line = String::new();
|
|
||||||
|
|
||||||
let mut sys_info = System::new_with_specifics(
|
let mut sys_info = System::new_with_specifics(
|
||||||
RefreshKind::new().with_processes(ProcessRefreshKind::new()),
|
RefreshKind::new().with_processes(ProcessRefreshKind::new()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Wait for it to say Chromedriver started successfully
|
// Wait for it to say Chromedriver started successfully
|
||||||
|
print!("chromedriver health check...");
|
||||||
loop {
|
loop {
|
||||||
child_out.read_line(&mut line).unwrap();
|
|
||||||
if line.contains("ChromeDriver was started successfully.") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if line.contains("bind() failed: Address already in use") || line.contains("Exiting...")
|
|
||||||
{
|
|
||||||
return Err(anyhow::anyhow!(line));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if chromedriver has exited
|
// Check if chromedriver has exited
|
||||||
sys_info.refresh_all();
|
sys_info.refresh_all();
|
||||||
if let None = sys_info.process(Pid::from(to_pid(child_id.clone()))) {
|
if let None = sys_info.process(Pid::from(to_pid(child_id.clone()))) {
|
||||||
|
@ -52,16 +35,66 @@ impl Driver {
|
||||||
"chromedriver exited with status code {}",
|
"chromedriver exited with status code {}",
|
||||||
status_code.code().unwrap_or(1337),
|
status_code.code().unwrap_or(1337),
|
||||||
));
|
));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
println!(" done.");
|
||||||
|
|
||||||
|
// Check if chrome/chromium is already running. Die if so.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let chrome_names = ("chrome", "chromium");
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let chrome_names = ("chrome.exe", "chromium.exe");
|
||||||
|
if sys_info.processes_by_exact_name(chrome_names.0).count() > 0
|
||||||
|
|| sys_info.processes_by_exact_name(chrome_names.1).count() > 0
|
||||||
|
{
|
||||||
|
return Err(anyhow::anyhow!("chrome/chromium is already running"));
|
||||||
|
}
|
||||||
|
|
||||||
println!("chromedriver started");
|
println!("chromedriver started");
|
||||||
Ok(Self { child })
|
Ok(Self { child })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(&mut self) -> Result<(), anyhow::Error> {
|
pub fn exit_waiter(&mut self, notify: Sender<()>) -> JoinHandle<()> {
|
||||||
self.child.kill()?;
|
// Check if chromedriver has exited
|
||||||
Ok(())
|
let mut sys_info = System::new_with_specifics(
|
||||||
|
RefreshKind::new().with_processes(ProcessRefreshKind::new()),
|
||||||
|
);
|
||||||
|
let pid = Pid::from(to_pid(self.child.id().clone()));
|
||||||
|
thread::spawn(move || loop {
|
||||||
|
sys_info.refresh_all();
|
||||||
|
let proc = sys_info.process(pid);
|
||||||
|
// Consider two cases of chromedriver being dead:
|
||||||
|
// 1) the process itself is gone (might be on windows)
|
||||||
|
// 2) the process is alive but not reaped (zombie) on linux
|
||||||
|
// in the second case, we check for if it has zero subtasks.
|
||||||
|
// For our purposes, that means it's dead.
|
||||||
|
if !proc.is_none() {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if proc.unwrap().tasks.len() != 0 {
|
||||||
|
thread::sleep(Duration::from_secs(3));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("chromedriver has exited");
|
||||||
|
notify.send(()).unwrap();
|
||||||
|
break;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit_with_output(mut self) -> Result<Output, anyhow::Error> {
|
||||||
|
match self.child.kill() {
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() != ErrorKind::InvalidInput {
|
||||||
|
return Err(anyhow::anyhow!(e));
|
||||||
|
} else {
|
||||||
|
println!("killing child: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(self.child.wait_with_output()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
66
src/main.rs
66
src/main.rs
|
@ -2,23 +2,32 @@ mod botdriver;
|
||||||
mod browser;
|
mod browser;
|
||||||
mod chromedriver;
|
mod chromedriver;
|
||||||
mod webserve;
|
mod webserve;
|
||||||
|
use botdriver::BotDriver;
|
||||||
|
use browser::BrowserSession;
|
||||||
|
use chromedriver::Driver;
|
||||||
|
use futures::channel::oneshot;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use tokio::time;
|
||||||
use botdriver::BotDriver;
|
|
||||||
use browser::BrowserSession;
|
|
||||||
use chromedriver::Driver;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
const STATIC_ADDR: &str = "127.0.0.1:8010";
|
const STATIC_ADDR: &str = "127.0.0.1:8010";
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), anyhow::Error> {
|
async fn main() {
|
||||||
|
if let Err(e) = main_but_with_result().await {
|
||||||
|
println!("frc exited with error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
async fn main_but_with_result() -> Result<(), anyhow::Error> {
|
||||||
let mut cfg: FRCConfig = confy::load_path(CFG_NAME)?;
|
let mut cfg: FRCConfig = confy::load_path(CFG_NAME)?;
|
||||||
if cfg.username == None || cfg.token == None {
|
if cfg.username == None || cfg.token == None {
|
||||||
cfg = create_config()?;
|
cfg = create_config()?;
|
||||||
|
@ -36,23 +45,48 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
let mut bot = BotDriver::new(cfg.token.unwrap(), username);
|
let mut bot = BotDriver::new(cfg.token.unwrap(), username);
|
||||||
browser.start(STATIC_ADDR).await?;
|
browser.start(STATIC_ADDR).await?;
|
||||||
println!("################");
|
println!();
|
||||||
println!("# starting frc #");
|
println!("#####################");
|
||||||
println!("################");
|
println!("# launching frc #");
|
||||||
tokio::spawn(async move {
|
println!("# #");
|
||||||
let term = Arc::new(AtomicBool::new(false));
|
println!("# frc can be closed #");
|
||||||
|
println!("# by an interrupt #");
|
||||||
|
println!("# (Ctl-C) #");
|
||||||
|
println!("#####################");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let should_exit = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
let (chr_sender, mut chr_receiver) = oneshot::channel();
|
||||||
|
chr.exit_waiter(chr_sender);
|
||||||
|
|
||||||
|
signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&should_exit)).unwrap();
|
||||||
|
signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&should_exit))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term)).unwrap();
|
|
||||||
signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&term)).unwrap();
|
|
||||||
loop {
|
loop {
|
||||||
if term.load(Ordering::Relaxed) {
|
if should_exit.load(Ordering::Relaxed) || chr_receiver.try_recv().unwrap().is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
time::sleep(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
println!("closing down");
|
println!("closing down");
|
||||||
static_handle.abort();
|
static_handle.abort();
|
||||||
driver.stop().await.expect("failed killing browser");
|
driver.stop().await.expect("failed killing browser");
|
||||||
chr.exit().expect("failed killing chromedriver");
|
let output = chr.exit_with_output().expect("failed killing chromedriver");
|
||||||
break;
|
println!("chromedriver exited with status code {}", output.status);
|
||||||
|
if output.stderr.len() > 0 {
|
||||||
|
print!("chromedriver stderr output:");
|
||||||
|
io::stdout().write(&output.stderr).unwrap();
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
|
if output.stdout.len() > 0 {
|
||||||
|
print!("chromedriver stdout output:");
|
||||||
|
io::stdout().write(&output.stdout).unwrap();
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
|
|
Loading…
Reference in New Issue