Skip to content

Commit

Permalink
refactor(catalog): better catalog and save
Browse files Browse the repository at this point in the history
  • Loading branch information
graelo committed Aug 18, 2022
1 parent 46c1c01 commit db73b72
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 42 deletions.
26 changes: 14 additions & 12 deletions src/actions/save.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,17 @@ use crate::{Report, Summary, PANES_DIR_NAME, SUMMARY_FILENAME};

/// Save the tmux sessions, windows and panes into a backup at `backup_dirpath`.
///
/// The provided directory will be created if necessary. Backups have a name similar to
/// `backup-20220731T222948.tar.zst`.
/// # Notes
///
/// - The `backup_dirpath` folder is assumed to exist (done during catalog initialization).
/// - Backups have a name similar to `backup-20220731T222948.tar.zst`.
///
/// The n-most recent backups are kept.
pub async fn save(backup_dirpath: &Path) -> Result<(PathBuf, Report)> {
fs::create_dir_all(&backup_dirpath).await?;

let new_backup_filepath = {
let timestamp_frag = Local::now().format("%Y%m%dT%H%M%S").to_string();
let backup_filename = format!("backup-{timestamp_frag}.tar.zst");
backup_dirpath.join(backup_filename)
};

// Prepare the temp directory.
let temp_dirpath = env::temp_dir().join("tmux-revive");
fs::create_dir_all(&temp_dirpath).await?;

// Save sessions & windows into `summary.yaml` in the temp folder.
let summary_task: task::JoinHandle<Result<(PathBuf, u16, u16)>> = {
let temp_dirpath = temp_dirpath.clone();

Expand All @@ -49,6 +43,7 @@ pub async fn save(backup_dirpath: &Path) -> Result<(PathBuf, Report)> {
})
};

// Save pane contents in the temp folder.
let (temp_panes_content_dir, num_panes) = {
let temp_panes_content_dir = temp_dirpath.join(PANES_DIR_NAME);
fs::create_dir_all(&temp_panes_content_dir).await?;
Expand All @@ -61,13 +56,20 @@ pub async fn save(backup_dirpath: &Path) -> Result<(PathBuf, Report)> {
};
let (temp_summary_filepath, num_sessions, num_windows) = summary_task.await?;

// Tar-compress content of temp folder into a new backup file in `backup_dirpath`.
let new_backup_filepath = {
let timestamp_frag = Local::now().format("%Y%m%dT%H%M%S").to_string();
let backup_filename = format!("backup-{timestamp_frag}.tar.zst");
backup_dirpath.join(backup_filename)
};

create_archive(
&new_backup_filepath,
&temp_summary_filepath,
&temp_panes_content_dir,
)?;

// fs::remove_dir_all(temp_panes_content_dir).await?;
// Cleanup the entire temp folder.
fs::remove_dir_all(temp_dirpath).await?;

let report = Report {
Expand Down
30 changes: 22 additions & 8 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,31 @@
use std::env;
use std::path::PathBuf;

use clap::{ArgAction, ArgGroup, Parser, Subcommand};
use clap::{ArgAction, ArgGroup, Parser, Subcommand, ValueEnum};

use crate::management::Strategy;

/// Which sublist to print.
#[derive(Debug, Clone, ValueEnum)]
pub enum SubList {
/// Retainable backups only.
Retainable,
/// Deletable backups only.
Deletable,
}

/// Catalog subcommands.
#[derive(Debug, Subcommand)]
pub enum CatalogSubcommand {
/// List all backups in the catalog to stdout.
/// List backups in the catalog to stdout.
///
/// The list indicates the outdated backups depending on the chosen backup strategy.
List,
/// If `--only deletable` or `--only retainable` are passed, print the corresponding list,
/// otherwise print all details in colored output.
List {
/// Only list deletable backups.
#[clap(long = "only", value_enum, value_parser)]
sublist: Option<SubList>,
},
}

/// Indicate whether to save (resp. restore) the Tmux sessions to (resp. from) a backup.
Expand All @@ -39,9 +53,9 @@ pub enum Command {

/// Restore the Tmux sessions.
///
/// Sessions, windows and panes geometry + content are read from the most recent backup inside
/// the backup folder. In that folder, the backup name is expected to be similar to
/// `backup-20220531T123456.tar.zst`.
/// Sessions, windows and panes geometry + content are read from the backup marked as "current"
/// (often the most recent backup) inside the backup folder. In that folder, the backup name is
/// expected to be similar to `backup-20220531T123456.tar.zst`.
///
/// If you run this command from the terminal, consider using the `--stdout` flag in order to
/// print the report in the terminal. Otherwise, if you run it via a Tmux keybinding, the
Expand All @@ -55,7 +69,7 @@ pub enum Command {

/// Operations on the catalog of backups.
Catalog {
/// List the backups in the catalog, indicating the outdated ones.
/// List the backups in the catalog, indicating the deletable ones.
#[clap(subcommand)]
command: CatalogSubcommand,
},
Expand Down
55 changes: 42 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use clap::Parser;

use tmux_revive::{
actions::save,
config::{CatalogSubcommand, Command, Config},
config::{CatalogSubcommand, Command, Config, SubList},
management::{Catalog, Plan},
tmux_display_message,
};
Expand Down Expand Up @@ -40,19 +40,48 @@ async fn run(config: Config) {
Command::Restore { .. } => unimplemented!(),

Command::Catalog { command } => match command {
CatalogSubcommand::List => {
println!(
"Catalog: {} backups in `{}`\n",
&catalog.size(),
&catalog.dirpath.to_string_lossy()
);
let Plan { to_remove, to_keep } = catalog.plan();
CatalogSubcommand::List { sublist } => {
let Plan {
deletable,
retainable,
} = catalog.plan();

for backup_path in to_remove {
println!("{} (outdated)", backup_path.to_string_lossy());
}
for backup_path in to_keep {
println!("{}", backup_path.to_string_lossy());
if let Some(sublist) = sublist {
match sublist {
SubList::Deletable => {
for backup_path in deletable.iter() {
println!("{}", backup_path.to_string_lossy());
}
}
SubList::Retainable => {
for backup_path in retainable.iter() {
println!("{}", backup_path.to_string_lossy());
}
}
}
} else {
println!("Catalog");
println!("- location: `{}`:", &catalog.location());
println!("- strategy: {}", &catalog.strategy);

let reset = "\u{001b}[0m";
let magenta = "\u{001b}[35m";
let green = "\u{001b}[32m";

println!("- deletable:");
for backup_path in deletable.iter() {
println!(" {magenta}{}{reset}", backup_path.to_string_lossy());
}
println!("- keep:");
for backup_path in retainable.iter() {
println!(" {green}{}{reset}", backup_path.to_string_lossy());
}
println!(
"\n{} backups: {} retainable, {} deletable",
&catalog.size(),
retainable.len(),
deletable.len(),
);
}
}
},
Expand Down
2 changes: 0 additions & 2 deletions src/management/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ impl Catalog {

backups.sort();

// let Plan { to_keep, to_remove } = strategy.plan(backup_files);

Ok(Catalog {
dirpath: dirpath.to_path_buf(),
strategy,
Expand Down
26 changes: 19 additions & 7 deletions src/management/compaction.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Compaction allows to keep the number of backup files under control.
use std::fmt;
use std::path::PathBuf;

/// Backups compaction strategy.
///
/// Determines if a backup should be kept or deleted.
#[derive(Debug, Clone)]
pub enum Strategy {
/// Keep the `k` most recent backups.
KeepMostRecent {
Expand Down Expand Up @@ -34,29 +36,39 @@ impl Strategy {
pub fn plan<'a>(&self, backup_files: &'a [PathBuf]) -> Plan<'a> {
match self {
Strategy::KeepMostRecent { k } => {
// let backup_files = backup_files.map(|p| *p).collect();
let index = std::cmp::max(0, backup_files.len() - k);
let (outdated_backups, recent_backups) = backup_files.split_at(index);

Plan {
to_remove: outdated_backups,
to_keep: recent_backups,
deletable: outdated_backups,
retainable: recent_backups,
}
}

Strategy::Classic => Plan {
to_remove: backup_files,
to_keep: backup_files,
deletable: backup_files,
retainable: backup_files,
},
}
}
}

impl fmt::Display for Strategy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Strategy::KeepMostRecent { k } => {
write!(f, "KeepMostRecent: {}", k)
}
Strategy::Classic => write!(f, "Classic"),
}
}
}

/// Describes what the strategy would do.
pub struct Plan<'a> {
/// List of backup files to delete.
pub to_remove: &'a [PathBuf],
pub deletable: &'a [PathBuf],

/// List of backup files to keep.
pub to_keep: &'a [PathBuf],
pub retainable: &'a [PathBuf],
}

0 comments on commit db73b72

Please sign in to comment.