use std::{ fs, net::{IpAddr, Ipv4Addr}, path::PathBuf, process, }; use anyhow::{bail, Context, Result}; use log::{error, info, warn}; use url::Url; use crate::{cli::OpenCommand, rofi, state::State, task}; pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> { match command { OpenCommand::Review => { for project in task::Project::all().context("Failed to get all project files")? { if project.is_touched() { info!("Reviewing project: '{}'", project.to_project_display()); open_in_browser(project, state, None).with_context(|| { format!( "Failed to open project ('{}') in Firefox", project.to_project_display() ) })?; project.untouch().with_context(|| { format!( "Failed to untouch project ('{}')", project.to_project_display() ) })?; } } } OpenCommand::Project { project, url } => { project.touch().context("Failed to touch project")?; open_in_browser(&project, state, url).with_context(|| { format!("Failed to open project: {}", project.to_project_display()) })?; } OpenCommand::Select { url } => { let selected_project: task::Project = task::Project::from_project_string( &rofi::select( task::Project::all() .context("Failed to get all registered projects")? .iter() .map(task::Project::to_project_display) .collect::<Vec<_>>() .as_slice(), ) .context("Failed to get selected project")?, ) .expect("This should work, as we send only projects in"); selected_project .touch() .context("Failed to touch project")?; open_in_browser(&selected_project, state, url).context("Failed to open project")?; } OpenCommand::ListTabs { project } => { let project = if let Some(p) = project { p } else if let Some(p) = task::Project::get_current().context("Failed to get currently focused project")? { p } else { bail!("You need to either supply a project or have a project active!"); }; let session_store = project.get_sessionstore().with_context(|| { format!( "Failed to get session store for project: '{}'", project.to_project_display() ) })?; let selected = session_store .windows .iter() .map(|w| w.selected) .collect::<Vec<_>>(); let tabs = session_store .windows .iter() .flat_map(|window| window.tabs.iter()) .map(|tab| tab.entries.get(tab.index - 1).expect("This should be Some")) .collect::<Vec<_>>(); for (index, entry) in tabs.iter().enumerate() { let index = index + 1; let is_selected = { if selected.contains(&index) { "🔻 " } else { " " } }; println!("{}{}", is_selected, entry.url); } } } Ok(()) } fn open_in_browser( selected_project: &task::Project, state: &mut State, url: Option<Url>, ) -> Result<()> { let old_project: Option<task::Project> = task::Project::get_current().context("Failed to get currently active project")?; let old_task: Option<task::Task> = task::Task::get_current(state).context("Failed to get currently active task")?; selected_project.activate().with_context(|| { format!( "Failed to active project: '{}'", selected_project.to_project_display() ) })?; let tracking_task = { let all_tasks = selected_project.get_tasks(state).with_context(|| { format!( "Failed to get assoctiated tasks for project: '{}'", selected_project.to_project_display() ) })?; let tracking_task = all_tasks.into_iter().find(|t| { let maybe_desc = t.description(state); if let Ok(desc) = maybe_desc { desc == "tracking" } else { error!( "Getting task description returned error: {}", maybe_desc.expect_err("We already check for Ok") ); false } }); if let Some(task) = tracking_task { info!( "Starting task {} -> tracking", selected_project.to_project_display() ); task.start(state) .with_context(|| format!("Failed to start task {task}"))?; } tracking_task }; let status = { let mut args = vec!["-P".to_owned(), selected_project.to_project_display()]; if let Some(url) = url { args.push(url.to_string()); } else { let lock_file = dirs::home_dir() .expect("Exists") .join(".mozilla/firefox") .join(selected_project.to_project_display()) .join("lock"); if lock_file.exists() { let (ip, pid): (IpAddr, u32) = { let link = fs::read_link(&lock_file).with_context(|| { format!("Failed to readlink lock at '{}'", lock_file.display()) })?; let (ip, pid) = link .to_str() .expect("Should work") .split_once(':') .expect("The split works"); ( ip.parse().expect("Should be a valid ip address"), pid.parse().expect("Should be a valid pid"), ) }; if ip != Ipv4Addr::new(127, 0, 0, 2) { warn!("Your ip is weird.."); } if PathBuf::from("/proc").join(pid.to_string()).exists() { // Another Firefox instance has already been started for this project // Add a buffer URL to force Firefox to open it in the already open instance args.push("about:newtab".to_owned()); } else { // This project does not yet have another Firefox instance // We do not need to add anything to the arguments, Firefox will open a new // instance. } } else { // There is no lock file and thus no instance already open. } }; process::Command::new("firefox") .args(args) .status() .context("Failed to start firefox")? }; if !status.success() { error!("Firefox run exited with error."); } if let Some(task) = tracking_task { task.stop(state) .with_context(|| format!("Failed to stop task {task}"))?; } if let Some(task) = old_task { task.start(state) .with_context(|| format!("Failed to start task {task}"))?; } if let Some(project) = old_project { project.activate().with_context(|| { format!("Failed to active project {}", project.to_project_display()) })?; } else { task::Project::clear().context("Failed to clear currently focused project")?; } Ok(()) }