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 From<&PackageCreationConfig> for PackageFileName {
161 fn from(value: &PackageCreationConfig) -> Self {
163 Self::new(
164 match value.package_input.package_info() {
165 alpm_pkginfo::PackageInfo::V1(package_info) => package_info.pkgname().clone(),
166 alpm_pkginfo::PackageInfo::V2(package_info) => package_info.pkgname().clone(),
167 },
168 match value.package_input.package_info() {
169 alpm_pkginfo::PackageInfo::V1(package_info) => package_info.pkgver().clone(),
170 alpm_pkginfo::PackageInfo::V2(package_info) => package_info.pkgver().clone(),
171 },
172 match value.package_input.package_info() {
173 alpm_pkginfo::PackageInfo::V1(package_info) => *package_info.arch(),
174 alpm_pkginfo::PackageInfo::V2(package_info) => *package_info.arch(),
175 },
176 value.compression.as_ref().map(|settings| settings.into()),
177 )
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use std::fs::File;
184
185 use tempfile::tempdir;
186 use testresult::TestResult;
187
188 use super::*;
189
190 #[test]
192 fn output_dir_new_creates_dir() -> TestResult {
193 let temp_dir = tempdir()?;
194 let non_existing_path = temp_dir.path().join("non-existing");
195 if let Err(error) = OutputDir::new(non_existing_path) {
196 return Err(format!("Failed although it should have succeeded:\n{error}").into());
197 }
198
199 Ok(())
200 }
201
202 #[test]
204 fn output_dir_new_fails() -> TestResult {
205 assert!(matches!(
206 OutputDir::new(PathBuf::from("test")),
207 Err(crate::Error::AlpmCommon(
208 alpm_common::Error::NonAbsolutePaths { paths: _ }
209 ))
210 ));
211
212 let temp_dir = tempdir()?;
213 let file_path = temp_dir.path().join("non-existing");
214 let _file = File::create(&file_path)?;
215 assert!(matches!(
216 OutputDir::new(file_path),
217 Err(crate::Error::AlpmCommon(
218 alpm_common::Error::NotADirectory { path: _ }
219 ))
220 ));
221
222 Ok(())
223 }
224
225 #[test]
227 fn output_dir_as_ref() -> TestResult {
228 let temp_dir = tempdir()?;
229 let path = temp_dir.path();
230
231 let output_dir = OutputDir::new(path.to_path_buf())?;
232
233 assert_eq!(output_dir.as_ref(), path);
234
235 Ok(())
236 }
237}