From e6adb8ded0882ea1ba3cf798a21d4c87e1b8b3f2 Mon Sep 17 00:00:00 2001 From: readme-bot Date: Sun, 26 Nov 2023 06:20:30 +0800 Subject: [PATCH] update symbol icon load logic --- src-tauri/Cargo.toml | 2 +- src-tauri/src/info.rs | 88 +++++++++++++++++---- src-tauri/tauri.conf.json | 1 + src/assets/icons/unknown-logo.svg | 9 +++ src/components/historical-data/index.tsx | 31 ++++++-- src/components/latest-assets-percentage.tsx | 29 ++++--- src/utils/app.ts | 12 ++- 7 files changed, 133 insertions(+), 39 deletions(-) create mode 100644 src/assets/icons/unknown-logo.svg diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e4f2285..8d17549 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,7 +23,7 @@ rand = "^0.3" serde = {version = "1.0", features = ["derive"] } serde_json = "1.0" sqlx = {version = "0.6", features = ["runtime-tokio-rustls", "sqlite"] } -tauri = {version = "1.2", features = ["app-all", "dialog-open", "dialog-save", "fs-read-file", "fs-write-file", "http-all", "path-all", "process-relaunch", "protocol-asset", "updater"] } +tauri = {version = "1.2", features = ["app-all", "dialog-open", "dialog-save", "fs-exists", "fs-read-file", "fs-write-file", "http-all", "path-all", "process-relaunch", "protocol-asset", "updater"] } tokio = {version = "1", features = ["sync"] } uuid = "1.3.3" tauri-plugin-aptabase = "0.3" diff --git a/src-tauri/src/info.rs b/src-tauri/src/info.rs index d33e8f9..5bbc0fd 100644 --- a/src-tauri/src/info.rs +++ b/src-tauri/src/info.rs @@ -1,9 +1,14 @@ -use std::{collections::HashMap, vec}; - -use coingecko::{ - CoinGeckoClient, +use std::{ + collections::HashMap, + f32::consts::E, + fs::OpenOptions, + io::{Bytes, Write}, + path::Path, + vec, }; +use coingecko::{response::coins::CoinsMarketItem, CoinGeckoClient}; + pub fn get_coin_info_provider() -> CoinGecko { CoinGecko::new() } @@ -92,24 +97,44 @@ impl CoinGecko { paths.push("assets".to_string()); paths.push("coins".to_string()); let path_str = paths.join("/"); - let download_dir = std::path::Path::new(path_str.as_str()); + let download_dir = Path::new(path_str.as_str()); // mkdir download_dr if not exists if !download_dir.exists() { std::fs::create_dir_all(download_dir)?; } - let non_exists_symbols = symbols.into_iter().filter(|s| { - let path = download_dir.clone(); - let asset_path = path.join(format!("{}.png", s.to_lowercase())); - !asset_path.exists() - }).collect::>(); + let non_exists_symbols = symbols + .into_iter() + .filter(|s| { + let path = download_dir.clone(); + let asset_path = path.join(format!("{}.png", s.to_lowercase())); + !asset_path.exists() + }) + .collect::>(); - println!("non_exists_symbols: {:?}", non_exists_symbols); + let invalid_asset_icon_record_file_path = + download_dir.join("invalid_asset_icon_record.txt"); + + // if non_exists_symbols in invalid_asset_icon_record_file_path, filter it + let non_exists_symbols = if invalid_asset_icon_record_file_path.exists() { + let invalid_asset_icon_record_file_content = + std::fs::read_to_string(invalid_asset_icon_record_file_path.clone())?; + let invalid_asset_icon_record_file_content = invalid_asset_icon_record_file_content + .split("\n") + .map(|s| s.to_string()) + .collect::>(); + non_exists_symbols + .into_iter() + .filter(|s| !invalid_asset_icon_record_file_content.contains(s)) + .collect::>() + } else { + non_exists_symbols + }; if non_exists_symbols.len() == 0 { return Ok(()); } - + println!("non_exists_symbols: {:?}", non_exists_symbols); let all_coins = self.list_all_coin_ids(non_exists_symbols.clone()).await?; // key: symbol, value: id let non_exists_ids = all_coins @@ -136,10 +161,9 @@ impl CoinGecko { let markets_size = markets.len() as i64; for m in markets { - println!("downloading coin logo: {:?}", m.image); - let logo = reqwest::get(m.image).await?.bytes().await?; - - std::fs::write(download_dir.join(format!("{}.png", m.symbol.to_lowercase())), logo)?; + let _ = self + .download_coin_logo(download_dir.clone(), m.clone()) + .await; } if markets_size >= page_size { @@ -152,10 +176,40 @@ impl CoinGecko { let path = download_dir.clone(); let asset_path = path.join(format!("{}.png", symbol.to_lowercase())); if !asset_path.exists() { - std::fs::write(asset_path, "")?; + let mut file = OpenOptions::new() + .create(true) + .write(true) + .append(true) + .open(invalid_asset_icon_record_file_path.clone())?; + // append to invalid asset icon record file + file.write_all(format!("{}\n", symbol).as_bytes())?; } } } Ok(()) } + + async fn download_coin_logo( + &self, + download_dir: &Path, + m: CoinsMarketItem, + ) -> Result<(), Box> { + println!("downloading coin logo: {:?}", m.image); + let mut res = reqwest::get(m.image).await; + if let Err(_) = res { + println!("fallback to download coin logo from github: {:?}", m.symbol); + let url = format!( + "https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/32/color/{}.png", + m.symbol.to_lowercase() + ); + res = reqwest::get(url).await; + } + let logo = res?.bytes().await?; + + std::fs::write( + download_dir.join(format!("{}.png", m.symbol.to_lowercase())), + logo, + )?; + Ok(()) + } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ef6dd2a..fe0e27c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -15,6 +15,7 @@ "fs": { "readFile": true, "writeFile": true, + "exists": true, "scope": ["$APP/**", "$RESOURCE/**", "$APPCACHE/**"] }, "path": { diff --git a/src/assets/icons/unknown-logo.svg b/src/assets/icons/unknown-logo.svg new file mode 100644 index 0000000..c222f37 --- /dev/null +++ b/src/assets/icons/unknown-logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/historical-data/index.tsx b/src/components/historical-data/index.tsx index 51ac7a8..264c243 100644 --- a/src/components/historical-data/index.tsx +++ b/src/components/historical-data/index.tsx @@ -22,6 +22,8 @@ import { appCacheDir as getAppCacheDir } from "@tauri-apps/api/path"; import { useWindowSize } from "@/utils/hook"; import ImageStack from "../common/image-stack"; import { getImageApiPath } from "@/utils/app"; +import UnknownLogo from "@/assets/icons/unknown-logo.svg"; +import bluebird from "bluebird"; type RankData = { id: number; @@ -47,17 +49,13 @@ const App = ({ const [data, setData] = useState([] as HistoricalData[]); const [rankData, setRankData] = useState([] as RankData[]); const [isModalOpen, setIsModalOpen] = useState(false); - const [appCacheDir, setAppCacheDir] = useState(""); + const [logoMap, setLogoMap] = useState<{ [x: string]: string }>({}); const wsize = useWindowSize(); const [pageNum, setPageNum] = useState(1); const pageSize = 10; - useEffect(() => { - getAppCacheDir().then((d) => setAppCacheDir(d)); - }, []); - useEffect(() => { const symbols = _(data) .map((d) => d.assets) @@ -66,12 +64,31 @@ const App = ({ .uniq() .value(); downloadCoinLogos(symbols); + + getLogoMap(data).then((m) => setLogoMap(m)); }, [data]); useEffect(() => { loadAllData(); }, []); + async function getLogoMap(d: HistoricalData[]) { + const acd = await getAppCacheDir(); + const kvs = await bluebird.map( + _(d) + .map((dd) => dd.assets) + .flatten() + .map("symbol") + .value(), + async (s) => { + const path = await getImageApiPath(acd, s); + return { [s]: path }; + } + ); + + return _.assign({}, ...kvs); + } + function loadAllData() { queryHistoricalData(-1).then((d) => setData(d)); } @@ -208,7 +225,7 @@ const App = ({ .sortBy("value") .reverse() .take(7) - .map((a) => getImageApiPath(appCacheDir, a.symbol)) + .map((a) => logoMap[a.symbol] || UnknownLogo) .value()} imageWidth={25} imageHeight={25} @@ -263,7 +280,7 @@ const App = ({ function renderDetailPage(data: RankData[]) { return _(data) .map((d) => { - const apiPath = getImageApiPath(appCacheDir, d.symbol); + const apiPath = logoMap[d.symbol]; return ( diff --git a/src/components/latest-assets-percentage.tsx b/src/components/latest-assets-percentage.tsx index 35a2958..4c4c23c 100644 --- a/src/components/latest-assets-percentage.tsx +++ b/src/components/latest-assets-percentage.tsx @@ -3,7 +3,7 @@ import { CurrencyRateDetail, LatestAssetsPercentageData, } from "@/middlelayers/types"; -import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; +import { Card, CardContent } from "./ui/card"; import _ from "lodash"; import { useEffect, useState } from "react"; import { appCacheDir as getAppCacheDir } from "@tauri-apps/api/path"; @@ -17,11 +17,12 @@ import { import { downloadCoinLogos } from "@/middlelayers/data"; import { Button } from "./ui/button"; import { Separator } from "./ui/separator"; +import UnknownLogo from "@/assets/icons/unknown-logo.svg"; import { - ArrowLeftIcon, ChevronLeftIcon, ChevronRightIcon, } from "@radix-ui/react-icons"; +import bluebird from 'bluebird' const App = ({ currency, @@ -30,17 +31,11 @@ const App = ({ currency: CurrencyRateDetail; data: LatestAssetsPercentageData; }) => { - const [appCacheDir, setAppCacheDir] = useState(""); const [dataPage, setDataPage] = useState(0); const [maxDataPage, setMaxDataPage] = useState(0); + const [logoMap, setLogoMap] = useState<{ [x: string]: string }>({}); const pageSize = 5; - useEffect(() => { - getAppCacheDir().then((dir) => { - setAppCacheDir(dir); - }); - }, []); - const [percentageData, setPercentageData] = useState< { coin: string; @@ -57,8 +52,21 @@ const App = ({ // set max data page setMaxDataPage(Math.floor(data.length / pageSize)); + + // set logo map + getLogoMap(data).then((m) => setLogoMap(m)); }, [data]); + async function getLogoMap(d: LatestAssetsPercentageData) { + const acd = await getAppCacheDir(); + const kvs = await bluebird.map(d, async (coin) => { + const path = await getImageApiPath(acd, coin.coin); + return {[coin.coin]: path} + }) + + return _.assign({}, ...kvs) + } + const options = { maintainAspectRatio: false, responsive: false, @@ -99,7 +107,6 @@ const App = ({ return d; } const top = _(d).sortBy("percentage").reverse().take(count).value(); - console.log(top); const other = _(d).sortBy("percentage").reverse().drop(count).value(); return _([ @@ -175,7 +182,7 @@ const App = ({
{d.coin}
diff --git a/src/utils/app.ts b/src/utils/app.ts index 976a05e..4a781d5 100644 --- a/src/utils/app.ts +++ b/src/utils/app.ts @@ -1,7 +1,7 @@ import * as api from '@tauri-apps/api' import { getClientIDConfiguration } from '../middlelayers/configuration' import { trackEvent } from '@aptabase/tauri' -import { appCacheDir } from "@tauri-apps/api/path" +import { exists } from '@tauri-apps/api/fs' import { convertFileSrc } from "@tauri-apps/api/tauri" export async function getVersion() { @@ -25,7 +25,13 @@ export async function trackEventWithClientID(event: string, props?: { [k: string } } -export function getImageApiPath(cacheDir: string, symbol: string) { +export async function getImageApiPath(cacheDir: string, symbol: string) { const filePath = `${cacheDir}assets/coins/${symbol.toLowerCase()}.png` - return convertFileSrc(filePath) + // check if file exists + return exists(filePath).then((res) => { + if (!res) { + return `https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/32/color/${symbol.toLowerCase()}.png` + } + return convertFileSrc(filePath) + }) }