Kaynağa Gözat

优化基础逻辑

john 1 yıl önce
ebeveyn
işleme
71e44ac2f2

+ 24 - 0
src-tauri/Cargo.lock

@@ -328,6 +328,15 @@ version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
 
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -4328,6 +4337,7 @@ dependencies = [
  "sha2",
  "tauri",
  "tauri-build",
+ "tauri-plugin-persisted-scope",
  "tauri-plugin-sql",
  "thiserror",
  "tracing",
@@ -4395,6 +4405,20 @@ dependencies = [
  "tauri-utils",
 ]
 
+[[package]]
+name = "tauri-plugin-persisted-scope"
+version = "0.1.3"
+source = "git+https://gitee.com/seamong/plugins-workspace?branch=v1#0d649843c6b49b7a69200816c8d5195f9953a4fb"
+dependencies = [
+ "aho-corasick",
+ "bincode",
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "thiserror",
+]
+
 [[package]]
 name = "tauri-plugin-sql"
 version = "0.0.0"

+ 4 - 0
src-tauri/Cargo.toml

@@ -38,6 +38,10 @@ git = "https://gitee.com/seamong/plugins-workspace"
 branch = "v1"
 features = ["sqlite"] # or "postgres", or "mysql"
 
+[dependencies.tauri-plugin-persisted-scope]
+git = "https://gitee.com/seamong/plugins-workspace"
+branch = "v1"
+
 [workspace]
 #members = ["modules"]
 

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

@@ -1,7 +1,5 @@
 #![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
 
-// mod files;
-// use crate::files::{file_struct, file_tools};
 mod menus;
 mod event_loop;
 mod self_plugin;
@@ -9,24 +7,17 @@ mod common;
 mod utils;
 mod servics;
 
-
 use crate::menus::default::use_memu;
 use crate::menus::event::m_event;
 use crate::event_loop::{greet, file_path, file_sort};
-// use self_plugin::tauri_plugin_sqlite;
 use self_plugin::tauri_plugin_file;
 use servics::files_servics;
-
-
-use tauri::{Manager, generate_context};
-// use tauri_plugin_sql::{Builder as SqlBuilder, TauriSql};
 use tauri::api::path::app_data_dir;
 
-use tauri_plugin_sql::{Migration, MigrationKind};
-
 fn main() {
     tauri::Builder::default()
         // .plugin(tauri_plugin_sqlite::init())
+        .plugin(tauri_plugin_persisted_scope::init())
         .plugin(tauri_plugin_file::init())
         .plugin(files_servics::init())
         .plugin(

+ 74 - 131
src-tauri/src/self_plugin/tauri_plugin_file/files.rs

@@ -1,5 +1,5 @@
 use hex;
-use serde::{Deserialize, Serialize, Serializer};
+use serde::{Deserialize, Serialize};
 use sha2::{Digest as OtherDigest, Sha256}; // 确保导入 `Digest`
 use std::fs;
 use std::path::{Path, PathBuf};
@@ -7,75 +7,6 @@ use std::time::UNIX_EPOCH;
 use tauri::command;
 extern crate trash;
 
-#[derive(Debug, thiserror::Error)]
-pub enum Error {
-    #[error(transparent)]
-    Io(#[from] std::io::Error),
-}
-
-impl Serialize for Error {
-    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        serializer.serialize_str(self.to_string().as_ref())
-    }
-}
-
-type Result<T> = std::result::Result<T, Error>;
-
-// 提取 /Users/sysadmin/code/rust_project/tauri-app/diff_source/node_modules 中,最后面的数据
-/* fn extract_last_value<'a>(path: &'a str, value: &'a str) -> &'a str {
-    if let Some(index) = path.rfind(value) {
-        let (content, _) = path.split_at(index);
-        content.trim_end_matches('/')
-    } else {
-        path
-    }
-} */
-
-// 过滤
-/* fn filter_other_directory(path: &str, directories: &[&str]) -> bool {
-    // let directories = ["node_modules", ".git", ".obsidian", ".DS_Store"];
-    for directory in directories.iter() {
-        if extract_last_value(path, directory) != path {
-            return false;
-        }
-    }
-    true
-} */
-
-// fn read_files_in_directory(directory: &Path, files: &mut Vec<PathBuf>) -> Result<()> {
-//     if let Ok(entries) = fs::read_dir(directory) {
-//         for entry in entries {
-//             if let Ok(entry) = entry {
-//                 let path = entry.path();
-//                 if path.is_file()
-//                     && filter_other_directory(
-//                         path.display().to_string().as_str(),
-//                         &[".obsidian", ".DS_Store"],
-//                     )
-//                 {
-//                     // 过滤文件
-//                     // TODO 后续加上需要过滤的文件
-//                     println!("59{}", path.display());
-//                     files.push(path.clone());
-//                 } else if path.is_dir()
-//                     && filter_other_directory(
-//                         path.display().to_string().as_str(),
-//                         &["node_modules", ".git", ".obsidian", ".DS_Store"],
-//                     )
-//                 {
-//                     // 过滤 目录
-//                     // println!("{}", path.display());
-//                     read_files_in_directory(&path, files)?;
-//                 }
-//             }
-//         }
-//     }
-//     Ok(())
-// }
-
 #[derive(Debug, Deserialize, Serialize)]
 pub enum FileSizeCategory {
     Huge,      // 4GB+
@@ -104,34 +35,23 @@ pub struct FileInfo {
 }
 
 #[command]
-pub fn get_all_directory(file_info: FileInfo) -> Result<Vec<PathBuf>> {
+pub fn get_all_directory(file_info: FileInfo) -> Vec<PathBuf> {
     let mut files = Vec::new();
     if let Some(ref path) = file_info.path {
         println!("Processing directory: {}", path);
         let directory = Path::new(path);
-        // 确保 read_files_in_directory 能返回一个 Result<(), Error>
         read_files_in_directory(
             directory,
             &mut files,
             &file_info.checked_size_values,
             &file_info.types,
-        )?;
-        Ok(files)
+        );
+        files
     } else {
-        // 当没有提供路径时返回错误
-        // Err(Error::new(std::io::ErrorKind::NotFound, "No path provided"))
-        Ok(files)
+        files
     }
 }
 
-// #[command]
-// fn getFileType(file_path: String) -> Option<String> {
-//     let path = Path::new(&file_path);
-//     path.extension()
-//         .and_then(|ext| ext.to_str())
-//         .map(|ext| ext.to_lowercase())
-// }
-
 #[command]
 pub fn get_file_type(file_path: &str) -> Option<&str> {
     let path = Path::new(file_path);
@@ -139,11 +59,11 @@ pub fn get_file_type(file_path: &str) -> Option<&str> {
 }
 
 #[command]
-pub fn get_file_type_by_path(file_path: String) -> Result<String> {
+pub fn get_file_type_by_path(file_path: String) -> String {
     if let Some(file_type) = get_file_type(&file_path) {
-        Ok(file_type.to_string())
+        file_type.to_string()
     } else {
-        Ok("Unknown file type".to_string())
+        "Unknown file type".to_string()
     }
 }
 
@@ -152,43 +72,36 @@ fn read_files_in_directory(
     files: &mut Vec<PathBuf>,
     filters: &Option<Vec<FileSizeCategory>>,
     types: &Option<Vec<String>>,
-) -> Result<()> {
+) {
     if dir.is_dir() {
-        for entry in fs::read_dir(dir)? {
-            let entry = entry?;
-            let path = entry.path();
-            if path.is_dir() {
-                read_files_in_directory(&path, files, filters, types)?;
-            } else {
-                if let Ok(metadata) = fs::metadata(&path) {
-                    let size = metadata.len();
-                    let size_matches =
-                        filters.is_none() || file_size_matches(size, filters.as_ref().unwrap());
-                    let type_matches =
-                        types.is_none() || file_type_matches(&path, types.as_ref().unwrap());
-
-                    if size_matches && type_matches {
-                        files.push(path);
+        // 尝试读取目录,忽略错误
+        if let Ok(entries) = fs::read_dir(dir) {
+            for entry in entries {
+                if let Ok(entry) = entry {
+                    let path = entry.path();
+                    if path.is_dir() {
+                        // 递归调用,忽略错误
+                        read_files_in_directory(&path, files, filters, types);
+                    } else {
+                        // 尝试获取文件元数据,忽略错误
+                        if let Ok(metadata) = fs::metadata(&path) {
+                            let size = metadata.len();
+                            let size_matches = filters.is_none()
+                                || file_size_matches(size, filters.as_ref().unwrap());
+                            let type_matches = types.is_none()
+                                || file_type_matches(&path, types.as_ref().unwrap());
+
+                            if size_matches && type_matches {
+                                files.push(path);
+                            }
+                        }
                     }
                 }
             }
         }
     }
-    Ok(())
 }
 
-/* fn file_size_matches(size: u64, categories: &Vec<FileSizeCategory>) -> bool {
-    categories.iter().any(|category| match category {
-        FileSizeCategory::Huge => size >= 4294967296,
-        FileSizeCategory::VeryLarge => size >= 1073741824 && size < 4294967296,
-        FileSizeCategory::Large => size >= 134217728 && size < 1073741823,
-        FileSizeCategory::Medium => size >= 1048576 && size < 134217728,
-        FileSizeCategory::Small => size >= 16384 && size < 1048576,
-        FileSizeCategory::Tiny => size >= 1 && size < 16384,
-        FileSizeCategory::Empty => size == 0,
-    })
-} */
-/// Determines if the given size matches any of the specified categories.
 fn file_size_matches(size: u64, categories: &Vec<FileSizeCategory>) -> bool {
     use FileSizeCategory::*;
     categories.iter().any(|category| match category {
@@ -212,9 +125,8 @@ fn file_type_matches(path: &Path, types: &Vec<String>) -> bool {
 }
 
 #[command]
-pub fn calculate_file_hash(file_path: String) -> Result<String> {
-    // 使用 `?` 代替 `.expect` 来优雅地处理错误
-    let file_bytes = fs::read(file_path)?;
+pub fn calculate_file_hash(file_path: String) -> String {
+    let file_bytes = fs::read(file_path).expect("Failed to read file");
 
     // 初始化 SHA256 哈希上下文
     let mut hasher = Sha256::new();
@@ -224,8 +136,7 @@ pub fn calculate_file_hash(file_path: String) -> Result<String> {
     let result = hasher.finalize();
 
     // 将结果转换为十六进制字符串
-    let hash = hex::encode(result);
-    Ok(hash)
+    hex::encode(result)
 }
 
 #[derive(Debug, Serialize, Deserialize)]
@@ -239,11 +150,24 @@ pub struct FileInfos {
 }
 
 #[command]
-pub fn get_file_info(file_path: String) -> Result<FileInfos> {
+pub fn get_file_info(file_path: String) -> FileInfos {
     let path = Path::new(&file_path);
 
-    // 正确地处理错误
-    let metadata = fs::metadata(&path)?;
+    // 使用 match 来处理可能的错误
+    let metadata = match fs::metadata(&path) {
+        Ok(meta) => meta,
+        Err(_) => {
+            return FileInfos {
+                // 在这里处理错误,可能是返回默认的 FileInfos
+                file_path: path.to_path_buf(),
+                file_name: None,
+                file_type: None,
+                file_size: 0,
+                modified_time: None,
+                creation_time: None,
+            };
+        }
+    };
 
     // 获取文件修改时间
     let modified_time = metadata
@@ -259,20 +183,18 @@ pub fn get_file_info(file_path: String) -> Result<FileInfos> {
         .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
         .map(|d| d.as_secs());
 
-    // 构造FileInfo结构
-    let file_info = FileInfos {
+    // 构造 FileInfo 结构
+    FileInfos {
         file_path: path.to_path_buf(),
         file_name: path
             .file_name()
             .and_then(|name| name.to_str())
             .map(|name| name.to_string()),
-        file_type: get_file_type(&file_path).map(|t| t.to_string()),
+        file_type: get_file_type(&file_path).map(|t| t.to_string()), // 确保 get_file_type 也不返回 Result 或 Option
         file_size: metadata.len(),
         modified_time,
         creation_time: accessed_time,
-    };
-
-    Ok(file_info)
+    }
 }
 
 #[derive(Debug, Serialize, Deserialize)]
@@ -300,3 +222,24 @@ pub fn mv_file_to_trash(file_path: String) -> RequestMvFile {
         }
     }
 }
+
+#[derive(Debug, Deserialize, Serialize)]
+enum AppError {
+    DataDirNotFound,
+    Other(String),
+}
+
+impl std::fmt::Display for AppError {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match *self {
+            AppError::DataDirNotFound => write!(f, "Application data directory not found"),
+            AppError::Other(ref err) => write!(f, "Error: {}", err),
+        }
+    }
+}
+
+#[command]
+pub fn get_app_data_dir() -> String {
+    std::env::var("MY_APP_DATA_DIR")
+    .unwrap_or_else(|_| "Environment variable for app data directory not set".to_string())
+}

+ 2 - 1
src-tauri/src/self_plugin/tauri_plugin_file/mod.rs

@@ -15,7 +15,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
             get_file_type_by_path,
             calculate_file_hash,
             get_file_info,
-            mv_file_to_trash
+            mv_file_to_trash,
+            get_app_data_dir
             // get_file_info_by_path,
         ])
         .setup(|_app| {

+ 82 - 54
src/pages/DuplicateFile/CalculateListPage.tsx

@@ -1,4 +1,14 @@
-import { Avatar, List, message, Checkbox, Row, Col, Space, Button, Spin } from "antd";
+import {
+  Avatar,
+  List,
+  message,
+  Checkbox,
+  Row,
+  Col,
+  Space,
+  Button,
+  Spin,
+} from "antd";
 import type { CheckboxProps } from "antd";
 import { useEffect, useState } from "react";
 import {
@@ -6,7 +16,8 @@ import {
   get_fileInfo_by_id,
   searchDuplicateFile,
 } from "@/services";
-import { message as tauriMessage } from "@tauri-apps/api/dialog";
+import { message as tauriMessage, save as dialogSave } from "@tauri-apps/api/dialog";
+import { appDataDir, join } from '@tauri-apps/api/path';
 import styles from "./CalculateListPage.module.less";
 import { useParams } from "react-router";
 import { insertSearchFilesPasamsType } from "@/types/files";
@@ -131,7 +142,7 @@ export default function CalculateListPage() {
     });
   };
   async function removeFilesByDB() {
-    setLoading(true)
+    setLoading(true);
     const filesRes = await Promise.allSettled(
       removeList.map((path) => File.rmFile(path))
     );
@@ -163,75 +174,92 @@ export default function CalculateListPage() {
         `${rmSuccess.length}个文件删除成功! ${filesRes.length - rmSuccess.length}个文件删除失败!`
       );
       appendData();
-      await waittime(1500)
-      setLoading(false)
+      await waittime(1500);
+      setLoading(false);
       return;
     }
-    await waittime(1500)
-    setLoading(false)
+    await waittime(1500);
+    setLoading(false);
     await tauriMessage("当前操作异常,请重新尝试!", {
       title: "删除失败",
       type: "error",
     });
   }
+  async function openDialogSave() {
+    // const appDataDir = await File.getAppDataDir();
+    const appDataDirPath = await appDataDir();
+    console.log(190, appDataDirPath);
 
+    return;
+    // dialogSave
+    const filePath = await dialogSave({
+      filters: [
+        {
+          name: "Image",
+          extensions: ["png", "jpeg"],
+        },
+      ],
+    });
+    console.log(186, filePath);
+  }
   return (
     <div className={styles.CalculateListPage}>
       <Spin spinning={loading}>
-      <div
-        style={{
-          padding: "24px",
-        }}
-      >
-        <Space>
-          <Button type="primary" danger onClick={() => removeFilesByDB()}>
-            删除选中的文件
-          </Button>
-          <Button type="primary">统一移动到指定目录</Button>
-          <Button type="primary">导出</Button>
-        </Space>
-        <div style={{ marginBottom: "12px" }}></div>
-        <Checkbox.Group
-          onChange={onChange}
-          style={{ width: "100%" }}
-          value={removeList}
+        <div
+          style={{
+            padding: "24px",
+          }}
         >
-          <div style={{ width: "100%" }}>
-            {data.map((item: FileItem) => (
-              <div
-                key={item.hash}
-                style={{
-                  backgroundColor: "var(--color-2)",
-                  marginBottom: "24px",
-                }}
-              >
-                <div className={styles.CheckboxGroup}>
-                  <Checkbox value={item.firstItem.path}>
-                    {CheckboxContent(item.firstItem)}
-                  </Checkbox>
-                </div>
+          <Space>
+            <Button type="primary" danger onClick={() => removeFilesByDB()}>
+              删除选中的文件
+            </Button>
+            <Button type="primary" onClick={() => openDialogSave()}>
+              统一移动到指定目录
+            </Button>
+            <Button type="primary">导出</Button>
+          </Space>
+          <div style={{ marginBottom: "12px" }}></div>
+          <Checkbox.Group
+            onChange={onChange}
+            style={{ width: "100%" }}
+            value={removeList}
+          >
+            <div style={{ width: "100%" }}>
+              {data.map((item: FileItem) => (
                 <div
+                  key={item.hash}
                   style={{
-                    border: "1px solid var(--color-1)",
-                    padding: "12px 3px",
+                    backgroundColor: "var(--color-2)",
+                    marginBottom: "24px",
                   }}
-                  className={styles.CheckboxGroup}
                 >
-                  {item.otherItems.map((otherItem) => (
-                    <div key={otherItem.path}>
-                      <Checkbox value={otherItem.path}>
-                        {CheckboxContent(otherItem)}
-                      </Checkbox>
-                    </div>
-                  ))}
+                  <div className={styles.CheckboxGroup}>
+                    <Checkbox value={item.firstItem.path}>
+                      {CheckboxContent(item.firstItem)}
+                    </Checkbox>
+                  </div>
+                  <div
+                    style={{
+                      border: "1px solid var(--color-1)",
+                      padding: "12px 3px",
+                    }}
+                    className={styles.CheckboxGroup}
+                  >
+                    {item.otherItems.map((otherItem) => (
+                      <div key={otherItem.path}>
+                        <Checkbox value={otherItem.path}>
+                          {CheckboxContent(otherItem)}
+                        </Checkbox>
+                      </div>
+                    ))}
+                  </div>
                 </div>
-              </div>
-            ))}
-          </div>
-        </Checkbox.Group>
-      </div>
+              ))}
+            </div>
+          </Checkbox.Group>
+        </div>
       </Spin>
-      
     </div>
   );
 }

+ 69 - 49
src/pages/DuplicateFile/DuplicateFile.tsx

@@ -13,7 +13,7 @@ import {
   PaginationProps,
   Popconfirm,
 } from "antd";
-import type { PopconfirmProps } from 'antd';
+import type { PopconfirmProps } from "antd";
 import { useEffect, useState } from "react";
 const { Option } = Select;
 import { historyListType, insertSearchFilesPasamsType } from "@/types/files";
@@ -26,17 +26,17 @@ import {
   get_all_history,
   get_info_by_path,
   insertSeletedFileHistory,
-  updateSelectedFileHistory
+  updateSelectedFileHistory,
 } from "@/services";
 import dayjs from "dayjs";
 import { DEFAULT_TIME_FORMAT } from "@/config";
 
 import Database from "tauri-plugin-sql-api";
 import { createSql } from "@/databases/createTableSql";
-import {useRoutes} from "react-router";
-import {useNavigate} from "react-router-dom";
-const db = await Database.load("sqlite:test.db");
-const filesDB = await Database.load("sqlite:files.db");
+import { useRoutes } from "react-router";
+import { useNavigate } from "react-router-dom";
+import { appDataDir } from "@tauri-apps/api/path";
+import File from "@/plugins/tauri-plugin-file/file";
 
 const { Search } = Input;
 const { TextArea } = Input;
@@ -50,7 +50,7 @@ export default function DuplicateFile() {
   const [isModalOpen, setIsModalOpen] = useState(false);
   const [fileInfo, setFileInfo] = useState<any>({});
   const [fileInfoSource, setFileInfoSource] = useState<FileInfoType>({});
-  const navigate = useNavigate()
+  const navigate = useNavigate();
 
   const columns = [
     {
@@ -59,7 +59,7 @@ export default function DuplicateFile() {
       key: "id",
       width: 30,
       render: (text: string, record: { id?: number }) => (
-        <CopyText width="30px" color="#333" name={record.id || ''}></CopyText>
+        <CopyText width="30px" color="#333" name={record.id || ""}></CopyText>
       ),
     },
     {
@@ -72,7 +72,7 @@ export default function DuplicateFile() {
           width="300px"
           ellipsisLine={1}
           color="#333"
-          name={record.path || ''}
+          name={record.path || ""}
         ></CopyText>
       ),
     },
@@ -86,7 +86,7 @@ export default function DuplicateFile() {
           width="100px"
           ellipsisLine={1}
           color="#333"
-          name={record.time || ''}
+          name={record.time || ""}
         ></CopyText>
       ),
     },
@@ -112,14 +112,19 @@ export default function DuplicateFile() {
           <Button onClick={() => openModal(record)} type="default">
             修改
           </Button>
-          <Button type="primary" onClick={() => calculateDuplicateFiles(record)}>运行</Button>
+          <Button
+            type="primary"
+            onClick={() => calculateDuplicateFiles(record)}
+          >
+            运行
+          </Button>
 
           <Popconfirm
-              title="Delete the task"
-              description="Are you sure to delete this task?"
-              onConfirm={() => delRow(record)}
-              okText="Yes"
-              cancelText="No"
+            title="Delete the task"
+            description="Are you sure to delete this task?"
+            onConfirm={() => delRow(record)}
+            okText="Yes"
+            cancelText="No"
           >
             <Button type="primary" danger>
               删除
@@ -131,28 +136,26 @@ export default function DuplicateFile() {
   ];
 
   useEffect(() => {
-    getFileList()
-  }, [current])
+    getFileList();
+  }, [current]);
 
   async function handleOk(newFileInfo: FileInfoType, callback?: Function) {
     try {
-      let method = insertSeletedFileHistory
-      if(fileInfoSource && JSON.stringify(fileInfoSource) !== '{}') {
-        method = updateSelectedFileHistory
+      let method = insertSeletedFileHistory;
+      if (fileInfoSource && JSON.stringify(fileInfoSource) !== "{}") {
+        method = updateSelectedFileHistory;
       }
       const res = await method(newFileInfo.path, newFileInfo);
-      if(res) {
-        message.error(`${res}`)
-        return
+      if (res) {
+        message.error(`${res}`);
+        return;
       }
       setIsModalOpen(false);
-      setFileInfoSource({})
-      setFileList([])
+      setFileInfoSource({});
+      setFileList([]);
       await getFileList();
       callback && callback();
-    } catch (err) {
-    }
-
+    } catch (err) {}
   }
   function handleCancel() {
     setFileInfo({});
@@ -160,45 +163,58 @@ export default function DuplicateFile() {
   }
 
   async function delRow(row: FileInfoType) {
-    const res = await delSelectedFileHistory(row.path)
-    if(!res) {
-      setFileInfoSource({})
-      setFileList([])
+    // 删除对应的查询数据库的文件
+    const appDataDirPath = await appDataDir();
+    const dbPath = `${appDataDirPath}/files_${row.id}.db`;
+    const dbShmPath = `${appDataDirPath}/files_${row.id}.db-shm`;
+    const dbWalPath = `${appDataDirPath}/files_${row.id}.db-wal`;
+    await File.rmFile(dbPath);
+    await File.rmFile(dbShmPath);
+    await File.rmFile(dbWalPath);
+    const res = await delSelectedFileHistory(row.path);
+    if (!res) {
+      setFileInfoSource({});
+      setFileList([]);
       await getFileList();
     } else {
-      message.error(`${res}`)
+      message.error(`${res}`);
     }
   }
 
   async function openModal(info?: FileInfoType) {
     setIsModalOpen(true);
-    if(info) {
+    if (info) {
       setFileInfoSource({
         ...info,
-        checkedSizeValues: info && info?.checkedSizeValues ? `${info.checkedSizeValues}`.split(',') : [],
-        checkedTypeValues: info && info?.checkedTypeValues ? `${info.checkedTypeValues}`.split(',') : []
-      })
+        checkedSizeValues:
+          info && info?.checkedSizeValues
+            ? `${info.checkedSizeValues}`.split(",")
+            : [],
+        checkedTypeValues:
+          info && info?.checkedTypeValues
+            ? `${info.checkedTypeValues}`.split(",")
+            : [],
+      });
     }
   }
   async function getFileList() {
-    const {data, total: localeTotal} = await get_all_history(current - 1, 10);
-    const newFileList = data.map(item => {
+    const { data, total: localeTotal } = await get_all_history(current - 1, 10);
+    const newFileList = data.map((item) => {
       return {
         ...item,
-        time: dayjs(item.time).format(DEFAULT_TIME_FORMAT)
-      }
-    })
-    setFileList(newFileList)
-    setTotal(localeTotal)
+        time: dayjs(item.time).format(DEFAULT_TIME_FORMAT),
+      };
+    });
+    setFileList(newFileList);
+    setTotal(localeTotal);
   }
 
-  const onPaginationChange: PaginationProps['onChange'] = (page) => {
+  const onPaginationChange: PaginationProps["onChange"] = (page) => {
     setCurrent(page);
   };
 
-
   function calculateDuplicateFiles(record: FileInfoType) {
-    navigate('calculate/' + record.id)
+    navigate("calculate/" + record.id);
   }
   return (
     <div className={styles.DuplicateFileBox}>
@@ -237,7 +253,11 @@ export default function DuplicateFile() {
         />
       </Row>
       <Row justify="end" style={{ width: "100%", marginTop: "12px" }}>
-        <Pagination current={current} total={total} onChange={onPaginationChange} />
+        <Pagination
+          current={current}
+          total={total}
+          onChange={onPaginationChange}
+        />
       </Row>
     </div>
   );

+ 3 - 0
src/plugins/tauri-plugin-file/file.ts

@@ -39,6 +39,9 @@ export class File {
       filePath: path,
     });
   }
+  static async getAppDataDir(): Promise<string> {
+    return await invoke<string>("plugin:st-files|get_app_data_dir");
+  }
 
   // async close(): Promise<boolean> {
   //     return await invoke('plugin:st-sqlite|close', { path: this.path })