From c3fa0453a467cdb8ce7ae1aa3b19a6c961808e04 Mon Sep 17 00:00:00 2001 From: FredApps <153173854+FredApps@users.noreply.github.com> Date: Tue, 10 Nov 2026 18:36:03 +0100 Subject: [PATCH] desktop: use tokio::fs for Sora and Midjourney file I/O (Sonar S7493) (#2804) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Replaces blocking `std::fs` calls inside `async fn`s in the desktop crate with `tokio::fs`. SonarCloud reports these as **rust:S7493** ("use an asynchronous file API instead of this blocking file operation"). This PR resolves 17 of the 26 desktop findings. | File (under `crates/desktop/artcraft/src/services/`) | Findings | Change | |----------------------------------------------------------------|----------|----------------------------------------------------------------------------| | `sora/state/read_sora_credentials_from_disk_with_upgrade.rs` | 10 | `read_to_string` and `OpenOptions`+`write_all`+`flush` → `tokio::fs::{read_to_string, write}` | | `sora/state/initialize_sora_jwt_bearer_token.rs` | 2 | `std::fs::{read_to_string, write}` → `tokio::fs` | | `sora/state/sora_credential_manager.rs` (`call_sentinel_refresh`) | 2 | `OpenOptions`+`write_all`+`flush` → `tokio::fs::write` | | `midjourney/utils/download_midjourney_image.rs` | 2 | `File::create`+`write_all` → `tokio::fs::write` | | `midjourney/threads/upload_midjourney_batch.rs` | 1 | `std::fs::remove_file` → `tokio::fs::remove_file(..).await` | `tokio::fs::write` creates or truncates the file and writes everything. That is the same as the `OpenOptions::new().create(true).write(true).truncate(true)` + `write_all` + `flush` sequence it replaces. The synchronous helpers in `sora_credential_manager.rs` (`persist_*_to_disk`) are not `async`, are not flagged, and are left unchanged. The `.exists()` guards are also left as they are. The desktop crate already depends on `tokio` with the `fs` feature, so no manifest or lockfile changes. ## Not included (overlap with open PRs) The remaining 9 desktop S7493 findings are in files that open PRs are rewriting. They are left alone to avoid conflicting changes: - `core/utils/simple_http_download.rs`, `sora/.../handle_successful_generations.rs`, `worldlabs/threads/worldlabs_marble_task_polling.rs`: #2777 (fail downloads on non-2xx responses). - `core/threads/.../fal/handle_fal_complete.rs`: #2550, #2373. - `core/utils/auto_download.rs`: #2785, #2686, #2393. The api_clients findings are in #2803. ## Testing - Each changed file parses with `rustfmt --check`. The formatting diffs it reports already exist on `main`, and no files were reformatted. - **I could not build the desktop crate locally.** `cargo check -p artcraft` stops at `libdbus-sys` because the build machine lacks the GTK, WebKit and D-Bus system libraries Tauri needs. Please rely on CI for compilation before marking this ready. - No new tests are added. This is a behaviour-preserving switch to non-blocking I/O, so no test fails before the change and passes after it, and these paths have no existing offline tests. ## Summary by CodeRabbit * **Chores** * Updated background handling for saved images, checkpoints, and account credentials. Existing cleanup, credential processing, and error reporting behavior remain unchanged. * These maintenance updates do not change the app’s available features or how people interact with it. --- .../threads/upload_midjourney_batch.rs | 2 +- .../utils/download_midjourney_image.rs | 5 +-- .../state/initialize_sora_jwt_bearer_token.rs | 4 +- ...sora_credentials_from_disk_with_upgrade.rs | 38 ++++--------------- .../sora/state/sora_credential_manager.rs | 9 +---- 5 files changed, 13 insertions(+), 45 deletions(-) diff --git a/crates/desktop/artcraft/src/services/midjourney/threads/upload_midjourney_batch.rs b/crates/desktop/artcraft/src/services/midjourney/threads/upload_midjourney_batch.rs index 8b0448e12a..9010ae09f3 100644 --- a/crates/desktop/artcraft/src/services/midjourney/threads/upload_midjourney_batch.rs +++ b/crates/desktop/artcraft/src/services/midjourney/threads/upload_midjourney_batch.rs @@ -119,7 +119,7 @@ pub(super) async fn upload_midjourney_batch(maybe_app_handle: Option<&AppHandle> } } - if let Err(err) = std::fs::remove_file(MidjourneyUploadCheckpoint::path(app_data_root, local_task)) { + if let Err(err) = tokio::fs::remove_file(MidjourneyUploadCheckpoint::path(app_data_root, local_task)).await { log::warn!("Could not remove completed Midjourney upload checkpoint: {}", err); } Ok(()) diff --git a/crates/desktop/artcraft/src/services/midjourney/utils/download_midjourney_image.rs b/crates/desktop/artcraft/src/services/midjourney/utils/download_midjourney_image.rs index fb63021c15..c07ecd6963 100644 --- a/crates/desktop/artcraft/src/services/midjourney/utils/download_midjourney_image.rs +++ b/crates/desktop/artcraft/src/services/midjourney/utils/download_midjourney_image.rs @@ -2,8 +2,6 @@ use crate::core::state::data_dir::app_data_root::AppDataRoot; use crate::core::state::data_dir::trait_data_subdir::DataSubdir; use errors::AnyhowResult; use midjourney_client::utils::get_image_url::get_image_url; -use std::fs::File; -use std::io::Write; use std::path::PathBuf; use url::Url; use midjourney_client::utils::image_downloader_client::ImageDownloaderClient; @@ -28,8 +26,7 @@ pub async fn download_midjourney_image( let download_filename = format!("{}_{}.{}", job_id, index, ext); let download_path = tempdir.join(download_filename); - let mut file = File::create(&download_path)?; - file.write_all(&image_bytes)?; + tokio::fs::write(&download_path, &image_bytes).await?; Ok(download_path) } diff --git a/crates/desktop/artcraft/src/services/sora/state/initialize_sora_jwt_bearer_token.rs b/crates/desktop/artcraft/src/services/sora/state/initialize_sora_jwt_bearer_token.rs index 5705cb165f..62b4c3983e 100644 --- a/crates/desktop/artcraft/src/services/sora/state/initialize_sora_jwt_bearer_token.rs +++ b/crates/desktop/artcraft/src/services/sora/state/initialize_sora_jwt_bearer_token.rs @@ -14,7 +14,7 @@ pub async fn initialize_sora_jwt_bearer_token(app_data_root: &AppDataRoot) -> An return Ok(()); // Bearer token already exists. } - let cookie = std::fs::read_to_string(app_data_root.get_sora_cookie_file_path())? + let cookie = tokio::fs::read_to_string(app_data_root.get_sora_cookie_file_path()).await? .trim() .to_string(); @@ -22,7 +22,7 @@ pub async fn initialize_sora_jwt_bearer_token(app_data_root: &AppDataRoot) -> An let bearer = generate_bearer_jwt_with_cookie(&cookie).await?; - std::fs::write(app_data_root.get_sora_bearer_token_file_path(), bearer)?; + tokio::fs::write(app_data_root.get_sora_bearer_token_file_path(), bearer).await?; Ok(()) } diff --git a/crates/desktop/artcraft/src/services/sora/state/read_sora_credentials_from_disk_with_upgrade.rs b/crates/desktop/artcraft/src/services/sora/state/read_sora_credentials_from_disk_with_upgrade.rs index 7538a1a067..eae939280f 100644 --- a/crates/desktop/artcraft/src/services/sora/state/read_sora_credentials_from_disk_with_upgrade.rs +++ b/crates/desktop/artcraft/src/services/sora/state/read_sora_credentials_from_disk_with_upgrade.rs @@ -8,8 +8,7 @@ use openai_sora_client::creds::sora_jwt_bearer_token::SoraJwtBearerToken; use openai_sora_client::creds::sora_sentinel::SoraSentinel; use openai_sora_client::creds::sora_sentinel_token::SoraSentinelToken; use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use std::fs::{read_to_string, OpenOptions}; -use std::io::Write; +use tokio::fs::{read_to_string, write}; pub async fn read_sora_credentials_from_disk_with_upgrade(app_data_root: &AppDataRoot) -> AnyhowResult { let cookie_file = app_data_root.get_sora_cookie_file_path(); @@ -21,7 +20,7 @@ pub async fn read_sora_credentials_from_disk_with_upgrade(app_data_root: &AppDat return Err(anyhow!("Cookie file does not exist: {:?}", cookie_file)); } - let value = read_to_string(cookie_file)? + let value = read_to_string(cookie_file).await? .trim() .to_string(); @@ -32,14 +31,14 @@ pub async fn read_sora_credentials_from_disk_with_upgrade(app_data_root: &AppDat let mut sentinel_token = None; if bearer_file.exists() { - let value = read_to_string(&bearer_file)? + let value = read_to_string(&bearer_file).await? .trim() .to_string(); bearer = Some(SoraJwtBearerToken::new(value)?); } if legacy_sentinel_file.exists() { - let value = read_to_string(&legacy_sentinel_file)? + let value = read_to_string(&legacy_sentinel_file).await? .trim() .to_string(); @@ -47,7 +46,7 @@ pub async fn read_sora_credentials_from_disk_with_upgrade(app_data_root: &AppDat } if sentinel_token_file.exists() { - let value = read_to_string(&sentinel_token_file)? + let value = read_to_string(&sentinel_token_file).await? .trim() .to_string(); @@ -73,14 +72,7 @@ pub async fn read_sora_credentials_from_disk_with_upgrade(app_data_root: &AppDat info!("Persisting bearer token value..."); let value = bearer.token_str(); - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&bearer_file)?; - - file.write_all(value.as_bytes())?; - file.flush()?; + write(&bearer_file, value.as_bytes()).await?; } } @@ -89,14 +81,7 @@ pub async fn read_sora_credentials_from_disk_with_upgrade(app_data_root: &AppDat info!("Persisting sentinel value..."); let value = sentinel.get_sentinel(); - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&legacy_sentinel_file)?; - - file.write_all(value.as_bytes())?; - file.flush()?; + write(&legacy_sentinel_file, value.as_bytes()).await?; } } @@ -105,14 +90,7 @@ pub async fn read_sora_credentials_from_disk_with_upgrade(app_data_root: &AppDat info!("Persisting sentinel token..."); let value = sentinel_token.to_persistent_storage_json()?; - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&sentinel_token_file)?; - - file.write_all(value.as_bytes())?; - file.flush()?; + write(&sentinel_token_file, value.as_bytes()).await?; } } diff --git a/crates/desktop/artcraft/src/services/sora/state/sora_credential_manager.rs b/crates/desktop/artcraft/src/services/sora/state/sora_credential_manager.rs index e10ddcf3f6..451e2ba1dd 100644 --- a/crates/desktop/artcraft/src/services/sora/state/sora_credential_manager.rs +++ b/crates/desktop/artcraft/src/services/sora/state/sora_credential_manager.rs @@ -162,14 +162,7 @@ impl SoraCredentialManager { self.holder.set_credentials(&creds)?; - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(self.app_data_root.get_sora_legacy_sentinel_file_path())?; - - file.write_all(sentinel.as_bytes())?; - file.flush()?; + tokio::fs::write(self.app_data_root.get_sora_legacy_sentinel_file_path(), sentinel.as_bytes()).await?; Ok(creds) }