use std::path::{Path, PathBuf};
use async_fs as fs;
use futures::future::join_all;
use smol;
use tempfile::TempDir;
use crate::{Result, management::archive::v1, tmux};
use tmux_lib::utils;
const DETECTED_SHELLS: &[&str] = &["zsh", "bash", "fish"];
pub async fn save<P: AsRef<Path>>(
backup_dirpath: P,
num_lines_to_drop: usize,
) -> Result<(PathBuf, v1::Overview)> {
let temp_dir = TempDir::new()?;
let metadata_task: smol::Task<Result<(PathBuf, PathBuf, u16, u16)>> = {
let temp_dirpath = temp_dir.path().to_path_buf();
smol::spawn(async move {
let temp_version_filepath = temp_dirpath.join(v1::VERSION_FILENAME);
fs::write(&temp_version_filepath, v1::FORMAT_VERSION).await?;
let metadata = v1::Metadata::new().await?;
let json = serde_json::to_string(&metadata)?;
let temp_metadata_filepath = temp_dirpath.join(v1::METADATA_FILENAME);
fs::write(temp_metadata_filepath.as_path(), json).await?;
Ok((
temp_version_filepath,
temp_metadata_filepath,
metadata.sessions.len() as u16,
metadata.windows.len() as u16,
))
})
};
let (temp_panes_content_dir, num_panes) = {
let temp_panes_content_dir = temp_dir.path().join(v1::PANES_DIR_NAME);
fs::create_dir_all(&temp_panes_content_dir).await?;
let panes = tmux::pane::available_panes().await?;
let num_panes = panes.len() as u16;
save_panes_content(panes, &temp_panes_content_dir, num_lines_to_drop).await?;
(temp_panes_content_dir, num_panes)
};
let (temp_version_filepath, temp_metadata_filepath, num_sessions, num_windows) =
metadata_task.await?;
let new_backup_filepath = v1::new_backup_filepath(backup_dirpath.as_ref());
v1::create_from_paths(
&new_backup_filepath,
&temp_version_filepath,
&temp_metadata_filepath,
&temp_panes_content_dir,
)?;
temp_dir.close()?;
let overview = v1::Overview {
version: v1::FORMAT_VERSION.to_string(),
num_sessions,
num_windows,
num_panes,
};
Ok((new_backup_filepath, overview))
}
fn is_shell_command(command: &str) -> bool {
DETECTED_SHELLS.contains(&command)
}
fn lines_to_drop_for_pane(pane_command: &str, num_lines_to_drop: usize) -> usize {
if is_shell_command(pane_command) {
num_lines_to_drop
} else {
0
}
}
async fn save_panes_content<P: AsRef<Path>>(
panes: Vec<tmux::pane::Pane>,
destination_dir: P,
num_lines_to_drop: usize,
) -> Result<()> {
let mut handles = Vec::new();
for pane in panes {
let dest_dir = destination_dir.as_ref().to_path_buf();
let drop_n_last_lines = lines_to_drop_for_pane(&pane.command, num_lines_to_drop);
let handle = smol::spawn(async move {
let stdout = pane.capture().await.unwrap();
let cleaned_buffer = utils::cleanup_captured_buffer(&stdout, drop_n_last_lines);
let filename = format!("pane-{}.txt", pane.id);
let filepath = dest_dir.join(filename);
fs::write(filepath, cleaned_buffer).await
});
handles.push(handle);
}
join_all(handles).await;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
mod shell_detection {
use super::*;
#[test]
fn recognizes_zsh() {
assert!(is_shell_command("zsh"));
}
#[test]
fn recognizes_bash() {
assert!(is_shell_command("bash"));
}
#[test]
fn recognizes_fish() {
assert!(is_shell_command("fish"));
}
#[test]
fn rejects_vim() {
assert!(!is_shell_command("vim"));
}
#[test]
fn rejects_nvim() {
assert!(!is_shell_command("nvim"));
}
#[test]
fn rejects_python() {
assert!(!is_shell_command("python"));
}
#[test]
fn rejects_empty_command() {
assert!(!is_shell_command(""));
}
#[test]
fn rejects_similar_but_different() {
assert!(!is_shell_command("zsh-5.9"));
assert!(!is_shell_command("/bin/zsh"));
assert!(!is_shell_command("bash-5.2"));
}
#[test]
fn case_sensitive() {
assert!(!is_shell_command("ZSH"));
assert!(!is_shell_command("BASH"));
assert!(!is_shell_command("Fish"));
}
}
mod lines_to_drop {
use super::*;
#[test]
fn drops_lines_for_shells() {
assert_eq!(lines_to_drop_for_pane("zsh", 2), 2);
assert_eq!(lines_to_drop_for_pane("bash", 3), 3);
assert_eq!(lines_to_drop_for_pane("fish", 1), 1);
}
#[test]
fn zero_drop_for_non_shells() {
assert_eq!(lines_to_drop_for_pane("vim", 5), 0);
assert_eq!(lines_to_drop_for_pane("python", 10), 0);
assert_eq!(lines_to_drop_for_pane("htop", 3), 0);
}
#[test]
fn zero_requested_means_zero_dropped() {
assert_eq!(lines_to_drop_for_pane("zsh", 0), 0);
assert_eq!(lines_to_drop_for_pane("bash", 0), 0);
}
}
mod constants {
use super::*;
#[test]
fn detected_shells_includes_common_shells() {
assert!(DETECTED_SHELLS.contains(&"zsh"));
assert!(DETECTED_SHELLS.contains(&"bash"));
assert!(DETECTED_SHELLS.contains(&"fish"));
}
}
}