diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-22 23:41:12 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-22 23:41:12 +0900 |
| commit | b291e714bc739262140323e08fe2fb9e91e00ee7 (patch) | |
| tree | c95f742064b9000e72b902f88e8905b4017d5dbc /crates/shirabe/src | |
| parent | 99ef82a9807578c1e5749156a027949efaba75c4 (diff) | |
| download | php-shirabe-b291e714bc739262140323e08fe2fb9e91e00ee7.tar.gz php-shirabe-b291e714bc739262140323e08fe2fb9e91e00ee7.tar.zst php-shirabe-b291e714bc739262140323e08fe2fb9e91e00ee7.zip | |
feat(php-shim): implement fopen-family stream API on PhpResource
Redesign PhpResource into a real stream handle (File/Memory backing with
tracked position, eof, closed state) and unify the whole fopen family
(fopen/fwrite/fread/fgets/fgetc/feof/fclose/ftell/fseek/rewind/fstat/
ftruncate/fflush and stream_get_contents/stream_copy_to_stream) on
&PhpResource, replacing the split PhpMixed/PhpResource APIs and their
todo!() stubs. fopen now returns Result; read functions stay String for
now (TODO(phase-e) to move to byte strings).
Propagate the signatures through callers: Process stdout/stderr, Cursor
input, curl header/body handles (extracted into typed maps keyed by job
id), Filesystem copy/safe_copy/files_are_equal, BufferIO, error_handler,
platform, perforce, zip. The proc_open pipe paths cannot carry a
PhpResource in a PhpMixed list, so they are left as todo!() with notes.
Diffstat (limited to 'crates/shirabe/src')
| -rw-r--r-- | crates/shirabe/src/downloader/gzip_downloader.rs | 6 | ||||
| -rw-r--r-- | crates/shirabe/src/installer/binary_installer.rs | 11 | ||||
| -rw-r--r-- | crates/shirabe/src/io/buffer_io.rs | 50 | ||||
| -rw-r--r-- | crates/shirabe/src/util/error_handler.rs | 10 | ||||
| -rw-r--r-- | crates/shirabe/src/util/filesystem.rs | 85 | ||||
| -rw-r--r-- | crates/shirabe/src/util/http/curl_downloader.rs | 173 | ||||
| -rw-r--r-- | crates/shirabe/src/util/perforce.rs | 73 | ||||
| -rw-r--r-- | crates/shirabe/src/util/platform.rs | 21 | ||||
| -rw-r--r-- | crates/shirabe/src/util/zip.rs | 4 |
9 files changed, 239 insertions, 194 deletions
diff --git a/crates/shirabe/src/downloader/gzip_downloader.rs b/crates/shirabe/src/downloader/gzip_downloader.rs index f59fe11..aefc6f9 100644 --- a/crates/shirabe/src/downloader/gzip_downloader.rs +++ b/crates/shirabe/src/downloader/gzip_downloader.rs @@ -52,16 +52,16 @@ impl GzipDownloader { fn extract_using_ext(&self, file: &str, target_filepath: &str) { let archive_file = gzopen(file, "rb"); - let target_file = fopen(target_filepath, "wb"); + let target_file = fopen(target_filepath, "wb").unwrap(); loop { let string = gzread(archive_file.clone(), 4096); if string.is_empty() { break; } - fwrite(target_file.clone(), &string, Platform::strlen(&string)); + fwrite(&target_file, &string, Some(Platform::strlen(&string))); } gzclose(archive_file); - fclose(target_file); + fclose(&target_file); } } diff --git a/crates/shirabe/src/installer/binary_installer.rs b/crates/shirabe/src/installer/binary_installer.rs index b9c56f6..8e7ade5 100644 --- a/crates/shirabe/src/installer/binary_installer.rs +++ b/crates/shirabe/src/installer/binary_installer.rs @@ -169,9 +169,14 @@ impl BinaryInstaller { return "call".to_string(); } - let handle = fopen(bin, "r"); - let line = fgets(handle.clone()).unwrap_or_default(); - fclose(handle); + let line = match fopen(bin, "r") { + Ok(handle) => { + let line = fgets(&handle, None).unwrap_or_default(); + fclose(&handle); + line + } + Err(_) => String::new(), + }; let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); if Preg::is_match3( r"{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m", diff --git a/crates/shirabe/src/io/buffer_io.rs b/crates/shirabe/src/io/buffer_io.rs index 2b07c6b..91d22d7 100644 --- a/crates/shirabe/src/io/buffer_io.rs +++ b/crates/shirabe/src/io/buffer_io.rs @@ -12,8 +12,8 @@ use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::input::StringInput; use shirabe_external_packages::symfony::console::output::OutputInterface; use shirabe_php_shim::{ - PHP_EOL, PhpMixed, RuntimeException, fopen, fseek, fwrite, rewind, stream_get_contents, - strip_tags, + PHP_EOL, PhpMixed, PhpResource, RuntimeException, SEEK_SET, fopen, fseek, fwrite, rewind, + stream_get_contents, strip_tags, }; #[derive(Debug)] @@ -30,14 +30,16 @@ impl BufferIO { let mut input_obj = StringInput::new(&input)?; input_obj.set_interactive(false); - let stream = fopen("php://memory", "rw"); - if matches!(stream, PhpMixed::Bool(false)) { - return Err(RuntimeException { - message: "Unable to open memory output stream".to_string(), - code: 0, + let stream = match fopen("php://memory", "rw") { + Ok(stream) => stream, + Err(_) => { + return Err(RuntimeException { + message: "Unable to open memory output stream".to_string(), + code: 0, + } + .into()); } - .into()); - } + }; let _decorated = formatter.as_ref().is_some_and(|f| f.is_decorated()); // TODO(phase-c): wire StreamOutput as the output. StreamOutput::new requires a @@ -75,11 +77,11 @@ impl BufferIO { pub fn get_output(&self) -> String { // TODO(phase-c): OutputInterface::get_stream returns PhpResource, while // fseek/stream_get_contents take PhpMixed. The PhpResource stream model is not yet defined. - let stream: PhpMixed = - todo!("PhpResource -> PhpMixed conversion for OutputInterface::get_stream"); - fseek(stream.clone(), 0); + let stream: PhpResource = + todo!("retrieve the StreamOutput's PhpResource from OutputInterface::get_stream"); + fseek(&stream, 0, SEEK_SET); - let output = stream_get_contents(stream).unwrap_or_default(); + let output = stream_get_contents(&stream).unwrap_or_default(); Preg::replace_callback( r"{(?<=^|\n|\x08)(.+?)(\x08+)}", @@ -119,21 +121,23 @@ impl BufferIO { todo!("BufferIO::set_user_inputs: needs an as_streamable accessor on InputInterface") } - fn create_stream(&self, inputs: Vec<String>) -> Result<PhpMixed> { - let stream = fopen("php://memory", "r+"); - if matches!(stream, PhpMixed::Bool(false)) { - return Err(RuntimeException { - message: "Unable to open memory output stream".to_string(), - code: 0, + fn create_stream(&self, inputs: Vec<String>) -> Result<PhpResource> { + let stream = match fopen("php://memory", "r+") { + Ok(stream) => stream, + Err(_) => { + return Err(RuntimeException { + message: "Unable to open memory output stream".to_string(), + code: 0, + } + .into()); } - .into()); - } + }; for input in inputs { - fwrite(stream.clone(), &format!("{}{}", input, PHP_EOL), -1); + fwrite(&stream, &format!("{}{}", input, PHP_EOL), None); } - rewind(stream.clone()); + rewind(&stream); Ok(stream) } diff --git a/crates/shirabe/src/util/error_handler.rs b/crates/shirabe/src/util/error_handler.rs index b9210fc..263d80c 100644 --- a/crates/shirabe/src/util/error_handler.rs +++ b/crates/shirabe/src/util/error_handler.rs @@ -4,8 +4,8 @@ use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use shirabe_php_shim::{ E_ALL, E_DEPRECATED, E_USER_DEPRECATED, E_USER_WARNING, E_WARNING, ErrorException, PHP_EOL, - PhpMixed, STDERR, debug_backtrace, error_reporting, filter_var_boolean, ini_get, is_resource, - set_error_handler, + PhpMixed, STDERR, debug_backtrace, error_reporting, filter_var_boolean, ini_get, + is_resource_value, set_error_handler, }; use std::cell::{Cell, RefCell}; use std::rc::Rc; @@ -126,11 +126,11 @@ impl ErrorHandler { } if output_even_without_io { - if is_resource(&PhpMixed::Int(STDERR)) { + if is_resource_value(&STDERR) { shirabe_php_shim::fwrite( - PhpMixed::Int(STDERR), + &STDERR, &format!("Warning: {}{}", message, PHP_EOL), - -1, + None, ); } else { print!("Warning: {}{}", message, PHP_EOL); diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs index 37ffabb..90deba1 100644 --- a/crates/shirabe/src/util/filesystem.rs +++ b/crates/shirabe/src/util/filesystem.rs @@ -417,20 +417,21 @@ impl Filesystem { // if copy fails we attempt to copy it manually as this can help bypass issues with VirtualBox shared folders // see https://github.com/composer/composer/issues/12057 if str_contains(&e.message, "Bad address") { - let source_handle = fopen(source, "r"); - let target_handle = fopen(&target, "w"); - if source_handle.is_null() || target_handle.is_null() { - return Err(e.into()); - } - while !feof(source_handle.clone()) { - let chunk = - fread(source_handle.clone(), 1024 * 1024).unwrap_or_default(); - if fwrite(target_handle.clone(), &chunk, chunk.len() as i64).is_none() { + let (source_handle, target_handle) = + match (fopen(source, "r"), fopen(&target, "w")) { + (Ok(source_handle), Ok(target_handle)) => { + (source_handle, target_handle) + } + _ => return Err(e.into()), + }; + while !feof(&source_handle) { + let chunk = fread(&source_handle, 1024 * 1024).unwrap_or_default(); + if fwrite(&target_handle, &chunk, None).is_none() { return Err(e.into()); } } - fclose(source_handle); - fclose(target_handle); + fclose(&source_handle); + fclose(&target_handle); return Ok(true); } @@ -1041,24 +1042,28 @@ impl Filesystem { /// Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 pub fn safe_copy(&self, source: &str, target: &str) -> anyhow::Result<()> { if !file_exists(target) || !file_exists(source) || !self.files_are_equal(source, target) { - let source_handle = fopen(source, "r"); - if source_handle.is_null() { - return Err(anyhow::anyhow!( - "Could not open \"{}\" for reading.", - source - )); - } - let target_handle = fopen(target, "w+"); - if target_handle.is_null() { - return Err(anyhow::anyhow!( - "Could not open \"{}\" for writing.", - target - )); - } + let source_handle = match fopen(source, "r") { + Ok(source_handle) => source_handle, + Err(_) => { + return Err(anyhow::anyhow!( + "Could not open \"{}\" for reading.", + source + )); + } + }; + let target_handle = match fopen(target, "w+") { + Ok(target_handle) => target_handle, + Err(_) => { + return Err(anyhow::anyhow!( + "Could not open \"{}\" for writing.", + target + )); + } + }; - shirabe_php_shim::stream_copy_to_stream(source_handle.clone(), target_handle.clone()); - fclose(source_handle); - fclose(target_handle); + shirabe_php_shim::stream_copy_to_stream(&source_handle, &target_handle); + fclose(&source_handle); + fclose(&target_handle); touch(target); // PHP also passes filemtime/fileatime — skipping detailed timestamp restore here. @@ -1076,25 +1081,25 @@ impl Filesystem { } // Check if content is different - let a_handle = fopen(a, "rb"); - if a_handle.is_null() { - return false; - } - let b_handle = fopen(b, "rb"); - if b_handle.is_null() { - return false; - } + let a_handle = match fopen(a, "rb") { + Ok(a_handle) => a_handle, + Err(_) => return false, + }; + let b_handle = match fopen(b, "rb") { + Ok(b_handle) => b_handle, + Err(_) => return false, + }; let mut result = true; - while !feof(a_handle.clone()) { - if fread(a_handle.clone(), 8192) != fread(b_handle.clone(), 8192) { + while !feof(&a_handle) { + if fread(&a_handle, 8192) != fread(&b_handle, 8192) { result = false; break; } } - fclose(a_handle); - fclose(b_handle); + fclose(&a_handle); + fclose(&b_handle); result } diff --git a/crates/shirabe/src/util/http/curl_downloader.rs b/crates/shirabe/src/util/http/curl_downloader.rs index 0519f26..94e703d 100644 --- a/crates/shirabe/src/util/http/curl_downloader.rs +++ b/crates/shirabe/src/util/http/curl_downloader.rs @@ -14,9 +14,9 @@ use shirabe_php_shim::{ CURLOPT_FILE, CURLOPT_FOLLOWLOCATION, CURLOPT_HTTP_VERSION, CURLOPT_IPRESOLVE, CURLOPT_PROTOCOLS, CURLOPT_SHARE, CURLOPT_TIMEOUT, CURLOPT_URL, CURLOPT_WRITEHEADER, CURLPROTO_HTTP, CURLPROTO_HTTPS, CURLSHOPT_SHARE, CurlMultiHandle, CurlShareHandle, - LogicException, PHP_VERSION_ID, PhpMixed, RuntimeException, array_diff, array_diff_key, - array_merge, count, curl_errno, curl_error, curl_getinfo, curl_handle_id, curl_init, - curl_multi_add_handle, curl_multi_exec, curl_multi_info_read, curl_multi_init, + LogicException, PHP_VERSION_ID, PhpMixed, PhpResource, RuntimeException, array_diff, + array_diff_key, array_merge, count, curl_errno, curl_error, curl_getinfo, curl_handle_id, + curl_init, curl_multi_add_handle, curl_multi_exec, curl_multi_info_read, curl_multi_init, curl_multi_select, curl_multi_setopt, curl_setopt, curl_setopt_array, curl_share_init, curl_share_setopt, curl_strerror, curl_version, defined, explode, fclose, fopen, function_exists, implode, in_array, ini_get, is_resource, json_decode, parse_url, preg_quote, @@ -47,6 +47,10 @@ pub struct CurlDownloader { share_handle: Option<CurlShareHandle>, /// @var Job[] jobs: IndexMap<i64, IndexMap<String, PhpMixed>>, + /// The `headerHandle`/`bodyHandle` stream resources of each job, keyed by job id. PHP keeps + /// them inside the Job array, but a PhpResource cannot live in the PhpMixed-typed job map. + header_handles: IndexMap<i64, PhpResource>, + body_handles: IndexMap<i64, PhpResource>, /// @var IOInterface io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, /// @var Config @@ -232,6 +236,8 @@ impl CurlDownloader { multi_handle, share_handle, jobs: IndexMap::new(), + header_handles: IndexMap::new(), + body_handles: IndexMap::new(), io, config, auth_helper, @@ -329,16 +335,18 @@ impl CurlDownloader { } let curl_handle = curl_init(); - let header_handle = fopen("php://temp/maxmemory:32768", "w+b"); - if matches!(header_handle, PhpMixed::Bool(false)) { - anyhow::bail!( - RuntimeException { - message: "Failed to open a temp stream to store curl headers".to_string(), - code: 0, - } - .message - ); - } + let header_handle = match fopen("php://temp/maxmemory:32768", "w+b") { + Ok(header_handle) => header_handle, + Err(_) => { + anyhow::bail!( + RuntimeException { + message: "Failed to open a temp stream to store curl headers".to_string(), + code: 0, + } + .message + ); + } + }; let body_target: String = if let Some(copy_to) = copy_to { format!("{}~", copy_to) @@ -350,18 +358,20 @@ impl CurlDownloader { // (stripping the "fopen(...): " prefix) into the message. Rust I/O reports failures through // return values, not warnings, so error_message stays empty until fopen surfaces its reason. let error_message = String::new(); - let body_handle = fopen(&body_target, "w+b"); - if matches!(body_handle, PhpMixed::Bool(false)) { - return Ok(Err(TransportException::new( - format!( - "The \"{}\" file could not be written to {}: {}", - url, - copy_to.unwrap_or("a temporary file"), - error_message - ), - 0, - ))); - } + let body_handle = match fopen(&body_target, "w+b") { + Ok(body_handle) => body_handle, + Err(_) => { + return Ok(Err(TransportException::new( + format!( + "The \"{}\" file could not be written to {}: {}", + url, + copy_to.unwrap_or("a temporary file"), + error_message + ), + 0, + ))); + } + }; curl_setopt(&curl_handle, CURLOPT_URL, PhpMixed::String(url.to_string())); curl_setopt(&curl_handle, CURLOPT_FOLLOWLOCATION, PhpMixed::Bool(false)); @@ -378,8 +388,10 @@ impl CurlDownloader { .max(300), ), ); - curl_setopt(&curl_handle, CURLOPT_WRITEHEADER, header_handle.clone()); - curl_setopt(&curl_handle, CURLOPT_FILE, body_handle.clone()); + // TODO(phase-d): CURLOPT_WRITEHEADER/CURLOPT_FILE hand the header/body stream resources to + // curl, but curl_setopt takes a PhpMixed and a PhpResource cannot be passed through it. + // curl_setopt(&curl_handle, CURLOPT_WRITEHEADER, header_handle); + // curl_setopt(&curl_handle, CURLOPT_FILE, body_handle); curl_setopt( &curl_handle, CURLOPT_ENCODING, @@ -601,8 +613,11 @@ impl CurlDownloader { .map(|s| PhpMixed::String(s.to_string())) .unwrap_or(PhpMixed::Null), ); - job.insert("headerHandle".to_string(), header_handle.clone()); - job.insert("bodyHandle".to_string(), body_handle.clone()); + // headerHandle/bodyHandle are PHP stream resources; they live in the Rust-side + // header_handles/body_handles maps (keyed by job id) since a PhpResource cannot be stored + // in the PhpMixed job map. + job.insert("headerHandle".to_string(), PhpMixed::Null); + job.insert("bodyHandle".to_string(), PhpMixed::Null); job.insert("resolve".to_string(), PhpMixed::Null); job.insert("reject".to_string(), PhpMixed::Null); job.insert("primaryIp".to_string(), PhpMixed::String(String::new())); @@ -611,7 +626,10 @@ impl CurlDownloader { // above) — blocked on the typed-Job refactor and the React\Promise model. let _ = (resolve, reject); - self.jobs.insert(curl_handle_id(&curl_handle), job); + let job_id = curl_handle_id(&curl_handle); + self.header_handles.insert(job_id, header_handle); + self.body_handles.insert(job_id, body_handle); + self.jobs.insert(job_id, job); let using_proxy = proxy .get_status(Some(" using proxy (%s)")) @@ -677,15 +695,17 @@ impl CurlDownloader { if PHP_VERSION_ID < 80000 { // curl_close($job['curlHandle']); } - if is_resource(job.get("headerHandle").unwrap_or(&PhpMixed::Null)) { - fclose(job.get("headerHandle").cloned().unwrap_or(PhpMixed::Null)); + if let Some(handle) = self.header_handles.get(&id) { + fclose(handle); } - if is_resource(job.get("bodyHandle").unwrap_or(&PhpMixed::Null)) { - fclose(job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null)); + if let Some(handle) = self.body_handles.get(&id) { + fclose(handle); } if let Some(PhpMixed::String(filename)) = job.get("filename") { unlink_silent(&format!("{}~", filename)); } + self.header_handles.shift_remove(&id); + self.body_handles.shift_remove(&id); self.jobs.shift_remove(&id); } } @@ -939,18 +959,19 @@ impl CurlDownloader { ))); } status_code = progress.get("http_code").and_then(|b| b.as_int()); - rewind(job.get("headerHandle").cloned().unwrap_or(PhpMixed::Null)); - headers = Some(explode( - "\r\n", - &rtrim( - &stream_get_contents( - job.get("headerHandle").cloned().unwrap_or(PhpMixed::Null), - ) - .unwrap_or_default(), - None, - ), - )); - fclose(job.get("headerHandle").cloned().unwrap_or(PhpMixed::Null)); + let header_handle = self.header_handles.get(&i).cloned(); + let header_contents = match &header_handle { + Some(handle) => { + rewind(handle); + stream_get_contents(handle).unwrap_or_default() + } + None => String::new(), + }; + headers = Some(explode("\r\n", &rtrim(&header_contents, None))); + if let Some(handle) = &header_handle { + fclose(handle); + } + self.header_handles.shift_remove(&i); if status_code == Some(0) { anyhow::bail!( @@ -984,17 +1005,15 @@ impl CurlDownloader { } // prepare response object + let body_handle = self.body_handles.get(&i).cloned(); let contents: PhpMixed; if let Some(PhpMixed::String(filename)) = job.get("filename") { let mut c: PhpMixed = PhpMixed::String(format!("{}~", filename)); if status_code.unwrap_or(0) >= 300 { - rewind(job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null)); - c = PhpMixed::String( - stream_get_contents( - job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null), - ) - .unwrap_or_default(), - ); + if let Some(handle) = &body_handle { + rewind(handle); + c = PhpMixed::String(stream_get_contents(handle).unwrap_or_default()); + } } contents = c; response = Some(CurlResponse::new( @@ -1027,12 +1046,16 @@ impl CurlDownloader { .and_then(|v| v.as_array()) .and_then(|a| a.get("max_file_size")) .and_then(|b| b.as_int()); - rewind(job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null)); + if let Some(handle) = &body_handle { + rewind(handle); + } if let Some(max_file_size) = max_file_size { - let c = stream_get_contents_with_max( - job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null), - Some(max_file_size), - ); + let c = match &body_handle { + Some(handle) => { + stream_get_contents_with_max(handle, Some(max_file_size)) + } + None => None, + }; // Gzipped responses with missing Content-Length header cannot be detected during the file download // because $progress['size_download'] refers to the gzipped size downloaded, not the actual file size if let Some(c_str) = c.as_deref() @@ -1053,12 +1076,10 @@ impl CurlDownloader { } contents = PhpMixed::String(c.unwrap_or_default()); } else { - contents = PhpMixed::String( - stream_get_contents( - job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null), - ) - .unwrap_or_default(), - ); + contents = PhpMixed::String(match &body_handle { + Some(handle) => stream_get_contents(handle).unwrap_or_default(), + None => String::new(), + }); } response = Some(CurlResponse::new( @@ -1086,7 +1107,10 @@ impl CurlDownloader { crate::io::DEBUG, ); } - fclose(job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null)); + if let Some(handle) = &body_handle { + fclose(handle); + } + self.body_handles.shift_remove(&i); let response_ref = response.as_ref().unwrap(); if response_ref.inner.get_status_code() >= 300 @@ -1310,11 +1334,11 @@ impl CurlDownloader { e.set_response(r.inner.get_body().map(|s| s.to_string())); } e.set_response_info(progress.values().cloned().collect::<Vec<_>>()); - self.reject_job(&job, anyhow::anyhow!(e.message)); + self.reject_job(i, &job, anyhow::anyhow!(e.message)); } Err(e) => { // Non-TransportException fatal error: pass through reject_job to mirror PHP catch (\Exception $e). - self.reject_job(&job, e); + self.reject_job(i, &job, e); } } } @@ -1369,6 +1393,7 @@ impl CurlDownloader { if max_file_size < download_content_length { let job = self.jobs.get(&i).cloned().unwrap_or_default(); self.reject_job( + i, &job, anyhow::anyhow!( MaxFileSizeExceededException(TransportException::new( @@ -1392,6 +1417,7 @@ impl CurlDownloader { if max_file_size < size_download { let job = self.jobs.get(&i).cloned().unwrap_or_default(); self.reject_job( + i, &job, anyhow::anyhow!( MaxFileSizeExceededException(TransportException::new( @@ -1438,6 +1464,7 @@ impl CurlDownloader { if blocked { let job = self.jobs.get(&i).cloned().unwrap_or_default(); self.reject_job( + i, &job, anyhow::anyhow!( TransportException::new( @@ -1801,13 +1828,15 @@ impl CurlDownloader { } /// @param Job $job - fn reject_job(&mut self, job: &IndexMap<String, PhpMixed>, _e: anyhow::Error) { - if is_resource(job.get("headerHandle").unwrap_or(&PhpMixed::Null)) { - fclose(job.get("headerHandle").cloned().unwrap_or(PhpMixed::Null)); + fn reject_job(&mut self, id: i64, job: &IndexMap<String, PhpMixed>, _e: anyhow::Error) { + if let Some(handle) = self.header_handles.get(&id) { + fclose(handle); } - if is_resource(job.get("bodyHandle").unwrap_or(&PhpMixed::Null)) { - fclose(job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null)); + if let Some(handle) = self.body_handles.get(&id) { + fclose(handle); } + self.header_handles.shift_remove(&id); + self.body_handles.shift_remove(&id); if let Some(PhpMixed::String(filename)) = job.get("filename") { unlink_silent(&format!("{}~", filename)); } diff --git a/crates/shirabe/src/util/perforce.rs b/crates/shirabe/src/util/perforce.rs index d5facd2..7ca3f26 100644 --- a/crates/shirabe/src/util/perforce.rs +++ b/crates/shirabe/src/util/perforce.rs @@ -6,7 +6,7 @@ use shirabe_external_packages::composer::pcre::Preg; use shirabe_external_packages::symfony::process::ExecutableFinder; use shirabe_external_packages::symfony::process::Process; use shirabe_php_shim::{ - Exception, PHP_EOL, PhpMixed, chdir, count, date, explode, fclose, feof, fgets, + Exception, PHP_EOL, PhpMixed, PhpResource, chdir, count, date, explode, fclose, feof, fgets, file_get_contents, fopen, fwrite, gethostname, json_decode, str_replace_array, strcmp, strlen, strpos, strrpos, substr, time, trim, }; @@ -416,73 +416,73 @@ impl Perforce { } /// @param resource|false $spec - pub fn write_client_spec_to_file(&mut self, spec: PhpMixed) { + pub fn write_client_spec_to_file(&mut self, spec: &PhpResource) { fwrite( - spec.clone(), + spec, &format!("Client: {}{}{}", self.get_client(), PHP_EOL, PHP_EOL), - 0, + None, ); fwrite( - spec.clone(), + spec, &format!( "Update: {}{}{}", date("Y/m/d H:i:s", None), PHP_EOL, PHP_EOL ), - 0, + None, ); fwrite( - spec.clone(), + spec, &format!("Access: {}{}", date("Y/m/d H:i:s", None), PHP_EOL), - 0, + None, ); fwrite( - spec.clone(), + spec, &format!( "Owner: {}{}{}", self.get_user().unwrap_or_default(), PHP_EOL, PHP_EOL ), - 0, + None, ); - fwrite(spec.clone(), &format!("Description:{}", PHP_EOL), 0); + fwrite(spec, &format!("Description:{}", PHP_EOL), None); fwrite( - spec.clone(), + spec, &format!( " Created by {} from composer.{}{}", self.get_user().unwrap_or_default(), PHP_EOL, PHP_EOL ), - 0, + None, ); fwrite( - spec.clone(), + spec, &format!("Root: {}{}{}", self.get_path(), PHP_EOL, PHP_EOL), - 0, + None, ); fwrite( - spec.clone(), + spec, &format!( "Options: noallwrite noclobber nocompress unlocked modtime rmdir{}{}", PHP_EOL, PHP_EOL ), - 0, + None, ); fwrite( - spec.clone(), + spec, &format!("SubmitOptions: revertunchanged{}{}", PHP_EOL, PHP_EOL), - 0, + None, ); fwrite( - spec.clone(), + spec, &format!("LineEnd: local{}{}", PHP_EOL, PHP_EOL), - 0, + None, ); if self.is_stream() { - fwrite(spec.clone(), &format!("Stream:{}", PHP_EOL), 0); + fwrite(spec, &format!("Stream:{}", PHP_EOL), None); let stream_clone = self.p4_stream.clone().unwrap_or_default(); fwrite( spec, @@ -491,7 +491,7 @@ impl Perforce { self.get_stream_without_label(&stream_clone), PHP_EOL ), - 0, + None, ); } else { let stream = self.get_stream(); @@ -499,38 +499,47 @@ impl Perforce { fwrite( spec, &format!("View: {}/... //{}/... {}", stream, client, PHP_EOL), - 0, + None, ); } } pub fn write_p4_client_spec(&mut self) -> Result<()> { let client_spec = self.get_p4_client_spec(); - let spec = fopen(&client_spec, "w"); + let spec = match fopen(&client_spec, "w") { + Ok(spec) => spec, + Err(e) => { + return Err(Exception { + message: e.to_string(), + code: 0, + } + .into()); + } + }; let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.write_client_spec_to_file(spec.clone()); + self.write_client_spec_to_file(&spec); })); if let Err(e) = result { - fclose(spec); + fclose(&spec); return Err(Exception { message: format!("{:?}", e), code: 0, } .into()); } - fclose(spec); + fclose(&spec); Ok(()) } /// @param resource $pipe /// @param mixed $name - pub(crate) fn read(&self, pipe: PhpMixed, _name: PhpMixed) { - if feof(pipe.clone()) { + pub(crate) fn read(&self, pipe: &PhpResource, _name: PhpMixed) { + if feof(pipe) { return; } - let mut line = fgets(pipe.clone()); + let mut line = fgets(pipe, None); while line.is_some() { - line = fgets(pipe.clone()); + line = fgets(pipe, None); } } diff --git a/crates/shirabe/src/util/platform.rs b/crates/shirabe/src/util/platform.rs index 449874e..442b4d9 100644 --- a/crates/shirabe/src/util/platform.rs +++ b/crates/shirabe/src/util/platform.rs @@ -291,13 +291,7 @@ impl Platform { /// @param ?resource $fd Open file descriptor or null to default to STDOUT pub fn is_tty(fd: Option<PhpResource>) -> bool { - let fd = match fd { - Some(f) => f, - None => { - // TODO(phase-c): STDOUT is not yet modeled as a `PhpResource` constant. - todo!("STDOUT resource constant") - } - }; + let fd = fd.unwrap_or(shirabe_php_shim::STDOUT); // detect msysgit/mingw and assume this is a tty because detection // does not work correctly, see https://github.com/composer/composer/issues/9690 @@ -323,19 +317,18 @@ impl Platform { return true; } - let stat = Silencer::call(|| Ok(fstat(fd))); + let stat = Silencer::call(|| Ok(fstat(&fd))); let stat = match stat { Ok(s) => s, Err(_) => return false, }; - if matches!(stat, PhpMixed::Bool(false)) { - return false; - } + let stat = match stat { + Some(stat) => stat, + None => return false, + }; // Check if formatted mode is S_IFCHR - if let Some(arr) = stat.as_array() - && let Some(mode) = arr.get("mode").and_then(|v| v.as_int()) - { + if let Some(mode) = stat.get("mode").and_then(|v| v.as_int()) { return 0o020000 == (mode & 0o170000); } diff --git a/crates/shirabe/src/util/zip.rs b/crates/shirabe/src/util/zip.rs index 4231db4..dbb2627 100644 --- a/crates/shirabe/src/util/zip.rs +++ b/crates/shirabe/src/util/zip.rs @@ -34,8 +34,8 @@ impl Zip { let configuration_file_name = zip.get_name_index(found_file_index); let stream = zip.get_stream(&configuration_file_name); - if stream.is_some() { - content = stream_get_contents(stream.unwrap()); + if let Some(stream) = &stream { + content = stream_get_contents(stream); } zip.close(); |
