aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util/filesystem.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-07-18 18:03:57 +0900
committernsfisis <nsfisis@gmail.com>2026-07-18 18:03:57 +0900
commit9b291d454b059977639db26c847af4e835cda0c3 (patch)
treee54770570a80e67681f1614841156b864452713c /crates/shirabe/src/util/filesystem.rs
parentf8e385f7a1bd752d39f4d4f87b0439596839dde9 (diff)
downloadphp-shirabe-9b291d454b059977639db26c847af4e835cda0c3.tar.gz
php-shirabe-9b291d454b059977639db26c847af4e835cda0c3.tar.zst
php-shirabe-9b291d454b059977639db26c847af4e835cda0c3.zip
refactor(downloader): take &self across the downloader hierarchy
Concurrent package operations call into the same downloader instances through Rc<RefCell<dyn DownloaderInterface>>; with &mut self methods every call holds a RefMut across its awaits, which panics with 'already mutably borrowed' the moment two operations overlap. This is groundwork for fanning out InstallationManager's download/install loops (same rework HttpDownloader/CurlDownloader already got). - DownloaderInterface/ChangeReportInterface/ArchiveDownloader/ VcsDownloader methods now take &self; as_change_report_interface returns &dyn instead of &mut dyn. - Implementors move their genuinely mutable state behind cells: FileDownloader.additional_cleanup_paths, the archive downloaders' cleanup_executed, ZipDownloader.zip_archive_object, VcsDownloaderBase.has_cleaned_changes, GitDownloader's stash/discard/ cache maps and GitUtil, SvnDownloader.cache_credentials, PerforceDownloader.perforce. FileDownloader.io gains a RefCell layer so get_local_changes can keep PHP's NullIO swap under &self. - ProcessExecutor::execute_async now returns a future that captures everything up front instead of borrowing the executor, and call sites build the future before awaiting, so no borrow on the shared executor is held while a subprocess runs. - Filesystem::remove_directory_async becomes remove_directory_async_via taking the Rc handle: the Filesystem is only borrowed for the sync head/tail, never across the rm subprocess await (sync borrow_mut users like rename/ensure_directory_exists would otherwise collide). - DownloadManager async call sites hold shared borrows only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/util/filesystem.rs')
-rw-r--r--crates/shirabe/src/util/filesystem.rs68
1 files changed, 39 insertions, 29 deletions
diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs
index e12d7fee..f3cb2e63 100644
--- a/crates/shirabe/src/util/filesystem.rs
+++ b/crates/shirabe/src/util/filesystem.rs
@@ -163,38 +163,48 @@ impl Filesystem {
///
/// Uses the process component if proc_open is enabled on the PHP
/// installation.
- pub async fn remove_directory_async(&mut self, directory: &str) -> anyhow::Result<bool> {
- if let Some(mock) = self.mock.as_mut()
- && let Some(result) = mock.remove_directory_async_result
- {
- mock.remove_directory_async_calls += 1;
- return Ok(result);
- }
+ ///
+ /// Takes the shared handle instead of `&mut self`: the Filesystem is borrowed only for the
+ /// synchronous head and tail, never across the subprocess await, so sibling futures can keep
+ /// using the same `Rc<RefCell<Filesystem>>` while the removal runs.
+ pub async fn remove_directory_async_via(
+ this: &std::rc::Rc<std::cell::RefCell<Filesystem>>,
+ directory: &str,
+ ) -> anyhow::Result<bool> {
+ let (process_executor, cmd) = {
+ let mut fs = this.borrow_mut();
- let edge_case_result = self.remove_edge_cases(directory, true)?;
- if let Some(r) = edge_case_result {
- return Ok(r);
- }
+ if let Some(mock) = fs.mock.as_mut()
+ && let Some(result) = mock.remove_directory_async_result
+ {
+ mock.remove_directory_async_calls += 1;
+ return Ok(result);
+ }
- let cmd: Vec<String> = if Platform::is_windows() {
- vec![
- "rmdir".to_string(),
- "/S".to_string(),
- "/Q".to_string(),
- Platform::realpath(directory),
- ]
- } else {
- vec!["rm".to_string(), "-rf".to_string(), directory.to_string()]
+ let edge_case_result = fs.remove_edge_cases(directory, true)?;
+ if let Some(r) = edge_case_result {
+ return Ok(r);
+ }
+
+ let cmd: Vec<String> = if Platform::is_windows() {
+ vec![
+ "rmdir".to_string(),
+ "/S".to_string(),
+ "/Q".to_string(),
+ Platform::realpath(directory),
+ ]
+ } else {
+ vec!["rm".to_string(), "-rf".to_string(), directory.to_string()]
+ };
+
+ (fs.get_process_handle(), cmd)
};
- let process_executor = self.get_process_handle();
- let mut process = process_executor
- .borrow()
- .execute_async(
- PhpMixed::List(cmd.iter().map(|s| PhpMixed::String(s.clone())).collect()),
- None,
- )
- .await?;
+ let process_future = process_executor.borrow().execute_async(
+ PhpMixed::List(cmd.iter().map(|s| PhpMixed::String(s.clone())).collect()),
+ None,
+ );
+ let mut process = process_future.await?;
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache2(false, "");
@@ -203,7 +213,7 @@ impl Filesystem {
return Ok(true);
}
- self.remove_directory_php(directory)
+ this.borrow_mut().remove_directory_php(directory)
}
/// Returns null when no edge case was hit. Otherwise a bool whether removal was successful