sqlarfs_cli/
command.rs

1use std::io::Write;
2use std::path::{Path, PathBuf};
3
4use sqlarfs::{ArchiveOptions, Connection, ExtractOptions, ListOptions};
5
6use super::cli::{Archive, Cli, Commands, Create, Extract, List, Remove};
7
8const SQLAR_EXTENSION: &str = "sqlar";
9
10fn file_name(path: &Path) -> Option<&Path> {
11    path.file_name()
12        .map(Path::new)
13        .or_else(|| path.parent().and_then(|p| p.file_name().map(Path::new)))
14}
15
16impl Create {
17    pub fn run(&self) -> eyre::Result<()> {
18        let archive_filename = if self.source.is_empty() {
19            self.archive.clone().ok_or(sqlarfs::Error::InvalidArgs {
20                reason: String::from("When no files are being added to the archive, the archive path must be specified."),
21            })?
22        } else if self.source.len() == 1 {
23            let source_filename =
24                file_name(&self.source[0]).ok_or(sqlarfs::Error::InvalidArgs {
25                    reason: String::from("The source path must have a filename."),
26                })?;
27
28            self.archive.to_owned().unwrap_or_else(|| {
29                let mut filename = source_filename.to_owned();
30                filename.set_extension(SQLAR_EXTENSION);
31                filename
32            })
33        } else {
34            self.archive.clone().ok_or(sqlarfs::Error::InvalidArgs {
35                reason: String::from(
36                    "When archiving multiple files, the archive path must be specified.",
37                ),
38            })?
39        };
40
41        let mut conn = Connection::create_new(archive_filename)?;
42
43        let opts = ArchiveOptions::new()
44            .follow_symlinks(self.follow)
45            .recursive(!self.no_recursive)
46            .preserve_metadata(!self.no_preserve)
47            .children(false);
48
49        conn.exec(|archive| {
50            for source_path in &self.source {
51                let source_filename =
52                    file_name(source_path).ok_or(sqlarfs::Error::InvalidArgs {
53                        reason: String::from("The source path must have a filename."),
54                    })?;
55
56                archive.archive_with(source_path, source_filename, &opts)?;
57            }
58
59            sqlarfs::Result::Ok(())
60        })?;
61
62        Ok(())
63    }
64}
65
66impl Extract {
67    pub fn run(&self) -> eyre::Result<()> {
68        let mut conn = Connection::open(&self.archive)?;
69
70        conn.exec(|archive| {
71            if self.source.is_empty() {
72                archive.extract_with(
73                    "",
74                    &self.dest,
75                    &ExtractOptions::new()
76                        .children(true)
77                        .recursive(!self.no_recursive),
78                )?;
79            }
80
81            for path in &self.source {
82                let file_name = path.file_name().ok_or(sqlarfs::Error::InvalidArgs {
83                    reason: format!(
84                        "The source path must have a filename: {}",
85                        path.to_string_lossy()
86                    ),
87                })?;
88
89                archive.extract_with(
90                    path,
91                    self.dest.join(file_name),
92                    &ExtractOptions::new()
93                        .children(false)
94                        .recursive(!self.no_recursive),
95                )?;
96            }
97
98            sqlarfs::Result::Ok(())
99        })?;
100
101        Ok(())
102    }
103}
104
105impl Archive {
106    pub fn run(&self) -> eyre::Result<()> {
107        let mut conn = Connection::open(&self.archive)?;
108
109        let opts = ArchiveOptions::new()
110            .follow_symlinks(self.follow)
111            .recursive(!self.no_recursive)
112            .preserve_metadata(!self.no_preserve)
113            .children(false);
114
115        conn.exec(|archive| {
116            let dest_path = if let Some(dest) = &self.dest {
117                dest
118            } else {
119                file_name(&self.source).ok_or(sqlarfs::Error::InvalidArgs {
120                    reason: String::from("The source path must have a filename."),
121                })?
122            };
123
124            if let Some(parent) = dest_path.parent() {
125                if parent != Path::new("") {
126                    archive.open(parent)?.create_dir_all()?;
127                }
128            }
129
130            archive.archive_with(&self.source, dest_path, &opts)?;
131
132            sqlarfs::Result::Ok(())
133        })?;
134
135        Ok(())
136    }
137}
138
139impl List {
140    pub fn run(&self, mut stdout: impl Write) -> eyre::Result<()> {
141        let mut conn = Connection::open(&self.archive)?;
142
143        // We always sort by depth.
144        let mut opts = ListOptions::new().by_depth();
145
146        if self.children {
147            opts = opts.children_of(self.parent.as_ref().unwrap_or(&PathBuf::from("")));
148        } else if self.tree {
149            opts = opts.descendants_of(self.parent.as_ref().unwrap_or(&PathBuf::from("")));
150        } else {
151            panic!("The `list` command must have either the --children or --tree flag set. This is a bug.");
152        }
153
154        if let Some(kind) = self.r#type {
155            opts = opts.file_type(kind.into());
156        }
157
158        conn.exec(|archive| {
159            for entry in archive.list_with(&opts)? {
160                writeln!(stdout, "{}", entry?.path().to_string_lossy())?;
161            }
162
163            sqlarfs::Result::Ok(())
164        })?;
165
166        Ok(())
167    }
168}
169
170impl Remove {
171    pub fn run(&self) -> eyre::Result<()> {
172        let mut conn = Connection::open(&self.archive)?;
173
174        conn.exec(|archive| archive.open(&self.path)?.delete())?;
175
176        Ok(())
177    }
178}
179
180impl Cli {
181    pub fn dispatch(&self, stdout: impl Write) -> eyre::Result<()> {
182        match &self.command {
183            Commands::Create(create) => create.run(),
184            Commands::Extract(extract) => extract.run(),
185            Commands::Archive(archive) => archive.run(),
186            Commands::List(list) => list.run(stdout),
187            Commands::Remove(remove) => remove.run(),
188        }
189    }
190}