john 1 год назад
Родитель
Сommit
271a52a763

+ 10 - 1
README.md

@@ -44,4 +44,13 @@ https://blog.csdn.net/cdc8596/article/details/94732238
 
 https://crates.io/search?q=sqlite
 
-https://github.com/launchbadge/sqlx
+https://github.com/launchbadge/sqlx
+
+由于国内开发环境的问题
+以下仓库是从 https://github.com/tauri-apps/plugins-workspace 拉取的
+- https://github.com/Johnhong9527/tauri-plugin-sql
+- https://gitee.com/seamong/tauri-plugin-sql
+
+
+- https://github.com/Johnhong9527/tauri-plugin-sql
+- https://gitee.com/seamong/tauri-plugin-sql

+ 1 - 1
package.json

@@ -21,7 +21,7 @@
     "react-dom": "^18.3.1",
     "react-router": "^6.23.1",
     "react-router-dom": "^6.23.1",
-    "tauri-plugin-sql-api": "https://github.com/Johnhong9527/tauri-plugin-sql.git#v1"
+    "tauri-plugin-sql-api": "https://gitee.com/seamong/tauri-plugin-sql.git#v1"
   },
   "devDependencies": {
     "@rollup/plugin-alias": "^5.1.0",

+ 1 - 1
src-tauri/Cargo.lock

@@ -4398,7 +4398,7 @@ dependencies = [
 [[package]]
 name = "tauri-plugin-sql"
 version = "0.0.0"
-source = "git+https://github.com/Johnhong9527/plugins-workspace?branch=v1#0d649843c6b49b7a69200816c8d5195f9953a4fb"
+source = "git+https://gitee.com/seamong/plugins-workspace?branch=v1#0d649843c6b49b7a69200816c8d5195f9953a4fb"
 dependencies = [
  "futures-core",
  "log",

+ 1 - 1
src-tauri/Cargo.toml

@@ -34,7 +34,7 @@ home = "0.5.9"
 trash = "1.3"
 
 [dependencies.tauri-plugin-sql]
-git = "https://github.com/Johnhong9527/plugins-workspace"
+git = "https://gitee.com/seamong/plugins-workspace"
 branch = "v1"
 features = ["sqlite"] # or "postgres", or "mysql"
 

+ 146 - 198
src/pages/DuplicateFile/CalculateDuplicateFiles.tsx

@@ -1,12 +1,6 @@
 import {
-  delSelectedFileHistory,
-  get_all_history,
   get_info_by_id,
-  get_info_by_path,
-  insertSeletedFileHistory,
-  updateSelectedFileHistory,
   insertSearchFiles,
-  searchDuplicateFile,
   updateSelectedFileHistoryFiles,
 } from "@/services";
 import { useEffect, useState } from "react";
@@ -20,19 +14,12 @@ import { message } from "@tauri-apps/api/dialog";
 import styles from "./CalculateDuplicateFiles.module.less";
 import File from "@/plugins/tauri-plugin-file/file";
 import { Button, Col, Row, Steps } from "antd";
-import {
-  LoadingOutlined,
-  SolutionOutlined,
-  UserOutlined,
-} from "@ant-design/icons";
-import { readDir, BaseDirectory } from "@tauri-apps/api/fs";
 import { fileTypeList } from "./config";
 import get_progress_by_sourceId, {
   get_fileInfo_by_path,
   get_list_by_sourceid,
   updateFileHsah,
 } from "@/services/file-service";
-import { resolve } from "path";
 
 export default function CalculateDuplicateFiles() {
   let { fileId } = useParams();
@@ -72,42 +59,64 @@ export default function CalculateDuplicateFiles() {
 
   async function getFiles() {
     if (fileInfo.path) {
-      console.log(4545);
       setStepsStatus({
         ...stepsStatus,
         fileOptions: "process",
       });
-      //
-      // const files = await File.getAllList(fileInfo.path);
-      // console.log(34, files)
-
-      // /Users/honghaitao/Downloads/PDF Expert Installer.app
-
-      // const hash = await File.getHash('/Users/honghaitao/Downloads/PDF Expert Installer.app')
-      // console.log(39, hash)
     }
   }
   async function scanDirAll() {
-    // const aabb = await get_progress_by_sourceId(`${fileId}`);
-    // console.log(737373, aabb);
-    // const progressRes = await get_progress_by_sourceId(fileId || '')
-    // console.log(93, progressRes, fileInfo);
-    
-    // return
-
-    // navigate('/calculate-list/' + fileId)
     if (fileInfo.path) {
       // 扫描目录文件
-      console.log('扫描目录文件 开始');
+
+      console.log("扫描目录文件 结束");
+      const files = await scanAllFilesInDir();
+
+      // 计算文件属性
+      console.log("计算文件属性 开始");
+      await computeFileMetadata(files);
+      console.log("计算文件属性 结束");
+
+      // 计算文件具体内容
+      console.log("计算每一个文件的hash 开始");
+      await computeFileChecksums();
+      console.log("计算每一个文件的hash 结束");
+
+      setStepsStatus({
+        ...stepsStatus,
+        scanDir: "finish",
+        fileOptions: "finish",
+        duplicateFiles: "finish",
+        done: "process",
+      });
+      setPercent(0);
+      // 分析重复文件
+      await waittime(300);
+      setPercent(50);
+      await waittime(300);
+      setPercent(100);
+      await waittime(300);
+      setStepsStatus({
+        scanDir: "finish",
+        fileOptions: "finish",
+        duplicateFiles: "finish",
+        done: "finish",
+      });
+      navigate("/calculate-list/" + fileId);
+    }
+  }
+
+  // 扫描目录文件
+  async function scanAllFilesInDir(): Promise<string[]> {
+    const [progressRes] = await get_progress_by_sourceId(`${fileId}`);
+    if (progressRes.total_entries !== fileInfo.files || !fileInfo.files) {
+      console.log("扫描目录文件 开始");
       setStepsStatus({
         ...stepsStatus,
         scanDir: "process",
       });
       setCurrent(1);
       setPercent(0);
-      // 排除指定的文件大小、或者筛选所有体量的文件
-      const size = []; // 全部为空
-
       // 排除指定的文件类型、或者筛选所有的文件类型
       const types = await getTypeValuesByCheckedTypeValues(); // 全部为空
 
@@ -116,179 +125,118 @@ export default function CalculateDuplicateFiles() {
         types,
       });
       setPercent(100);
-      console.log('扫描目录文件 结束');
+      console.log("扫描目录文件 结束");
+      return Promise.resolve(files);
+    }
+    setPercent(100);
+    return Promise.resolve([]);
+  }
 
-      console.log(118, files);
+  // 计算文件属性
+  async function computeFileMetadata(files: string[]) {
+    if(!files.length) {
+      setStepsStatus({
+        ...stepsStatus,
+        scanDir: "finish",
+        fileOptions: "finish",
+      });
+      setPercent(100);
+      return Promise.resolve(0)
+    }
+    /* 如果文件数目为0 ,查询数据库进行 */
+    // 更新当前查询目录的总文件数目
+    await updateSelectedFileHistoryFiles(`${fileInfo.path}`, files.length);
+    setStepsStatus({
+      ...stepsStatus,
+      scanDir: "finish",
+      fileOptions: "process",
+    });
+    setPercent(0);
+    let fileIndex = -1;
+    let allFilesLength = files.length;
+    await files.reduce(
+      async (prevPromise: any, currentFile: any) => {
+        // 等待上一个 Promise 完成
+        await prevPromise;
+        // ishaveFile: true 表示文件数据已经存在; false 表示文件数据不存在xuy;
+        const [ishaveFile, fileinfo] = await get_fileInfo_by_path(
+          currentFile,
+          `${fileId}`
+        );
+        if (!ishaveFile) {
+          // 获取文件类型和哈希
+          const fileInfo = await File.getInfo(currentFile);
+          fileIndex++;
+          setPercent(Math.floor((fileIndex / allFilesLength) * 100));
+          return insertSearchFiles({
+            // 组装数据
+            sourceId: `${fileId}`,
+            path: currentFile,
+            name: fileInfo.file_name,
+            creation_time: fileInfo.creation_time,
+            modified_time: fileInfo.modified_time,
+            file_size: fileInfo.file_size,
+            type: fileInfo.file_type,
+            // 由于 计算单个文件的hash 时间较长,所以单独起一个事件,专门做这个事情
+            hash: "",
+          });
+        }
+        return Promise.resolve(0);
+      },
+      Promise.resolve(0)
+    );
+    setPercent(100);
+    return waittime(300);
+  }
 
-      // 计算文件属性
-      console.log('计算文件属性 开始');
-      if (files.length) {
-        // 更新当前查询目录的总文件数目
-        await updateSelectedFileHistoryFiles(fileInfo.path, files.length, )
-        setStepsStatus({
-          ...stepsStatus,
-          scanDir: "finish",
-          fileOptions: "process",
-        });
-        // setCurrent(1)
-        setPercent(0);
-        // await files.reduce(async ())
-        let fileIndex = -1;
-        let allFilesLength = files.length;
-        const result = await files.reduce(
-          async (prevPromise: any, currentFile: any) => {
+  // 计算每一个文件的hash
+  async function computeFileChecksums() {
+    const [allList, allListMsg] = await get_list_by_sourceid(`${fileId}`);
+    if (allList && Array.isArray(allList)) {
+      let fileIndex = -1;
+      let allFilesLength = allList.length;
+      setStepsStatus({
+        ...stepsStatus,
+        scanDir: "finish",
+        fileOptions: "finish",
+        duplicateFiles: "process",
+      });
+      setPercent(0);
+      await allList
+        .filter((currentFile: insertSearchFilesPasamsType) => !currentFile.hash)
+        .reduce(
+          async (
+            prevPromise: any,
+            currentFile: insertSearchFilesPasamsType
+          ) => {
             // 等待上一个 Promise 完成
             await prevPromise;
-            const [ishaveFile, fileinfo] = await get_fileInfo_by_path(currentFile, `${fileId}`)
-            if(!ishaveFile) {
-              // 获取文件类型和哈希
-              const fileInfo = await File.getInfo(currentFile);
-              // const hash = await File.getHash(currentFile);
-              const hash = "";
-              fileIndex++;
-              setPercent(Math.floor((fileIndex / allFilesLength) * 100));
-              // await waittime(300);
-              return insertSearchFiles({
-                // 组装数据
-                sourceId: `${fileId}`,
-                path: currentFile,
-                // type: await File.getType(elm),
-                name: fileInfo.file_name,
-                creation_time: fileInfo.creation_time,
-                modified_time: fileInfo.modified_time,
-                file_size: fileInfo.file_size,
-                type: fileInfo.file_type,
-                hash,
-              });
-            }
-            return Promise.resolve(0) 
+            // 获取文件类型和哈希
+            const hash = await File.getHash(currentFile.path);
+            fileIndex++;
+            await waittime();
+            setPercent(Math.floor((fileIndex / allFilesLength) * 100));
+            return updateFileHsah(currentFile.path, hash, `${fileId}`);
           },
           Promise.resolve(0)
         );
-        console.log('计算文件属性 结束');
-        setPercent(100);
-        await waittime(1000);
-        // 计算文件具体内容
-        const [allList, allListMsg] = await get_list_by_sourceid(`${fileId}`);
-        console.log({
-          allList,
-          allListMsg,
-        });
-
-        if (allList) {
-          let fileIndex = -1;
-          let allFilesLength = allList.length;
-          setStepsStatus({
-            ...stepsStatus,
-            scanDir: "finish",
-            fileOptions: "finish",
-            duplicateFiles: "process",
-          });
-          setPercent(0);
-          console.log(173, allFilesLength);
 
-          const allListresult = await allList
-            .filter(
-              (currentFile: insertSearchFilesPasamsType) => !currentFile.hash
-            )
-            .reduce(
-              async (
-                prevPromise: any,
-                currentFile: insertSearchFilesPasamsType
-              ) => {
-                // 等待上一个 Promise 完成
-                await prevPromise;
-                // 获取文件类型和哈希
-                // const type = await File.getType(currentFile);
-                const hash = await File.getHash(currentFile.path);
-                fileIndex++;
-                await waittime();
-                setPercent(Math.floor((fileIndex / allFilesLength) * 100));
-                return updateFileHsah(currentFile.path, hash, `${fileId}`);
-              },
-              Promise.resolve(0)
-            );
-
-          await waittime(1000);
-          setStepsStatus({
-            ...stepsStatus,
-            scanDir: "finish",
-            fileOptions: "finish",
-            duplicateFiles: "finish",
-          });
-          setPercent(100);
-        } else {
-          setStepsStatus({
-            ...stepsStatus,
-            scanDir: "finish",
-            fileOptions: "finish",
-            duplicateFiles: "finish",
-          });
-          setPercent(100);
-          await waittime(2000);
-        }
-
-        setStepsStatus({
-          ...stepsStatus,
-          scanDir: "finish",
-          fileOptions: "finish",
-          duplicateFiles: "finish",
-          done: "process",
-        });
-        setPercent(0);
-        // 分析重复文件
-        /* const searchDuplicateFileRes = await searchDuplicateFile({
-          sourceId: fileId || "",
-        }); */
-        /* 
-            [
-                {count: 6, hash: "3ba7bbfc03e3bed23bf066e2e9a6a5389dd33fd8637bc0220d9e6d642ccf5946", ids: "17,21,22,26,27,31", },
-                {count: 6, hash: "75b7c31709e1529be7bec1c8a40ec98edbda146a09904a5ffad8685da966f90b", ids: "19,23,24,25,29,30", },
-                {count: 3, hash: "7707b032ff2fea855a1bc22b7be536de13d3ad6d418cc7021893a97cf488e1a3", ids: "20,28,32", }
-            ]
-    
-    
-    
-            [
-                {
-                    count: 6, 
-                    hash: "3ba7bbfc03e3bed23bf066e2e9a6a5389dd33fd8637bc0220d9e6d642ccf5946", 
-                    paths: "/Users/sysadmin/Pictures/test/欧洲4_副本.jpeg,/Users/s…4.jpeg,/Users/sysadmin/Pictures/test/欧洲4_副本5.jpeg", 
-                    ids: "17,21,22,26,27,31", 
-                    times: "1718613803964,1718613804035,1718613804041,1718613804070,1718613804080,1718613804112"
-                },
-                {
-                    hash: "75b7c31709e1529be7bec1c8a40ec98edbda146a09904a5ffad8685da966f90b", 
-                    times: "1718613804012,1718613804051,1718613804057,1718613804063,1718613804094,1718613804104", 
-                    paths: "/Users/sysadmin/Pictures/test/欧洲2.jpeg,/Users/sysa…3.jpeg,/Users/sysadmin/Pictures/test/欧洲2_副本2.jpeg", 
-                    ids: "19,23,24,25,29,30", 
-                    count: 6
-                }
-                {
-                    times: "1718613804018,1718613804086,1718613804118", 
-                    ids: "20,28,32", 
-                    paths: "/Users/sysadmin/Pictures/test/欧洲1_副本2.jpeg,/Users/…洲1.jpeg,/Users/sysadmin/Pictures/test/欧洲1_副本.jpeg", 
-                    count: 3, 
-                    hash: "7707b032ff2fea855a1bc22b7be536de13d3ad6d418cc7021893a97cf488e1a3"
-                }
-            ] 
-    
-        */
-        /* console.log(747474, searchDuplicateFileRes);
-        if (searchDuplicateFileRes[0]) {
-        } */
-
-        setStepsStatus({
-          scanDir: "finish",
-          fileOptions: "finish",
-          duplicateFiles: "finish",
-          done: "finish",
-        });
-        setPercent(100);
-        await waittime(1000);
-        navigate('/calculate-list/' + fileId)
-      }
+      setStepsStatus({
+        ...stepsStatus,
+        scanDir: "finish",
+        fileOptions: "finish",
+        duplicateFiles: "finish",
+      });
+    } else {
+      setStepsStatus({
+        ...stepsStatus,
+        scanDir: "finish",
+        fileOptions: "finish",
+        duplicateFiles: "finish",
+      });
     }
+    setPercent(100);
+    return waittime(1000);
   }
 
   async function getTypeValuesByCheckedTypeValues() {

+ 74 - 40
src/pages/DuplicateFile/CalculateListPage.tsx

@@ -1,4 +1,4 @@
-import { Avatar, List, message, Checkbox, Row, Col, Space, Button } 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 {
@@ -17,6 +17,7 @@ import { CopyText } from "@/components/Table/CopyText";
 export default function CalculateListPage() {
   let { fileId } = useParams();
   const [data, setData] = useState<FileItem[]>([]);
+  const [loading, setLoading] = useState<boolean>(false);
   const [removeList, setRemoveList] = useState<string[]>([]);
   interface FileItem {
     sourceId: number;
@@ -64,7 +65,7 @@ export default function CalculateListPage() {
             otherItems: otherItems
               .map((elm) => {
                 if (elm.status === "fulfilled" && !elm.value[1]) {
-                  setRemoveList()
+                  setRemoveList([]);
                   return elm.value[0];
                 }
                 return false;
@@ -83,13 +84,11 @@ export default function CalculateListPage() {
     appendData();
   }, []);
 
-  const onChange = (
-    checkedValues: string[]
-  ) => {
+  const onChange = (checkedValues: string[]) => {
     console.log("checked = ", checkedValues);
     if (Array.isArray(checkedValues)) {
       // setRemoveList(checkedValues.filter(elm => typeof elm === 'string'));
-      setRemoveList(checkedValues)
+      setRemoveList(checkedValues);
     }
     // value={removeList}
   };
@@ -97,58 +96,86 @@ export default function CalculateListPage() {
   const CheckboxContent = (item: insertSearchFilesPasamsType) => (
     <div className={styles.CheckboxContent}>
       <div className={styles.path}>
-        <CopyText width="300px" color="#333" ellipsisLine={1} name={item.path || ''}></CopyText>
+        <CopyText
+          width="300px"
+          color="#333"
+          ellipsisLine={1}
+          name={item.path || ""}
+        ></CopyText>
       </div>
       <div className={styles.modified_time}>
-        <CopyText width="100px" color="#333" name={item.modified_time || ''}></CopyText>
+        <CopyText
+          width="100px"
+          color="#333"
+          name={item.modified_time || ""}
+        ></CopyText>
       </div>
       <div className={styles.modified_time}>
-        <CopyText width="100px" color="#333" name={item.file_size || ''}></CopyText>
+        <CopyText
+          width="100px"
+          color="#333"
+          name={item.file_size || ""}
+        ></CopyText>
       </div>
       <div className={styles.modified_time}>
-        <CopyText width="100px" color="#333" ellipsisLine={1} name={item.name || ''}></CopyText>
+        <CopyText
+          width="100px"
+          color="#333"
+          ellipsisLine={1}
+          name={item.name || ""}
+        ></CopyText>
       </div>
     </div>
   );
-
+  const waittime = (time = 100) => {
+    return new Promise((resolve) => {
+      setTimeout(() => {
+        resolve(0);
+      }, time);
+    });
+  };
   async function removeFilesByDB() {
-    const filesRes = await Promise.allSettled(removeList.map(path => File.rmFile(path)))
-    if(removeList.length === 1) {
+    setLoading(true)
+    const filesRes = await Promise.allSettled(
+      removeList.map((path) => File.rmFile(path))
+    );
+    if (removeList.length === 1) {
       console.log(106, filesRes);
-      if(filesRes[0].status === "fulfilled" && filesRes[0].value.code === 200) {
-        setRemoveList([])
-        del_file_by_id(
-          removeList[0],
-          `${fileId}`
-        );
-        message.success(`${removeList[0]} 删除成功!`)
+      if (
+        filesRes[0].status === "fulfilled" &&
+        filesRes[0].value.code === 200
+      ) {
+        setRemoveList([]);
+        del_file_by_id(removeList[0], `${fileId}`);
+        message.success(`${removeList[0]} 删除成功!`);
         appendData();
-        return
-      } 
+        return;
+      }
       await tauriMessage(removeList[0], {
         title: "删除失败",
         type: "error",
       });
     }
-    const rmSuccess = filesRes.filter(res => {
+    const rmSuccess = filesRes.filter((res) => {
       console.log(116, res);
-      return res.status === 'fulfilled' && res.value.code === 200
-    })
+      return res.status === "fulfilled" && res.value.code === 200;
+    });
     if (rmSuccess.length) {
-      await rmSuccess.reduce(async(prev: any, item: any)=> {
-        await prev();
-        console.log(119, item.value.data);
-        
-        return del_file_by_id(
-          item.value.data,
-          `${fileId}`
-        );
-      }, Promise.resolve(0));
-      message.success( `${rmSuccess.length}个文件删除成功! ${filesRes.length - rmSuccess.length}个文件删除失败!`)
+      await rmSuccess.reduce(async (prevPromise: any, item: any) => {
+        await prevPromise;
+        return del_file_by_id(item.value.data, `${fileId}`);
+      }, Promise.resolve());
+      message.success(
+        `${rmSuccess.length}个文件删除成功! ${filesRes.length - rmSuccess.length}个文件删除失败!`
+      );
       appendData();
+      await waittime(1500)
+      setLoading(false)
       return;
     }
-    await tauriMessage('当前操作异常,请重新尝试!', {
+    await waittime(1500)
+    setLoading(false)
+    await tauriMessage("当前操作异常,请重新尝试!", {
       title: "删除失败",
       type: "error",
     });
@@ -156,6 +183,7 @@ export default function CalculateListPage() {
 
   return (
     <div className={styles.CalculateListPage}>
+      <Spin spinning={loading}>
       <div
         style={{
           padding: "24px",
@@ -169,14 +197,18 @@ export default function CalculateListPage() {
           <Button type="primary">导出</Button>
         </Space>
         <div style={{ marginBottom: "12px" }}></div>
-        <Checkbox.Group onChange={onChange} style={{ width: "100%" }} value={removeList}>
+        <Checkbox.Group
+          onChange={onChange}
+          style={{ width: "100%" }}
+          value={removeList}
+        >
           <div style={{ width: "100%" }}>
             {data.map((item: FileItem) => (
               <div
                 key={item.hash}
                 style={{
-                  backgroundColor: 'var(--color-2)',
-                  marginBottom: '24px'
+                  backgroundColor: "var(--color-2)",
+                  marginBottom: "24px",
                 }}
               >
                 <div className={styles.CheckboxGroup}>
@@ -186,7 +218,7 @@ export default function CalculateListPage() {
                 </div>
                 <div
                   style={{
-                    border: '1px solid var(--color-1)',
+                    border: "1px solid var(--color-1)",
                     padding: "12px 3px",
                   }}
                   className={styles.CheckboxGroup}
@@ -204,6 +236,8 @@ export default function CalculateListPage() {
           </div>
         </Checkbox.Group>
       </div>
+      </Spin>
+      
     </div>
   );
 }

+ 4 - 5
src/services/file-service.ts

@@ -388,7 +388,7 @@ export default async function get_progress_by_sourceId(
   const res: DuplicateFileInfo[] = await DB.select(
     `SELECT 
     COUNT(*) AS total_entries,
-    COUNT(CASE WHEN sourceId = $1 THEN 1 ELSE NULL END) AS sourceId_1_count,
+    COUNT(CASE WHEN sourceId = $1 THEN 1 ELSE NULL END) AS sourceId_count,
     COUNT(CASE WHEN hash IS NULL OR hash = '' THEN 1 ELSE NULL END) AS hash_null_count
 FROM search_files;`,
     [sourceId]
@@ -473,20 +473,19 @@ export async function del_file_by_id(path: string, sourceId: string) {
     const DB = await Database.load(`sqlite:files_${sourceId}.db`);
     // 创建表
     await DB.execute(createSql.search_files);
-    const result = await DB.execute(
+    await DB.execute(
       `DELETE FROM search_files WHERE path = $1 and sourceId = $2`,
       [
         path, // 假设 path 变量是预定义的
         sourceId,
       ]
     );
-    console.log(206, result);
-    return false;
+    return Promise.resolve(false);
   } catch (error) {
     console.log(595959, error);
     if (error && `${error}`.indexOf("UNIQUE constraint failed") > -1) {
       return "当前数据格式异常";
     }
-    return error;
+    return Promise.resolve(error);
   }
 }

+ 1 - 0
src/types/files.d.ts

@@ -8,6 +8,7 @@ export interface FileInfoType {
   checkedSizeValues?: unknown[];
   checkboxSizeAll?: boolean;
   checkedTypeValues?: unknown[];
+  files?: unknown[];
   time?: string;
   id?: number;
   progress?: number;