aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-05 01:22:46 +0900
committernsfisis <nsfisis@gmail.com>2026-06-05 01:22:46 +0900
commitadf14510b00929aaee85ccb8dedf9164878a0164 (patch)
treec540e711155ee7bc65d2ac8b1a5ebdcbc33c7320 /crates/shirabe/src
parentc5bcf222f98d13b104231713bf4a0aa0833c420a (diff)
downloadphp-shirabe-adf14510b00929aaee85ccb8dedf9164878a0164.tar.gz
php-shirabe-adf14510b00929aaee85ccb8dedf9164878a0164.tar.zst
php-shirabe-adf14510b00929aaee85ccb8dedf9164878a0164.zip
feat(downloader): wire ArchiveDownloader extraction and Path/Zip overrides
Implement ArchiveDownloader for Zip/Tar/Gzip/Xz/Phar/Rar so install() extracts the archive (extract + rename) instead of doing FileDownloader's plain file rename, and route install/prepare/cleanup through the mixin. ArchiveDownloader::extract becomes &mut self to match the concrete implementations. Route ZipDownloader's bespoke download() (unzip-command static init) and PathDownloader's symlink/junction/mirror download/install/remove through the DownloaderInterface trait (path: String -> &str). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src')
-rw-r--r--crates/shirabe/src/downloader/archive_downloader.rs2
-rw-r--r--crates/shirabe/src/downloader/gzip_downloader.rs58
-rw-r--r--crates/shirabe/src/downloader/path_downloader.rs452
-rw-r--r--crates/shirabe/src/downloader/phar_downloader.rs34
-rw-r--r--crates/shirabe/src/downloader/rar_downloader.rs30
-rw-r--r--crates/shirabe/src/downloader/tar_downloader.rs34
-rw-r--r--crates/shirabe/src/downloader/xz_downloader.rs32
-rw-r--r--crates/shirabe/src/downloader/zip_downloader.rs317
8 files changed, 497 insertions, 462 deletions
diff --git a/crates/shirabe/src/downloader/archive_downloader.rs b/crates/shirabe/src/downloader/archive_downloader.rs
index f2ec719..61ecfd1 100644
--- a/crates/shirabe/src/downloader/archive_downloader.rs
+++ b/crates/shirabe/src/downloader/archive_downloader.rs
@@ -24,7 +24,7 @@ pub trait ArchiveDownloader {
fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool>;
async fn extract(
- &self,
+ &mut self,
package: PackageInterfaceHandle,
file: &str,
path: &str,
diff --git a/crates/shirabe/src/downloader/gzip_downloader.rs b/crates/shirabe/src/downloader/gzip_downloader.rs
index 0c47b67..05ac244 100644
--- a/crates/shirabe/src/downloader/gzip_downloader.rs
+++ b/crates/shirabe/src/downloader/gzip_downloader.rs
@@ -50,7 +50,39 @@ impl GzipDownloader {
}
}
- pub(crate) async fn extract(
+ fn extract_using_ext(&self, file: &str, target_filepath: &str) {
+ let archive_file = gzopen(file, "rb");
+ let target_file = fopen(target_filepath, "wb");
+ loop {
+ let string = gzread(archive_file.clone(), 4096);
+ if string.is_empty() {
+ break;
+ }
+ fwrite(target_file.clone(), &string, Platform::strlen(&string));
+ }
+ gzclose(archive_file);
+ fclose(target_file);
+ }
+}
+
+impl ArchiveDownloader for GzipDownloader {
+ fn inner(&self) -> &FileDownloader {
+ &self.inner
+ }
+
+ fn inner_mut(&mut self) -> &mut FileDownloader {
+ &mut self.inner
+ }
+
+ fn cleanup_executed(&self) -> &IndexMap<String, bool> {
+ &self.cleanup_executed
+ }
+
+ fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> {
+ &mut self.cleanup_executed
+ }
+
+ async fn extract(
&mut self,
package: PackageInterfaceHandle,
file: &str,
@@ -114,20 +146,6 @@ impl GzipDownloader {
Ok(None)
}
-
- fn extract_using_ext(&self, file: &str, target_filepath: &str) {
- let archive_file = gzopen(file, "rb");
- let target_file = fopen(target_filepath, "wb");
- loop {
- let string = gzread(archive_file.clone(), 4096);
- if string.is_empty() {
- break;
- }
- fwrite(target_file.clone(), &string, Platform::strlen(&string));
- }
- gzclose(archive_file);
- fclose(target_file);
- }
}
impl ChangeReportInterface for GzipDownloader {
@@ -171,9 +189,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .prepare(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await
}
async fn install(
@@ -182,7 +198,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader {
path: &str,
output: bool,
) -> Result<Option<PhpMixed>> {
- self.inner.install(package, path, output).await
+ <Self as ArchiveDownloader>::install(self, package, path, output).await
}
async fn update(
@@ -210,8 +226,6 @@ impl crate::downloader::DownloaderInterface for GzipDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .cleanup(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await
}
}
diff --git a/crates/shirabe/src/downloader/path_downloader.rs b/crates/shirabe/src/downloader/path_downloader.rs
index 99d72e7..a215bb3 100644
--- a/crates/shirabe/src/downloader/path_downloader.rs
+++ b/crates/shirabe/src/downloader/path_downloader.rs
@@ -62,14 +62,191 @@ impl PathDownloader {
}
}
- pub async fn download(
+ pub fn get_vcs_reference(&self, package: PackageInterfaceHandle, path: &str) -> Option<String> {
+ let path = Filesystem::trim_trailing_slash(path);
+ let parser = VersionParser::new();
+ let mut guesser = VersionGuesser::new(
+ self.inner.config.clone(),
+ self.inner.process.clone(),
+ parser.clone(),
+ Some(self.inner.io.clone()),
+ );
+ let dumper = ArrayDumper::new();
+
+ let package_config = dumper.dump(package.clone());
+ let package_version = guesser.guess_version(&package_config, &path);
+ if let Ok(Some(version)) = package_version {
+ return version.commit;
+ }
+
+ None
+ }
+
+ pub(crate) fn get_install_operation_appendix(
+ &self,
+ package: PackageInterfaceHandle,
+ path: &str,
+ ) -> Result<String> {
+ let url = package.get_dist_url().ok_or_else(|| RuntimeException {
+ message: format!(
+ "The package {} has no dist url configured, cannot install.",
+ package.get_pretty_name()
+ ),
+ code: 0,
+ })?;
+ let real_url = realpath(&url).ok_or_else(|| RuntimeException {
+ message: format!("Failed to realpath {}", url),
+ code: 0,
+ })?;
+
+ if realpath(path).as_deref() == Some(&real_url) {
+ return Ok(": Source already present".to_string());
+ }
+
+ let (current_strategy, _) =
+ self.compute_allowed_strategies(&package.get_transport_options())?;
+
+ if current_strategy == Self::STRATEGY_SYMLINK {
+ if Platform::is_windows() {
+ return Ok(format!(
+ ": Junctioning from {}",
+ package.get_dist_url().unwrap_or_default()
+ ));
+ }
+
+ return Ok(format!(
+ ": Symlinking from {}",
+ package.get_dist_url().unwrap_or_default()
+ ));
+ }
+
+ Ok(format!(
+ ": Mirroring from {}",
+ package.get_dist_url().unwrap_or_default()
+ ))
+ }
+
+ fn compute_allowed_strategies(
+ &self,
+ transport_options: &IndexMap<String, PhpMixed>,
+ ) -> Result<(i64, Vec<i64>)> {
+ // When symlink transport option is null, both symlink and mirror are allowed
+ let mut current_strategy = Self::STRATEGY_SYMLINK;
+ let mut allowed_strategies = vec![Self::STRATEGY_SYMLINK, Self::STRATEGY_MIRROR];
+
+ let mirror_path_repos = Platform::get_env("COMPOSER_MIRROR_PATH_REPOS");
+ if mirror_path_repos.map_or(false, |v| !v.is_empty()) {
+ current_strategy = Self::STRATEGY_MIRROR;
+ }
+
+ let symlink_option = transport_options.get("symlink");
+
+ match symlink_option {
+ Some(PhpMixed::Bool(true)) => {
+ current_strategy = Self::STRATEGY_SYMLINK;
+ allowed_strategies = vec![Self::STRATEGY_SYMLINK];
+ }
+ Some(PhpMixed::Bool(false)) => {
+ current_strategy = Self::STRATEGY_MIRROR;
+ allowed_strategies = vec![Self::STRATEGY_MIRROR];
+ }
+ _ => {}
+ }
+
+ // Check we can use junctions safely if we are on Windows
+ if Platform::is_windows()
+ && Self::STRATEGY_SYMLINK == current_strategy
+ && !self.safe_junctions()
+ {
+ if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) {
+ return Err(RuntimeException {
+ message: "You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(),
+ code: 0,
+ }
+ .into());
+ }
+ current_strategy = Self::STRATEGY_MIRROR;
+ allowed_strategies = vec![Self::STRATEGY_MIRROR];
+ }
+
+ // Check we can use symlink() otherwise
+ if !Platform::is_windows()
+ && Self::STRATEGY_SYMLINK == current_strategy
+ && !function_exists("symlink")
+ {
+ if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) {
+ return Err(RuntimeException {
+ message: "Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(),
+ code: 0,
+ }
+ .into());
+ }
+ current_strategy = Self::STRATEGY_MIRROR;
+ allowed_strategies = vec![Self::STRATEGY_MIRROR];
+ }
+
+ Ok((current_strategy, allowed_strategies))
+ }
+
+ // Returns true if junctions can be created and safely used on Windows.
+ //
+ // A PHP bug makes junction detection fragile, leading to possible data loss when removing a
+ // package. See https://bugs.php.net/bug.php?id=77552
+ //
+ // For safety we require a minimum version of Windows 7, so we can call the system rmdir which
+ // will preserve target content if given a junction.
+ //
+ // The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7).
+ fn safe_junctions(&self) -> bool {
+ // We need to call mklink, and rmdir on Windows 7 (version 6.1)
+ function_exists("proc_open")
+ && (PHP_WINDOWS_VERSION_MAJOR > 6
+ || (PHP_WINDOWS_VERSION_MAJOR == 6 && PHP_WINDOWS_VERSION_MINOR >= 1))
+ }
+}
+
+impl VcsCapableDownloaderInterface for PathDownloader {
+ fn get_vcs_reference(&self, package: PackageInterfaceHandle, path: String) -> Option<String> {
+ PathDownloader::get_vcs_reference(self, package, &path)
+ }
+}
+
+impl crate::downloader::ChangeReportInterface for PathDownloader {
+ fn get_local_changes(
+ &mut self,
+ package: PackageInterfaceHandle,
+ path: &str,
+ ) -> anyhow::Result<Option<String>> {
+ self.inner.get_local_changes(package, path)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl DownloaderInterface for PathDownloader {
+ fn get_installation_source(&self) -> String {
+ self.inner.get_installation_source()
+ }
+
+ fn as_change_report_interface(
+ &mut self,
+ ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> {
+ Some(self)
+ }
+
+ fn as_vcs_capable_downloader_interface(
+ &self,
+ ) -> Option<&dyn crate::downloader::VcsCapableDownloaderInterface> {
+ Some(self)
+ }
+
+ async fn download(
&mut self,
package: PackageInterfaceHandle,
- path: String,
- _prev_package: Option<PackageInterfaceHandle>,
- _output: bool,
+ path: &str,
+ prev_package: Option<PackageInterfaceHandle>,
+ output: bool,
) -> Result<Option<PhpMixed>> {
- let path = Filesystem::trim_trailing_slash(&path);
+ let path = Filesystem::trim_trailing_slash(path);
let url = package.get_dist_url().ok_or_else(|| RuntimeException {
message: format!(
"The package {} has no dist url configured, cannot download.",
@@ -124,13 +301,25 @@ impl PathDownloader {
Ok(None)
}
- pub async fn install(
+ async fn prepare(
+ &mut self,
+ r#type: &str,
+ package: PackageInterfaceHandle,
+ path: &str,
+ prev_package: Option<PackageInterfaceHandle>,
+ ) -> Result<Option<PhpMixed>> {
+ self.inner
+ .prepare(r#type, package, path, prev_package)
+ .await
+ }
+
+ async fn install(
&mut self,
package: PackageInterfaceHandle,
- path: String,
+ path: &str,
output: bool,
) -> Result<Option<PhpMixed>> {
- let path = Filesystem::trim_trailing_slash(&path);
+ let path = Filesystem::trim_trailing_slash(path);
let url = package.get_dist_url().ok_or_else(|| RuntimeException {
message: format!(
"The package {} has no dist url configured, cannot install.",
@@ -295,13 +484,22 @@ impl PathDownloader {
Ok(None)
}
- pub async fn remove(
+ async fn update(
+ &mut self,
+ initial: PackageInterfaceHandle,
+ target: PackageInterfaceHandle,
+ path: &str,
+ ) -> Result<Option<PhpMixed>> {
+ self.inner.update(initial, target, path).await
+ }
+
+ async fn remove(
&mut self,
package: PackageInterfaceHandle,
- path: String,
+ path: &str,
output: bool,
) -> Result<Option<PhpMixed>> {
- let path = Filesystem::trim_trailing_slash(&path);
+ let path = Filesystem::trim_trailing_slash(path);
// realpath() may resolve Windows junctions to the source path, so we'll check for a junction
// first to prevent a false positive when checking if the dist and install paths are the same.
// See https://bugs.php.net/bug.php?id=77639
@@ -386,238 +584,6 @@ impl PathDownloader {
self.inner.remove(package, &path, output).await
}
- pub fn get_vcs_reference(&self, package: PackageInterfaceHandle, path: &str) -> Option<String> {
- let path = Filesystem::trim_trailing_slash(path);
- let parser = VersionParser::new();
- let mut guesser = VersionGuesser::new(
- self.inner.config.clone(),
- self.inner.process.clone(),
- parser.clone(),
- Some(self.inner.io.clone()),
- );
- let dumper = ArrayDumper::new();
-
- let package_config = dumper.dump(package.clone());
- let package_version = guesser.guess_version(&package_config, &path);
- if let Ok(Some(version)) = package_version {
- return version.commit;
- }
-
- None
- }
-
- pub(crate) fn get_install_operation_appendix(
- &self,
- package: PackageInterfaceHandle,
- path: &str,
- ) -> Result<String> {
- let url = package.get_dist_url().ok_or_else(|| RuntimeException {
- message: format!(
- "The package {} has no dist url configured, cannot install.",
- package.get_pretty_name()
- ),
- code: 0,
- })?;
- let real_url = realpath(&url).ok_or_else(|| RuntimeException {
- message: format!("Failed to realpath {}", url),
- code: 0,
- })?;
-
- if realpath(path).as_deref() == Some(&real_url) {
- return Ok(": Source already present".to_string());
- }
-
- let (current_strategy, _) =
- self.compute_allowed_strategies(&package.get_transport_options())?;
-
- if current_strategy == Self::STRATEGY_SYMLINK {
- if Platform::is_windows() {
- return Ok(format!(
- ": Junctioning from {}",
- package.get_dist_url().unwrap_or_default()
- ));
- }
-
- return Ok(format!(
- ": Symlinking from {}",
- package.get_dist_url().unwrap_or_default()
- ));
- }
-
- Ok(format!(
- ": Mirroring from {}",
- package.get_dist_url().unwrap_or_default()
- ))
- }
-
- fn compute_allowed_strategies(
- &self,
- transport_options: &IndexMap<String, PhpMixed>,
- ) -> Result<(i64, Vec<i64>)> {
- // When symlink transport option is null, both symlink and mirror are allowed
- let mut current_strategy = Self::STRATEGY_SYMLINK;
- let mut allowed_strategies = vec![Self::STRATEGY_SYMLINK, Self::STRATEGY_MIRROR];
-
- let mirror_path_repos = Platform::get_env("COMPOSER_MIRROR_PATH_REPOS");
- if mirror_path_repos.map_or(false, |v| !v.is_empty()) {
- current_strategy = Self::STRATEGY_MIRROR;
- }
-
- let symlink_option = transport_options.get("symlink");
-
- match symlink_option {
- Some(PhpMixed::Bool(true)) => {
- current_strategy = Self::STRATEGY_SYMLINK;
- allowed_strategies = vec![Self::STRATEGY_SYMLINK];
- }
- Some(PhpMixed::Bool(false)) => {
- current_strategy = Self::STRATEGY_MIRROR;
- allowed_strategies = vec![Self::STRATEGY_MIRROR];
- }
- _ => {}
- }
-
- // Check we can use junctions safely if we are on Windows
- if Platform::is_windows()
- && Self::STRATEGY_SYMLINK == current_strategy
- && !self.safe_junctions()
- {
- if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) {
- return Err(RuntimeException {
- message: "You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(),
- code: 0,
- }
- .into());
- }
- current_strategy = Self::STRATEGY_MIRROR;
- allowed_strategies = vec![Self::STRATEGY_MIRROR];
- }
-
- // Check we can use symlink() otherwise
- if !Platform::is_windows()
- && Self::STRATEGY_SYMLINK == current_strategy
- && !function_exists("symlink")
- {
- if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) {
- return Err(RuntimeException {
- message: "Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(),
- code: 0,
- }
- .into());
- }
- current_strategy = Self::STRATEGY_MIRROR;
- allowed_strategies = vec![Self::STRATEGY_MIRROR];
- }
-
- Ok((current_strategy, allowed_strategies))
- }
-
- // Returns true if junctions can be created and safely used on Windows.
- //
- // A PHP bug makes junction detection fragile, leading to possible data loss when removing a
- // package. See https://bugs.php.net/bug.php?id=77552
- //
- // For safety we require a minimum version of Windows 7, so we can call the system rmdir which
- // will preserve target content if given a junction.
- //
- // The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7).
- fn safe_junctions(&self) -> bool {
- // We need to call mklink, and rmdir on Windows 7 (version 6.1)
- function_exists("proc_open")
- && (PHP_WINDOWS_VERSION_MAJOR > 6
- || (PHP_WINDOWS_VERSION_MAJOR == 6 && PHP_WINDOWS_VERSION_MINOR >= 1))
- }
-}
-
-impl VcsCapableDownloaderInterface for PathDownloader {
- fn get_vcs_reference(&self, package: PackageInterfaceHandle, path: String) -> Option<String> {
- PathDownloader::get_vcs_reference(self, package, &path)
- }
-}
-
-impl crate::downloader::ChangeReportInterface for PathDownloader {
- fn get_local_changes(
- &mut self,
- package: PackageInterfaceHandle,
- path: &str,
- ) -> anyhow::Result<Option<String>> {
- self.inner.get_local_changes(package, path)
- }
-}
-
-// TODO(phase-b): wire up PathDownloader trait properly. PathDownloader extends FileDownloader and
-// overrides download/install/remove with &mut self signatures that diverge from the trait. The
-// trait methods here delegate to the inner FileDownloader; the bespoke overrides on the struct
-// itself are not yet routed through the trait.
-#[async_trait::async_trait(?Send)]
-impl DownloaderInterface for PathDownloader {
- fn get_installation_source(&self) -> String {
- self.inner.get_installation_source()
- }
-
- fn as_change_report_interface(
- &mut self,
- ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> {
- Some(self)
- }
-
- fn as_vcs_capable_downloader_interface(
- &self,
- ) -> Option<&dyn crate::downloader::VcsCapableDownloaderInterface> {
- Some(self)
- }
-
- async fn download(
- &mut self,
- package: PackageInterfaceHandle,
- path: &str,
- prev_package: Option<PackageInterfaceHandle>,
- output: bool,
- ) -> Result<Option<PhpMixed>> {
- self.inner
- .download(package, path, prev_package, output)
- .await
- }
-
- async fn prepare(
- &mut self,
- r#type: &str,
- package: PackageInterfaceHandle,
- path: &str,
- prev_package: Option<PackageInterfaceHandle>,
- ) -> Result<Option<PhpMixed>> {
- self.inner
- .prepare(r#type, package, path, prev_package)
- .await
- }
-
- async fn install(
- &mut self,
- package: PackageInterfaceHandle,
- path: &str,
- output: bool,
- ) -> Result<Option<PhpMixed>> {
- self.inner.install(package, path, output).await
- }
-
- async fn update(
- &mut self,
- initial: PackageInterfaceHandle,
- target: PackageInterfaceHandle,
- path: &str,
- ) -> Result<Option<PhpMixed>> {
- self.inner.update(initial, target, path).await
- }
-
- async fn remove(
- &mut self,
- package: PackageInterfaceHandle,
- path: &str,
- output: bool,
- ) -> Result<Option<PhpMixed>> {
- self.inner.remove(package, path, output).await
- }
-
async fn cleanup(
&mut self,
r#type: &str,
diff --git a/crates/shirabe/src/downloader/phar_downloader.rs b/crates/shirabe/src/downloader/phar_downloader.rs
index 8180764..4b3fc51 100644
--- a/crates/shirabe/src/downloader/phar_downloader.rs
+++ b/crates/shirabe/src/downloader/phar_downloader.rs
@@ -45,10 +45,28 @@ impl PharDownloader {
cleanup_executed: IndexMap::new(),
}
}
+}
- pub(crate) async fn extract(
- &self,
- package: PackageInterfaceHandle,
+impl ArchiveDownloader for PharDownloader {
+ fn inner(&self) -> &FileDownloader {
+ &self.inner
+ }
+
+ fn inner_mut(&mut self) -> &mut FileDownloader {
+ &mut self.inner
+ }
+
+ fn cleanup_executed(&self) -> &IndexMap<String, bool> {
+ &self.cleanup_executed
+ }
+
+ fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> {
+ &mut self.cleanup_executed
+ }
+
+ async fn extract(
+ &mut self,
+ _package: PackageInterfaceHandle,
file: &str,
path: &str,
) -> Result<Option<PhpMixed>> {
@@ -105,9 +123,7 @@ impl DownloaderInterface for PharDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .prepare(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await
}
async fn install(
@@ -116,7 +132,7 @@ impl DownloaderInterface for PharDownloader {
path: &str,
output: bool,
) -> Result<Option<PhpMixed>> {
- self.inner.install(package, path, output).await
+ <Self as ArchiveDownloader>::install(self, package, path, output).await
}
async fn update(
@@ -144,8 +160,6 @@ impl DownloaderInterface for PharDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .cleanup(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await
}
}
diff --git a/crates/shirabe/src/downloader/rar_downloader.rs b/crates/shirabe/src/downloader/rar_downloader.rs
index 5676a96..f2c89c3 100644
--- a/crates/shirabe/src/downloader/rar_downloader.rs
+++ b/crates/shirabe/src/downloader/rar_downloader.rs
@@ -48,8 +48,26 @@ impl RarDownloader {
cleanup_executed: IndexMap::new(),
}
}
+}
+
+impl ArchiveDownloader for RarDownloader {
+ fn inner(&self) -> &FileDownloader {
+ &self.inner
+ }
+
+ fn inner_mut(&mut self) -> &mut FileDownloader {
+ &mut self.inner
+ }
+
+ fn cleanup_executed(&self) -> &IndexMap<String, bool> {
+ &self.cleanup_executed
+ }
- pub(crate) async fn extract(
+ fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> {
+ &mut self.cleanup_executed
+ }
+
+ async fn extract(
&mut self,
_package: PackageInterfaceHandle,
file: &str,
@@ -185,9 +203,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .prepare(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await
}
async fn install(
@@ -196,7 +212,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader {
path: &str,
output: bool,
) -> Result<Option<PhpMixed>> {
- self.inner.install(package, path, output).await
+ <Self as ArchiveDownloader>::install(self, package, path, output).await
}
async fn update(
@@ -224,8 +240,6 @@ impl crate::downloader::DownloaderInterface for RarDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .cleanup(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await
}
}
diff --git a/crates/shirabe/src/downloader/tar_downloader.rs b/crates/shirabe/src/downloader/tar_downloader.rs
index 8835e7d..833a958 100644
--- a/crates/shirabe/src/downloader/tar_downloader.rs
+++ b/crates/shirabe/src/downloader/tar_downloader.rs
@@ -45,10 +45,28 @@ impl TarDownloader {
cleanup_executed: IndexMap::new(),
}
}
+}
- pub(crate) async fn extract(
- &self,
- package: PackageInterfaceHandle,
+impl ArchiveDownloader for TarDownloader {
+ fn inner(&self) -> &FileDownloader {
+ &self.inner
+ }
+
+ fn inner_mut(&mut self) -> &mut FileDownloader {
+ &mut self.inner
+ }
+
+ fn cleanup_executed(&self) -> &IndexMap<String, bool> {
+ &self.cleanup_executed
+ }
+
+ fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> {
+ &mut self.cleanup_executed
+ }
+
+ async fn extract(
+ &mut self,
+ _package: PackageInterfaceHandle,
file: &str,
path: &str,
) -> Result<Option<PhpMixed>> {
@@ -100,9 +118,7 @@ impl DownloaderInterface for TarDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .prepare(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await
}
async fn install(
@@ -111,7 +127,7 @@ impl DownloaderInterface for TarDownloader {
path: &str,
output: bool,
) -> Result<Option<PhpMixed>> {
- self.inner.install(package, path, output).await
+ <Self as ArchiveDownloader>::install(self, package, path, output).await
}
async fn update(
@@ -139,8 +155,6 @@ impl DownloaderInterface for TarDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .cleanup(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await
}
}
diff --git a/crates/shirabe/src/downloader/xz_downloader.rs b/crates/shirabe/src/downloader/xz_downloader.rs
index 10a7edf..1c03f1d 100644
--- a/crates/shirabe/src/downloader/xz_downloader.rs
+++ b/crates/shirabe/src/downloader/xz_downloader.rs
@@ -44,10 +44,28 @@ impl XzDownloader {
cleanup_executed: IndexMap::new(),
}
}
+}
+
+impl ArchiveDownloader for XzDownloader {
+ fn inner(&self) -> &FileDownloader {
+ &self.inner
+ }
+
+ fn inner_mut(&mut self) -> &mut FileDownloader {
+ &mut self.inner
+ }
+
+ fn cleanup_executed(&self) -> &IndexMap<String, bool> {
+ &self.cleanup_executed
+ }
- pub(crate) async fn extract(
+ fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> {
+ &mut self.cleanup_executed
+ }
+
+ async fn extract(
&mut self,
- package: PackageInterfaceHandle,
+ _package: PackageInterfaceHandle,
file: &str,
path: &str,
) -> Result<Option<PhpMixed>> {
@@ -119,9 +137,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .prepare(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await
}
async fn install(
@@ -130,7 +146,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader {
path: &str,
output: bool,
) -> Result<Option<PhpMixed>> {
- self.inner.install(package, path, output).await
+ <Self as ArchiveDownloader>::install(self, package, path, output).await
}
async fn update(
@@ -158,8 +174,6 @@ impl crate::downloader::DownloaderInterface for XzDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .cleanup(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await
}
}
diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs
index 90772ce..3875e7e 100644
--- a/crates/shirabe/src/downloader/zip_downloader.rs
+++ b/crates/shirabe/src/downloader/zip_downloader.rs
@@ -61,146 +61,6 @@ impl ZipDownloader {
}
}
- pub async fn download(
- &mut self,
- package: PackageInterfaceHandle,
- path: &str,
- prev_package: Option<PackageInterfaceHandle>,
- output: bool,
- ) -> Result<Option<PhpMixed>> {
- {
- let mut unzip_commands = UNZIP_COMMANDS.lock().unwrap();
- if unzip_commands.is_none() {
- *unzip_commands = Some(vec![]);
- let finder = ExecutableFinder::new();
- let commands = unzip_commands.as_mut().unwrap();
- if Platform::is_windows() {
- if let Some(cmd) =
- finder.find("7z", None, &[r"C:\Program Files\7-Zip".to_string()])
- {
- commands.push(vec![
- "7z".to_string(),
- cmd,
- "x".to_string(),
- "-bb0".to_string(),
- "-y".to_string(),
- "%file%".to_string(),
- "-o%path%".to_string(),
- ]);
- }
- }
- if let Some(cmd) = finder.find("unzip", None, &[]) {
- commands.push(vec![
- "unzip".to_string(),
- cmd,
- "-qq".to_string(),
- "%file%".to_string(),
- "-d".to_string(),
- "%path%".to_string(),
- ]);
- }
- if !Platform::is_windows() {
- if let Some(cmd) = finder.find("7z", None, &[]) {
- // 7z linux/macOS support is only used if unzip is not present
- commands.push(vec![
- "7z".to_string(),
- cmd,
- "x".to_string(),
- "-bb0".to_string(),
- "-y".to_string(),
- "%file%".to_string(),
- "-o%path%".to_string(),
- ]);
- } else if let Some(cmd) = finder.find("7zz", None, &[]) {
- // 7zz linux/macOS support is only used if unzip is not present
- commands.push(vec![
- "7zz".to_string(),
- cmd,
- "x".to_string(),
- "-bb0".to_string(),
- "-y".to_string(),
- "%file%".to_string(),
- "-o%path%".to_string(),
- ]);
- } else if let Some(cmd) = finder.find("7za", None, &[]) {
- // 7za linux/macOS support is only used if unzip is not present
- commands.push(vec![
- "7za".to_string(),
- cmd,
- "x".to_string(),
- "-bb0".to_string(),
- "-y".to_string(),
- "%file%".to_string(),
- "-o%path%".to_string(),
- ]);
- }
- }
- }
- }
-
- let proc_open_missing = !function_exists("proc_open");
- if proc_open_missing {
- *UNZIP_COMMANDS.lock().unwrap() = Some(vec![]);
- }
-
- {
- let mut has_zip_archive = HAS_ZIP_ARCHIVE.lock().unwrap();
- if has_zip_archive.is_none() {
- *has_zip_archive = Some(class_exists("ZipArchive"));
- }
- }
-
- let has_zip_archive = HAS_ZIP_ARCHIVE.lock().unwrap().unwrap_or(false);
- let unzip_commands_empty = UNZIP_COMMANDS
- .lock()
- .unwrap()
- .as_ref()
- .map_or(true, |v| v.is_empty());
-
- if !has_zip_archive && unzip_commands_empty {
- let ini_message = IniHelper::get_message();
- let error = if proc_open_missing {
- format!(
- "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n{}",
- ini_message
- )
- } else {
- format!(
- "The zip extension and unzip/7z commands are both missing, skipping.\n{}",
- ini_message
- )
- };
- return Err(RuntimeException {
- message: error,
- code: 0,
- }
- .into());
- }
-
- {
- let mut is_windows_guard = IS_WINDOWS.lock().unwrap();
- if is_windows_guard.is_none() {
- *is_windows_guard = Some(Platform::is_windows());
-
- if !is_windows_guard.unwrap() && unzip_commands_empty {
- if proc_open_missing {
- self.inner.io.write_error("<warning>proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension.</warning>");
- self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
- self.inner.io.write_error("<warning>Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them.</warning>");
- } else {
- self.inner.io.write_error("<warning>As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.</warning>");
- self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
- self.inner.io.write_error("<warning>Installing 'unzip' or '7z' (21.01+) may remediate them.</warning>");
- }
- }
- }
- }
-
- self.inner
- .download(package, path, prev_package, output)
- .await
- }
-
async fn extract_with_system_unzip(
&mut self,
package: PackageInterfaceHandle,
@@ -522,15 +382,6 @@ impl ZipDownloader {
})
}
- pub(crate) async fn extract(
- &mut self,
- package: PackageInterfaceHandle,
- file: &str,
- path: &str,
- ) -> Result<Option<PhpMixed>> {
- self.extract_with_system_unzip(package, file, path).await
- }
-
pub fn get_error_message(&self, retval: i64, file: &str) -> String {
match retval {
ZipArchive::ER_EXISTS => format!("File '{}' already exists.", file),
@@ -554,6 +405,33 @@ impl ZipDownloader {
}
}
+impl ArchiveDownloader for ZipDownloader {
+ fn inner(&self) -> &FileDownloader {
+ &self.inner
+ }
+
+ fn inner_mut(&mut self) -> &mut FileDownloader {
+ &mut self.inner
+ }
+
+ fn cleanup_executed(&self) -> &IndexMap<String, bool> {
+ &self.cleanup_executed
+ }
+
+ fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> {
+ &mut self.cleanup_executed
+ }
+
+ async fn extract(
+ &mut self,
+ package: PackageInterfaceHandle,
+ file: &str,
+ path: &str,
+ ) -> Result<Option<PhpMixed>> {
+ self.extract_with_system_unzip(package, file, path).await
+ }
+}
+
impl ChangeReportInterface for ZipDownloader {
fn get_local_changes(
&mut self,
@@ -564,9 +442,6 @@ impl ChangeReportInterface for ZipDownloader {
}
}
-// TODO(phase-b): ZipDownloader::download is overridden with extra setup (UNZIP_COMMANDS init,
-// etc.). The trait method here delegates straight to the inner FileDownloader; the bespoke
-// override on the struct itself takes &mut self and is not yet routed through the trait.
#[async_trait::async_trait(?Send)]
impl crate::downloader::DownloaderInterface for ZipDownloader {
fn get_installation_source(&self) -> String {
@@ -586,6 +461,134 @@ impl crate::downloader::DownloaderInterface for ZipDownloader {
prev_package: Option<PackageInterfaceHandle>,
output: bool,
) -> Result<Option<PhpMixed>> {
+ {
+ let mut unzip_commands = UNZIP_COMMANDS.lock().unwrap();
+ if unzip_commands.is_none() {
+ *unzip_commands = Some(vec![]);
+ let finder = ExecutableFinder::new();
+ let commands = unzip_commands.as_mut().unwrap();
+ if Platform::is_windows() {
+ if let Some(cmd) =
+ finder.find("7z", None, &[r"C:\Program Files\7-Zip".to_string()])
+ {
+ commands.push(vec![
+ "7z".to_string(),
+ cmd,
+ "x".to_string(),
+ "-bb0".to_string(),
+ "-y".to_string(),
+ "%file%".to_string(),
+ "-o%path%".to_string(),
+ ]);
+ }
+ }
+ if let Some(cmd) = finder.find("unzip", None, &[]) {
+ commands.push(vec![
+ "unzip".to_string(),
+ cmd,
+ "-qq".to_string(),
+ "%file%".to_string(),
+ "-d".to_string(),
+ "%path%".to_string(),
+ ]);
+ }
+ if !Platform::is_windows() {
+ if let Some(cmd) = finder.find("7z", None, &[]) {
+ // 7z linux/macOS support is only used if unzip is not present
+ commands.push(vec![
+ "7z".to_string(),
+ cmd,
+ "x".to_string(),
+ "-bb0".to_string(),
+ "-y".to_string(),
+ "%file%".to_string(),
+ "-o%path%".to_string(),
+ ]);
+ } else if let Some(cmd) = finder.find("7zz", None, &[]) {
+ // 7zz linux/macOS support is only used if unzip is not present
+ commands.push(vec![
+ "7zz".to_string(),
+ cmd,
+ "x".to_string(),
+ "-bb0".to_string(),
+ "-y".to_string(),
+ "%file%".to_string(),
+ "-o%path%".to_string(),
+ ]);
+ } else if let Some(cmd) = finder.find("7za", None, &[]) {
+ // 7za linux/macOS support is only used if unzip is not present
+ commands.push(vec![
+ "7za".to_string(),
+ cmd,
+ "x".to_string(),
+ "-bb0".to_string(),
+ "-y".to_string(),
+ "%file%".to_string(),
+ "-o%path%".to_string(),
+ ]);
+ }
+ }
+ }
+ }
+
+ let proc_open_missing = !function_exists("proc_open");
+ if proc_open_missing {
+ *UNZIP_COMMANDS.lock().unwrap() = Some(vec![]);
+ }
+
+ {
+ let mut has_zip_archive = HAS_ZIP_ARCHIVE.lock().unwrap();
+ if has_zip_archive.is_none() {
+ *has_zip_archive = Some(class_exists("ZipArchive"));
+ }
+ }
+
+ let has_zip_archive = HAS_ZIP_ARCHIVE.lock().unwrap().unwrap_or(false);
+ let unzip_commands_empty = UNZIP_COMMANDS
+ .lock()
+ .unwrap()
+ .as_ref()
+ .map_or(true, |v| v.is_empty());
+
+ if !has_zip_archive && unzip_commands_empty {
+ let ini_message = IniHelper::get_message();
+ let error = if proc_open_missing {
+ format!(
+ "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n{}",
+ ini_message
+ )
+ } else {
+ format!(
+ "The zip extension and unzip/7z commands are both missing, skipping.\n{}",
+ ini_message
+ )
+ };
+ return Err(RuntimeException {
+ message: error,
+ code: 0,
+ }
+ .into());
+ }
+
+ {
+ let mut is_windows_guard = IS_WINDOWS.lock().unwrap();
+ if is_windows_guard.is_none() {
+ *is_windows_guard = Some(Platform::is_windows());
+
+ if !is_windows_guard.unwrap() && unzip_commands_empty {
+ if proc_open_missing {
+ self.inner.io.write_error("<warning>proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension.</warning>");
+ self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
+ self.inner.io.write_error("<warning>Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them.</warning>");
+ } else {
+ self.inner.io.write_error("<warning>As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.</warning>");
+ self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
+ self.inner.io.write_error("<warning>Installing 'unzip' or '7z' (21.01+) may remediate them.</warning>");
+ }
+ }
+ }
+ }
+
self.inner
.download(package, path, prev_package, output)
.await
@@ -598,9 +601,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .prepare(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await
}
async fn install(
@@ -609,7 +610,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader {
path: &str,
output: bool,
) -> Result<Option<PhpMixed>> {
- self.inner.install(package, path, output).await
+ <Self as ArchiveDownloader>::install(self, package, path, output).await
}
async fn update(
@@ -637,8 +638,6 @@ impl crate::downloader::DownloaderInterface for ZipDownloader {
path: &str,
prev_package: Option<PackageInterfaceHandle>,
) -> Result<Option<PhpMixed>> {
- self.inner
- .cleanup(r#type, package, path, prev_package)
- .await
+ <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await
}
}