ソースを参照

备份:2023-07-03

john 2 年 前
コミット
12c28ca316

+ 2 - 0
README.md

@@ -37,3 +37,5 @@ This template should help get you started developing with Tauri, React and Types
 ## 致谢
 
 1、ChatGpt
+
+

+ 44 - 0
docs/sql.md

@@ -0,0 +1,44 @@
+```rs
+use rusqlite::{Connection, Result};
+
+fn main() -> Result<()> {
+    // 打开第一个数据库连接
+    let conn1 = Connection::open("database1.db")?;
+
+    // 打开第二个数据库连接
+    let conn2 = Connection::open("database2.db")?;
+
+    // 在第一个数据库中创建一个表
+    conn1.execute(
+        "CREATE TABLE IF NOT EXISTS database1_table (id INTEGER PRIMARY KEY, name TEXT)",
+        [],
+    )?;
+
+    // 在第二个数据库中创建一个表
+    conn2.execute(
+        "CREATE TABLE IF NOT EXISTS database2_table (id INTEGER PRIMARY KEY, age INTEGER)",
+        [],
+    )?;
+
+    // 在第一个数据库中插入一些数据
+    conn1.execute("INSERT INTO database1_table (id, name) VALUES (1, 'John')", [])?;
+
+    // 在第二个数据库中插入一些数据
+    conn2.execute("INSERT INTO database2_table (id, age) VALUES (1, 25)", [])?;
+
+    // 跨数据库查询
+    let query = "SELECT d1.name, d2.age FROM database1.db.database1_table AS d1
+                 JOIN database2.db.database2_table AS d2 ON d1.id = d2.id";
+    let mut stmt = conn1.prepare(query)?;
+    let rows = stmt.query_map([], |row| {
+        Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1)?))
+    })?;
+
+    for row in rows {
+        let (name, age) = row?;
+        println!("Name: {}, Age: {}", name, age);
+    }
+
+    Ok(())
+}
+```

+ 1 - 1
src-tauri/src/common/sqlite/mod.rs

@@ -28,7 +28,7 @@ pub async fn open_with_flags(path: &String, flags: OpenFlags) -> Result<Arc<Mute
         }
     } else {
         //
-        let mut storage_path = crate::utils::rrai_home_path()?.join("sqlite");
+        let mut storage_path = crate::utils::system_tools_home_path()?.join("sqlite");
         storage_path.push(path.clone());
 
         let prefix = storage_path.parent().unwrap_or(storage_path.as_path());

+ 70 - 5
src-tauri/src/self_plugin/tauri_plugin_file/files.rs

@@ -1,13 +1,20 @@
+use std::collections::HashSet;
 use std::fs;
-use std::path::{Path, PathBuf};
+// use crypto::digest::Digest;
+// use crypto::sha2::Sha256;
+use ring::digest::{Context, Digest, SHA256};
+use serde::Serialize;
+use serde::Serializer;
+use std::path::Path;
+use std::path::PathBuf;
+// use tauri::api::path::resolve_path;
 use tauri::command;
-use serde::{Serialize, Serializer};
 // use tauri::api::file::IntoInvokeHandler;
 
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
     #[error(transparent)]
-    Io(#[from] std::io::Error)
+    Io(#[from] std::io::Error),
 }
 
 impl Serialize for Error {
@@ -21,14 +28,39 @@ impl Serialize for Error {
 
 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() {
+                if path.is_file() && filter_other_directory(path.display().to_string().as_str(), &[".obsidian", ".DS_Store"]) {
+                    // 过滤文件
+                    println!("{}", path.display());
                     files.push(path.clone());
-                } else if path.is_dir() {
+                } 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)?;
                 }
             }
@@ -44,3 +76,36 @@ pub fn get_all_directory(path: String) -> Result<Vec<PathBuf>> {
     read_files_in_directory(directory, &mut files)?;
     Ok(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);
+    path.extension().and_then(|ext| ext.to_str())
+}
+
+#[command]
+pub fn get_file_type_by_path(file_path: String) -> Result<String> {
+    if let Some(file_type) = get_file_type(&file_path) {
+        Ok(file_type.to_string())
+    } else {
+        Ok("Unknown file type".to_string())
+    }
+}
+
+#[command]
+pub fn calculate_file_hash(file_path: String) -> Result<String> {
+    let file_bytes = fs::read(file_path).expect("无法读取文件");
+    let mut hasher = Context::new(&SHA256);
+    hasher.update(&file_bytes);
+    let digest: Digest = hasher.finish();
+    let hash = hex::encode(digest.as_ref());
+    Ok(hash)
+}

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

@@ -11,7 +11,7 @@ use self::files::*;
 pub fn init<R: Runtime>() -> TauriPlugin<R> {
     Builder::new("st-files")
         .invoke_handler(tauri::generate_handler![
-            get_all_directory
+            get_all_directory,get_file_type_by_path,calculate_file_hash
         ])
         .setup(|app| {
             // app.manage(SqliteMap::default());

+ 2 - 2
src-tauri/src/utils/mod.rs

@@ -10,13 +10,13 @@
 use anyhow::{anyhow, Result};
 use std::path::PathBuf;
 
-pub fn rrai_home_path() -> Result<PathBuf> {
+pub fn system_tools_home_path() -> Result<PathBuf> {
     let home_path = std::env::var_os("IDNS_RRAI_PATH")
         .map(PathBuf::from)
         .or_else(|| {
             home::home_dir().map(|tilde: PathBuf| {
                 let mut path = PathBuf::from(tilde);
-                path.push(".rrai");
+                path.push(".system_tools");
                 path
             })
         });

+ 1 - 0
src/config/file.ts

@@ -0,0 +1 @@
+export const FILE_DB_PATH = 'files.db'

+ 1 - 0
src/config/index.ts

@@ -0,0 +1 @@
+export * from './file'

+ 19 - 0
src/databases/createTableSql.ts

@@ -0,0 +1,19 @@
+export const createSql = {
+    select_history: `CREATE TABLE select_history (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        time TIMESTAMP,
+        name TEXT CHECK(length(name) <= 255),
+        path TEXT CHECK(length(path) <= 500),
+        unique(path)
+    );`,
+    search_files: `CREATE TABLE search_files (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        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)
+    );`
+}

+ 16 - 24
src/databases/index.ts

@@ -1,25 +1,17 @@
-import { SQLite } from '@/plugins/tauri-plugin-sqlite';
+import { SQLite } from "@/plugins/tauri-plugin-sqlite";
+import { createSql } from "./createTableSql";
+import { TableName } from "@/types/table";
+export const table_init = async (dbName: string, tableName: TableName) => {
+  const dbversion = await SQLite.open(dbName);
+  //查询是否有该表
+  const rows = await dbversion.queryWithArgs<Array<{ count: number }>>(
+    `SELECT count(1) count FROM sqlite_master WHERE type='table' and name = '${tableName}' `
+  );
+  if (!!rows && rows.length > 0 && rows[0].count > 0) {
+  } else {
+    //创建表
+    await dbversion.execute(createSql[tableName]);
+  }
+};
 
-export const getVersion = async (dbName: string): Promise<number> => {
-    const dbversion = await SQLite.open(dbName);
-
-    //查询是否有该表
-    const rows = await dbversion.queryWithArgs<Array<{ count: number }>>("SELECT count(1) count FROM sqlite_master WHERE type='table' and name = 'select_history' ");
-
-    console.log(rows);
-    if (!!rows && rows.length > 0 && rows[0].count > 0) {
-
-    } else {
-        //创建表
-        await dbversion.execute(`CREATE TABLE select_history (name TEXT, version INTEGER, unique(name));`)
-    }
-
-    //查询
-    const versions = await dbversion.queryWithArgs<Array<{ version: number }>>("SELECT version FROM databases_version WHERE name = :name", { ':name': dbName });
-
-    if (!!versions && versions.length > 0) {
-        return versions[0].version;
-    }
-
-    return 0;
-}
+export const DB = async (dbName: string) => await SQLite.open(dbName);

+ 120 - 9
src/pages/DuplicateFile/DuplicateFile.tsx

@@ -1,14 +1,46 @@
 import styles from "./DuplicateFile.module.less";
-import { invoke } from "@tauri-apps/api/tauri";
+// import { invoke } from "@tauri-apps/api/tauri";
 import { open } from "@tauri-apps/api/dialog";
-import { Col, Row, Button } from "antd";
+import { Col, Row, Button, message, Table, Select, Space } from "antd";
 import { appDataDir } from "@tauri-apps/api/path";
 import File from "@/plugins/tauri-plugin-file/file";
-import { useState } from "react";
-import {SQLite} from '@/plugins/tauri-plugin-sqlite'
+import { useEffect, useState } from "react";
+// import { SQLite } from "@/plugins/tauri-plugin-sqlite";
+// import {select_history_init} from '@/databases/index'
+const { Option } = Select;
+import {
+  insertSeletedFileHistory,
+  insertSearchFiles,
+  get_info_by_path,
+  get_all_history,
+  get_list_by_sourceid,
+} from "@/services";
+import { insertSearchFilesPasamsType, historyListType } from "@/types/files";
 
 export default function DuplicateFile() {
-  const [usePath, setUsePath] = useState<string>("111111111111111");
+  const [usePath, setUsePath] = useState<string>(
+    "/Users/sysadmin/code/rust_project/tauri-app/diff_source"
+  );
+  const [fileList, setFileList] = useState<insertSearchFilesPasamsType[]>([]);
+  const [historyList, setHistoryList] = useState<historyListType[]>([]);
+
+  const columns = [
+    {
+      title: "编号",
+      dataIndex: "id",
+      key: "id",
+    },
+    {
+      title: "路径",
+      dataIndex: "name",
+      key: "name",
+    },
+    {
+      title: "哈希值",
+      dataIndex: "hash",
+      key: "hash",
+    },
+  ];
   async function sort() {
     // 打开本地的系统目录,暂时不支持多选
     const selected = await open({
@@ -29,10 +61,63 @@ export default function DuplicateFile() {
 
   // 存储用户的历史选择记录
   async function opens() {
-    const sql = await SQLite.open('./files.db3')
-    // console.log(sql.execute);
+    const res = await insertSeletedFileHistory(usePath);
+    fileHistoryListInit();
+    if (res) {
+      // return message.error(`${res}`)
+    }
+    const [info, msg] = await get_info_by_path(`${usePath}`);
+    if (!info) {
+      return message.error(msg);
+    }
+    // 最多记录 100 条用户操作的历史数据
+    const files = await File.getAllList(usePath);
+
+    if (files.length) {
+      files.forEach(async (elm) => {
+        const [res, msg] = await insertSearchFiles({
+          // 组装数据
+          sourceId: (info as any).id,
+          path: elm,
+          type: await File.getType(elm),
+          name: elm,
+          hash: await File.getHash(elm),
+        });
+        // console.log(67, res, msg);
+      });
+    }
+    getProcessedQueryData();
+  }
+  useEffect(() => {
+    fileHistoryListInit();
+    getProcessedQueryData();
+  }, []);
 
-    // SQLite.execute
+  // 查询用户历史纪录
+  async function fileHistoryListInit() {
+    const res = await get_all_history();
+    setHistoryList(res);
+  }
+
+  const historyHandleChange = (value: string) => {
+    // console.log(`selected ${value}`);
+    setUsePath(value);
+  };
+
+  // 获取处理好的查询数据数据,根据
+  async function getProcessedQueryData() {
+    console.log(102, usePath);
+    let [info, msg1] = await get_info_by_path(`${usePath}`);
+    if (!info) return;
+    console.log(104, info);
+
+    const [res, msg2] = await get_list_by_sourceid((info as any).id);
+    console.log(109, res);
+    if (!res) return;
+    setFileList(res);
+
+    // const res = await get_all_history();
+    // setHistoryList(res);
   }
 
   return (
@@ -45,7 +130,33 @@ export default function DuplicateFile() {
         <Col>设置文件路径</Col>
       </Row>
       <Row>已选择路径:{usePath}</Row>
-      {usePath && <Row><Button onClick={() => opens()}>开始</Button></Row>}
+      <Row>
+        <Select style={{ width: "100%" }} onChange={historyHandleChange}>
+          {historyList.length > 0 && historyList.map((elm, index) => (
+            <Option key={index}>{elm.path}</Option>
+          ))}
+        </Select>
+      </Row>
+      {usePath && (
+        <Row>
+          <Button onClick={() => opens()}>开始</Button>
+        </Row>
+      )}
+      {fileList.length > 0 && (
+        <div>
+          <br />
+          <Row>
+            <Space>
+              <Button type="link">Link Button</Button>
+              <Button type="link">Link Button</Button>
+            </Space>
+          </Row>
+          <br />
+          <Row>
+            <Table rowKey={"id"} dataSource={fileList} columns={columns} />;
+          </Row>
+        </div>
+      )}
     </div>
   );
 }

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

@@ -12,8 +12,17 @@ export class File {
     return await invoke<string[]>("plugin:st-files|get_all_directory", {
       path,
     });
-    // console.log(15,path);
-    // return new Promise(() => res);
+  }
+  static async getType(path: string): Promise<string> {
+    return await invoke<string>("plugin:st-files|get_file_type_by_path", {
+      filePath: path,
+    });
+  }
+
+  static async getHash(path: string): Promise<string> {
+    return await invoke<string>("plugin:st-files|calculate_file_hash", {
+      filePath: path,
+    });
   }
 
   // async close(): Promise<boolean> {

+ 0 - 1
src/plugins/tauri-plugin-sqlite/sqlite.ts

@@ -9,7 +9,6 @@ export class SQLite {
 
     static async open(path: string): Promise<SQLite> {
         let res = await invoke<string>('plugin:st-sqlite|open', { path });
-        console.log(res);
         return new SQLite(path);
     }
 

+ 112 - 0
src/services/file-service.ts

@@ -0,0 +1,112 @@
+import { table_init } from "@/databases/index";
+import { SQLite } from "@/plugins/tauri-plugin-sqlite";
+import { FILE_DB_PATH } from "@/config";
+import { historyListType, insertSearchFilesPasamsType } from "@/types/files";
+
+export async function insertSeletedFileHistory(path: string) {
+  try {
+    await table_init(FILE_DB_PATH, "select_history");
+    const DB = await SQLite.open(FILE_DB_PATH);
+    await DB.execute(
+      `INSERT INTO select_history (time,name,path) VALUES (:time,:name,:path)`,
+      {
+        ":time": new Date().getTime(),
+        ":name": path,
+        ":path": path,
+      }
+    );
+    return false;
+  } catch (err) {
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return "当前路径重复";
+    }
+    return err;
+  }
+}
+export async function get_info_by_path(path: string):Promise<[{id: number}|boolean, string]>{
+  try {
+    await table_init(FILE_DB_PATH, "select_history");
+    const DB = await SQLite.open(FILE_DB_PATH);
+    const res = await DB.queryWithArgs<Array<{ id: number }>>(
+      "SELECT * FROM select_history WHERE path = :path",
+      { ":path": path }
+    );
+    console.log(3434, res);
+    
+    if(res.length) {
+      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 [true, ""];
+  } catch (err) {
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return [false, "当前路径重复"];
+    }
+    return [false, err];
+  }
+}
+
+export async function get_all_history(): Promise<historyListType[]>{
+  await table_init(FILE_DB_PATH, "select_history");
+  const DB = await SQLite.open(FILE_DB_PATH);
+  return await DB.queryWithArgs<Array<historyListType>>(
+    "SELECT * FROM select_history"
+  );
+}
+
+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 res = await DB.queryWithArgs<Array<insertSearchFilesPasamsType>>(
+      "SELECT * FROM search_files WHERE sourceId = :sourceId",
+      { ":sourceId": sourceid }
+    ); */
+    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(res.length) {
+      return [res, ""];  
+    }
+    return [false, "暂无数据"];
+  } catch (err) {
+    if (err && `${err}`.indexOf("UNIQUE constraint failed") > -1) {
+      return [false, "当前路径重复"];
+    }
+    return [false, `${err}`];
+  }
+}

+ 5 - 0
src/services/index.ts

@@ -0,0 +1,5 @@
+// export * from './auth-service';
+export * from './file-service';
+// export * from './message-service';
+// export * from './lexica-service';
+// export * from './types';

+ 0 - 0
src/services/lexica-service.ts


+ 0 - 0
src/services/message-service.ts


+ 0 - 6
src/types/clsx.d.ts

@@ -1,6 +0,0 @@
-export type ClassValue = ClassArray | ClassDictionary | string | number | null | boolean | undefined;
-export type ClassDictionary = Record<string, any>;
-export type ClassArray = ClassValue[];
-
-export declare function clsx(...inputs: ClassValue[]): string;
-export default clsx;

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

@@ -0,0 +1,17 @@
+export type insertSearchFilesPasamsType = {
+    id?: number|string|any,
+    sourceId: number|string|any,
+    path: string,
+    type: string,
+    name: string,
+    hash: string,
+}
+
+export type historyListType = {
+    id?: number|string|any,
+    time: number|string|any,
+    path: string,
+    type: string,
+    name: string,
+    hash: string,
+}

+ 8 - 0
src/types/table.d.ts

@@ -0,0 +1,8 @@
+// export type ClassValue = ClassArray | ClassDictionary | string | number | null | boolean | undefined;
+// export type ClassDictionary = Record<string, any>;
+// export type ClassArray = ClassValue[];
+
+// export declare function clsx(...inputs: ClassValue[]): string;
+// export default clsx;
+
+export type TableName = 'select_history' | 'search_files';

+ 2 - 2
tsconfig.json

@@ -17,8 +17,8 @@
     "jsx": "react-jsx",
     "paths": {
       "@/*": ["./src/*"],
-      "clsx": [
-        "./src/types/clsx.d.ts"
+      "table": [
+        "./src/types/table.d.ts"
       ]
     }
   },