Compare commits
No commits in common. "master" and "0.0.1" have entirely different histories.
@ -9,9 +9,9 @@ fn main() {
|
||||
win_sparkle_set_appcast_url(
|
||||
"https://dl.wuembed.com/hardware_tk/appcast.xml\0".as_ptr() as *const i8
|
||||
);
|
||||
// win_sparkle_set_eddsa_public_key(
|
||||
// "pXr0FyLTCvtX2BP7d/i3Ot8T9hL+ODBQforwfBp2oLo=\0".as_ptr() as *const i8
|
||||
// );
|
||||
win_sparkle_set_eddsa_public_key(
|
||||
"pXr0FyLTCvtX2BP7d/i3Ot8T9hL+ODBQforwfBp2oLo=\0".as_ptr() as *const i8
|
||||
);
|
||||
win_sparkle_init();
|
||||
}
|
||||
ui::main_window::main_window();
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
use iced::{alignment::Horizontal, widget::Column, Length, Task};
|
||||
use iced::{Length, Task, alignment::Horizontal, widget::Column};
|
||||
use tracing::info;
|
||||
use crate::utils::winsparkle;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::ui::main_window::{MainWindowMsg, TabContent};
|
||||
@ -92,16 +91,8 @@ impl TabContent for HomePage {
|
||||
}
|
||||
HomePageMsg::CheckUpdate => {
|
||||
info!("To check update.");
|
||||
unsafe{
|
||||
winsparkle::win_sparkle_check_update_with_ui_and_install();
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
impl HomePage {
|
||||
pub fn set_theme(&mut self, theme: iced::Theme) {
|
||||
self.theme = theme;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,118 +1,26 @@
|
||||
use crate::ui::main_window::{MainWindowMsg, TabContent};
|
||||
use crate::utils::step_downloader::{self as downloader, FetchResultItem, SearchResultItem};
|
||||
use crate::utils::step_downloader::{self as downloader, JlcSearchResultItem};
|
||||
#[allow(unused_imports)]
|
||||
use anyhow::Result;
|
||||
use iced::widget::{Row, button, keyed_column};
|
||||
use iced::{Element, Length, Task, alignment::Horizontal, widget::Column};
|
||||
use iced::{Length, Task, alignment::Horizontal, widget::Column};
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JlcDownloader {
|
||||
search_word: String,
|
||||
search_status: String,
|
||||
search_available: bool,
|
||||
search_index: usize,
|
||||
result_list: Vec<SearchResultItem>,
|
||||
fetch_list: Vec<FetchResultItem>,
|
||||
msg_disp: String,
|
||||
step_data: String,
|
||||
theme: iced::Theme,
|
||||
item_clickable:bool,
|
||||
current_step_content:String,
|
||||
current_step_name:String,
|
||||
download_log:Vec<String>,
|
||||
}
|
||||
impl JlcDownloader {
|
||||
pub fn set_theme(&mut self, theme: iced::Theme) {
|
||||
self.theme = theme;
|
||||
}
|
||||
fn fetch_something(&mut self) -> Task<MainWindowMsg> {
|
||||
info!("Fetching data : {}", self.search_index);
|
||||
self.msg_disp = format!("Fetching : {}", self.search_index);
|
||||
return if self.search_index < self.result_list.len() {
|
||||
Task::perform(
|
||||
downloader::fetch_item(self.result_list[self.search_index].clone()),
|
||||
|x| match x {
|
||||
Ok(v) => MainWindowMsg::JlcDownloader(JlcDownloaderMsg::ItemFetchResult(v)),
|
||||
Err(e) => MainWindowMsg::JlcDownloader(JlcDownloaderMsg::ItemFetchError(
|
||||
e.to_string(),
|
||||
)),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
}
|
||||
fn create_fetched_item_button(
|
||||
&self,
|
||||
item: &FetchResultItem,
|
||||
width: u32,
|
||||
) -> iced::widget::Button<'_, MainWindowMsg> {
|
||||
let theme = self.theme.clone();
|
||||
let palette = theme.extended_palette();
|
||||
let info = iced::widget::column![
|
||||
iced::widget::text(format!("元件:{}",item.name)),
|
||||
iced::widget::text(format!("编号:{}", item.code)),
|
||||
iced::widget::text(format!("模型:{}", item.model)),
|
||||
]
|
||||
.width(Length::FillPortion(2));
|
||||
let mut btn_content = Row::new().spacing(10);
|
||||
for img in item.imgs.clone().iter() {
|
||||
btn_content = btn_content
|
||||
.push(iced::widget::Image::new(img.clone()).width(Length::FillPortion(1)));
|
||||
}
|
||||
btn_content = btn_content.push(info);
|
||||
if self.item_clickable{
|
||||
let attr = DownloadAttr{
|
||||
name:item.name.clone(),
|
||||
id:item.model_id.clone(),
|
||||
};
|
||||
iced::widget::Button::new(btn_content).on_press(MainWindowMsg::JlcDownloader(
|
||||
JlcDownloaderMsg::PartClicked(attr),
|
||||
))
|
||||
}else{
|
||||
iced::widget::Button::new(btn_content)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for JlcDownloader {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
search_word: "".to_string(),
|
||||
search_status: "".to_string(),
|
||||
search_available: true,
|
||||
search_index: 0,
|
||||
result_list: vec![],
|
||||
fetch_list: vec![],
|
||||
msg_disp: "".to_string(),
|
||||
step_data: "".to_string(),
|
||||
theme: Default::default(),
|
||||
item_clickable: true,
|
||||
current_step_content: "".to_string(),
|
||||
current_step_name: "".to_string(),
|
||||
download_log: vec![],
|
||||
}
|
||||
}
|
||||
search_results: Vec<JlcSearchResultItem>,
|
||||
search_error: String,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum JlcDownloaderMsg {
|
||||
Nothing,
|
||||
KeywordChanged(String),
|
||||
ItemFetchResult(FetchResultItem),
|
||||
ItemFetchError(String),
|
||||
KeywordSearchResult(Vec<SearchResultItem>),
|
||||
KeywordSearchResult(Vec<JlcSearchResultItem>),
|
||||
KeywordSearchError(String),
|
||||
PartClicked(DownloadAttr),
|
||||
StepFetched(String),
|
||||
StepFetchErr(String),
|
||||
OpenDatasheet(String),
|
||||
SearchPart,
|
||||
}
|
||||
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||
struct DownloadAttr{
|
||||
name:String,
|
||||
id:String,
|
||||
}
|
||||
|
||||
impl TabContent for JlcDownloader {
|
||||
type TabMessage = JlcDownloaderMsg;
|
||||
|
||||
@ -123,17 +31,10 @@ impl TabContent for JlcDownloader {
|
||||
}
|
||||
JlcDownloaderMsg::KeywordChanged(k) => {
|
||||
self.search_word = k;
|
||||
self.search_status.clear();
|
||||
}
|
||||
JlcDownloaderMsg::SearchPart => {
|
||||
self.search_status = "Searching...".to_string();
|
||||
self.search_index = 0;
|
||||
self.search_available = false;
|
||||
self.fetch_list.clear();
|
||||
self.result_list.clear();
|
||||
return Task::perform(
|
||||
downloader::search_keyword(self.search_word.clone(), 1, 20),
|
||||
|x| match x {
|
||||
return Task::perform(downloader::search_keyword(self.search_word.clone()), |x| {
|
||||
match x {
|
||||
Ok(v) => {
|
||||
MainWindowMsg::JlcDownloader(JlcDownloaderMsg::KeywordSearchResult(v))
|
||||
}
|
||||
@ -141,53 +42,16 @@ impl TabContent for JlcDownloader {
|
||||
let e = format!("{e:?}");
|
||||
MainWindowMsg::JlcDownloader(JlcDownloaderMsg::KeywordSearchError(e))
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
JlcDownloaderMsg::KeywordSearchResult(rest) => {
|
||||
self.search_status.clear();
|
||||
self.search_available = true;
|
||||
self.result_list.clear();
|
||||
self.result_list = rest;
|
||||
self.search_index = 0;
|
||||
return self.fetch_something();
|
||||
}
|
||||
JlcDownloaderMsg::KeywordSearchError(e) => {
|
||||
self.search_status = e;
|
||||
self.search_available = true;
|
||||
}
|
||||
JlcDownloaderMsg::ItemFetchResult(item) => {
|
||||
self.fetch_list.push(item);
|
||||
//开始fetch下一条
|
||||
self.search_index += 1;
|
||||
return self.fetch_something();
|
||||
}
|
||||
JlcDownloaderMsg::ItemFetchError(e) => {
|
||||
info!("JlcDownloaderMsg::ItemFetchError({:?})", e);
|
||||
self.search_index += 1;
|
||||
return self.fetch_something();
|
||||
}
|
||||
JlcDownloaderMsg::PartClicked(id) => {
|
||||
self.current_step_name = id.name.clone();
|
||||
info!("JlcDownloaderMsg::PartClicked {:?}", id);
|
||||
self.item_clickable = false;
|
||||
return Task::perform(downloader::download_step(id.id), |x| match x {
|
||||
Ok(s) => MainWindowMsg::JlcDownloader(JlcDownloaderMsg::StepFetched(s)),
|
||||
Err(e) => MainWindowMsg::JlcDownloader(JlcDownloaderMsg::ItemFetchError(
|
||||
e.to_string(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
}
|
||||
JlcDownloaderMsg::StepFetched(s) => {
|
||||
self.download_log.push(format!("3D for {} 下载成功",self.current_step_name));
|
||||
self.item_clickable = true;
|
||||
JlcDownloaderMsg::KeywordSearchResult(rest) => {
|
||||
info!("JlcDownloaderMsg::KeywordSearchResult");
|
||||
self.search_error.clear();
|
||||
self.search_results = rest;
|
||||
}
|
||||
JlcDownloaderMsg::StepFetchErr(e) => {
|
||||
self.item_clickable = true;
|
||||
info!("JlcDownloaderMsg::StepFetchError({:?})", e);
|
||||
}
|
||||
JlcDownloaderMsg::OpenDatasheet(url) => {
|
||||
todo!("To open the url!");
|
||||
JlcDownloaderMsg::KeywordSearchError(e) => {
|
||||
self.search_error = e;
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
@ -195,43 +59,20 @@ impl TabContent for JlcDownloader {
|
||||
|
||||
fn content(&self) -> iced::Element<'_, MainWindowMsg> {
|
||||
let h = iced::widget::row![
|
||||
iced::widget::text("搜索元件:").align_y(iced::Alignment::Center),
|
||||
iced::widget::text_input("元件名或嘉立创编号", &self.search_word)
|
||||
.on_input(MainWindowMsg::SearchKeywordChanged)
|
||||
.width(iced::FillPortion(6)),
|
||||
if !self.search_available {
|
||||
iced::widget::button("搜索").width(iced::FillPortion(1))
|
||||
} else {
|
||||
iced::widget::button("搜索")
|
||||
.width(iced::FillPortion(1))
|
||||
.on_press(MainWindowMsg::JlcDownloader(JlcDownloaderMsg::SearchPart))
|
||||
},
|
||||
iced::widget::horizontal_space().width(iced::FillPortion(1)),
|
||||
];
|
||||
let mut results = iced::widget::column![];
|
||||
for item in self.fetch_list.iter() {
|
||||
results = results.push(self.create_fetched_item_button(&item, 1000));
|
||||
}
|
||||
|
||||
let mut logs = iced::widget::column!["预览暂不可用,期待后期iced更新","日志:"];
|
||||
for item in self.download_log.iter(){
|
||||
logs = logs.push(iced::widget::text(item.clone()));
|
||||
}
|
||||
let body = iced::widget::row![
|
||||
iced::widget::column![
|
||||
iced::widget::text(self.msg_disp.clone()).height(Length::Shrink),
|
||||
iced::widget::scrollable(iced::widget::column![results.spacing(10),]),
|
||||
]
|
||||
.width(iced::Length::FillPortion(6)),
|
||||
// logs.width(iced::Length::FillPortion(4)),
|
||||
iced::widget::scrollable(logs).width(iced::Length::FillPortion(4)),
|
||||
.on_input(MainWindowMsg::SearchKeywordChanged),
|
||||
iced::widget::button("搜索")
|
||||
.on_press(MainWindowMsg::JlcDownloader(JlcDownloaderMsg::SearchPart)),
|
||||
]
|
||||
.height(Length::Fill);
|
||||
Column::new()
|
||||
.height(Length::Shrink)
|
||||
.spacing(40);
|
||||
let c = Column::new()
|
||||
.align_x(Horizontal::Left)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.push(h)
|
||||
.push(body)
|
||||
.into()
|
||||
.push(h);
|
||||
|
||||
return c.into();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ use crate::ui::home_page::HomePage;
|
||||
use crate::ui::home_page::HomePageMsg;
|
||||
use crate::ui::jlc_downloader::JlcDownloaderMsg;
|
||||
use crate::ui::part_viewer::PartViewerMsg;
|
||||
use iced::color;
|
||||
use iced::Subscription;
|
||||
use iced::Task;
|
||||
use tracing::info;
|
||||
@ -17,16 +16,16 @@ use super::home_page;
|
||||
use super::jlc_downloader;
|
||||
#[allow(unused_imports)]
|
||||
use super::part_viewer;
|
||||
use iced::Theme;
|
||||
#[allow(unused_imports)]
|
||||
use iced::widget as w;
|
||||
use iced::widget::button;
|
||||
use iced::widget::row;
|
||||
use iced::Theme;
|
||||
#[allow(unused_imports)]
|
||||
use iced::{
|
||||
alignment::{Horizontal, Vertical}, widget::{column, Column, Container, Text},
|
||||
Element,
|
||||
Length,
|
||||
Element, Length,
|
||||
alignment::{Horizontal, Vertical},
|
||||
widget::{Column, Container, Text, column},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
@ -39,7 +38,6 @@ struct MainWindow {
|
||||
jlc_downloader: crate::ui::jlc_downloader::JlcDownloader,
|
||||
db_browser: crate::ui::db_browser::DbBrowser,
|
||||
part_viewer: crate::ui::part_viewer::PartViewer,
|
||||
explain: bool,
|
||||
}
|
||||
impl Default for MainWindow {
|
||||
fn default() -> Self {
|
||||
@ -49,23 +47,25 @@ impl Default for MainWindow {
|
||||
theme = Theme::ALL[saved_theme as usize % Theme::ALL.len()].clone();
|
||||
home_page.theme = theme.clone();
|
||||
}
|
||||
let mut jlc_downloader = crate::ui::jlc_downloader::JlcDownloader::default();
|
||||
jlc_downloader.set_theme(theme.clone());
|
||||
Self {
|
||||
title: "HardwareToolkit".into(),
|
||||
theme,
|
||||
curr_tab: Default::default(),
|
||||
home_page,
|
||||
jlc_downloader,
|
||||
jlc_downloader: Default::default(),
|
||||
db_browser: Default::default(),
|
||||
part_viewer: Default::default(),
|
||||
explain: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MainWindow {
|
||||
pub fn new() -> (Self, Task<MainWindowMsg>) {
|
||||
(Self::default(), Task::batch([iced::widget::focus_next()]))
|
||||
(
|
||||
Self::default(),
|
||||
Task::batch([
|
||||
iced::widget::focus_next(),
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +89,6 @@ pub enum MainWindowMsg {
|
||||
JlcDownloader(JlcDownloaderMsg),
|
||||
DbBrowser(DbBrowserMsg),
|
||||
PartViewer(PartViewerMsg),
|
||||
Explain(bool),
|
||||
Nothing,
|
||||
}
|
||||
|
||||
@ -183,14 +182,14 @@ impl MainWindow {
|
||||
btn.on_press(MainWindowMsg::TabSelected(tab)).into()
|
||||
}
|
||||
fn update(&mut self, msg: MainWindowMsg) -> Task<MainWindowMsg> {
|
||||
info!("Process the msg: {msg:?}");
|
||||
match msg {
|
||||
MainWindowMsg::ThemeChanged(theme) => {
|
||||
self.theme = theme.clone();
|
||||
if let Some(idx) = Theme::ALL.iter().position(|x| x == &theme) {
|
||||
crate::utils::app_settings::set_curr_theme(idx as u32).unwrap();
|
||||
}
|
||||
self.home_page.set_theme(theme);
|
||||
self.home_page.update(HomePageMsg::Nothing)
|
||||
Task::none()
|
||||
}
|
||||
MainWindowMsg::TitleChanged(title) => {
|
||||
self.title = title;
|
||||
@ -216,10 +215,6 @@ impl MainWindow {
|
||||
MainWindowMsg::SearchKeywordChanged(k) => self
|
||||
.jlc_downloader
|
||||
.update(JlcDownloaderMsg::KeywordChanged(k)),
|
||||
MainWindowMsg::Explain(explain) => {
|
||||
self.explain = explain;
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
fn view(&self) -> Element<'_, MainWindowMsg> {
|
||||
@ -227,39 +222,13 @@ impl MainWindow {
|
||||
self.create_tab_btn(TabId::HomePage),
|
||||
self.create_tab_btn(TabId::JlcDownloader),
|
||||
self.create_tab_btn(TabId::DbBrowser),
|
||||
self.create_tab_btn(TabId::PartViewer),
|
||||
iced::widget::horizontal_space().width(Length::Fill),
|
||||
iced::widget::checkbox("Explain", self.explain).on_toggle(MainWindowMsg::Explain),
|
||||
self.create_tab_btn(TabId::PartViewer)
|
||||
];
|
||||
let v = match self.curr_tab {
|
||||
TabId::HomePage => {
|
||||
if self.explain {
|
||||
self.home_page.view().explain(color!(0xff0000))
|
||||
} else {
|
||||
self.home_page.view()
|
||||
}
|
||||
}
|
||||
TabId::JlcDownloader => {
|
||||
if self.explain {
|
||||
self.jlc_downloader.view().explain(color!(0xff0000))
|
||||
} else {
|
||||
self.jlc_downloader.view()
|
||||
}
|
||||
}
|
||||
TabId::DbBrowser => {
|
||||
if self.explain {
|
||||
self.db_browser.view().explain(color!(0xff0000))
|
||||
} else {
|
||||
self.db_browser.view()
|
||||
}
|
||||
}
|
||||
TabId::PartViewer => {
|
||||
if self.explain {
|
||||
self.part_viewer.view().explain(color!(0xff0000))
|
||||
} else {
|
||||
self.part_viewer.view()
|
||||
}
|
||||
}
|
||||
TabId::HomePage => self.home_page.view(),
|
||||
TabId::JlcDownloader => self.jlc_downloader.view(),
|
||||
TabId::DbBrowser => self.db_browser.view(),
|
||||
TabId::PartViewer => self.part_viewer.view(),
|
||||
};
|
||||
// let content = iced::widget::Button::new("Click").on_press(MainWindowMsg::Nothing);
|
||||
column![h, v].into()
|
||||
|
||||
@ -1,206 +1,16 @@
|
||||
use anyhow::Result;
|
||||
use image::EncodableLayout;
|
||||
#[allow(unused_imports)]
|
||||
use tracing::{error, info, warn};
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct SearchResultItem {
|
||||
name: String,
|
||||
code: String,
|
||||
has_device: String,
|
||||
img_urls: Vec<String>,
|
||||
}
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct FetchResultItem {
|
||||
pub imgs: Vec<iced::widget::image::Handle>,
|
||||
pub name: String,
|
||||
pub model:String,
|
||||
pub code :String,
|
||||
pub model_id: String,
|
||||
pub datasheet:String,
|
||||
}
|
||||
/// 访问一次api,得到需要的部分数据
|
||||
pub async fn search_keyword(
|
||||
keyword: String,
|
||||
cur_page: u32,
|
||||
page_size: u32,
|
||||
) -> Result<Vec<SearchResultItem>> {
|
||||
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)?;
|
||||
let mut r = Vec::new();
|
||||
for i in j.result.productList.iter() {
|
||||
if let Some(has) = i.hasDevice.clone() {
|
||||
let mut imgs = Vec::new();
|
||||
if let Some(s) = i.image.clone() {
|
||||
for img in s.imgs() {
|
||||
imgs.push(img.clone());
|
||||
}
|
||||
}
|
||||
let ii = SearchResultItem {
|
||||
name: i.model.clone(),
|
||||
code:i.code.clone(),
|
||||
has_device: has,
|
||||
img_urls: imgs,
|
||||
};
|
||||
r.push(ii);
|
||||
}
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
pub async fn download_step(id: String) -> Result<String> {
|
||||
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)
|
||||
}
|
||||
pub async fn fetch_item(item: SearchResultItem) -> Result<FetchResultItem> {
|
||||
let h = search_has_device(&item.has_device).await?;
|
||||
let mut has_device = String::new();
|
||||
let v: serde_json::Value = serde_json::from_str(h.as_str())?;
|
||||
let id = v["result"][0]["attributes"]["3D Model"].clone();
|
||||
if let serde_json::Value::String(id) = id {
|
||||
has_device = id.clone();
|
||||
}
|
||||
|
||||
let mut title = String::new();
|
||||
let mut footprint = String::new();
|
||||
let mut datasheet = String::new();
|
||||
|
||||
if let serde_json::Value::String(t) = v["result"][0]["title"].clone(){
|
||||
title = t;
|
||||
}
|
||||
if let serde_json::Value::String(t) = v["result"][0]["attributes"]["Supplier Footprint"].clone(){
|
||||
footprint = t;
|
||||
}
|
||||
if let serde_json::Value::String(t) = v["result"][0]["attributes"]["Datasheet"].clone(){
|
||||
datasheet = t;
|
||||
}
|
||||
|
||||
let mut step_id = String::new();
|
||||
let resp = search_model_id(has_device.as_str()).await?;
|
||||
let mut model = String::new();
|
||||
let v: serde_json::Value = serde_json::from_str(resp.as_str())?;
|
||||
info!("In search_model_id: The v is : {v:#?}");
|
||||
let data_str: serde_json::Value = v["result"][0]["dataStr"].clone();
|
||||
|
||||
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(m) = data_str.model {
|
||||
step_id = m;
|
||||
}else{
|
||||
return Err(anyhow::Error::msg("Failed to get model"));
|
||||
}
|
||||
}
|
||||
|
||||
let mut imgs = Vec::new();
|
||||
for img_url in item.img_urls {
|
||||
info!("Fetching img url: {}", img_url);
|
||||
let img = reqwest::get(img_url).await?.bytes().await?;
|
||||
// let img = image::load_from_memory(&img)?;
|
||||
let img = iced::widget::image::Handle::from_bytes(img.clone());
|
||||
imgs.push(img);
|
||||
}
|
||||
let rst = FetchResultItem {
|
||||
name: item.name.clone(),
|
||||
model_id: step_id,
|
||||
model:footprint,
|
||||
code:item.code,
|
||||
imgs,
|
||||
datasheet,
|
||||
};
|
||||
Ok(rst)
|
||||
}
|
||||
async fn search_has_device(has_device: &str) -> Result<String> {
|
||||
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?;
|
||||
Ok(text)
|
||||
#[derive(Debug,Clone,Eq,PartialEq)]
|
||||
pub struct JlcSearchResultItem{
|
||||
pub chip_name:String,
|
||||
pub imgs_url:Vec<String>,
|
||||
}
|
||||
|
||||
async fn search_model_id(uuid: &str) -> Result<String> {
|
||||
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?;
|
||||
info!("In search_model_id: The v is : {}",text.clone());
|
||||
return Ok(text)
|
||||
}
|
||||
#[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
||||
struct KeywordSearchRoot {
|
||||
code: u32,
|
||||
success: bool,
|
||||
result: KeywordSearchResult,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq)]
|
||||
struct KeywordSearchResult {
|
||||
total: u32,
|
||||
productList: Vec<KeywordSearchListItem>,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq)]
|
||||
struct KeywordSearchListItem {
|
||||
id: u32,
|
||||
code: String,
|
||||
image: Option<KeywordChipImages>,
|
||||
model: String,
|
||||
hasDevice: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq)]
|
||||
struct KeywordChipImages(String);
|
||||
#[allow(dead_code)]
|
||||
impl KeywordChipImages {
|
||||
pub fn imgs(&self) -> Vec<String> {
|
||||
let urls = self.0.clone();
|
||||
let list = urls.split("<$>");
|
||||
let mut rst = Vec::new();
|
||||
for i in list {
|
||||
rst.push(i.to_string());
|
||||
}
|
||||
rst
|
||||
pub async fn search_keyword(keyword:String)->Result<Vec<JlcSearchResultItem>>{
|
||||
if keyword.is_empty(){
|
||||
return Err(anyhow::anyhow!("No keyword found"));
|
||||
}
|
||||
}
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct KeywordDevInfo {
|
||||
uuid: String,
|
||||
description: String,
|
||||
title: String,
|
||||
images: Vec<String>,
|
||||
attributes: KeywordAttributes,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct KeywordAttributes {
|
||||
Datasheet: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct DataStr {
|
||||
model: Option<String>,
|
||||
src: Option<String>,
|
||||
_type: Option<String>,
|
||||
Err(anyhow::Error::msg("Search error"))
|
||||
}
|
||||
409
src/utils/step_downloader.rs.old.rs
Normal file
409
src/utils/step_downloader.rs.old.rs
Normal file
@ -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<SearchStatus>,
|
||||
tx_cmd: Sender<SearchCmd>,
|
||||
rx_total: Receiver<SearchTotalInfo>,
|
||||
rx_item: Receiver<SearchItemInfo>,
|
||||
}
|
||||
impl Default for StepDownloader {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SearchTotalInfo {
|
||||
total: u32,
|
||||
curr_page: u32,
|
||||
}
|
||||
pub struct SearchItemInfo {
|
||||
name: String,
|
||||
imgs: Vec<DynamicImage>,
|
||||
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<SearchStatus>,
|
||||
Sender<SearchCmd>,
|
||||
Receiver<SearchTotalInfo>,
|
||||
Receiver<SearchItemInfo>,
|
||||
) {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<KeywordSearchRoot> {
|
||||
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<KeywordSearchListItem>,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct KeywordSearchListItem {
|
||||
id: u32,
|
||||
code: String,
|
||||
image: Option<KeywordChipImages>,
|
||||
hasDevice: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct KeywordChipImages(String);
|
||||
impl KeywordChipImages {
|
||||
pub fn imgs(&self) -> Vec<String> {
|
||||
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<String>,
|
||||
attributes: KeywordAttributes,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct KeywordAttributes {
|
||||
Datasheet: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct DataStr {
|
||||
model: Option<String>,
|
||||
src: Option<String>,
|
||||
_type: Option<String>,
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<channel>
|
||||
<title>
|
||||
硬件工具箱
|
||||
</title>
|
||||
<link>
|
||||
https://dl.wuembed.com/hardware_tk/appcast.xml
|
||||
</link>
|
||||
<language>
|
||||
en
|
||||
</language>
|
||||
<item>
|
||||
<title>
|
||||
Version {{VERSION}}
|
||||
</title>
|
||||
<sparkle:releaseNotesLink>
|
||||
https://dl.wuembed.com/hardware_tk/{{VERSION}}.html
|
||||
</sparkle:releaseNotesLink>
|
||||
<enclosure url="https://dl.wuembed.com/hardware_tk/hardware_tk_{{VERSION}}.exe"
|
||||
sparkle:version="{{VERSION}}"
|
||||
length="{{LENGTH}}"
|
||||
type="application/octet-stream"
|
||||
sparkle:dsaSignature="{{SIGNATURE}}" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
Loading…
x
Reference in New Issue
Block a user