瀏覽代碼

重复数据筛选

john 1 年之前
父節點
當前提交
73008ed425

+ 53 - 22
src-tauri/src/self_plugin/tauri_plugin_file/files.rs

@@ -1,12 +1,12 @@
-use std::fs;
+use hex;
 use ring::digest::{Context, Digest, SHA256};
-use serde::{Serialize, Serializer, Deserialize};
+use serde::{Deserialize, Serialize, Serializer};
+use sha2::{Digest as OtherDigest, Sha256}; // 确保导入 `Digest`
 use std::io::{self, Read};
-use sha2::{Sha256, Digest as OtherDigest};  // 确保导入 `Digest`
-use hex;
-use tauri::command;
 use std::path::{Path, PathBuf};
 use std::result::Result as new_Result;
+use std::{fs, option};
+use tauri::command;
 // use std::result::Result;
 // use tauri::api::file::IntoInvokeHandler;
 
@@ -79,20 +79,17 @@ fn filter_other_directory(path: &str, directories: &[&str]) -> bool {
 //     Ok(())
 // }
 
-
 #[derive(Debug, Deserialize, Serialize)]
 pub enum FileSizeCategory {
-    Huge,       // 4GB+
-    VeryLarge,  // 1GB to 4GB-
-    Large,      // 128MB to 1GB-
-    Medium,     // 1MB to 128MB-
-    Small,      // 16KB to 1MB-
-    Tiny,       // 1B to 16KB-
-    Empty,      // Empty files or directories
+    Huge,      // 4GB+
+    VeryLarge, // 1GB to 4GB-
+    Large,     // 128MB to 1GB-
+    Medium,    // 1MB to 128MB-
+    Small,     // 16KB to 1MB-
+    Tiny,      // 1B to 16KB-
+    Empty,     // Empty files or directories
 }
 
-
-
 #[derive(Debug, Deserialize, Serialize)]
 pub struct FileInfo {
     pub path: Option<String>,
@@ -106,9 +103,9 @@ pub struct FileInfo {
     pub time: Option<String>,
     pub id: Option<u32>,
     pub progress: Option<f32>,
+    pub types: Option<Vec<String>>,
 }
 
-
 #[command]
 pub fn get_all_directory(file_info: FileInfo) -> Result<Vec<PathBuf>> {
     let mut files = Vec::new();
@@ -116,7 +113,12 @@ pub fn get_all_directory(file_info: FileInfo) -> Result<Vec<PathBuf>> {
         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)?;
+        read_files_in_directory(
+            directory,
+            &mut files,
+            &file_info.checked_size_values,
+            &file_info.types,
+        )?;
         Ok(files)
     } else {
         // 当没有提供路径时返回错误
@@ -148,17 +150,25 @@ pub fn get_file_type_by_path(file_path: String) -> Result<String> {
     }
 }
 
-fn read_files_in_directory(dir: &Path, files: &mut Vec<PathBuf>, filters: &Option<Vec<FileSizeCategory>>) -> Result<()> {
+fn read_files_in_directory(
+    dir: &Path,
+    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)?;
+                read_files_in_directory(&path, files, filters, types)?;
             } else {
                 if let Ok(metadata) = fs::metadata(&path) {
                     let size = metadata.len();
-                    if filters.is_none() || file_size_matches(size, filters.as_ref().unwrap()) {
+                    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);
                     }
                 }
@@ -168,8 +178,7 @@ fn read_files_in_directory(dir: &Path, files: &mut Vec<PathBuf>, filters: &Optio
     Ok(())
 }
 
-
-fn file_size_matches(size: u64, categories: &Vec<FileSizeCategory>) -> bool {
+/* 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,
@@ -179,6 +188,28 @@ fn file_size_matches(size: u64, categories: &Vec<FileSizeCategory>) -> bool {
         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 {
+        Huge => size >= 4_294_967_296,
+        VeryLarge => (1_073_741_824..4_294_967_296).contains(&size),
+        Large => (134_217_728..1_073_741_824).contains(&size),
+        Medium => (1_048_576..134_217_728).contains(&size),
+        Small => (16_384..1_048_576).contains(&size),
+        Tiny => (1..16_384).contains(&size),
+        Empty => size == 0,
+    })
+}
+
+fn file_type_matches(path: &Path, types: &Vec<String>) -> bool {
+    if let Some(ext) = path.extension() {
+        if let Some(ext_str) = ext.to_str() {
+            return types.iter().any(|type_str| type_str == ext_str);
+        }
+    }
+    false
 }
 
 #[command]

+ 4 - 4
src/databases/createTableSql.ts

@@ -17,9 +17,9 @@ export const createSql = {
         time TIMESTAMP,
         sourceId INTEGER,
         type TEXT CHECK(length(name) <= 255),
-        name TEXT CHECK(length(name) <= 255),
-        path TEXT CHECK(length(path) <= 500),
-        hash TEXT CHECK(length(path) <= 2000),
-        unique(path)
+        name TEXT,
+        path TEXT,
+        hash TEXT,
+        db_version TEXT
     );`
 }

+ 206 - 100
src/pages/DuplicateFile/CalculateDuplicateFiles.tsx

@@ -1,111 +1,217 @@
 import {
-    delSelectedFileHistory,
-    get_all_history, get_info_by_id,
-    get_info_by_path,
-    insertSeletedFileHistory,
-    updateSelectedFileHistory
+  delSelectedFileHistory,
+  get_all_history,
+  get_info_by_id,
+  get_info_by_path,
+  insertSeletedFileHistory,
+  updateSelectedFileHistory,
+  insertSearchFiles,
+  searchDuplicateFile,
 } from "@/services";
-import {useEffect, useState} from "react";
-import {useNavigate, useParams} from "react-router-dom";
-import {FileInfoType, stepsStatusType} from "@/types/files";
-import {message} from "@tauri-apps/api/dialog";
-import styles from './CalculateDuplicateFiles.module.less'
+import { useEffect, useState } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { FileInfoType, stepsStatusType } from "@/types/files";
+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 { 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";
 
 export default function CalculateDuplicateFiles() {
-    let {fileId} = useParams();
-    let navigate = useNavigate();
-    const [fileInfo, setFileInfo] = useState<FileInfoType>({})
-    const [current, setCurrent] = useState(1);
-    const [percent, setPercent] = useState(85);
-    const [stepsStatus, setStepsStatus] = useState<stepsStatusType>({
-        scanDir: 'finish',
-        fileOptions: 'process',
-        duplicateFiles: 'wait',
-        done: 'wait',
-    })
-    useEffect(() => {
-        pageInit()
-    }, []);
-
-    async function pageInit() {
-        if (fileId) {
-            const [data, errorMsg] = await get_info_by_id(Number.parseInt(fileId));
-            if (data && typeof data === 'object') {
-                setFileInfo(data)
-            } else {
-                await message(errorMsg, {title: '查询失败', type: 'error'});
-            }
-        }
+  let { fileId } = useParams();
+  let navigate = useNavigate();
+  const [fileInfo, setFileInfo] = useState<FileInfoType>({});
+  const [current, setCurrent] = useState(1);
+  const [percent, setPercent] = useState(85);
+  const [stepsStatus, setStepsStatus] = useState<stepsStatusType>({
+    scanDir: "finish",
+    fileOptions: "process",
+    duplicateFiles: "wait",
+    done: "wait",
+  });
+  useEffect(() => {
+    pageInit();
+  }, []);
+
+  async function pageInit() {
+    if (fileId) {
+      const [data, errorMsg] = await get_info_by_id(Number.parseInt(fileId));
+      if (data && typeof data === "object") {
+        setFileInfo(data);
+      } else {
+        await message(errorMsg, { title: "查询失败", type: "error" });
+      }
     }
+  }
+
+  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
 
-    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)
-        }
+      // const hash = await File.getHash('/Users/honghaitao/Downloads/PDF Expert Installer.app')
+      // console.log(39, hash)
     }
-    async function scanDirAll() {
-        navigate('/calculate-list/' + fileId)
-        // if(fileInfo.path) {
-        //     console.log(626262, fileInfo)
-        //     const files = await File.getAllList(fileInfo);
-        //     console.log(636363, files)
-        // }
+  }
+  async function scanDirAll() {
+    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);
+    return
+    // navigate('/calculate-list/' + fileId)
+    if (fileInfo.path) {
+      // 扫描目录文件
+
+      // 排除指定的文件大小、或者筛选所有体量的文件
+      const size = []; // 全部为空
+
+      // 排除指定的文件类型、或者筛选所有的文件类型
+      const types = await getTypeValuesByCheckedTypeValues(); // 全部为空
+
+      const files = await File.getAllList({
+        path: fileInfo.path,
+        types,
+      });
+
+      console.log(636363, files);
+
+      // 计算文件属性
+      if (files.length) {
+        // await files.reduce(async ())
+
+        const result = await files.reduce(
+          async (prevPromise: any, currentFile: any) => {
+            // 等待上一个 Promise 完成
+            await prevPromise;
+            console.log(95, currentFile);
+            // 获取文件类型和哈希
+            const type = await File.getType(currentFile);
+            const hash = await File.getHash(currentFile);
+            return insertSearchFiles({
+              // 组装数据
+              sourceId: `${fileId}`,
+              path: currentFile,
+              // type: await File.getType(elm),
+              name: currentFile,
+              hash,
+              type,
+            });
+          },
+          Promise.resolve(0)
+        );
+
+        console.log(result); // 顺序处理每个项,然后输出最终结果
+
+
+        // 分析重复文件
+      }
     }
+  }
+
+  async function getTypeValuesByCheckedTypeValues() {
+    let types: any[] = [];
+    if (!fileInfo.checkedTypeValues?.length || !fileInfo.checkedTypeValues)
+      return [];
+    const checkedTypeValues = `${fileInfo.checkedTypeValues}`?.split(",");
+    console.log(84884, checkedTypeValues);
+    fileTypeList.map((elm) => {
+      if (checkedTypeValues.indexOf(elm.name) > -1) {
+        types = types.concat(elm.valus);
+      }
+    });
+    return types;
+  }
+
+  return (
+    <div className={styles.CalculateDuplicateFiles}>
+      <Row justify="start" align="middle">
+        <Col>
+          <div className={styles.pageTitle} onClick={() => getFiles()}>
+            路径: {fileInfo.path}
+          </div>
+        </Col>
+        <Col>
+          <Button type="primary" onClick={() => scanDirAll()}>
+            开始
+          </Button>
+        </Col>
+      </Row>
 
-    return (
-        <div className={styles.CalculateDuplicateFiles}>
-            <Row justify="start" align="middle">
-                <Col>
-                    <div className={styles.pageTitle} onClick={() => getFiles()}>路径: {fileInfo.path}</div>
-                </Col>
-                <Col>
-                    <Button type="primary" onClick={() => scanDirAll()}>
-                        开始
-                    </Button>
-                </Col>
-            </Row>
-
-            <div className={styles.stepsBox}>
-                <Steps
-                    current={current} percent={percent}
-                    labelPlacement="horizontal"
-                    direction="vertical"
-                    items={[
-                        {
-                            title: '扫描目录文件',
-                            status: stepsStatus.scanDir,
-                        },
-                        {
-                            title: '计算文件属性',
-                            status: stepsStatus.fileOptions,
-                        },
-                        {
-                            title: '分析重复文件',
-                            status: stepsStatus.duplicateFiles,
-                        },
-                        {
-                            title: '完成',
-                            status: stepsStatus.done,
-                        },
-                    ]}
-                />
-            </div>
-        </div>
-    );
+      <div className={styles.stepsBox}>
+        <Steps
+          current={current}
+          percent={percent}
+          labelPlacement="horizontal"
+          direction="vertical"
+          items={[
+            {
+              title: "扫描目录文件",
+              status: stepsStatus.scanDir,
+            },
+            {
+              title: "计算文件属性",
+              status: stepsStatus.fileOptions,
+            },
+            {
+              title: "分析重复文件",
+              status: stepsStatus.duplicateFiles,
+            },
+            {
+              title: "完成",
+              status: stepsStatus.done,
+            },
+          ]}
+        />
+      </div>
+    </div>
+  );
 }

+ 3 - 2
src/pages/DuplicateFile/FileInfoEditer.tsx

@@ -85,8 +85,8 @@ export default function FileInfoEditer({
     const otherTypes = [
       "其他所有带扩展名的类型",
       "其他所有无扩展名的类型",
-      "指定",
-      "排除",
+      // "指定",
+      // "排除",
     ];
     const checkedValues = fileTypeList.map((typeInfo) => typeInfo.name);
     setFileInfo({
@@ -111,6 +111,7 @@ export default function FileInfoEditer({
     checkedValues
   ) => {
     console.log("checked = ", checkedValues);
+    // TODO 全选、全不选的交互 等主体功能完善之后, 再开发
     setFileInfo({
       ...fileInfo,
       checkedTypeValues: checkedValues,

+ 2 - 1
src/plugins/tauri-plugin-file/file.ts

@@ -10,7 +10,8 @@ export class File {
     this.path = path;
   }
 
-  static async getAllList(fileInfo: FileInfoType): Promise<string[]> {
+  // static async getAllList(fileInfo: FileInfoType): Promise<string[]> {
+  static async getAllList(fileInfo: any): Promise<string[]> {
     return await invoke<string[]>("plugin:st-files|get_all_directory", {
       fileInfo,
     });

+ 241 - 173
src/services/file-service.ts

@@ -1,8 +1,13 @@
-import {table_init} from "@/databases/index";
+import { table_init } from "@/databases/index";
 // import { SQLite } from "@/plugins/tauri-plugin-sqlite";
 import Database from "tauri-plugin-sql-api";
-import {FILE_DB_PATH} from "@/config";
-import {FileInfoType, historyListType, insertSearchFilesPasamsType} from "@/types/files";
+import { FILE_DB_PATH } from "@/config";
+import {
+  FileInfoType,
+  historyListType,
+  insertSearchFilesPasamsType,
+} from "@/types/files";
+import { createSql } from "@/databases/createTableSql";
 
 /**
  * 写入用户选择好的目录和处理规则数据
@@ -10,8 +15,11 @@ import {FileInfoType, historyListType, insertSearchFilesPasamsType} from "@/type
  * @param fileInfoParams 配置好的文件信息
  * @returns false 表示写入成功
  */
-export async function insertSeletedFileHistory(path?: string, fileInfoParams?: FileInfoType) {
-    /*
+export async function insertSeletedFileHistory(
+  path?: string,
+  fileInfoParams?: FileInfoType
+) {
+  /*
       addType: ".1231,.kidd"
       checkboxAll: true
       checkboxSizeAll: true
@@ -20,58 +28,63 @@ export async function insertSeletedFileHistory(path?: string, fileInfoParams?: F
       passType: ".1231,.2113"
       path: "/Users/sysadmin/Downloads"
    */
-    try {
-        // await table_init(FILE_DB_PATH, "select_history");
-
-        const DB = await Database.load("sqlite:files.db");
-        // const DB = await SQLite.open(FILE_DB_PATH);
-        await DB.execute(
-            `INSERT INTO select_history (time, name, path, addType, checkboxAll, checkboxSizeAll, checkedSizeValues, checkedTypeValues, passType) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
-                new Date().getTime(),  // 获取当前时间的时间戳
-                path,                  // 假设 path 变量是预定义的
-                path,                  // path 变量用于 name 和 path
-                fileInfoParams?.addType || '',
-                fileInfoParams?.checkboxAll ? 1 : 0,
-                fileInfoParams?.checkboxSizeAll ? 1 : 0,
-                fileInfoParams?.checkedSizeValues?.toString() || '',
-                fileInfoParams?.checkedTypeValues?.toString() || '',
-                fileInfoParams?.passType || '',
-            ],
-        );
-        return false;
-    } catch (err) {
-        console.log(5454, err)
-        if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
-            return "当前路径重复";
-        }
-        return err;
+  try {
+    // await table_init(FILE_DB_PATH, "select_history");
+
+    const DB = await Database.load("sqlite:files.db");
+    // const DB = await SQLite.open(FILE_DB_PATH);
+    await DB.execute(
+      `INSERT INTO select_history (time, name, path, addType, checkboxAll, checkboxSizeAll, checkedSizeValues, checkedTypeValues, passType) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
+      [
+        new Date().getTime(), // 获取当前时间的时间戳
+        path, // 假设 path 变量是预定义的
+        path, // path 变量用于 name 和 path
+        fileInfoParams?.addType || "",
+        fileInfoParams?.checkboxAll ? 1 : 0,
+        fileInfoParams?.checkboxSizeAll ? 1 : 0,
+        fileInfoParams?.checkedSizeValues?.toString() || "",
+        fileInfoParams?.checkedTypeValues?.toString() || "",
+        fileInfoParams?.passType || "",
+      ]
+    );
+    return false;
+  } catch (err) {
+    console.log(5454, err);
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return "当前路径重复";
     }
+    return err;
+  }
 }
 
-export async function updateSelectedFileHistory(path?: string, fileInfoParams?: FileInfoType) {
-    try {
-        const DB = await Database.load("sqlite:files.db");
-        const result = await DB.execute(
-            `UPDATE select_history 
+export async function updateSelectedFileHistory(
+  path?: string,
+  fileInfoParams?: FileInfoType
+) {
+  try {
+    const DB = await Database.load("sqlite:files.db");
+    const result = await DB.execute(
+      `UPDATE select_history 
              SET addType = $1, checkboxAll = $2, checkboxSizeAll = $3, checkedSizeValues = $4, checkedTypeValues = $5, passType = $6 
-             WHERE path = $7;`, [
-                fileInfoParams?.addType || '',
-                fileInfoParams?.checkboxAll ? 1 : 0,
-                fileInfoParams?.checkboxSizeAll ? 1 : 0,
-                fileInfoParams?.checkedSizeValues?.toString() || '',
-                fileInfoParams?.checkedTypeValues?.toString() || '',
-                fileInfoParams?.passType || '',
-                path,                  // 假设 path 变量是预定义的
-            ],
-        );
-        return false;
-    } catch (error) {
-        console.log(595959, error)
-        if (error && `${error}`.indexOf("UNIQUE constraint failed") > -1) {
-            return "当前数据格式异常";
-        }
-        return error;
+             WHERE path = $7;`,
+      [
+        fileInfoParams?.addType || "",
+        fileInfoParams?.checkboxAll ? 1 : 0,
+        fileInfoParams?.checkboxSizeAll ? 1 : 0,
+        fileInfoParams?.checkedSizeValues?.toString() || "",
+        fileInfoParams?.checkedTypeValues?.toString() || "",
+        fileInfoParams?.passType || "",
+        path, // 假设 path 变量是预定义的
+      ]
+    );
+    return false;
+  } catch (error) {
+    console.log(595959, error);
+    if (error && `${error}`.indexOf("UNIQUE constraint failed") > -1) {
+      return "当前数据格式异常";
     }
+    return error;
+  }
 }
 
 /**
@@ -79,24 +92,27 @@ export async function updateSelectedFileHistory(path?: string, fileInfoParams?:
  * @param path 文件的路径
  * @returns FileInfoType
  */
-export async function get_info_by_path(path: string): Promise<[FileInfoType | boolean, string]> {
-    try {
-        // await table_init(FILE_DB_PATH, "select_history");
-        // const DB = await SQLite.open(FILE_DB_PATH);
-        const DB = await Database.load("sqlite:files.db");
-        const res = await DB.select(
-            "SELECT * FROM select_history WHERE path = $1", [path]
-        );
-        if (Array.isArray(res)) {
-            return [res[0], ""];
-        }
-        return [false, "暂无数据"];
-    } catch (err) {
-        if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
-            return [false, "当前路径重复"];
-        }
-        return [false, `${err}`];
+export async function get_info_by_path(
+  path: string
+): Promise<[FileInfoType | boolean, string]> {
+  try {
+    // await table_init(FILE_DB_PATH, "select_history");
+    // const DB = await SQLite.open(FILE_DB_PATH);
+    const DB = await Database.load("sqlite:files.db");
+    const res = await DB.select(
+      "SELECT * FROM select_history WHERE path = $1",
+      [path]
+    );
+    if (Array.isArray(res)) {
+      return [res[0], ""];
     }
+    return [false, "暂无数据"];
+  } catch (err) {
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return [false, "当前路径重复"];
+    }
+    return [false, `${err}`];
+  }
 }
 
 /**
@@ -104,58 +120,55 @@ export async function get_info_by_path(path: string): Promise<[FileInfoType | bo
  * @param path 文件的路径
  * @returns FileInfoType
  */
-export async function get_info_by_id(id: number): Promise<[FileInfoType | boolean, string]> {
-    try {
-        // await table_init(FILE_DB_PATH, "select_history");
-        // const DB = await SQLite.open(FILE_DB_PATH);
-        const DB = await Database.load("sqlite:files.db");
-        const res = await DB.select(
-            "SELECT * FROM select_history WHERE id = $1", [id]
-        );
-        if (Array.isArray(res)) {
-            return [res[0], ""];
-        }
-        return [false, "暂无数据"];
-    } catch (err) {
-        if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
-            return [false, "当前路径重复"];
-        }
-        return [false, `${err}`];
+export async function get_info_by_id(
+  id: number
+): Promise<[FileInfoType | boolean, string]> {
+  try {
+    // await table_init(FILE_DB_PATH, "select_history");
+    // const DB = await SQLite.open(FILE_DB_PATH);
+    const DB = await Database.load("sqlite:files.db");
+    const res = await DB.select("SELECT * FROM select_history WHERE id = $1", [
+      id,
+    ]);
+    if (Array.isArray(res)) {
+      return [res[0], ""];
     }
+    return [false, "暂无数据"];
+  } catch (err) {
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return [false, "当前路径重复"];
+    }
+    return [false, `${err}`];
+  }
 }
 
 // export async function getSource(path: string) {
 
 // }
-// export async function insertSearchFiles({
-//   path,
-//   sourceId,
-//   type,
-//   name,
-//   hash
-// }: insertSearchFilesPasamsType) {
-//   try {
-//     await table_init(FILE_DB_PATH, "search_files");
-//     const DB = await SQLite.open(FILE_DB_PATH);
-//     await DB.execute(
-//       `INSERT INTO search_files (time,sourceId,name,type,path,hash) VALUES (:time,:sourceId,:name,:type,:path,:hash)`,
-//       {
-//         ":time": new Date().getTime(),
-//         ":sourceId": sourceId,
-//         ":path": path,
-//         ":type": type,
-//         ":name": name,
-//         ":hash": hash,
-//       }
-//     );
-//     return Promise.resolve([true, ""]);
-//   } catch (err) {
-//     if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
-//       return Promise.resolve([false, "当前路径重复"]);
-//     }
-//     return Promise.resolve([false, err]);
-//   }
-// }
+export async function insertSearchFiles({
+  path,
+  sourceId,
+  type,
+  name,
+  hash,
+}: insertSearchFilesPasamsType) {
+  try {
+    const DB = await Database.load(`sqlite:files_${sourceId}.db`);
+    // 创建表
+    // await DB.execute(createSql.search_files);
+    await DB.execute(
+      "INSERT into search_files (time, sourceId, name,type,path,hash, db_version) VALUES ($1, $2, $3, $4, $5, $6, $7)",
+      [new Date().getTime(), sourceId, path, type, name, hash, "1"]
+    );
+    return Promise.resolve([true, ""]);
+  } catch (err) {
+    console.log(145, err);
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return Promise.resolve([false, "当前路径重复"]);
+    }
+    return Promise.resolve([false, err]);
+  }
+}
 
 /**
  * 获取“select_history”表中的历史记录,并进行分页。
@@ -167,74 +180,129 @@ export async function get_info_by_id(id: number): Promise<[FileInfoType | boolea
  *          - data: FileInfoType[] - 当前页的记录数据数组。
  *          - total: number - 表中的总记录数,用于前端计算总页数。
  */
-export async function get_all_history(page?: number, pageSize?: number): Promise<{
-    [x: string]: any; data: insertSearchFilesPasamsType[], total: number
+export async function get_all_history(
+  page?: number,
+  pageSize?: number
+): Promise<{
+  [x: string]: any;
+  data: insertSearchFilesPasamsType[];
+  total: number;
 }> {
-    // await table_init(FILE_DB_PATH, "select_history");
-    const DB = await Database.load("sqlite:files.db");
+  // await table_init(FILE_DB_PATH, "select_history");
+  const DB = await Database.load("sqlite:files.db");
 
-    // 查询总记录数
-    const totalResult = await DB.select("SELECT COUNT(*) AS total FROM select_history");
-    console.log(128, totalResult);
-    // [Log] 128 – {lastInsertId: 0, rowsAffected: 0} (file-service.ts, line 51)
-    const total = Array.isArray(totalResult) && totalResult[0].total;  // 获取总记录数
-    // 计算分页偏移量
-    const offset = (page || 1 - 1) * (pageSize || 10);
-
-    // 获取当前页的数据
-    const data = await DB.select(
-        "SELECT * FROM select_history LIMIT ? OFFSET ?", [pageSize, offset]
-    );
-    console.log(138, data, pageSize, offset)
-    DB.close()
-    return {data: Array.isArray(data) ? data : [], total};  // 返回包含数据和总记录数的对象
-}
+  // 查询总记录数
+  const totalResult = await DB.select(
+    "SELECT COUNT(*) AS total FROM select_history"
+  );
+  console.log(128, totalResult);
+  // [Log] 128 – {lastInsertId: 0, rowsAffected: 0} (file-service.ts, line 51)
+  const total = Array.isArray(totalResult) && totalResult[0].total; // 获取总记录数
+  // 计算分页偏移量
+  const offset = (page || 1 - 1) * (pageSize || 10);
 
+  // 获取当前页的数据
+  const data = await DB.select(
+    "SELECT * FROM select_history LIMIT ? OFFSET ?",
+    [pageSize, offset]
+  );
+  console.log(138, data, pageSize, offset);
+  DB.close();
+  return { data: Array.isArray(data) ? data : [], total }; // 返回包含数据和总记录数的对象
+}
 
-export async function get_list_by_sourceid(sourceId: number): Promise<[insertSearchFilesPasamsType[] | false, string]> {
-    try {
-        // await table_init(FILE_DB_PATH, "select_history");
-        // const DB = await SQLite.open(FILE_DB_PATH);
-        const DB = await Database.load("sqlite:test.db");
-        const res = await DB.execute(
-            "SELECT * FROM search_files WHERE sourceId = $1", [sourceId]
-        );
-        console.log(969696, sourceId);
+export async function get_list_by_sourceid(
+  sourceId: number
+): Promise<[insertSearchFilesPasamsType[] | false, string]> {
+  try {
+    // await table_init(FILE_DB_PATH, "select_history");
+    // const DB = await SQLite.open(FILE_DB_PATH);
+    const DB = await Database.load("sqlite:test.db");
+    const res = await DB.execute(
+      "SELECT * FROM search_files WHERE sourceId = $1",
+      [sourceId]
+    );
+    console.log(969696, sourceId);
 
-        /* const res = await DB.queryWithArgs<Array<insertSearchFilesPasamsType>>(
+    /* const res = await DB.queryWithArgs<Array<insertSearchFilesPasamsType>>(
           "SELECT * FROM search_files WHERE sourceId = :sourceId GROUP BY hash HAVING COUNT(*) > 1",
           { ":sourceId": sourceid }
         ); */
-        console.log(3434, res);
-
-        if (Array.isArray(res)) {
-            return [res, ""];
-        }
-        return [false, "暂无数据"];
-    } catch (err) {
-        if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
-            return [false, "当前路径重复"];
-        }
-        return [false, `${err}`];
+    console.log(3434, res);
+
+    if (Array.isArray(res)) {
+      return [res, ""];
     }
+    return [false, "暂无数据"];
+  } catch (err) {
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return [false, "当前路径重复"];
+    }
+    return [false, `${err}`];
+  }
 }
 
-
 export async function delSelectedFileHistory(path?: string) {
-    try {
-        const DB = await Database.load("sqlite:files.db");
-        const result = await DB.execute(
-            `DELETE FROM select_history WHERE path = $1`, [
-                path,                  // 假设 path 变量是预定义的
-            ],
-        );
-        console.log(206, result)
-        return false;
-    } catch (error) {
-        console.log(595959, error)
-        if (error && `${error}`.indexOf("UNIQUE constraint failed") > -1) {
-            return "当前数据格式异常";
-        }
-        return error;
+  try {
+    const DB = await Database.load("sqlite:files.db");
+    const result = await DB.execute(
+      `DELETE FROM select_history WHERE path = $1`,
+      [
+        path, // 假设 path 变量是预定义的
+      ]
+    );
+    console.log(206, result);
+    return false;
+  } catch (error) {
+    console.log(595959, error);
+    if (error && `${error}`.indexOf("UNIQUE constraint failed") > -1) {
+      return "当前数据格式异常";
     }
-}
+    return error;
+  }
+}
+
+export async function searchDuplicateFile({ sourceId }: { sourceId: string }) {
+  try {
+    const DB = await Database.load(`sqlite:files_${sourceId}.db`);
+    // 创建表
+    // await DB.execute(createSql.search_files);
+    /* 
+    select * from search_files where sourceId = $1 in (select sourceId from search_files group by hash having count(hash) > 1)
+ */
+    // const res = await DB.select("SELECT * from search_files WHERE sourceId = $1", [sourceId]);
+    const res = await DB.select(
+//       `SELECT sf.*
+// FROM search_files sf
+// JOIN (
+//     SELECT hash
+//     FROM search_files
+//     WHERE sourceId = $1
+//     GROUP BY hash
+//     HAVING COUNT(*) > 1
+// ) dup ON sf.hash = dup.hash
+// WHERE sf.sourceId = $1;
+// `,
+`SELECT hash, 
+        GROUP_CONCAT(id) AS ids, 
+        GROUP_CONCAT(path) AS paths,
+        GROUP_CONCAT(time) AS times,
+        COUNT(*) AS count
+    FROM search_files
+    WHERE sourceId = $1
+    GROUP BY hash
+    HAVING COUNT(*) > 1;
+`,
+      [sourceId]
+    );
+    console.log(285, res);
+
+    return Promise.resolve([true, res]);
+  } catch (err) {
+    console.log(145, err);
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return Promise.resolve([false, "当前路径重复"]);
+    }
+    return Promise.resolve([false, err]);
+  }
+}

+ 6 - 3
src/types/files.d.ts

@@ -22,11 +22,14 @@ export interface FileInfoEditerType {
 }
 
 export type insertSearchFilesPasamsType = {
-  id: number;
+  id?: number;
   sourceId?: number | string | any;
   path: string;
-  time: string;
-  progress: number;
+  time?: string;
+  // progress: number;
+  type: string,
+  name: string,
+  hash: string
 };
 
 export type historyListType = {