Browse Source

文件清理

john 1 year ago
parent
commit
bd978bf9bd

+ 10 - 0
src-tauri/Cargo.lock

@@ -4332,6 +4332,7 @@ dependencies = [
  "thiserror",
  "tracing",
  "tracing-subscriber",
+ "trash",
  "uuid",
 ]
 
@@ -4816,6 +4817,15 @@ dependencies = [
  "tracing-log",
 ]
 
+[[package]]
+name = "trash"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90df96afb154814e214f37eac04920c66886fd95962f22febb4d537b0dacd512"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "tree_magic_mini"
 version = "3.1.5"

+ 1 - 0
src-tauri/Cargo.toml

@@ -31,6 +31,7 @@ tracing = "0.1.40"
 tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
 lazy_static = "1.4.0"
 home = "0.5.9"
+trash = "1.3"
 
 [dependencies.tauri-plugin-sql]
 git = "https://github.com/tauri-apps/plugins-workspace"

+ 24 - 1
src-tauri/src/self_plugin/tauri_plugin_file/files.rs

@@ -1,10 +1,11 @@
 use hex;
 use serde::{Deserialize, Serialize, Serializer};
 use sha2::{Digest as OtherDigest, Sha256}; // 确保导入 `Digest`
+use std::fs;
 use std::path::{Path, PathBuf};
 use std::time::UNIX_EPOCH;
-use std::fs;
 use tauri::command;
+extern crate trash;
 
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
@@ -273,3 +274,25 @@ pub fn get_file_info(file_path: String) -> Result<FileInfos> {
 
     Ok(file_info)
 }
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct RequestMvFile {
+    code: Option<u64>,
+    msg: Option<String>,
+}
+
+#[command]
+pub fn mv_file_to_trash(file_path: String) -> RequestMvFile {
+    if let Err(e) = trash::delete(file_path) {
+        RequestMvFile {
+            code: Some(500),
+            msg: Some(format!("Error moving file to trash: {}", e)),
+        }
+    } else {
+        println!("File successfully moved to trash.");
+        RequestMvFile {
+            code: Some(200),
+            msg: Some("File successfully moved to trash.".to_string()),
+        }
+    }
+}

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

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

+ 5 - 0
src/Router.tsx

@@ -13,6 +13,7 @@ import CalculateDuplicateFiles from "@/pages/DuplicateFile/CalculateDuplicateFil
 import DuplicateFileInfo from "@/pages/DuplicateFile/FileInfo";
 // import CalculateListPage from "@/pages/DuplicateFile/CalculateListPage";
 import CalculateListPage from '@/pages/DuplicateFile/CalculateListPage'
+import ManageDuplicateFiles from '@/pages/DuplicateFile/ManageDuplicateFiles'
 /* export default function Router() {
   return (
     <Routes>
@@ -65,6 +66,10 @@ const router = createBrowserRouter([
           {
             path: "calculate-list/:fileId",
             element: <CalculateListPage />,
+          },
+          {
+            path: "ManageDuplicateFiles/:fileId",
+            element: <ManageDuplicateFiles />,
           }
         ]
       },

+ 6 - 4
src/pages/DuplicateFile/CalculateDuplicateFiles.tsx

@@ -223,9 +223,9 @@ export default function CalculateDuplicateFiles() {
         });
         setPercent(0);
         // 分析重复文件
-        const searchDuplicateFileRes = await searchDuplicateFile({
+        /* const searchDuplicateFileRes = await searchDuplicateFile({
           sourceId: fileId || "",
-        });
+        }); */
         /* 
             [
                 {count: 6, hash: "3ba7bbfc03e3bed23bf066e2e9a6a5389dd33fd8637bc0220d9e6d642ccf5946", ids: "17,21,22,26,27,31", },
@@ -260,9 +260,9 @@ export default function CalculateDuplicateFiles() {
             ] 
     
         */
-        console.log(747474, searchDuplicateFileRes);
+        /* console.log(747474, searchDuplicateFileRes);
         if (searchDuplicateFileRes[0]) {
-        }
+        } */
 
         setStepsStatus({
           scanDir: "finish",
@@ -271,6 +271,8 @@ export default function CalculateDuplicateFiles() {
           done: "finish",
         });
         setPercent(100);
+        await waittime(1000);
+        navigate('/calculate-list/' + fileId)
       }
     }
   }

+ 1 - 0
src/pages/DuplicateFile/CalculateListPage.module.less

@@ -1,3 +1,4 @@
 .CalculateListPage {
+  background-color: #99999908;
   
 }

+ 122 - 73
src/pages/DuplicateFile/CalculateListPage.tsx

@@ -1,59 +1,120 @@
 import { Avatar, List, message, Checkbox, Row, Col, Space, Button } from "antd";
 import type { CheckboxProps } from "antd";
-import VirtualList from "rc-virtual-list";
 import { useEffect, useState } from "react";
-
+import {
+  del_file_by_id,
+  get_fileInfo_by_id,
+  searchDuplicateFile,
+} from "@/services";
+import { message as tauriMessage } from "@tauri-apps/api/dialog";
 import styles from "./CalculateListPage.module.less";
-import { UploadOutlined } from "@ant-design/icons";
+import { useParams } from "react-router";
+import { insertSearchFilesPasamsType } from "@/types/files";
+import type { GetProp } from "antd";
+import File from "@/plugins/tauri-plugin-file/file";
+
 export default function CalculateListPage() {
-  const [data, setData] = useState<UserItem[]>([]);
-  interface UserItem {
-    email: string;
-    gender: string;
-    name: {
-      first: string;
-      last: string;
-      title: string;
-    };
-    nat: string;
-    picture: {
-      large: string;
-      medium: string;
-      thumbnail: string;
-    };
+  let { fileId } = useParams();
+  const [data, setData] = useState<FileItem[]>([]);
+  const [removeList, setRemoveList] = useState<string[]>([]);
+  interface FileItem {
+    sourceId: number;
+    ids: string;
+    hash: string;
+    count: number;
+    firstItem: insertSearchFilesPasamsType;
+    otherItems: insertSearchFilesPasamsType[];
   }
-  const ContainerHeight = 600;
-  const fakeDataUrl =
-    "https://randomuser.me/api/?results=20&inc=name,gender,email,nat,picture&noinfo";
-  const onScroll = (e: React.UIEvent<HTMLElement, UIEvent>) => {
-    // Refer to: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#problems_and_solutions
-    if (
-      Math.abs(
-        e.currentTarget.scrollHeight -
-          e.currentTarget.scrollTop -
-          ContainerHeight
-      ) <= 1
-    ) {
-      appendData();
+  const appendData = async () => {
+    const [isError, searchDuplicateFileRes] = await searchDuplicateFile({
+      sourceId: `${fileId}`,
+    });
+    if (!isError) {
+      typeof searchDuplicateFileRes === "string" &&
+        (await tauriMessage(searchDuplicateFileRes, {
+          title: "查询失败",
+          type: "error",
+        }));
+      console.log(searchDuplicateFileRes);
+    }
+
+    if (Array.isArray(searchDuplicateFileRes)) {
+      const newData: any[] = [];
+      await searchDuplicateFileRes.reduce(
+        async (prevPromise: any, currentFile: any) => {
+          // 等待上一个 Promise 完成
+          await prevPromise;
+          const ids = currentFile.ids.split(",");
+          const firstItem = await get_fileInfo_by_id(ids[0], `${fileId}`);
+          const otherItems = await Promise.allSettled(
+            ids
+              .map((id: string) => {
+                if (id === ids[0]) {
+                  return false;
+                }
+                return get_fileInfo_by_id(id, `${fileId}`);
+              })
+              .filter((elm: any) => elm)
+          );
+
+          newData.push({
+            ...currentFile,
+            firstItem: firstItem[0],
+            otherItems: otherItems
+              .map((elm) => {
+                if (elm.status === "fulfilled" && !elm.value[1]) {
+                  return elm.value[0];
+                }
+                return false;
+              })
+              .filter((elm: any) => elm),
+          });
+          return Promise.resolve(0);
+        },
+        Promise.resolve(0)
+      );
+      setData(newData);
     }
-  };
-  const appendData = () => {
-    fetch(fakeDataUrl)
-      .then((res) => res.json())
-      .then((body) => {
-        setData(data.concat(body.results));
-        // message.success(`${body.results.length} more items loaded!`);
-      });
   };
 
   useEffect(() => {
     appendData();
   }, []);
 
-  const onChange: CheckboxProps["onChange"] = (e) => {
-    console.log(`checked = ${e.target.checked}`);
+  const onChange: GetProp<typeof Checkbox.Group, "onChange"> = (
+    checkedValues
+  ) => {
+    console.log("checked = ", checkedValues);
+    if (Array.isArray(checkedValues)) {
+      // setRemoveList(checkedValues.filter(elm => typeof elm === 'string'));
+      // setRemoveList(checkedValues)
+    }
+    // value={removeList}
   };
 
+  const CheckboxContent = (item: insertSearchFilesPasamsType) => (
+    <div>{item.path}</div>
+  );
+
+  async function removeFilesByDB() {
+    const filesRes = await File.rmFile(
+      "/Users/sysadmin/Pictures/test/欧洲4_副本5.jpeg"
+    );
+    console.log(9797, filesRes);
+    if (filesRes.code === 200) {
+      await del_file_by_id(
+        "/Users/sysadmin/Pictures/test/欧洲4_副本5.jpeg",
+        "24"
+      );
+      message.success('删除成功!')
+      return;
+    }
+    await tauriMessage(filesRes.msg, {
+      title: "删除失败",
+      type: "error",
+    });
+  }
+
   return (
     <div className={styles.CalculateListPage}>
       <div
@@ -62,28 +123,25 @@ export default function CalculateListPage() {
         }}
       >
         <Space>
-          <Button type="primary" danger>删除选中的文件</Button>
+          <Button type="primary" danger onClick={() => removeFilesByDB()}>
+            删除选中的文件
+          </Button>
           <Button type="primary">统一移动到指定目录</Button>
           <Button type="primary">导出</Button>
         </Space>
-        <div style={{marginBottom: '12px'}}></div>
-        <List>
-          <VirtualList
-            data={data}
-            height={ContainerHeight}
-            itemHeight={47}
-            itemKey="email"
-            onScroll={onScroll}
-          >
-            {(item: UserItem) => (
+        <div style={{ marginBottom: "12px" }}></div>
+        <Checkbox.Group onChange={onChange} style={{ width: "100%" }}>
+          <div style={{ width: "100%" }}>
+            {data.map((item: FileItem) => (
               <div
+                key={item.hash}
                 style={{
                   marginBottom: "12px",
                 }}
               >
                 <div>
-                  <Checkbox onChange={onChange}>
-                    /Users/sysadmin/Downloads/Android-SDK@3.6.18.81676_20230117.zip
+                  <Checkbox value={item.firstItem.path}>
+                    {CheckboxContent(item.firstItem)}
                   </Checkbox>
                 </div>
                 <div
@@ -92,27 +150,18 @@ export default function CalculateListPage() {
                     padding: "12px 3px",
                   }}
                 >
-                  <div>
-                    <Checkbox onChange={onChange}>重复的文件路径1</Checkbox>
-                  </div>
-                  <div>
-                    <Checkbox onChange={onChange}>重复的文件路径2</Checkbox>
-                  </div>
+                  {item.otherItems.map((otherItem) => (
+                    <div key={otherItem.path}>
+                      <Checkbox value={otherItem.path}>
+                        {CheckboxContent(otherItem)}
+                      </Checkbox>
+                    </div>
+                  ))}
                 </div>
               </div>
-              //   <List.Item key={item.email}>
-              //     <List.Item.Meta
-              //       avatar={
-              //         <Checkbox onChange={onChange}></Checkbox>
-              //       }
-              //       title={ <div>文件路径</div>}
-              //     //   description={item.email}
-              //     />
-              //     <div>Content</div>
-              //   </List.Item>
-            )}
-          </VirtualList>
-        </List>
+            ))}
+          </div>
+        </Checkbox.Group>
       </div>
     </div>
   );

+ 3 - 0
src/pages/DuplicateFile/ManageDuplicateFiles.module.less

@@ -0,0 +1,3 @@
+.ManageDuplicateFilesBox {
+  padding: 24px;
+}

+ 40 - 0
src/pages/DuplicateFile/ManageDuplicateFiles.tsx

@@ -0,0 +1,40 @@
+import { useParams } from "react-router";
+import styles from "./ManageDuplicateFiles.module.less";
+import { useEffect } from "react";
+import { searchDuplicateFile } from "@/services";
+import { message } from "@tauri-apps/api/dialog";
+import { Button, Col, Row, Steps, Table } from "antd";
+
+export default function ManageDuplicateFiles() {
+  let { fileId } = useParams();
+
+  useEffect(() => {
+    initPage();
+  }, []);
+  async function initPage() {
+    const [searchDuplicateFileRes, isError] = await searchDuplicateFile({
+      sourceId: fileId || "",
+    });
+    if (!isError) {
+      typeof searchDuplicateFileRes === "string" &&
+        (await message(searchDuplicateFileRes, {
+          title: "查询失败",
+          type: "error",
+        }));
+      console.log(searchDuplicateFileRes);
+    }
+    console.log(1515151515, searchDuplicateFileRes);
+  }
+  return (
+    <div className={styles.ManageDuplicateFilesBox}>ManageDuplicateFiles
+    
+    <Row>
+    {/* <Table
+        columns={columns}
+        expandable={{ expandedRowRender, defaultExpandedRowKeys: ['0'] }}
+        dataSource={data}
+      /> */}
+    </Row>
+    </div>
+  );
+}

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

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

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

@@ -328,8 +328,6 @@ export async function searchDuplicateFile({
       `SELECT hash,
        sourceId,
        GROUP_CONCAT(id)    AS ids,
-       GROUP_CONCAT(path)  AS paths,
-       GROUP_CONCAT(creation_time)  AS creation_tims,
        COUNT(*)           AS count
 FROM search_files
 WHERE sourceId = $1
@@ -338,12 +336,11 @@ WHERE sourceId = $1
   AND hash != ""
 GROUP BY hash, sourceId
 HAVING COUNT(*) > 1
-ORDER BY [creation_time] ASC
-LIMIT $3 OFFSET ($2 - 1) * $3;
 `,
+/* ORDER BY [creation_time] ASC
+LIMIT $3 OFFSET ($2 - 1) * $3; */
       [sourceId, page, pageSize]
     );
-    console.log(285, res);
     return Promise.resolve([true, res]);
   } catch (err) {
     // console.log(145, err);
@@ -401,3 +398,47 @@ export async function updateFileHsah(
     return error;
   }
 }
+
+
+export async function get_fileInfo_by_id(id: string, sourceId: string) {
+  try {
+    const DB = await Database.load(`sqlite:files_${sourceId}.db`);
+    // 创建表
+    await DB.execute(createSql.search_files);
+    const res = await DB.select("SELECT * FROM search_files WHERE id = $1 and sourceId = $2", [
+      id, sourceId
+    ]);
+    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 del_file_by_id(path: string, sourceId: string) {
+  try {
+    const DB = await Database.load(`sqlite:files_${sourceId}.db`);
+    // 创建表
+    await DB.execute(createSql.search_files);
+    const result = await DB.execute(
+      `DELETE FROM search_files WHERE path = $1 and sourceId = $2`,
+      [
+        path, // 假设 path 变量是预定义的
+        sourceId,
+      ]
+    );
+    console.log(206, result);
+    return false;
+  } catch (error) {
+    console.log(595959, error);
+    if (error && `${error}`.indexOf("UNIQUE constraint failed") > -1) {
+      return "当前数据格式异常";
+    }
+    return error;
+  }
+}