Jelajahi Sumber

feat: Try to cache remote images

#603
MystiPanda 1 tahun lalu
induk
melakukan
0e1e27b35a

+ 17 - 0
src-tauri/src/cmds.rs

@@ -275,6 +275,23 @@ pub fn get_app_dir() -> CmdResult<String> {
     Ok(app_home_dir)
 }
 
+#[tauri::command]
+pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
+    let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
+    let icon_path = icon_cache_dir.join(name);
+    if !icon_cache_dir.exists() {
+        let _ = std::fs::create_dir_all(&icon_cache_dir);
+    }
+    if !icon_path.exists() {
+        let response = wrap_err!(reqwest::get(url).await)?;
+
+        let mut file = wrap_err!(std::fs::File::create(&icon_path))?;
+
+        let content = wrap_err!(response.bytes().await)?;
+        wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
+    }
+    Ok(icon_path.to_string_lossy().to_string())
+}
 #[tauri::command]
 pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
     let file_path = std::path::Path::new(&path);

+ 1 - 0
src-tauri/src/main.rs

@@ -57,6 +57,7 @@ fn main() -> std::io::Result<()> {
             cmds::test_delay,
             cmds::get_app_dir,
             cmds::copy_icon_file,
+            cmds::download_icon_cache,
             cmds::open_devtools,
             cmds::exit_app,
             // cmds::update_hotkeys,

+ 21 - 1
src/components/proxy/proxy-render.tsx

@@ -19,6 +19,9 @@ import type { IRenderItem } from "./use-render-list";
 import { useVerge } from "@/hooks/use-verge";
 import { useRecoilState } from "recoil";
 import { atomThemeMode } from "@/services/states";
+import { useEffect, useState } from "react";
+import { convertFileSrc } from "@tauri-apps/api/tauri";
+import { downloadIconCache } from "@/services/cmds";
 
 interface RenderProps {
   item: IRenderItem;
@@ -38,6 +41,23 @@ export const ProxyRender = (props: RenderProps) => {
   const [mode] = useRecoilState(atomThemeMode);
   const isDark = mode === "light" ? false : true;
   const itembackgroundcolor = isDark ? "#282A36" : "#ffffff";
+  const [iconCachePath, setIconCachePath] = useState("");
+
+  useEffect(() => {
+    initIconCachePath();
+  }, [group]);
+
+  async function initIconCachePath() {
+    if (group.icon && group.icon.trim().startsWith("http")) {
+      const fileName = getFileName(group.icon);
+      const iconPath = await downloadIconCache(group.icon, fileName);
+      setIconCachePath(convertFileSrc(iconPath));
+    }
+  }
+
+  function getFileName(url: string) {
+    return url.substring(url.lastIndexOf("/") + 1);
+  }
 
   if (type === 0 && !group.hidden) {
     return (
@@ -55,7 +75,7 @@ export const ProxyRender = (props: RenderProps) => {
           group.icon &&
           group.icon.trim().startsWith("http") && (
             <img
-              src={group.icon}
+              src={iconCachePath === "" ? group.icon : iconCachePath}
               height="32px"
               style={{ marginRight: "12px", borderRadius: "6px" }}
             />

+ 0 - 1
src/components/setting/mods/layout-viewer.tsx

@@ -19,7 +19,6 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
   const [sysproxyIcon, setSysproxyIcon] = useState("");
   const [tunIcon, setTunIcon] = useState("");
 
-  // const { menu_icon } = verge ?? {};
   useEffect(() => {
     initIconPath();
   }, []);

+ 23 - 2
src/components/test/test-item.tsx

@@ -17,8 +17,9 @@ import { LanguageTwoTone } from "@mui/icons-material";
 import { Notice } from "@/components/base";
 import { TestBox } from "./test-box";
 import delayManager from "@/services/delay";
-import { cmdTestDelay } from "@/services/cmds";
+import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
 import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
+import { convertFileSrc } from "@tauri-apps/api/tauri";
 
 interface Props {
   id: string;
@@ -39,6 +40,23 @@ export const TestItem = (props: Props) => {
   const [position, setPosition] = useState({ left: 0, top: 0 });
   const [delay, setDelay] = useState(-1);
   const { uid, name, icon, url } = itemData;
+  const [iconCachePath, setIconCachePath] = useState("");
+
+  useEffect(() => {
+    initIconCachePath();
+  }, [icon]);
+
+  async function initIconCachePath() {
+    if (icon && icon.trim().startsWith("http")) {
+      const fileName = getFileName(icon);
+      const iconPath = await downloadIconCache(icon, fileName);
+      setIconCachePath(convertFileSrc(iconPath));
+    }
+  }
+
+  function getFileName(url: string) {
+    return url.substring(url.lastIndexOf("/") + 1);
+  }
 
   const onDelay = async () => {
     setDelay(-2);
@@ -104,7 +122,10 @@ export const TestItem = (props: Props) => {
           {icon && icon.trim() !== "" ? (
             <Box sx={{ display: "flex", justifyContent: "center" }}>
               {icon.trim().startsWith("http") && (
-                <img src={icon} height="40px" />
+                <img
+                  src={iconCachePath === "" ? icon : iconCachePath}
+                  height="40px"
+                />
               )}
               {icon.trim().startsWith("data") && (
                 <img src={icon} height="40px" />

+ 4 - 0
src/services/cmds.ts

@@ -227,3 +227,7 @@ export async function copyIconFile(
 ) {
   return invoke<void>("copy_icon_file", { path, name });
 }
+
+export async function downloadIconCache(url: string, name: string) {
+  return invoke<string>("download_icon_cache", { url, name });
+}