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