1use std::{
4 fs::create_dir_all,
5 path::{Path, PathBuf},
6};
7
8#[cfg(doc)]
9use alpm_pkginfo::PackageInfo;
10use alpm_types::PackageFileName;
11
12#[cfg(doc)]
13use crate::package::Package;
14use crate::{compression::CompressionSettings, input::PackageInput};
15
16#[derive(Clone, Debug)]
18pub struct OutputDir(PathBuf);
19
20impl OutputDir {
21 pub fn new(path: PathBuf) -> Result<Self, crate::Error> {
36 if !path.is_absolute() {
37 return Err(alpm_common::Error::NonAbsolutePaths {
38 paths: vec![path.clone()],
39 }
40 .into());
41 }
42
43 if !path.exists() {
44 create_dir_all(&path).map_err(|source| crate::Error::IoPath {
45 path: path.clone(),
46 context: "creating output directory",
47 source,
48 })?;
49 }
50
51 let metadata = path.metadata().map_err(|source| crate::Error::IoPath {
52 path: path.clone(),
53 context: "retrieving metadata",
54 source,
55 })?;
56
57 if !metadata.is_dir() {
58 return Err(alpm_common::Error::NotADirectory { path: path.clone() }.into());
59 }
60
61 if metadata.permissions().readonly() {
62 return Err(crate::Error::PathIsReadOnly { path: path.clone() });
63 }
64
65 Ok(Self(path))
66 }
67
68 pub fn as_path(&self) -> &Path {
70 self.0.as_path()
71 }
72
73 pub fn to_path_buf(&self) -> PathBuf {
75 self.0.to_path_buf()
76 }
77
78 pub fn join(&self, path: impl AsRef<Path>) -> PathBuf {
80 self.0.join(path)
81 }
82}
83
84impl AsRef<Path> for OutputDir {
85 fn as_ref(&self) -> &Path {
86 &self.0
87 }
88}
89
90#[derive(Clone, Debug)]
98pub struct PackageCreationConfig {
99 package_input: PackageInput,
100 output_dir: OutputDir,
101 compression: Option<CompressionSettings>,
102}
103
104impl PackageCreationConfig {
105 pub fn new(
115 package_input: PackageInput,
116 output_dir: OutputDir,
117 compression: Option<CompressionSettings>,
118 ) -> Result<Self, crate::Error> {
119 if package_input.input_dir() == output_dir.as_path() {
120 return Err(crate::Error::InputDirIsOutputDir {
121 path: package_input.input_dir().to_path_buf(),
122 });
123 }
124 if output_dir.as_path().starts_with(package_input.input_dir()) {
125 return Err(crate::Error::OutputDirInInputDir {
126 input_path: package_input.input_dir().to_path_buf(),
127 output_path: output_dir.to_path_buf(),
128 });
129 }
130 if package_input.input_dir().starts_with(output_dir.as_path()) {
131 return Err(crate::Error::InputDirInOutputDir {
132 input_path: package_input.input_dir().to_path_buf(),
133 output_path: output_dir.to_path_buf(),
134 });
135 }
136
137 Ok(Self {
138 compression,
139 package_input,
140 output_dir,
141 })
142 }
143
144 pub fn package_input(&self) -> &PackageInput {
146 &self.package_input
147 }
148
149 pub fn output_dir(&self) -> &OutputDir {
151 &self.output_dir
152 }
153
154 pub fn compression(&self) -> Option<&CompressionSettings> {
156 self.compression.as_ref()
157 }
158}
159
160impl TryFrom<&PackageCreationConfig> for PackageFileName {
161 type Error = crate::Error;
162
163 fn try_from(value: &PackageCreationConfig) -> Result<Self, Self::Error> {
169 Ok(Self::new(
170 match value.package_input.package_info() {
171 alpm_pkginfo::PackageInfo::V1(package_info) => package_info.pkgname().clone(),
172 alpm_pkginfo::PackageInfo::V2(package_info) => package_info.pkgname().clone(),
173 },
174 match value.package_input.package_info() {
175 alpm_pkginfo::PackageInfo::V1(package_info) => package_info.pkgver().clone(),
176 alpm_pkginfo::PackageInfo::V2(package_info) => package_info.pkgver().clone(),
177 },
178 match value.package_input.package_info() {
179 alpm_pkginfo::PackageInfo::V1(package_info) => *package_info.arch(),
180 alpm_pkginfo::PackageInfo::V2(package_info) => *package_info.arch(),
181 },
182 value.compression.as_ref().map(|settings| settings.into()),
183 )?)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use std::fs::File;
190
191 use tempfile::tempdir;
192 use testresult::TestResult;
193
194 use super::*;
195
196 #[test]
198 fn output_dir_new_creates_dir() -> TestResult {
199 let temp_dir = tempdir()?;
200 let non_existing_path = temp_dir.path().join("non-existing");
201 if let Err(error) = OutputDir::new(non_existing_path) {
202 return Err(format!("Failed although it should have succeeded:\n{error}").into());
203 }
204
205 Ok(())
206 }
207
208 #[test]
210 fn output_dir_new_fails() -> TestResult {
211 assert!(matches!(
212 OutputDir::new(PathBuf::from("test")),
213 Err(crate::Error::AlpmCommon(
214 alpm_common::Error::NonAbsolutePaths { paths: _ }
215 ))
216 ));
217
218 let temp_dir = tempdir()?;
219 let file_path = temp_dir.path().join("non-existing");
220 let _file = File::create(&file_path)?;
221 assert!(matches!(
222 OutputDir::new(file_path),
223 Err(crate::Error::AlpmCommon(
224 alpm_common::Error::NotADirectory { path: _ }
225 ))
226 ));
227
228 Ok(())
229 }
230
231 #[test]
233 fn output_dir_as_ref() -> TestResult {
234 let temp_dir = tempdir()?;
235 let path = temp_dir.path();
236
237 let output_dir = OutputDir::new(path.to_path_buf())?;
238
239 assert_eq!(output_dir.as_ref(), path);
240
241 Ok(())
242 }
243}