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 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}