Get file scanning working & focus window on show
This commit is contained in:
10 files changed
+193
-16
No files matched your search
@@ -12,7 +12,9 @@
|
|||||||
"@fortawesome/react-fontawesome": "^3.3.0",
|
"@fortawesome/react-fontawesome": "^3.3.0",
|
||||||
"@tailwindcss/vite": "4.2.2",
|
"@tailwindcss/vite": "4.2.2",
|
||||||
"@tauri-apps/api": "2.10.1",
|
"@tauri-apps/api": "2.10.1",
|
||||||
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-opener": "2.5.3",
|
"@tauri-apps/plugin-opener": "2.5.3",
|
||||||
|
"@tauri-apps/plugin-shell": "~2",
|
||||||
"pretty-bytes": "^7.1.0",
|
"pretty-bytes": "^7.1.0",
|
||||||
"react": "19.2.5",
|
"react": "19.2.5",
|
||||||
"react-dom": "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/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-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=="],
|
"@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=="],
|
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
"@fortawesome/react-fontawesome": "^3.3.0",
|
"@fortawesome/react-fontawesome": "^3.3.0",
|
||||||
"@tailwindcss/vite": "4.2.2",
|
"@tailwindcss/vite": "4.2.2",
|
||||||
"@tauri-apps/api": "2.10.1",
|
"@tauri-apps/api": "2.10.1",
|
||||||
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-opener": "2.5.3",
|
"@tauri-apps/plugin-opener": "2.5.3",
|
||||||
|
"@tauri-apps/plugin-shell": "~2",
|
||||||
"pretty-bytes": "^7.1.0",
|
"pretty-bytes": "^7.1.0",
|
||||||
"react": "19.2.5",
|
"react": "19.2.5",
|
||||||
"react-dom": "19.2.5",
|
"react-dom": "19.2.5",
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ tauri = { version = "2.10.3", features = ["macos-private-api"] }
|
|||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
sysinfo = "0.38.4"
|
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]
|
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
|
||||||
window-vibrancy = "0.7.1"
|
window-vibrancy = "0.7.1"
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -2,8 +2,12 @@
|
|||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Capability for the main window",
|
"description": "Capability for the main window",
|
||||||
"windows": ["main"],
|
"windows": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default"
|
"core:default",
|
||||||
|
"shell:default",
|
||||||
|
"dialog:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+108
-8
@@ -1,9 +1,35 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use sysinfo::Disks;
|
use sysinfo::Disks;
|
||||||
use tauri::{WebviewUrl, WebviewWindowBuilder};
|
use tauri::{WebviewUrl, WebviewWindowBuilder};
|
||||||
|
|
||||||
|
use tauri_plugin_shell::ShellExt;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use window_vibrancy::{NSVisualEffectMaterial, apply_vibrancy};
|
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]
|
#[tauri::command]
|
||||||
fn list_disks() -> Vec<DiskInfo> {
|
fn list_disks() -> Vec<DiskInfo> {
|
||||||
Disks::new_with_refreshed_list()
|
Disks::new_with_refreshed_list()
|
||||||
@@ -22,19 +48,92 @@ fn list_disks() -> Vec<DiskInfo> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[tauri::command]
|
||||||
struct DiskInfo {
|
async fn get_file_info_from_dirs(
|
||||||
name: String,
|
app: tauri::AppHandle,
|
||||||
mount_point: String,
|
path: String,
|
||||||
total_space: u64,
|
min_ratio: String,
|
||||||
available_space: u64,
|
) -> Result<DirScanResult, String> {
|
||||||
is_removable: bool,
|
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)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
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| {
|
.setup(|app| {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
let mut transparency = false;
|
let mut transparency = false;
|
||||||
@@ -82,6 +181,7 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.show().unwrap();
|
window.show().unwrap();
|
||||||
|
window.set_focus().unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
],
|
],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"minimumSystemVersion": "13.0"
|
"minimumSystemVersion": "13.0"
|
||||||
}
|
},
|
||||||
|
"externalBin": ["binaries/pdu"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+43
-4
@@ -5,6 +5,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|||||||
import { faHardDrive } from '@fortawesome/free-solid-svg-icons'
|
import { faHardDrive } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { v5 as uuidv5 } from 'uuid'
|
import { v5 as uuidv5 } from 'uuid'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
|
import { FileInfo, scanDir } from './scanlib'
|
||||||
|
import { message, open } from '@tauri-apps/plugin-dialog'
|
||||||
|
|
||||||
interface Disk {
|
interface Disk {
|
||||||
name: string
|
name: string
|
||||||
@@ -16,6 +18,9 @@ interface Disk {
|
|||||||
|
|
||||||
export default function App () {
|
export default function App () {
|
||||||
const [disks, setDisks] = useState<Array<Disk>>([])
|
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(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
@@ -23,8 +28,26 @@ export default function App () {
|
|||||||
})()
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
const handleFolderSelect = async (path: string) => {
|
||||||
<div>
|
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>
|
<p className='text-center text-2xl mt-2'>Disks</p>
|
||||||
<div className='flex flex-col gap-2 p-2'>
|
<div className='flex flex-col gap-2 p-2'>
|
||||||
{disks.map(d => {
|
{disks.map(d => {
|
||||||
@@ -34,6 +57,7 @@ export default function App () {
|
|||||||
<div
|
<div
|
||||||
key={uuidv5(JSON.stringify(d), uuidv5.DNS)}
|
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'
|
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
|
<FontAwesomeIcon
|
||||||
icon={faHardDrive}
|
icon={faHardDrive}
|
||||||
@@ -69,10 +93,25 @@ export default function App () {
|
|||||||
</div>
|
</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
|
Or select a folder to scan
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>{selectedFolder}</p>
|
||||||
|
<p>{JSON.stringify(files)}</p>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in new issue
Block a user