Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ required-features = ["offline"]
tauri-build = { version = "2.3.1", features = [], optional = true }

[dependencies]
reqwest = "0.12.4"
reqwest = { version = "0.12.4", features = ["stream", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0"
Expand Down Expand Up @@ -82,6 +82,7 @@ once_cell = "1.21.3"
idf-env = { git = "https://github.com/espressif/idf-env", rev="fd69ab4f550ef35647bb32d1584caa6623cbfc4e" }
fs_extra = { version = "1.3.0", optional = true }
lnk = "0.6.3"
url = "2.5.7"


# GUI-related dependencies (optional)
Expand Down
141 changes: 98 additions & 43 deletions src-tauri/src/cli/prompts.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use std::path::PathBuf;

use crate::cli::helpers::{
first_defaulted_multiselect, generic_confirm, generic_input, generic_select, run_with_spinner,
use idf_im_lib::{
settings::Settings,
system_dependencies,
utils::{mirror_entries_to_display, sorted_mirror_entries, url_from_display_line},
};
use idf_im_lib::settings::Settings;
use idf_im_lib::system_dependencies;
use log::{debug, info};
use rust_i18n::t;

// no runtime creation here; we run inside the app's existing Tokio runtime
use crate::cli::helpers::generic_confirm_with_default;
use crate::cli::helpers::{
first_defaulted_multiselect, generic_confirm, generic_input, generic_select, run_with_spinner,
};

pub async fn select_target() -> Result<Vec<String>, String> {
let mut available_targets = idf_im_lib::idf_versions::get_avalible_targets().await?;
Expand Down Expand Up @@ -176,49 +180,100 @@ pub fn check_and_install_python(
Ok(())
}

pub fn select_mirrors(mut config: Settings) -> Result<Settings, String> {
if (config.wizard_all_questions.unwrap_or_default()
|| config.idf_mirror.is_none()
|| config.is_default("idf_mirror"))
&& config.non_interactive == Some(false)
{
config.idf_mirror = Some(generic_select(
"wizard.idf.mirror",
&idf_im_lib::get_idf_mirrors_list()
.iter()
.map(|&s| s.to_string())
.collect(),
)?)
}
async fn select_single_mirror<FGet, FSet>(
config: &mut Settings,
field_name: &str, // e.g. "idf_mirror"
get_value: FGet, // e.g. |c: &Settings| &c.idf_mirror
set_value: FSet, // e.g. |c: &mut Settings, v| c.idf_mirror = Some(v)
candidates: Vec<String>, // list of mirror URLs
wizard_key: &str, // e.g. "wizard.idf.mirror"
log_prefix: &str, // e.g. "IDF", "Tools", "PyPI"
) -> Result<(), String>
where
FGet: Fn(&Settings) -> &Option<String>,
FSet: Fn(&mut Settings, String),
{
let interactive = config.non_interactive == Some(false);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is non idiomatic way and also it's reversing the current logic. Curently when non_insteractive is None the wizard work in interactive mode in your case it's the opposite.
just use unwarp_or_default() as all the settings has implemented defaults.

let wizard_all = config.wizard_all_questions.unwrap_or_default();
let current = get_value(config);
let needs_value = current.is_none() || config.is_default(field_name);

if (config.wizard_all_questions.unwrap_or_default()
|| config.mirror.is_none()
|| config.is_default("mirror"))
&& config.non_interactive == Some(false)
{
config.mirror = Some(generic_select(
"wizard.tools.mirror",
&idf_im_lib::get_idf_tools_mirrors_list()
.iter()
.map(|&s| s.to_string())
.collect(),
)?)
}
// Measure and sort mirrors by latency
let entries = sorted_mirror_entries(candidates).await;

if (config.wizard_all_questions.unwrap_or_default()
|| config.pypi_mirror.is_none()
|| config.is_default("pypi_mirror"))
&& config.non_interactive == Some(false)
{
config.pypi_mirror = Some(generic_select(
"wizard.pypi.mirror",
&idf_im_lib::get_pypi_mirrors_list()
.iter()
.map(|&s| s.to_string())
.collect(),
)?)
if interactive && (wizard_all || needs_value) {
// Interactive mode: show list and let user pick
let display = mirror_entries_to_display(&entries);
let selected = generic_select(wizard_key, &display)?;
let url = url_from_display_line(&selected);
set_value(config, url);
} else if needs_value {
// Non-interactive or wizard not requesting this: pick best automatically
if let Some((url, score)) = entries.first() {
if *score == u32::MAX {
info!("Selected {log_prefix} mirror: {url} (timeout)");
} else {
info!("Selected {log_prefix} mirror: {url} ({score} ms)");
}
set_value(config, url.clone());
}
}

Ok(())
}

pub async fn select_mirrors(mut config: Settings) -> Result<Settings, String> {
// IDF mirror
let idf_candidates: Vec<String> = idf_im_lib::get_idf_mirrors_list()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are cloning the mirror list just to iterate over it.

.iter()
.map(|&s| s.to_string())
.collect();

select_single_mirror(
&mut config,
"idf_mirror",
|c: &Settings| &c.idf_mirror,
|c: &mut Settings, v| c.idf_mirror = Some(v),
idf_candidates,
"wizard.idf.mirror",
"IDF",
)
.await?;

// Tools mirror
let tools_candidates: Vec<String> = idf_im_lib::get_idf_tools_mirrors_list()
.iter()
.map(|&s| s.to_string())
.collect();

select_single_mirror(
&mut config,
"mirror",
|c: &Settings| &c.tools_mirror,
|c: &mut Settings, v| c.tools_mirror = Some(v),
tools_candidates,
"wizard.tools.mirror",
"Tools",
)
.await?;

// PyPI mirror
let pypi_candidates: Vec<String> = idf_im_lib::get_pypi_mirrors_list()
.iter()
.map(|&s| s.to_string())
.collect();

select_single_mirror(
&mut config,
"pypi_mirror",
|c: &Settings| &c.pypi_mirror,
|c: &mut Settings, v| c.pypi_mirror = Some(v),
pypi_candidates,
"wizard.pypi.mirror",
"PyPI",
)
.await?;

Ok(config)
}

Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/cli/wizard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ async fn download_and_extract_tools(
config.target.clone().unwrap(),
download_dir,
install_dir,
config.mirror.as_deref(),
config.tools_mirror.as_deref(),
progress_callback,
)
.await
Expand Down Expand Up @@ -396,7 +396,7 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> {
config = select_targets_and_versions(config).await?;

// mirrors select
config = select_mirrors(config)?;
config = select_mirrors(config).await?;

config = select_installation_path(config)?;

Expand Down
30 changes: 28 additions & 2 deletions src-tauri/src/gui/commands/idf_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,27 @@ pub async fn setup_tools(
}
}
};
let default_mirror = settings.tools_mirror.as_deref().unwrap();
let mirror_latency_map = settings.get_tools_mirror_latency_map().await.unwrap();
let best_mirror = mirror_latency_map
.iter()
.min_by_key(|(_, latency)| *latency)
.unwrap()
.0
.clone();
let mirror = match best_mirror.is_empty() {
true => default_mirror,
false => best_mirror.as_str(),
};


// Use the library's setup_tools function
let installed_tools_list = idf_tools::setup_tools(
&tools,
settings.target.clone().unwrap_or_default(),
&PathBuf::from(&tool_setup.download_dir),
&PathBuf::from(&tool_setup.install_dir),
settings.mirror.as_deref(),
Some(mirror),
progress_callback,
)
.await
Expand Down Expand Up @@ -353,6 +366,19 @@ pub async fn setup_tools(
}
};

let default_pypi_mirror = settings.pypi_mirror.as_deref().unwrap();
let pypi_mirror_latency_map = settings.get_pypi_mirror_latency_map().await.unwrap();
let best_pypi_mirror = pypi_mirror_latency_map
.iter()
.min_by_key(|(_, latency)| *latency)
.unwrap()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will cause panic! ... for example if two keys will have same value min_by_key return None. None does not have .0 ...
plese awoid using unwrap if you are not 100% sure it's the right thing to do!!

.0
.clone();
let pypi_mirror = match best_pypi_mirror.is_empty() {
true => default_pypi_mirror,
false => best_pypi_mirror.as_str(),
};

// Install Python environment
match idf_im_lib::python_utils::install_python_env(
&paths,
Expand All @@ -361,7 +387,7 @@ pub async fn setup_tools(
true, //TODO: actually read from config
&settings.idf_features.clone().unwrap_or_default(),
offline_archive_dir, // Offline archive directory
&settings.pypi_mirror, // PyPI mirror
&Some(pypi_mirror.to_string()), // PyPI mirror
).await {
Ok(_) => {
info!("Python environment installed");
Expand Down
Loading
Loading