nuts_tool/cli/archive/
list.rs

1// MIT License
2//
3// Copyright (c) 2023,2024 Robin Doer
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23use anyhow::Result;
24use clap::{ArgAction, Args};
25use log::debug;
26use nuts_archive::{Entry, Group};
27use std::cmp;
28use std::fmt::{self, Write};
29
30use crate::backend::PluginBackend;
31use crate::cli::archive::open_archive;
32use crate::say;
33use crate::time::TimeFormat;
34
35const SIZE_WIDTH: usize = 9;
36
37struct Type<'a>(&'a Entry<'a, PluginBackend>);
38
39impl<'a> fmt::Display for Type<'a> {
40    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
41        match self.0 {
42            Entry::File(_) => fmt.write_char('-'),
43            Entry::Directory(_) => fmt.write_char('d'),
44            Entry::Symlink(_) => fmt.write_char('l'),
45        }
46    }
47}
48
49struct Permission<'a>(&'a Entry<'a, PluginBackend>, Group);
50
51impl<'a> fmt::Display for Permission<'a> {
52    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
53        if self.0.can_read(self.1) {
54            fmt.write_char('r')?;
55        } else {
56            fmt.write_char('-')?;
57        };
58
59        if self.0.can_write(self.1) {
60            fmt.write_char('w')?;
61        } else {
62            fmt.write_char('-')?;
63        };
64
65        if self.0.can_execute(self.1) {
66            fmt.write_char('x')?;
67        } else {
68            fmt.write_char('-')?;
69        };
70
71        Ok(())
72    }
73}
74
75struct Name<'a>(&'a Entry<'a, PluginBackend>);
76
77impl<'a> fmt::Display for Name<'a> {
78    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
79        if let Some(symlink) = self.0.as_symlink() {
80            write!(fmt, "{} -> {}", self.0.name(), symlink.target())
81        } else {
82            write!(fmt, "{}", self.0.name())
83        }
84    }
85}
86
87#[derive(Debug)]
88struct PrintLongContext {
89    size_width: usize,
90}
91
92impl Default for PrintLongContext {
93    fn default() -> Self {
94        Self {
95            size_width: SIZE_WIDTH,
96        }
97    }
98}
99
100#[derive(Args, Debug)]
101pub struct ArchiveListArgs {
102    /// Lists the content in the long format
103    #[clap(short, long, action = ArgAction::SetTrue)]
104    long: bool,
105
106    /// Use appended time instead of last modification for long printing
107    #[clap(short, long, action = ArgAction::SetTrue)]
108    appended: bool,
109
110    /// Use creation time instead of last modification for long printing
111    #[clap(short = 'r', long, action = ArgAction::SetTrue)]
112    created: bool,
113
114    /// Use change time instead of last modification for long printing
115    #[clap(short = 'n', long, action = ArgAction::SetTrue)]
116    changed: bool,
117
118    /// Specifies the format used for timestamps
119    #[clap(
120        short,
121        long,
122        value_parser,
123        value_name = "FORMAT",
124        default_value = "local"
125    )]
126    time_format: TimeFormat,
127
128    /// Starts the migration when the container/archive is opened
129    #[clap(long, action = ArgAction::SetTrue)]
130    pub migrate: bool,
131
132    /// Specifies the name of the container
133    #[clap(short, long, env = "NUTS_CONTAINER")]
134    container: String,
135}
136
137impl ArchiveListArgs {
138    fn print_short(&self, entry: &Entry<PluginBackend>) {
139        say!("{}", entry.name());
140    }
141
142    fn print_long(&self, entry: &Entry<PluginBackend>, ctx: &mut PrintLongContext) {
143        let tstamp = if self.appended {
144            entry.appended()
145        } else if self.created {
146            entry.created()
147        } else if self.changed {
148            entry.changed()
149        } else {
150            entry.modified()
151        };
152
153        let size = entry.size().to_string();
154
155        ctx.size_width = cmp::max(ctx.size_width, size.len());
156
157        say!(
158            "{}{}{}{} {:>size_width$} {} {}",
159            Type(entry),
160            Permission(entry, Group::User),
161            Permission(entry, Group::Group),
162            Permission(entry, Group::Other),
163            size,
164            self.time_format.format(tstamp, "%d %b %H:%M"),
165            Name(entry),
166            size_width = ctx.size_width,
167        );
168    }
169
170    pub fn run(&self) -> Result<()> {
171        debug!("args: {:?}", self);
172
173        let mut archive = open_archive(&self.container, self.migrate)?;
174
175        let mut entry_opt = archive.first();
176        let mut ctx_opt = None;
177
178        loop {
179            match entry_opt {
180                Some(Ok(entry)) => {
181                    if self.long {
182                        let ctx = ctx_opt.get_or_insert_with(Default::default);
183                        self.print_long(&entry, ctx);
184                    } else {
185                        self.print_short(&entry);
186                    }
187
188                    entry_opt = entry.next();
189                }
190                Some(Err(err)) => return Err(err.into()),
191                None => break,
192            }
193        }
194
195        Ok(())
196    }
197}