From 5e4e1833c5976891388efb5b07f5c16cec080905 Mon Sep 17 00:00:00 2001 From: Jisu-Woniu <31986081+Jisu-Woniu@users.noreply.github.com> Date: Mon, 20 Nov 2023 01:14:34 +0800 Subject: [PATCH] feat: :sparkles: Complex scene of FileSelector. --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/error.rs | 46 +++++++++++++++++++++++++++ src-tauri/src/fs.rs | 55 +++++++++++++++++++++++++++++++++ src-tauri/src/keygen.rs | 13 ++++++++ src-tauri/src/main.rs | 8 ++++- src/App.vue | 14 ++++----- src/components/FileSelector.vue | 47 +++++++++++++++++++--------- src/views/KeygenView.vue | 18 +++++++++-- src/views/SignView.vue | 5 ++- src/views/ValidateView.vue | 5 ++- 11 files changed, 187 insertions(+), 26 deletions(-) create mode 100644 src-tauri/src/error.rs create mode 100644 src-tauri/src/fs.rs create mode 100644 src-tauri/src/keygen.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 6b056f4..92eaefc 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -794,6 +794,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tokio", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 09ec2d5..2f7a25f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -25,6 +25,7 @@ tauri-build = { version = "1.5.0", features = [] } anyhow = { version = "1.0.75", features = ["backtrace"] } serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" +tokio = { version = "1.34.0", features = ["fs", "macros", "rt", "rt-multi-thread"] } [dependencies.digital-signature-crypto] version = "0.1.0" diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs new file mode 100644 index 0000000..6e436f0 --- /dev/null +++ b/src-tauri/src/error.rs @@ -0,0 +1,46 @@ +use std::{ + fmt::{self, Display, Formatter}, + io, +}; + +use serde::Serialize; + +#[derive(Debug)] +pub struct Error(anyhow::Error); + +pub type Result = std::result::Result; + +impl From for Error { + fn from(value: anyhow::Error) -> Self { + Self(value) + } +} + +impl From for Error { + fn from(value: digital_signature_crypto::error::Error) -> Self { + Self(anyhow::Error::from(value)) + } +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self(anyhow::Error::from(value)) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for Error {} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::prelude::v1::Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.0.to_string()) + } +} diff --git a/src-tauri/src/fs.rs b/src-tauri/src/fs.rs new file mode 100644 index 0000000..3a78692 --- /dev/null +++ b/src-tauri/src/fs.rs @@ -0,0 +1,55 @@ +use std::path::Path; + +use serde::Serialize; +use tokio::fs; + +use crate::error::Result; + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum FileType { + File = 0, + Dir = 1, + Other = 2, + Inexist = 3, +} + +impl Serialize for FileType { + fn serialize(&self, serializer: S) -> std::prelude::v1::Result + where + S: serde::Serializer, + { + serializer.serialize_u8(*self as u8) + } +} + +#[tauri::command] +pub async fn file_type(path: &Path) -> Result { + Ok(match fs::metadata(path).await { + Ok(metadata) => { + if metadata.is_dir() { + FileType::Dir + } else if metadata.is_file() { + FileType::File + } else { + FileType::Other + } + } + Err(_) => FileType::Inexist, + }) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::file_type; + + #[tokio::test] + async fn test() { + println!("{:?}", file_type(&PathBuf::from("/home/")).await); + println!("{:?}", file_type(&PathBuf::from("/etc/fstab")).await); + println!("{:?}", file_type(&PathBuf::from("/home/inexist")).await); + println!("{:?}", file_type(&PathBuf::from("/bin/sh")).await); + } +} diff --git a/src-tauri/src/keygen.rs b/src-tauri/src/keygen.rs new file mode 100644 index 0000000..4e17b70 --- /dev/null +++ b/src-tauri/src/keygen.rs @@ -0,0 +1,13 @@ +//! Used for key generation. + +use std::path::Path; + +use digital_signature_crypto::keygen::write_key_pair; + +use crate::error::Result; + +#[tauri::command] +pub async fn generate_key_pair(name: &str, email: &str, path: &Path) -> Result<()> { + write_key_pair(name, email, path).await?; + Ok(()) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e7db413..ba7fc42 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,9 +3,15 @@ // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command +use crate::{fs::file_type, keygen::generate_key_pair}; + +mod error; +mod fs; +mod keygen; + fn main() -> anyhow::Result<()> { tauri::Builder::default() - .invoke_handler(tauri::generate_handler![]) + .invoke_handler(tauri::generate_handler![generate_key_pair, file_type]) .run(tauri::generate_context!())?; Ok(()) } diff --git a/src/App.vue b/src/App.vue index ad9d0f4..c94caf2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,10 +8,10 @@ import { VWindowItem, } from "vuetify/components"; import { mdiFileKey, mdiFileCheck, mdiKeyChain } from "@mdi/js"; -import SignView from "./views/SignView.vue"; -import ValidateView from "./views/ValidateView.vue"; -import KeygenView from "./views/KeygenView.vue"; -import ColorSwitcher from "./components/ColorSwitcher.vue"; +import SignView from "@/views/SignView.vue"; +import ValidateView from "@/views/ValidateView.vue"; +import KeygenView from "@/views/KeygenView.vue"; +import ColorSwitcher from "@/components/ColorSwitcher.vue"; import { useSessionStorage } from "@vueuse/core"; const enum Tab { @@ -40,13 +40,13 @@ const tab = useSessionStorage("tab", Tab.sign); - + - + - + diff --git a/src/components/FileSelector.vue b/src/components/FileSelector.vue index 0253e8b..529b479 100644 --- a/src/components/FileSelector.vue +++ b/src/components/FileSelector.vue @@ -7,46 +7,63 @@ import { VBtn, VCard, VDialog, VIcon } from "vuetify/components"; import UploadFile from "~icons/ic/twotone-upload-file"; import FolderOpen from "~icons/ic/twotone-folder-open"; +import Reject from "~icons/ic/twotone-highlight-off"; import { useVModel } from "@vueuse/core"; +import { invoke } from "@tauri-apps/api/tauri"; +import { onActivated } from "vue"; +import { onDeactivated } from "vue"; const props = defineProps<{ modelValue: string | undefined; - directory: boolean; + directory?: boolean; }>(); const emits = defineEmits<{ (event: "update:modelValue", value: string | undefined): void; }>(); -const files = useVModel(props, "modelValue", emits); +const file = useVModel(props, "modelValue", emits); const SelectFile = async () => { const selected = (await open({ multiple: false, directory: props.directory, })) as string | null; - files.value = selected ?? undefined; + file.value = selected ?? undefined; }; const hover = ref(false); +const hover_accept = ref(false); let listeners: UnlistenFn[]; +const enum FileType { + file = 0, + dir = 1, + other = 2, + inexist = 3, +} + +const checkFileType = async (path: string) => { + const fileType = await invoke("file_type", { path }); + return fileType === (props.directory ? FileType.dir : FileType.file); +}; + onMounted(async () => { listeners = await Promise.all([ - listen(TauriEvent.WINDOW_FILE_DROP_HOVER, () => { + listen(TauriEvent.WINDOW_FILE_DROP_HOVER, async (e) => { hover.value = true; + hover_accept.value = + e.payload.length == 1 && (await checkFileType(e.payload[0])); }), listen(TauriEvent.WINDOW_FILE_DROP, async (e) => { - console.log(e.payload); - if (e.payload.length != 1) { + console.log("DROP!!"); + if (e.payload.length != 1 || !(await checkFileType(e.payload[0]))) { await message( - "You can only drop one file here.\n" + - "You have selected:\n" + - e.payload.join("\n"), + props.directory ? "请拖放一个文件夹到此处" : "请拖放一个文件到此处", ); - } else files.value = e.payload[0]; + } else file.value = e.payload[0]; hover.value = false; }), listen(TauriEvent.WINDOW_FILE_DROP_CANCELLED, () => { @@ -58,19 +75,21 @@ onMounted(async () => { ]); }); -onUnmounted(() => { - listeners.forEach((unlisten) => unlisten()); -}); +onUnmounted(() => listeners.forEach((unlisten) => unlisten()));