From d25ee8d327bcb1d7ed10403fc7026b80daae4302 Mon Sep 17 00:00:00 2001 From: z_lenovo Date: Sat, 28 Jun 2025 16:43:09 +0800 Subject: [PATCH] commit before cargo fix --- .cargo/config.toml | 0 .gitignore | 1 + Cargo.toml | 19 +- README.md | 5 + makepad_state0.ron | 140 ++++++++++ src/ui/db_browser.rs | 5 +- src/ui/home_page.rs | 28 +- src/ui/jlc_downloader.rs | 62 ++++- src/ui/main_window.rs | 106 ++++++- src/ui/part_viewer.rs | 5 +- src/utils/lazy.rs | 22 ++ src/utils/mod.rs | 1 + src/utils/step_downloader.rs | 38 +++ src/utils/step_downloader.rs.old.rs | 409 ++++++++++++++++++++++++++++ 14 files changed, 811 insertions(+), 30 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 makepad_state0.ron create mode 100644 src/utils/lazy.rs create mode 100644 src/utils/step_downloader.rs.old.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index ab951f8..081e3f7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # will have compiled files and executables debug/ target/ +web_temp/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/Cargo.toml b/Cargo.toml index 06c6ee5..6e0ae43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ iced = { git = "https://github.com/iced-rs/iced.git", features = [ "debug", "advanced", "image", - + "sipper", ] } tokio = { version = "1.45.1", features = ["full"] } reqwest = "0.12.20" @@ -30,6 +30,23 @@ anyhow = { version = "1.0.98", features = ["backtrace"] } log = "0.4.27" env_logger = "0.11.8" iced_fonts = { version = "0.2.1", features = ["full"] } +image = "0.25.6" +lazy_static = "1.5.0" +serde_json = "1.0.140" +serde = { version = "1.0.219", features = [ + "alloc", + "rc", + "derive", + "serde_derive", +] } +rfd = { version = "0.15.3", features = ["tokio"] } +iced_futures = { version = "0.13.2", features = [ + "async-std", + "smol", + "tokio", + "thread-pool", +] } +num_enum = "0.7.4" [build-dependencies] diff --git a/README.md b/README.md index 361a618..c33ef72 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,8 @@ ## 周期性待办 * 把所有的警告消灭掉 + + +## 忐忑 +* 本软件的3D封装下载调用了jlc的api,不知道哪天就收到了某函,所以暂时只在本站开源了,在未想办法解决掉该可能引起纠纷的事项之前不想广泛传播,所以也请各位道友手下留情,不要随意传播本软件 +中 东奔西跑黑暗 \ No newline at end of file diff --git a/makepad_state0.ron b/makepad_state0.ron new file mode 100644 index 0000000..4b0ded0 --- /dev/null +++ b/makepad_state0.ron @@ -0,0 +1,140 @@ +( + dock_items:{ + edit_tabs:Tabs( + tabs:[ + edit_first, + ], + selected:0, + closable:true, + ), + split3:Splitter( + axis:Horizontal, + align:FromA(20), + a:split4, + b:run_tabs, + ), + split4:Splitter( + axis:Horizontal, + align:Weighted(0.5), + a:outline_tabs, + b:design_tabs, + ), + search:Tab( + name:"Search", + template:SearchTab, + kind:Search, + ), + snapshot_tab:Tab( + name:"Snapshot", + template:SnapshotTab, + kind:Snapshot, + ), + file_tree_tabs:Tabs( + tabs:[ + file_tree_tab, + run_list_tab, + search, + snapshot_tab, + ], + selected:1, + closable:true, + ), + log_tabs:Tabs( + tabs:[ + log_list_tab, + profiler, + ], + selected:0, + closable:true, + ), + file_tree_tab:Tab( + name:"Files", + template:FilesTab, + kind:StudioFileTree, + ), + ai_first:Tab( + name:"", + template:AiFirstTab, + kind:AiFirst, + ), + run_first:Tab( + name:"", + template:RunFirstTab, + kind:RunFirst, + ), + log_list_tab:Tab( + name:"Log", + template:LogTab, + kind:LogList, + ), + edit_first:Tab( + name:"", + template:EditFirstTab, + kind:EditFirst, + ), + design_first:Tab( + name:"", + template:DesignFirstTab, + kind:DesignFirst, + ), + outline_first:Tab( + name:"", + template:OutlineFirstTab, + kind:OutlineFirst, + ), + split2:Splitter( + axis:Horizontal, + align:Weighted(0.5), + a:edit_tabs, + b:split3, + ), + run_list_tab:Tab( + name:"Run", + template:RunListTab, + kind:RunList, + ), + root:Splitter( + axis:Horizontal, + align:FromA(250), + a:file_tree_tabs, + b:split1, + ), + outline_tabs:Tabs( + tabs:[ + outline_first, + ], + selected:0, + closable:true, + ), + design_tabs:Tabs( + tabs:[ + design_first, + ], + selected:0, + closable:true, + ), + run_tabs:Tabs( + tabs:[ + run_first, + ai_first, + ], + selected:0, + closable:true, + ), + profiler:Tab( + name:"Profiler", + template:ProfilerTab, + kind:Profiler, + ), + split1:Splitter( + axis:Vertical, + align:FromB(200), + a:split2, + b:log_tabs, + ), + }, + processes:[ + ], + tab_id_to_file_node_id:{ + }, +) \ No newline at end of file diff --git a/src/ui/db_browser.rs b/src/ui/db_browser.rs index 5d1aa5d..7fad1a9 100644 --- a/src/ui/db_browser.rs +++ b/src/ui/db_browser.rs @@ -1,4 +1,5 @@ use iced::{Length, alignment::Horizontal, widget::Column}; +use log::info; use crate::ui::main_window::{MainWindowMsg, TabContent}; @@ -13,10 +14,10 @@ pub enum DbBrowserMsg { impl TabContent for DbBrowser { type TabMessage = DbBrowserMsg; - fn update(&self, msg: Self::TabMessage) { + fn update(&mut self, msg: Self::TabMessage) { match msg { DbBrowserMsg::Nothing => { - println!("This function not allowed"); + info!("This function not allowed"); } } } diff --git a/src/ui/home_page.rs b/src/ui/home_page.rs index ffae9c3..966135a 100644 --- a/src/ui/home_page.rs +++ b/src/ui/home_page.rs @@ -1,4 +1,5 @@ use iced::{Length, alignment::Horizontal, widget::Column}; +use log::info; #[allow(unused_imports)] use crate::ui::main_window::{MainWindowMsg, TabContent}; @@ -65,13 +66,34 @@ impl TabContent for HomePage { type TabMessage = HomePageMsg; - fn update(&self, msg: Self::TabMessage) { + fn update(&mut self, msg: Self::TabMessage) { match msg { HomePageMsg::Nothing => { - println!("This way ok."); + info!("This way ok."); + } + HomePageMsg::OpenStepDir => { + info!("To open the dir."); + let _ = std::process::Command::new(r"explorer.exe") + .arg(self.step_dir.as_str()) + .output() + .unwrap() + .stdout; + } + HomePageMsg::ChooseStepDir => { + info!("To choose the step dir."); + if let Some(path) = rfd::FileDialog::new().pick_folder() { + let path = path.to_str(); + if let Some(path) = path { + self.step_dir = path.to_string(); + let _ = crate::utils::app_settings::set_step_dir(path.to_string().as_str()); + } + } + } + HomePageMsg::CheckUpdate => { + info!("To check update."); } _ => { - println!("Is the message you should process ? =====>> {msg:?}"); + info!("Is the message you should process ? =====>> {msg:?}"); } } } diff --git a/src/ui/jlc_downloader.rs b/src/ui/jlc_downloader.rs index e7feaf4..7e1c573 100644 --- a/src/ui/jlc_downloader.rs +++ b/src/ui/jlc_downloader.rs @@ -1,34 +1,78 @@ -use iced::{Length, alignment::Horizontal, widget::Column}; - use crate::ui::main_window::{MainWindowMsg, TabContent}; +use crate::utils::step_downloader; +use anyhow::Result; +use iced::{Length, alignment::Horizontal, widget::Column}; +use log::info; #[derive(Default)] -pub struct JlcDownloader {} +pub struct JlcDownloader { + search_word: String, +} #[derive(Debug, Clone, Eq, PartialEq)] pub enum JlcDownloaderMsg { Nothing, + KeywordChanged(String), + KeywordSearchResult(Vec), + KeywordSearchError(String), + SearchPart, } impl TabContent for JlcDownloader { type TabMessage = JlcDownloaderMsg; - fn update(&self, msg: Self::TabMessage) { + fn update(&mut self, msg: Self::TabMessage) { match msg { JlcDownloaderMsg::Nothing => { - println!("JlcDownloaderMsg::Nothing"); + info!("JlcDownloaderMsg::Nothing"); + } + JlcDownloaderMsg::KeywordChanged(k) => { + self.search_word = k; + } + JlcDownloaderMsg::SearchPart => { + info!("Clicked Search"); + } + _ => { + info!("Whach out the msg: {msg:?}"); } } } fn content(&self) -> iced::Element<'_, MainWindowMsg> { - let btn = iced::widget::button("JlcDownloader") - .on_press(MainWindowMsg::JlcDownloader(JlcDownloaderMsg::Nothing)); - + let h = iced::widget::row![ + iced::widget::text("搜索元件:").align_y(iced::Alignment::Center), + iced::widget::text_input("元件名或嘉立创编号", &self.search_word) + .on_input(MainWindowMsg::SearchKeywordChanged), + iced::widget::button("搜索") + .on_press(MainWindowMsg::JlcDownloader(JlcDownloaderMsg::SearchPart)), + ] + .height(Length::Shrink) + .spacing(20); Column::new() .align_x(Horizontal::Left) .width(Length::Fill) .height(Length::Fill) - .push(btn) + .push(h) .into() } } + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct KeywordSearchItem { + name: String, +} +impl JlcDownloader { + pub async fn search_keyword(keyword: &str) -> Result> { + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + for i in 0..10 { + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + info!("In async search_keyword print tick {i}"); + } + Ok(Vec::new()) + } + pub async fn fetch_item_images( + item: &KeywordSearchItem, + ) -> Result> { + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + Ok(Vec::new()) + } +} diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs index 4a6154c..6361bca 100644 --- a/src/ui/main_window.rs +++ b/src/ui/main_window.rs @@ -1,8 +1,15 @@ use crate::ui::db_browser::DbBrowserMsg; use crate::ui::home_page::HomePage; use crate::ui::home_page::HomePageMsg; +use crate::ui::jlc_downloader::JlcDownloader; use crate::ui::jlc_downloader::JlcDownloaderMsg; use crate::ui::part_viewer::PartViewerMsg; +use crate::utils::step_downloader; +use iced::Subscription; +use iced::Task; +use iced::widget::button::warning; +use log::info; +use log::warn; #[allow(unused_imports)] use super::db_browser; @@ -16,8 +23,6 @@ use iced::Theme; #[allow(unused_imports)] use iced::widget as w; use iced::widget::button; -use iced::widget::button::Status; -use iced::widget::button::Style; use iced::widget::row; #[allow(unused_imports)] use iced::{ @@ -26,7 +31,6 @@ use iced::{ widget::{Column, Container, Text, column}, }; use std::fmt::Display; -use std::ops::Index; #[allow(dead_code)] struct MainWindow { @@ -57,12 +61,29 @@ impl Default for MainWindow { } } } +impl MainWindow { + pub fn new() -> (Self, Task) { + ( + Self::default(), + Task::batch([ + Task::perform(JlcDownloader::search_keyword("test"), |x| match x { + Ok(v) => MainWindowMsg::JlcDownloader(JlcDownloaderMsg::KeywordSearchResult(v)), + Err(e) => MainWindowMsg::JlcDownloader(JlcDownloaderMsg::KeywordSearchError( + format!("{e:?}"), + )), + }), + iced::widget::focus_next(), + ]), + ) + } +} pub fn main_window() { - let _ = iced::application(MainWindow::default, MainWindow::update, MainWindow::view) + let _ = iced::application(MainWindow::new, MainWindow::update, MainWindow::view) .theme(MainWindow::theme) .default_font(iced::Font::with_name("微软雅黑")) .title(MainWindow::title) + .subscription(MainWindow::subscription) .run(); } @@ -70,6 +91,7 @@ pub fn main_window() { #[derive(Debug, Clone)] pub enum MainWindowMsg { ThemeChanged(iced::Theme), + SearchKeywordChanged(String), TitleChanged(String), TabSelected(TabId), HomePage(HomePageMsg), @@ -77,17 +99,36 @@ pub enum MainWindowMsg { DbBrowser(DbBrowserMsg), PartViewer(PartViewerMsg), Nothing, + StepDownloader(step_downloader::StepDownloaderEvent), } -#[allow(dead_code)] -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum TabId { - #[default] HomePage, JlcDownloader, DbBrowser, PartViewer, } +impl Default for TabId { + fn default() -> Self { + if let Ok(tab) = crate::utils::app_settings::get_curr_page() { + Self::from(tab) + } else { + Self::HomePage + } + } +} +impl TabId { + pub fn save(&self) { + let v = *self; + match crate::utils::app_settings::set_curr_page(v.into()) { + Ok(_) => {} + Err(_) => { + warn!("Failed to save the current page."); + } + } + } +} impl Display for TabId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -102,6 +143,27 @@ impl Display for TabId { ) } } +impl From for u32 { + fn from(val: TabId) -> Self { + match val { + TabId::HomePage => 0, + TabId::JlcDownloader => 1, + TabId::DbBrowser => 2, + TabId::PartViewer => 3, + } + } +} +impl From for TabId { + fn from(value: u32) -> Self { + match value { + 0 => Self::HomePage, + 1 => Self::JlcDownloader, + 2 => Self::DbBrowser, + 3 => Self::PartViewer, + _ => Self::HomePage, + } + } +} impl MainWindow { fn title(&self) -> String { self.title.clone() @@ -109,6 +171,17 @@ impl MainWindow { fn theme(&self) -> Theme { self.theme.clone() } + + fn subscription(&self) -> Subscription { + info!("subscription run once."); + Subscription::run(step_downloader::StepDownloader::create_process) + .map(MainWindowMsg::StepDownloader) + // iced::keyboard::on_key_press(|key, modifiers| { + // info!("Press the key: {key:?}"); + // info!("The modifiers: {modifiers:?}"); + // Some(MainWindowMsg::Nothing) + // }) + } fn create_tab_btn(&self, tab: TabId) -> Element<'_, MainWindowMsg> { let bstyle = if self.curr_tab == tab { button::danger @@ -116,13 +189,12 @@ impl MainWindow { button::primary }; let txt = format!("{tab}"); - let txt = iced::widget::text(txt); let btn = button(txt).style(bstyle); - btn.on_press(MainWindowMsg::TabSelected(tab.clone())).into() + btn.on_press(MainWindowMsg::TabSelected(tab)).into() } fn update(&mut self, msg: MainWindowMsg) { - println!("Process the msg: {msg:?}"); + info!("Process the msg: {msg:?}"); match msg { MainWindowMsg::ThemeChanged(theme) => { self.theme = theme.clone(); @@ -134,14 +206,15 @@ impl MainWindow { self.title = title; } MainWindowMsg::Nothing => { - println!("Nothing"); + info!("Nothing"); iced::debug::time("Test".to_owned()); } MainWindowMsg::TabSelected(i) => { self.curr_tab = i; + self.curr_tab.save(); } MainWindowMsg::HomePage(msg) => { - println!("update HomePage"); + info!("update HomePage"); self.home_page.update(msg); } MainWindowMsg::JlcDownloader(msg) => { @@ -153,6 +226,13 @@ impl MainWindow { MainWindowMsg::PartViewer(part_viewer_msg) => { self.part_viewer.update(part_viewer_msg); } + MainWindowMsg::SearchKeywordChanged(k) => { + self.jlc_downloader + .update(JlcDownloaderMsg::KeywordChanged(k)); + } + MainWindowMsg::StepDownloader(msg) => { + info!("StepDownloaderEvent: {msg:?}"); + } } } fn view(&self) -> Element<'_, MainWindowMsg> { @@ -175,7 +255,7 @@ impl MainWindow { pub trait TabContent { type TabMessage; - fn update(&self, msg: Self::TabMessage); + fn update(&mut self, msg: Self::TabMessage); fn view(&self) -> Element<'_, MainWindowMsg> { iced::widget::Container::new(self.content()) .width(Length::Fill) diff --git a/src/ui/part_viewer.rs b/src/ui/part_viewer.rs index a844e2c..49660c2 100644 --- a/src/ui/part_viewer.rs +++ b/src/ui/part_viewer.rs @@ -1,4 +1,5 @@ use iced::{Length, alignment::Horizontal, widget::Column}; +use log::info; use crate::ui::main_window::{MainWindowMsg, TabContent}; @@ -13,10 +14,10 @@ pub enum PartViewerMsg { impl TabContent for PartViewer { type TabMessage = PartViewerMsg; - fn update(&self, msg: Self::TabMessage) { + fn update(&mut self, msg: Self::TabMessage) { match msg { PartViewerMsg::Nothing => { - println!("This function not allowed."); + info!("This function not allowed."); } } } diff --git a/src/utils/lazy.rs b/src/utils/lazy.rs new file mode 100644 index 0000000..a9b7827 --- /dev/null +++ b/src/utils/lazy.rs @@ -0,0 +1,22 @@ +use lazy_static::lazy_static; +use tokio::sync::broadcast; + +#[derive(Debug, Clone)] +pub enum UiCmd { + Repaint, +} + +lazy_static! { + static ref UiBroadcast: broadcast::Sender = { + let (tx, _) = broadcast::channel(10); + tx + }; +} + +pub fn get_ui_broadcast_sender() -> broadcast::Sender { + UiBroadcast.clone() +} + +pub fn get_ui_broadcast_receiver() -> broadcast::Receiver { + UiBroadcast.subscribe() +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 711ae84..1dc88ba 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,4 @@ pub mod app_settings; +pub mod lazy; pub mod step_downloader; pub mod winsparkle; diff --git a/src/utils/step_downloader.rs b/src/utils/step_downloader.rs index e69de29..53c0427 100644 --- a/src/utils/step_downloader.rs +++ b/src/utils/step_downloader.rs @@ -0,0 +1,38 @@ +use iced::futures; +use log::info; +use std::time::Duration; + +use iced::task::{Never, Sipper, sipper}; +pub struct StepDownloader { + nothing: String, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum StepDownloaderMsg { + Nothing, + SearchKeyword(String), +} +#[derive(Debug, Clone)] +pub enum StepDownloaderEvent { + Nothing, +} + +impl StepDownloader { + pub fn create_process() -> impl Sipper { + info!("Start create the process."); + sipper(async |mut output| { + loop { + output.send(StepDownloaderEvent::Nothing).await; + info!("In process print once."); + + loop { + info!("In process print tick A."); + tokio::time::sleep(Duration::from_millis(1000)).await; + // output.send(StepDownloaderEvent::Nothing).await; + info!("In process print tick A."); + tokio::time::sleep(Duration::from_millis(1000)).await; + } + } + }) + } +} diff --git a/src/utils/step_downloader.rs.old.rs b/src/utils/step_downloader.rs.old.rs new file mode 100644 index 0000000..843eb4b --- /dev/null +++ b/src/utils/step_downloader.rs.old.rs @@ -0,0 +1,409 @@ +use anyhow::{Result, anyhow}; +use image::DynamicImage; +use log::info; +use tokio::sync::mpsc::{Receiver, Sender, channel}; + +use crate::utils::lazy; + +pub struct StepDownloader { + status: SearchStatus, + rx_status: Receiver, + tx_cmd: Sender, + rx_total: Receiver, + rx_item: Receiver, +} +impl Default for StepDownloader { + fn default() -> Self { + Self::new() + } +} + +pub struct SearchTotalInfo { + total: u32, + curr_page: u32, +} +pub struct SearchItemInfo { + name: String, + imgs: Vec, + stp_id: String, +} + +impl StepDownloader { + pub fn new() -> Self { + let status = SearchStatus::Ready; + let (rx_status, tx_cmd, rx_total, rx_item) = Self::search_process(); + Self { + status, + rx_status, + tx_cmd, + rx_total, + rx_item, + } + } +} + +impl StepDownloader { + fn search_process() -> ( + Receiver, + Sender, + Receiver, + Receiver, + ) { + log::info!("Create the search_process."); + let (tx_status, rx_status) = channel(5); + let (tx_cmd, mut rx_cmd) = channel(5); + let (tx_total, mut rx_total) = channel(10); + let (tx_item, mut rx_item) = channel(10); + + tokio::spawn(async move { + loop { + if let Some(cmd) = rx_cmd.recv().await { + match cmd { + SearchCmd::Search(keyword, cur, lim) => { + log::info!("To search : {}", keyword.clone()); + if tx_status.send(SearchStatus::Searching).await.is_err() { + log::error!("Failed to send the searching state.") + } + match Self::search_keyword(&keyword, cur, lim).await { + Ok(r) => { + //搜索到了Root列表 + let total = SearchTotalInfo { + total: r.result.total, + curr_page: cur, + }; + tx_total.send(total).await.unwrap(); + + //TODO: complete the search process. + } + Err(e) => { + log::error!("Failed to search by keyword : {e:?}"); + if tx_status.send(SearchStatus::Error).await.is_err() { + log::error!("Failed to send tx_status:Error"); + } + } + } + log::info!("Search for {} done.", keyword.clone()); + if tx_status.send(SearchStatus::Done).await.is_err() { + log::error!("Failed to send the searching state.") + } + } + SearchCmd::Stop => { + log::warn!( + "Received cmd to Stop the search process in first rx_cmd.recv, This may cause error!" + ); + if tx_status.send(SearchStatus::Error).await.is_err() { + log::error!("Failed to send tx_status:Error"); + } + } + SearchCmd::Exit => { + log::warn!( + "Received cmd to Exit the search_process, that means to exit the whole Application, Whatch that!" + ); + break; + } + } + } + } + }); + (rx_status, tx_cmd, rx_total, rx_item) + } + pub fn get_status(&mut self) -> SearchStatus { + if let Ok(status) = self.rx_status.try_recv() { + self.status = status; + } + self.status.clone() + } + pub fn search(&self, keyword: &str, cur_page: u32, page_lim: u32) { + if let Ok(()) = + self.tx_cmd + .try_send(SearchCmd::Search(keyword.to_owned(), cur_page, page_lim)) + { + log::info!("Success send the search {keyword} cmd."); + } else { + log::warn!("Failed to send the search {keyword} cmd."); + } + } + pub fn exit(&self) { + let _ = self.tx_cmd.try_send(SearchCmd::Exit); + } + pub fn stop(&self) { + let _ = self.tx_cmd.try_send(SearchCmd::Stop); + } + async fn search_has_device(has_device: &str) -> Result { + let mut form_maps = std::collections::HashMap::new(); + form_maps.insert("uuids[]", has_device); + let url = "https://pro.lceda.cn/api/devices/searchByIds"; + let resp = reqwest::Client::new() + .post(url) + .form(&form_maps) + .send() + .await?; + let text = resp.text().await?; + let v: serde_json::Value = serde_json::from_str(text.as_str())?; + let id = v["result"][0]["attributes"]["3D Model"].clone(); + if let serde_json::Value::String(id) = id { + return Ok(id.clone()); + } + Err(anyhow::Error::msg("Failed to parse the json.")) + } + async fn search_model_id(uuid: &str) -> Result { + let mut form_maps = std::collections::HashMap::new(); + form_maps.insert("uuids[]", uuid); + form_maps.insert("dataStr", "yes"); + let url = "https://pro.lceda.cn/api/components/searchByIds?forceOnline=1"; + + let resp = reqwest::Client::new() + .post(url) + .form(&form_maps) + .send() + .await?; + let text = resp.text().await?; + let v: serde_json::Value = serde_json::from_str(text.as_str())?; + info!("In search_model_id: The v is : {v:#?}"); + let data_str: serde_json::Value = v["result"][0]["dataStr"].clone(); + info!("The data str Value: {data_str:#?}"); + if let serde_json::Value::String(data_str) = data_str { + let data_str: DataStr = serde_json::from_str(data_str.as_str())?; + if let Some(model) = data_str.model { + return Ok(model); + } + } + Err(anyhow::Error::msg("Failed to parse the json.")) + } + async fn download_step(id: &str) -> Result { + let url = format!("https://modules.lceda.cn/qAxj6KHrDKw4blvCG8QJPs7Y/{id}"); + let url = url.as_str(); + let resp = reqwest::get(url).await?; + let text = resp.text().await?; + Ok(text) + } + async fn search_keyword( + keyword: &str, + cur_page: u32, + page_size: u32, + ) -> Result { + let mut form_maps = std::collections::HashMap::new(); + form_maps.insert("keyword", keyword); + let cur_page = &format!("{cur_page}"); + let page_size = &format!("{page_size}"); + form_maps.insert("curPage", cur_page); + form_maps.insert("pageSize", page_size); + let resp = reqwest::Client::new() + .post("https://pro.lceda.cn/api/eda/product/search") + .form(&form_maps) + .send() + .await?; + let text = resp.text().await?; + let j: KeywordSearchRoot = serde_json::from_str(&text)?; + Ok(j) + } +} +#[derive(Clone)] +pub enum SearchCmd { + Search(String, u32, u32), + Stop, + Exit, +} +#[derive(Clone)] +pub enum SearchStatus { + Ready, + Error, + Searching, + Done, +} + +#[cfg(test)] +mod StepDownloaderTest { + use std::io::Write; + + use super::*; + use log::info; + use reqwest; + + // #[tokio::test] + // pub async fn test_search() { + // env_logger::try_init().unwrap(); + // log::info!("To test search `STM32`"); + // let d = StepDownloader::default(); + // d.search("STM32"); + // d.exit(); + // } + #[tokio::test] + async fn test_search_func() { + assert!(env_logger::try_init().is_ok()); + log::info!("Start test for call search_keyword function."); + test_search("STM32").await; + // test_search("STEM32F103").await; + // test_search("TPS54302").await; + } + async fn test_search_uuids(d: &str) { + let rst = super::StepDownloader::search_has_device(d).await; + match rst { + Ok(v) => { + log::info!("Get the uuids: {}", v.clone()); + match super::StepDownloader::search_model_id(v.clone().as_str()).await { + Ok(i) => { + log::info!("Get the model_id: {}", i.clone()); + match super::StepDownloader::download_step(i.as_str()).await { + Ok(t) => { + info!("Success to get the content."); + + let mut file = std::fs::OpenOptions::new() + .read(false) + .write(true) + .create(true) + .append(false) + .open("web_temp/first_step.step") + .unwrap(); + file.write_all(t.as_bytes()).unwrap(); + let _ = file.flush(); + } + Err(e) => todo!(), + } + } + Err(e) => { + log::error!("Failed to call search_model_id: {e:?}"); + } + } + } + Err(e) => { + log::error!("Failed to call search_has_device: {e:?}"); + } + } + } + async fn test_search(k: &str) { + let rst = super::StepDownloader::search_keyword(k, 1, 10).await; + match rst { + Ok(v) => { + log::info!("Search done! ========================"); + log::info!("The result:\r\n\r\n {v:#?}"); + let item = &v.result.productList[0]; + let uuids = item.hasDevice.clone(); + if let Some(v) = uuids { + test_search_uuids(v.as_str()).await; + } + } + Err(e) => { + log::error!("Error occured."); + log::error!("Error : {e:?}"); + } + } + } + // #[tokio::test] + pub async fn test_post() { + if env_logger::try_init().is_ok() { + log::info!("The env_logger::try_init done."); + } + log::info!("Start test"); + match reqwest::get("https://www.baidu.com").await { + Ok(r) => { + log::info!("Get www.baidu.com : {r:?}"); + } + Err(e) => { + log::error!("Failed to get www.baidu.com : {e:?}"); + } + } + log::info!("=========================================="); + let mut form = std::collections::HashMap::new(); + form.insert("keyword", "STM32"); + form.insert("currPage", "1"); + form.insert("pageSize", "10"); + let resp = reqwest::Client::new() + .post("https://pro.lceda.cn/api/eda/product/search") + .form(&form) + .send() + .await; + + match resp { + Ok(mut r) => { + if std::fs::create_dir("web_temp").is_ok() { + log::info!("Created the dir: web_temp"); + } else { + log::warn!("Failed to create the dir: web_temp"); + } + let mut file = std::fs::OpenOptions::new() + .read(false) + .write(true) + // .create(true) + .append(false) + .open("web_temp/first_resp.json") + .unwrap(); + let enc = r.headers(); + info!("Get the headers: {enc:?}"); + info!("-------------------------------------------"); + info!("Get the status: {:?}", r.status()); + info!("-------------------------------------------"); + info!("Get the http version: {:?}", r.version()); + info!("-------------------------------------------"); + info!("Get the url : {:?}", r.url()); + // let text = r.text_with_charset("utf-8").await.unwrap(); + // file.write_all(text.as_bytes()).unwrap(); + + let chunk = r.chunk().await.unwrap(); + if let Some(data) = chunk { + let v = data.to_vec(); + let data = v.as_slice(); + file.write_all(data).unwrap(); + log::info!("All content has been wrote to {file:?}"); + } else { + log::warn!("Failed to parse and save data to file."); + } + } + Err(e) => { + log::error!("Error occured: {e:?}"); + } + } + } +} +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct KeywordSearchRoot { + code: u32, + success: bool, + result: KeywordSearchResult, +} +#[allow(non_snake_case)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct KeywordSearchResult { + total: u32, + productList: Vec, +} +#[allow(non_snake_case)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct KeywordSearchListItem { + id: u32, + code: String, + image: Option, + hasDevice: Option, +} +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct KeywordChipImages(String); +impl KeywordChipImages { + pub fn imgs(&self) -> Vec { + let urls = self.0.clone(); + let list = urls.split("<$>"); + let mut rst = Vec::new(); + for i in list { + rst.push(i.to_string()); + } + rst + } +} +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct KeywordDevInfo { + uuid: String, + description: String, + title: String, + images: Vec, + attributes: KeywordAttributes, +} +#[allow(non_snake_case)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct KeywordAttributes { + Datasheet: Option, +} +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct DataStr { + model: Option, + src: Option, + _type: Option, +}