Get file scanning working & focus window on show

This commit is contained in:
Lncvrt committed 2026-04-14 22:48:39 -07:00
1 parent ca4555be90
commit 10caf9d56e
10 files changed
+192 -15

No files matched your search

+6
View File
@@ -12,7 +12,9 @@
"@fortawesome/react-fontawesome": "^3.3.0",
"@tailwindcss/vite": "4.2.2",
"@tauri-apps/api": "2.10.1",
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-opener": "2.5.3",
"@tauri-apps/plugin-shell": "~2",
"pretty-bytes": "^7.1.0",
"react": "19.2.5",
"react-dom": "19.2.5",
@@ -150,8 +152,12 @@
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="],
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw=="],
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="],
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.5", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
+2
View File
@@ -17,7 +17,9 @@
"@fortawesome/react-fontawesome": "^3.3.0",
"@tailwindcss/vite": "4.2.2",
"@tauri-apps/api": "2.10.1",
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-opener": "2.5.3",
"@tauri-apps/plugin-shell": "~2",
"pretty-bytes": "^7.1.0",
"react": "19.2.5",
"react-dom": "19.2.5",
+3
View File
@@ -16,6 +16,9 @@ tauri = { version = "2.10.3", features = ["macos-private-api"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
sysinfo = "0.38.4"
walkdir = "2.5.0"
tauri-plugin-shell = "2"
tauri-plugin-dialog = "2"
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
window-vibrancy = "0.7.1"
Binary file not shown.
Binary file not shown.
+6 -2
View File
@@ -2,8 +2,12 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"windows": [
"main"
],
"permissions": [
"core:default"
"core:default",
"shell:default",
"dialog:default"
]
}
+108 -8
View File
@@ -1,9 +1,35 @@
use serde::{Deserialize, Serialize};
use sysinfo::Disks;
use tauri::{WebviewUrl, WebviewWindowBuilder};
use tauri_plugin_shell::ShellExt;
#[cfg(target_os = "macos")]
use window_vibrancy::{NSVisualEffectMaterial, apply_vibrancy};
#[derive(Serialize)]
struct DiskInfo {
name: String,
mount_point: String,
total_space: u64,
available_space: u64,
is_removable: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FileInfo {
pub name: String,
pub path: String,
pub size: u64,
pub is_dir: bool,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DirScanResult {
pub items: Vec<FileInfo>,
pub total_size: u64,
pub error: Option<String>,
}
#[tauri::command]
fn list_disks() -> Vec<DiskInfo> {
Disks::new_with_refreshed_list()
@@ -22,19 +48,92 @@ fn list_disks() -> Vec<DiskInfo> {
.collect()
}
#[derive(serde::Serialize)]
struct DiskInfo {
name: String,
mount_point: String,
total_space: u64,
available_space: u64,
is_removable: bool,
#[tauri::command]
async fn get_file_info_from_dirs(
app: tauri::AppHandle,
path: String,
min_ratio: String,
) -> Result<DirScanResult, String> {
let cmd = app
.shell()
.sidecar("pdu")
.map_err(|e| e.to_string())?
.args([
"--json-output",
"--min-ratio",
&min_ratio,
"--max-depth",
"inf",
&path,
]);
let output = cmd.output().await.map_err(|e| e.to_string())?;
if !output.status.success() {
return Err(format!(
"pdu failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let json: serde_json::Value =
serde_json::from_str(&stdout).map_err(|e| format!("JSON parse failed: {}", e))?;
let mut items = Vec::new();
if let Some(tree) = json.get("tree") {
extract_files(tree, "", &mut items);
}
let total_size = items.iter().map(|i| i.size).sum();
Ok(DirScanResult {
items,
total_size,
error: None,
})
}
fn extract_files(node: &serde_json::Value, parent_path: &str, items: &mut Vec<FileInfo>) {
if let Some(name) = node.get("name").and_then(|v| v.as_str()) {
let size = node.get("size").and_then(|v| v.as_u64()).unwrap_or(0);
let children = node.get("children").and_then(|v| v.as_array());
let is_dir = children.as_ref().map(|c| !c.is_empty()).unwrap_or(false);
let path = if parent_path.is_empty() {
name.to_string()
} else {
format!("{}/{}", parent_path, name)
};
items.push(FileInfo {
name: name.to_string(),
path: path.clone(),
size,
is_dir,
});
if let Some(children_array) = children {
if !children_array.is_empty() {
for child in children_array {
extract_files(child, &path, items);
}
}
}
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![list_disks])
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![
list_disks,
get_file_info_from_dirs
])
.setup(|app| {
#[allow(unused)]
let mut transparency = false;
@@ -82,6 +181,7 @@ pub fn run() {
}
window.show().unwrap();
window.set_focus().unwrap();
Ok(())
})
+2 -1
View File
@@ -29,6 +29,7 @@
],
"macOS": {
"minimumSystemVersion": "13.0"
}
},
"externalBin": ["binaries/pdu"]
}
}
+43 -4
View File
@@ -5,6 +5,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faHardDrive } from '@fortawesome/free-solid-svg-icons'
import { v5 as uuidv5 } from 'uuid'
import prettyBytes from 'pretty-bytes'
import { FileInfo, scanDir } from './scanlib'
import { message, open } from '@tauri-apps/plugin-dialog'
interface Disk {
name: string
@@ -16,6 +18,9 @@ interface Disk {
export default function App () {
const [disks, setDisks] = useState<Array<Disk>>([])
const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
const [scanning, setScanning] = useState<boolean>(false)
const [files, setFiles] = useState<FileInfo[]>([])
useEffect(() => {
;(async () => {
@@ -23,8 +28,26 @@ export default function App () {
})()
}, [])
return (
<div>
const handleFolderSelect = async (path: string) => {
setSelectedFolder(path)
setScanning(true)
console.log('scanning', path)
const scanResult = await scanDir(path)
console.log('done', scanResult)
if (scanResult.error) {
await message(`Error: ${scanResult.error}`, {
title: 'Tauri',
kind: 'error'
})
setSelectedFolder(null)
} else {
setFiles(scanResult.items)
}
setScanning(false)
}
return !selectedFolder ? (
<>
<p className='text-center text-2xl mt-2'>Disks</p>
<div className='flex flex-col gap-2 p-2'>
{disks.map(d => {
@@ -34,6 +57,7 @@ export default function App () {
<div
key={uuidv5(JSON.stringify(d), uuidv5.DNS)}
className='w-full bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 rounded-lg px-3 py-1.5 flex flex-row gap-4 items-center relative cursor-pointer transition-colors'
onClick={() => handleFolderSelect(d.mount_point)}
>
<FontAwesomeIcon
icon={faHardDrive}
@@ -69,10 +93,25 @@ export default function App () {
</div>
)
})}
<p className='w-full bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 rounded-lg px-3 py-1.5 cursor-pointer transition-colors'>
<p
className='w-full bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 rounded-lg px-3 py-1.5 cursor-pointer transition-colors'
onClick={async () => {
const folder = await open({
multiple: false,
directory: true
})
if (!folder) return
handleFolderSelect(folder)
}}
>
Or select a folder to scan
</p>
</div>
</div>
</>
) : (
<>
<p>{selectedFolder}</p>
<p>{JSON.stringify(files)}</p>
</>
)
}
+22
View File
@@ -0,0 +1,22 @@
import { invoke } from '@tauri-apps/api/core'
export interface FileInfo {
name: string
path: string
size: number
is_dir: boolean
}
export interface DirScanResult {
items: FileInfo[]
total_size: number
error: string | null
}
export const scanDir = async (path: string) => {
const result: DirScanResult = await invoke('get_file_info_from_dirs', {
path,
minRatio: '0'
})
return result
}