feat(browse):基本完成文件夹浏览器的功能。

This commit is contained in:
徐涛
2023-03-20 16:42:18 +08:00
parent 55de6f7993
commit 733dd48663
13 changed files with 557 additions and 20 deletions

8
src-tauri/.cargo/config Normal file
View File

@@ -0,0 +1,8 @@
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-nostartfiles"]
[target.'cfg(target_os = "windows")']
rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
[target.'cfg(target_os = "macos")']
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]

23
src-tauri/Cargo.lock generated
View File

@@ -326,11 +326,13 @@ dependencies = [
[[package]]
name = "comic_viewer"
version = "0.0.0"
version = "0.2.0"
dependencies = [
"anyhow",
"chrono",
"image",
"md-5",
"mountpoints",
"once_cell",
"serde",
"serde_json",
@@ -1590,6 +1592,15 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "md-5"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca"
dependencies = [
"digest",
]
[[package]]
name = "memchr"
version = "2.5.0"
@@ -1626,6 +1637,16 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "mountpoints"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3090e3ea5226b07cedd89a57c735d3ed0374a84236a85529a1d8bfec98eb8ec5"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "nanorand"
version = "0.7.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "comic_viewer"
version = "0.0.0"
version = "0.2.0"
description = "A Tauri App"
authors = ["you"]
license = ""
@@ -14,7 +14,7 @@ tauri-build = { version = "1.2", features = [] }
[dependencies]
once_cell = "1.17.0"
tauri = { version = "1.2", features = ["dialog-open", "fs-read-file", "protocol-all", "shell-open"] }
tauri = { version = "1.2", features = ["dialog-open", "fs-exists", "fs-read-dir", "fs-read-file", "protocol-asset", "shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4.23", features = ["serde"] }
@@ -25,6 +25,8 @@ serde_repr = "0.1.10"
tokio = { version = "1.23.1", features = ["full"] }
image = "0.24.5"
uuid = "1.3.0"
mountpoints = "0.2.1"
md-5 = "0.10.5"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem

View File

@@ -1,6 +1,12 @@
use std::path::Path;
use anyhow::anyhow;
use mountpoints::mountinfos;
use serde::Serialize;
use walkdir::WalkDir;
use tauri::Runtime;
use walkdir::{DirEntry, WalkDir};
use crate::utils;
#[derive(Debug, Clone, Serialize)]
pub struct FileItem {
@@ -11,16 +17,54 @@ pub struct FileItem {
pub width: u32,
}
#[derive(Debug, Clone, Serialize)]
pub struct DirItem {
pub id: String,
pub dirname: String,
pub path: String,
pub root: bool,
}
fn is_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with(".") || s.starts_with("$"))
.unwrap_or(false)
}
fn is_self<P: AsRef<Path>>(entry: &DirEntry, target: P) -> bool {
entry.path().eq(target.as_ref())
}
fn is_root(entry: &DirEntry) -> bool {
entry
.path()
.to_str()
.map(|s| s.eq_ignore_ascii_case("/"))
.unwrap_or(false)
}
#[tauri::command]
pub fn scan_directory(target: String) -> Result<Vec<FileItem>, String> {
pub async fn scan_directory(target: String) -> Result<Vec<FileItem>, String> {
let mut file_items = WalkDir::new(target)
.max_depth(1)
.into_iter()
.filter_entry(|entry| !is_hidden(entry))
.filter_map(|f| f.ok())
.filter(|f| f.path().is_file())
.map(|f| {
let (width, height) = image::image_dimensions(f.path())?;
let file_hash_id = f
.path()
.to_str()
.map(crate::utils::md5_hash)
.map(|hash| utils::uuid_from(hash.as_slice()))
.transpose()
.map_err(|e| anyhow!(e))?
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
Ok(FileItem {
id: uuid::Uuid::new_v4().to_string(),
id: file_hash_id,
filename: f
.path()
.file_name()
@@ -44,3 +88,101 @@ pub fn scan_directory(target: String) -> Result<Vec<FileItem>, String> {
Ok(file_items)
}
#[tauri::command]
pub async fn show_drives<R: Runtime>(
_app: tauri::AppHandle<R>,
_window: tauri::Window<R>,
) -> Result<Vec<DirItem>, String> {
let mut drives = vec![];
match mountinfos() {
Ok(mounts) => {
#[cfg(any(target_os = "macos", target_os = "linux"))]
let mounts = mounts
.iter()
.filter(|m| !m.path.starts_with("/System") && !m.path.starts_with("/dev"));
for mount in mounts {
let dirname = mount
.path
.as_path()
.file_name()
.unwrap_or_default()
.to_os_string()
.into_string()
.unwrap();
let dirname = if dirname.len() == 0 {
String::from("/")
} else {
dirname
};
let dir_hash_id = mount
.path
.to_str()
.map(crate::utils::md5_hash)
.map(|hash| utils::uuid_from(hash.as_slice()))
.transpose()?
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
drives.push(DirItem {
id: dir_hash_id,
dirname,
path: String::from(mount.path.to_str().unwrap_or_default()),
root: true,
})
}
}
Err(err) => return Err(format!("不能列出系统中已挂载的磁盘:{}", err)),
}
Ok(drives)
}
#[tauri::command]
pub async fn scan_for_child_dirs<R: Runtime>(
_app: tauri::AppHandle<R>,
_window: tauri::Window<R>,
target: String,
) -> Result<Vec<DirItem>, String> {
println!("请求扫描文件夹:{}", target);
let target = if target.eq_ignore_ascii_case("/") {
Path::new(r"/")
} else {
Path::new(&target)
};
let mut child_dirs = WalkDir::new(target)
.max_depth(1)
.into_iter()
.filter_entry(|entry| !is_hidden(entry) && !is_root(entry))
.filter_map(|d| d.ok())
.filter(|d| d.path().is_dir() && !is_self(d, target))
.map(|d| {
println!("扫描到的文件夹:{}", d.path().display());
let dir_hash_id = d
.path()
.to_str()
.map(crate::utils::md5_hash)
.map(|hash| utils::uuid_from(hash.as_slice()))
.transpose()
.map_err(|e| anyhow!(e))?
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
Ok(DirItem {
id: dir_hash_id,
dirname: d
.path()
.file_name()
.ok_or(anyhow!("不能获取到文件夹的名称。"))?
.to_owned()
.into_string()
.unwrap(),
path: d
.path()
.clone()
.to_str()
.ok_or(anyhow!("不能获取到文件夹路径。"))?
.to_string(),
root: false,
})
})
.collect::<Result<Vec<DirItem>, anyhow::Error>>()
.map_err(|e| e.to_string())?;
child_dirs.sort_by(|a, b| a.dirname.partial_cmp(&b.dirname).unwrap());
Ok(child_dirs)
}

View File

@@ -2,13 +2,18 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod commands;
mod utils;
use commands::AppHold;
use tauri::Manager;
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![commands::prelude::scan_directory])
.invoke_handler(tauri::generate_handler![
commands::prelude::scan_directory,
commands::prelude::show_drives,
commands::prelude::scan_for_child_dirs
])
.setup(|app| {
let main_window = app.get_window("main").unwrap();
#[cfg(debug_assertions)]

19
src-tauri/src/utils.rs Normal file
View File

@@ -0,0 +1,19 @@
use md5::{Digest, Md5};
use uuid::Builder;
pub fn md5_hash(source: &str) -> Vec<u8> {
let mut hasher = Md5::new();
hasher.update(source);
let hash = hasher.finalize();
hash.to_vec()
}
pub fn uuid_from(source: &[u8]) -> Result<String, String> {
let builder = Builder::from_md5_bytes(
source
.try_into()
.map_err(|_| String::from("源内容长度不足。"))?,
);
Ok(builder.into_uuid().to_string())
}

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "漫画阅读器",
"version": "0.1.0"
"version": "0.2.0"
},
"tauri": {
"allowlist": {
@@ -17,17 +17,23 @@
"all": false,
"copyFile": false,
"createDir": false,
"exists": false,
"readDir": false,
"exists": true,
"readDir": true,
"readFile": true,
"removeDir": false,
"removeFile": false,
"renameFile": false,
"scope": [],
"scope": [
"*"
],
"writeFile": false
},
"protocol": {
"all": true
"all": false,
"asset": true,
"assetScope": [
"*"
]
},
"shell": {
"all": false,
@@ -60,8 +66,7 @@
}
},
"security": {
"devCsp": "default-src 'self'; img-src 'self' asset: https://asset.localhost",
"csp": null
"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost"
},
"updater": {
"active": false