1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use clap::{ArgMatches, Command};

use crate::{
    api::LocksReq,
    cli::cmds::locks::{common, common::SeaplaneLocksCommonArgMatches, CliCommand},
    context::{Ctx, LocksCtx},
    error::{CliError, CliErrorKind, Result},
    ops::locks::{self, ListedLock, LockName},
    printer::OutputFormat,
};

static OUTPUT_PAGE_SIZE: usize = 10;

static LONG_ABOUT: &str = "Get information around currently held locks.

There are 3 ways to list locks with this command:
- Omit the LOCK_NAME argument to list all locks
- Use a single lock name as the argument, without a trailing slash, this will list only that single lock
- Use a lock name followed by a trailing slash to list all locks under that directory

Locknames will be displayed in base64 encoded format by default because they may contain
arbitrary binary data. Using --decode to output the decoded values instead.";

#[derive(Copy, Clone, Debug)]
pub struct SeaplaneLocksList;

impl SeaplaneLocksList {
    pub fn command() -> Command<'static> {
        Command::new("list")
            .visible_alias("ls")
            .about("Get information around currently held locks")
            .long_about(LONG_ABOUT)
            .arg(
                arg!(lock_name = ["LOCK_NAME"] !required)
                    .help("The name of a lock. If omitted, all locks are shown. Append a trailing slash to list directory contents"),
            )
            .arg(common::base64().requires("lock_name"))
            .args(common::display_args())
    }
}

fn run_one_info(ctx: &mut Ctx) -> Result<()> {
    let locksctx = ctx.locks_ctx.get_or_init();
    let lock_name = locksctx.lock_name.as_ref().unwrap();
    let model_name = lock_name.to_model();

    let mut req = LocksReq::new(ctx)?;
    req.set_name(model_name)?;

    let resp = req.get_lock_info()?;
    let out = ListedLock::from(resp);

    match ctx.args.out_format {
        OutputFormat::Json => cli_println!("{}", serde_json::to_string(&out)?),
        OutputFormat::Table => locks::print_lock_table(!locksctx.no_header, vec![out], ctx)?,
    };

    Ok(())
}

/// Looks up all held locks within this directory, using the root directory if `dir_name` is None.
fn run_dir_info(ctx: &mut Ctx, dir_name: Option<LockName>) -> Result<()> {
    let mut last_key = None;
    let dir = dir_name.map(|d| d.to_model());
    let mut headers = !ctx.locks_ctx.get_or_init().no_header;
    let mut table_page = Vec::with_capacity(OUTPUT_PAGE_SIZE);

    loop {
        let mut req = LocksReq::new(ctx)?;
        let page = req.get_page(last_key, dir.clone())?;

        // We use the regular paging interface rather than
        // get_all_pages so that we don't have to store
        // all of the locks in memory at once.
        for info in page.infos {
            let out = ListedLock::from(info);
            match ctx.args.out_format {
                OutputFormat::Json => cli_println!("{}", serde_json::to_string(&out)?),
                OutputFormat::Table => {
                    table_page.push(out);
                    if table_page.len() >= OUTPUT_PAGE_SIZE {
                        locks::print_lock_table(headers, table_page.drain(..), ctx)?;
                        headers = false;
                    }
                }
            }
        }

        if let Some(next_key) = page.next {
            last_key = Some(next_key);
        } else {
            if !table_page.is_empty() {
                locks::print_lock_table(headers, table_page, ctx)?;
            }

            break;
        }
    }

    Ok(())
}

impl CliCommand for SeaplaneLocksList {
    fn run(&self, ctx: &mut Ctx) -> Result<()> {
        let locksctx = ctx.locks_ctx.get_or_init();

        // Check if the lock argument is root, directory or single lock
        match &locksctx.lock_name {
            // If there's no lock name given it's an "all locks" query
            None => run_dir_info(ctx, None),
            Some(name) => {
                // We need to at least peek at the final character of the decoded lock name to
                // determine if its a directory query or not.
                let mut decoded_lock_name = name
                    .name
                    .decoded()
                    .expect("decoding of a string we encoded shouldn't ever fail");

                if *decoded_lock_name
                    .last()
                    .expect("Lock name should hold something else it'd be None")
                    == b'/'
                {
                    // The SDK expects a lock name without the trailing slash for getting a
                    // directory, so we remove the `/`
                    decoded_lock_name.pop();
                    run_dir_info(ctx, Some(LockName::from_name_unencoded(decoded_lock_name)))
                } else {
                    run_one_info(ctx)
                }
            }
        }
    }

    fn update_ctx(&self, matches: &ArgMatches, ctx: &mut Ctx) -> Result<()> {
        ctx.locks_ctx
            .init(LocksCtx::from_locks_common(&SeaplaneLocksCommonArgMatches(matches))?);

        ctx.args.out_format = matches.get_one("format").copied().unwrap_or_default();
        let mut locksctx = ctx.locks_ctx.get_mut().unwrap();
        locksctx.base64 = matches.contains_id("base64");
        locksctx.decode = matches.contains_id("decode");
        locksctx.no_header = matches.contains_id("no-header");

        if locksctx.decode && ctx.args.out_format != OutputFormat::Table {
            let format_arg = format!("--format {}", ctx.args.out_format);
            return Err(CliError::from(CliErrorKind::ConflictingArguments(
                "--decode".to_owned(),
                format_arg,
            )));
        }

        Ok(())
    }
}