aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/downloader
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-09 18:33:14 +0900
committernsfisis <nsfisis@gmail.com>2026-08-09 18:33:14 +0900
commit72cbecaec29cecc723ce29c87d10048114aeef5b (patch)
tree99ee7288595afbf1166503d603d7c5a7d08aaae0 /crates/shirabe/src/downloader
parentf1215ade6e1d6e91a36c8b1c407f5a59a1c2ee0e (diff)
downloadphp-shirabe-72cbecaec29cecc723ce29c87d10048114aeef5b.tar.gz
php-shirabe-72cbecaec29cecc723ce29c87d10048114aeef5b.tar.zst
php-shirabe-72cbecaec29cecc723ce29c87d10048114aeef5b.zip
feat(symfony-filesystem): finish the Filesystem port and trim its API
doRemove() renames a directory to a random hidden name before emptying it, and undoes that rename when the final rmdir fails, so a concurrent process cannot recreate the path mid-removal. It also walks one level at a time through FilesystemIterator instead of flattening the whole tree, and lets an inner rmdir failure pass, both as upstream does. copy() keeps the mode fopen($targetFile, 'w') would have left rather than the origin's, symlink() and mirror() call readlink() and getLinkTarget() where upstream does, and a directory iterator that cannot be opened propagates its UnexpectedValueException instead of being swallowed or flattened into an IOException. Error message text is out of scope per docs/known-incompatibilities.md, so the three TODO(phase-c) markers that only tracked wording are gone, along with linkException()'s Windows-only branch. So are the arguments no caller varies -- symlink()'s copyOnWindows, mirror()'s iterator and options, copy()'s overwriteNewerFiles -- which removes the last TODO(phase-c) in the file. New shim functions: readlink, filesystem_iterator, stream_is_local, strrev and SplFileInfo::getLinkTarget. base64_encode takes bytes so random_bytes() can feed it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/downloader')
-rw-r--r--crates/shirabe/src/downloader/path_downloader.rs104
1 files changed, 50 insertions, 54 deletions
diff --git a/crates/shirabe/src/downloader/path_downloader.rs b/crates/shirabe/src/downloader/path_downloader.rs
index 2f385e8e..076f313d 100644
--- a/crates/shirabe/src/downloader/path_downloader.rs
+++ b/crates/shirabe/src/downloader/path_downloader.rs
@@ -348,61 +348,57 @@ impl DownloaderInterface for PathDownloader {
let mut is_fallback = false;
if Self::STRATEGY_SYMLINK == current_strategy {
- let symlink_result: anyhow::Result<anyhow::Result<()>> =
- (|| {
- if Platform::is_windows() {
- // Implement symlinks as NTFS junctions on Windows
- if output {
- self.inner.io.borrow().write_error3(
- &format!("Junctioning from {}", url),
- false,
- io_interface::NORMAL,
- );
- }
- Ok(self
- .inner
- .filesystem
- .borrow_mut()
- .junction(&real_url, &path))
+ let symlink_result: anyhow::Result<anyhow::Result<()>> = (|| {
+ if Platform::is_windows() {
+ // Implement symlinks as NTFS junctions on Windows
+ if output {
+ self.inner.io.borrow().write_error3(
+ &format!("Junctioning from {}", url),
+ false,
+ io_interface::NORMAL,
+ );
+ }
+ Ok(self
+ .inner
+ .filesystem
+ .borrow_mut()
+ .junction(&real_url, &path))
+ } else {
+ let path = path.trim_end_matches('/').to_string();
+ if output {
+ self.inner.io.borrow().write_error3(
+ &format!("Symlinking from {}", url),
+ false,
+ io_interface::NORMAL,
+ );
+ }
+ if transport_options
+ .get("relative")
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false)
+ {
+ let absolute_path =
+ if !self.inner.filesystem.borrow_mut().is_absolute_path(&path) {
+ std::path::Path::new(&Platform::get_cwd(false)?)
+ .join(&path)
+ .into_os_string()
+ .into_string()
+ .unwrap()
+ } else {
+ path.clone()
+ };
+ let shortest_path = self.inner.filesystem.borrow_mut().find_shortest_path(
+ &absolute_path,
+ &real_url,
+ false,
+ true,
+ );
+ Ok(symfony_filesystem.symlink(&format!("{}/", shortest_path), &path))
} else {
- let path = path.trim_end_matches('/').to_string();
- if output {
- self.inner.io.borrow().write_error3(
- &format!("Symlinking from {}", url),
- false,
- io_interface::NORMAL,
- );
- }
- if transport_options
- .get("relative")
- .and_then(|v| v.as_bool())
- .unwrap_or(false)
- {
- let absolute_path =
- if !self.inner.filesystem.borrow_mut().is_absolute_path(&path) {
- std::path::Path::new(&Platform::get_cwd(false)?)
- .join(&path)
- .into_os_string()
- .into_string()
- .unwrap()
- } else {
- path.clone()
- };
- let shortest_path = self
- .inner
- .filesystem
- .borrow_mut()
- .find_shortest_path(&absolute_path, &real_url, false, true);
- Ok(symfony_filesystem.symlink(
- &format!("{}/", shortest_path),
- &path,
- false,
- ))
- } else {
- Ok(symfony_filesystem.symlink(&format!("{}/", real_url), &path, false))
- }
+ Ok(symfony_filesystem.symlink(&format!("{}/", real_url), &path))
}
- })();
+ }
+ })();
match symlink_result? {
Ok(()) => {}
@@ -453,7 +449,7 @@ impl DownloaderInterface for PathDownloader {
// argument, but the external-package Filesystem stub does not model the iterator type
// that ArchivableFilesFinder (an IteratorAggregate) would be wrapped into, so None is
// passed and the mirrored file list is not restricted.
- symfony_filesystem.mirror(&real_url, &path, None, &IndexMap::new())?;
+ symfony_filesystem.mirror(&real_url, &path)?;
}
if output {