diff --git a/docs/contributors/tauri.md b/docs/contributors/tauri.md index d32ad0a7..54882e3c 100644 --- a/docs/contributors/tauri.md +++ b/docs/contributors/tauri.md @@ -11,12 +11,17 @@ #### `path` - `app_cache_dir()`: + - Linux: `~/.cache/com.clamav-desktop.app` - Windows: `C:\Users\%USER%\AppData\Local\com.clamav-desktop.app` - `app_config_dir()`: + - Linux: `~/.config/com.clamav-desktop.app` - Windows: `C:\Users\%USER%\AppData\Roaming\com.clamav-desktop.app` - `app_data_dir()`: + - Linux: `~/.local/share/com.clamav-desktop.app` - Windows: `C:\Users\%USER%\AppData\Roaming\com.clamav-desktop.app` - `app_local_data_dir()`: + - Linux: `~/.local/share/com.clamav-desktop.app` - Windows: `C:\Users\%USER%\AppData\Local\com.clamav-desktop.app` - `app_log_dir()`: + - Linux: `~/.config/com.clamav-desktop.app/logs` - Windows: `C:\Users\%USER%\AppData\Roaming\com.clamav-desktop.app\logs` diff --git a/src-tauri/src/libs/cli.rs b/src-tauri/src/libs/cli.rs index 027b603a..d65295d9 100644 --- a/src-tauri/src/libs/cli.rs +++ b/src-tauri/src/libs/cli.rs @@ -7,6 +7,7 @@ use tokio::process::{Child, Command}; // } #[cfg(not(tarpaulin_include))] +#[allow(dead_code)] pub async fn run(binary_path: String, args: Vec) -> Child { Command::new(binary_path) .args(args) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index db63f824..af7ffb10 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -47,17 +47,17 @@ fn main() { let config_binding = app_handle.config(); let config = config_binding.as_ref(); - // let app_cache_dir = api::path::app_cache_dir(config).expect("Could not get cache directory."); - // println!("Cache directory: {:?}", app_cache_dir); - // let app_config_dir = api::path::app_config_dir(config).expect("Could not get config directory."); - // println!("Config directory: {:?}", app_config_dir); - // let app_data_dir = api::path::app_data_dir(config).expect("Could not get data directory."); - // println!("Data directory: {:?}", app_data_dir); - // let app_local_data_dir = - // api::path::app_local_data_dir(config).expect("Could not get local data directory."); - // println!("Local data directory: {:?}", app_local_data_dir); - // let app_log_dir = api::path::app_log_dir(config).expect("Could not get log directory."); - // println!("Log directory: {:?}", app_log_dir); + let app_cache_dir = api::path::app_cache_dir(config).expect("Could not get cache directory."); + println!("Cache directory: {:?}", app_cache_dir); + let app_config_dir = api::path::app_config_dir(config).expect("Could not get config directory."); + println!("Config directory: {:?}", app_config_dir); + let app_data_dir = api::path::app_data_dir(config).expect("Could not get data directory."); + println!("Data directory: {:?}", app_data_dir); + let app_local_data_dir = + api::path::app_local_data_dir(config).expect("Could not get local data directory."); + println!("Local data directory: {:?}", app_local_data_dir); + let app_log_dir = api::path::app_log_dir(config).expect("Could not get log directory."); + println!("Log directory: {:?}", app_log_dir); let mut config_directory_path = globals::CONFIG_DIRECTORY_PATH.lock().await; *config_directory_path = diff --git a/src-tauri/src/modules/scanner/commands.rs b/src-tauri/src/modules/scanner/commands.rs index cb070a85..6b0ee41b 100644 --- a/src-tauri/src/modules/scanner/commands.rs +++ b/src-tauri/src/modules/scanner/commands.rs @@ -44,6 +44,7 @@ pub async fn start_scanner(app_handle: AppHandle, paths: Vec) -> Result< let args: Vec = vec![ // "clamscan".to_string(), "-rv".to_string(), + format!("--database={}", "~/.local/share/com.clamav-desktop.app").to_string(), "--follow-dir-symlinks=0".to_string(), "--follow-file-symlinks=0".to_string(), // "--gen-json=yes".to_string(), @@ -52,6 +53,7 @@ pub async fn start_scanner(app_handle: AppHandle, paths: Vec) -> Result< .into_iter() .chain(paths.to_owned().into_iter()) .collect(); + println!("{:?}", args); state::set_public_state( &app_handle, @@ -88,22 +90,6 @@ pub async fn start_scanner(app_handle: AppHandle, paths: Vec) -> Result< .spawn() .expect("Failed to spawn sidecar"); - // tauri::async_runtime::spawn(async move { - // // read events such as stdout - // while let Some(event) = rx.recv().await { - // if let CommandEvent::Stdout(line) = event { - // window - // .emit("message", Some(format!("'{}'", line))) - // .expect("failed to emit event"); - // // write to stdin - // child.write("message from Rust\n".as_bytes()).unwrap(); - // } - // } - // }); - - // let child = libs::cli::run(String::from("clamscan"), args).await; - // child.pid(); - // Attach child ID to private state let mut child_id_mutex_guard = app_handle .state::() @@ -125,17 +111,34 @@ pub async fn start_scanner(app_handle: AppHandle, paths: Vec) -> Result< .await; *child_mutex_guard = Some(child); - let app_handle_clone_for_log = app_handle.clone(); - // tokio::spawn(utils::handle_scanner_output(app_handle_clone_for_log, total_file_count)); + let app_handle_traveller = app_handle.clone(); + // tokio::spawn(utils::handle_scanner_output(app_handle_traveller, total_file_count)); tauri::async_runtime::spawn(async move { let mut file_index: usize = 0; while let Some(event) = rx.recv().await { - if let CommandEvent::Stdout(line) = event { - debug!("handle_scanner_output()", "Output: `{}`.", line); + if let CommandEvent::Stdout(ref line) = event { + #[cfg(debug_assertions)] + { + println!("[CommandEvent::Stdout] {}", line); + } + + if utils::filter_log(line.to_owned()) { + let next_public_state = utils::get_status_from_log(line.to_owned(), file_index, total_file_count); + state::set_public_state(&app_handle_traveller, next_public_state).await; + + file_index += 1; + } + } + + if let CommandEvent::Stderr(ref line) = event { + #[cfg(debug_assertions)] + { + println!("[CommandEvent::Stderr] {}", line); + } if utils::filter_log(line.to_owned()) { let next_public_state = utils::get_status_from_log(line.to_owned(), file_index, total_file_count); - state::set_public_state(&app_handle_clone_for_log, next_public_state).await; + state::set_public_state(&app_handle_traveller, next_public_state).await; file_index += 1; } diff --git a/src-tauri/src/modules/scanner/state.rs b/src-tauri/src/modules/scanner/state.rs index 550ac48e..332eb3f3 100644 --- a/src-tauri/src/modules/scanner/state.rs +++ b/src-tauri/src/modules/scanner/state.rs @@ -26,6 +26,7 @@ pub struct ScannerPublicState { pub step: ScannerStatusStep, } impl ScannerPublicState { + #[allow(dead_code)] pub fn patch(&mut self, patch: ScannerPublicStatePatch) { if let Some(current_path) = patch.current_path { self.current_path = current_path; @@ -76,6 +77,7 @@ pub async fn broadcast_state(app_handle: &AppHandle) { .unwrap(); } +#[allow(dead_code)] pub async fn patch_public_state(app_handle: &AppHandle, patch: ScannerPublicStatePatch) { let mut public_state_mutex_guard = app_handle.state::().inner().0.public.lock().await; diff --git a/src/App.tsx b/src/App.tsx index dbd43ce3..6ddebd60 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -import { useCoreStateHub } from '@libs/CoreStateHub/useCoreStateHub' import { Dashboard } from '@screens/Dashboard' import { LoaderScreen } from '@screens/Loader' import { Scanner } from '@screens/Scanner' @@ -12,8 +11,6 @@ export function App() { const [isCoreReady, setIsCoreReady] = useState(true) const [screen, setScreen] = useState(Screen.Dashboard) - useCoreStateHub(isCoreReady) - const disableIsLoading = () => { setIsCoreReady(false) } diff --git a/src/core/Scanner/index.ts b/src/core/Scanner/index.ts new file mode 100644 index 00000000..1dd8643b --- /dev/null +++ b/src/core/Scanner/index.ts @@ -0,0 +1,11 @@ +import { invoke } from '@tauri-apps/api' + +export const ScannerModule = { + startScanner: async (paths: string[]): Promise => { + return await invoke('start_scanner', { paths }) + }, + + stopScanner: async (): Promise => { + return await invoke('stop_scanner') + }, +} diff --git a/src/core/Scanner/types.ts b/src/core/Scanner/types.ts index ebe961bd..e2d711f9 100644 --- a/src/core/Scanner/types.ts +++ b/src/core/Scanner/types.ts @@ -1,8 +1,22 @@ -import type { Core } from '../types' - export namespace Scanner { export interface State { - currently_scanned_file_path: string | undefined - module_status: Core.ModuleStatus + current_path: string | null + progress: number | null + step: ScannerStatusStep + } + + export enum ScannerStatusStep { + /** Counting the files to scan. */ + Counting = 'Counting', + /** Default step (= waiting for a new job). */ + Idle = 'Idle', + /** Listing the files to scan. */ + Listing = 'Listing', + /** Scanning the files. */ + Running = 'Running', + /** Starting (= has called `clamscan` CLI command). */ + Starting = 'Starting', + /** Stopping (= has called `clamscan` CLI command). */ + Stopping = 'Stopping', } } diff --git a/src/libs/CoreStateHub/index.ts b/src/libs/CoreStateHub/index.ts index 5b2e932a..d69157e1 100644 --- a/src/libs/CoreStateHub/index.ts +++ b/src/libs/CoreStateHub/index.ts @@ -1,19 +1,50 @@ -import { listen } from '@tauri-apps/api/event' +import { listen, type Event } from '@tauri-apps/api/event' import type { Scanner } from '../../core/Scanner/types' +import { invoke } from '@tauri-apps/api' +import type { CoreStateListener, CoreStateStore, CoreStateStoreKey } from './types' /** * Listen to all Core states changes and keep track of them. */ export class CoreStateHub { - #scannerState: Scanner.State | undefined + #store: CoreStateStore - get scannerState() { - return this.#scannerState + constructor() { + this.#store = { + scanner: { + listeners: [], + state: undefined, + }, + } + + this.#init() + } + + get store() { + return this.#store + } + + addListener(key: K, callback: CoreStateListener) { + this.#store[key].listeners.push(callback) + } + + removeListener(key: K, callback: CoreStateListener) { + this.#store[key].listeners = this.#store[key].listeners.filter(listener => listener !== callback) + } + + #init() { + listen('scanner:state', this.#initScannerState.bind(this)) + + invoke('get_scanner_state') } - init() { - listen('scanner:state', event => { - this.#scannerState = event.payload - }) + #initScannerState(event: Event) { + this.#store.scanner.state = event.payload + + for (const listener of this.#store.scanner.listeners) { + listener(event.payload) + } } } + +export const coreStateHub = new CoreStateHub() diff --git a/src/libs/CoreStateHub/types.ts b/src/libs/CoreStateHub/types.ts new file mode 100644 index 00000000..08117af9 --- /dev/null +++ b/src/libs/CoreStateHub/types.ts @@ -0,0 +1,12 @@ +import type { Scanner } from '@core/Scanner/types' + +export interface CoreStateStore { + scanner: { + listeners: Array<(nextState: Scanner.State) => void> + state: Scanner.State | undefined + } +} + +export type CoreStateStoreKey = keyof CoreStateStore + +export type CoreStateListener = (nextState: CoreStateStore[K]['state']) => void diff --git a/src/libs/CoreStateHub/useCoreStateHub.ts b/src/libs/CoreStateHub/useCoreStateHub.ts index 1f46f491..83e73f72 100644 --- a/src/libs/CoreStateHub/useCoreStateHub.ts +++ b/src/libs/CoreStateHub/useCoreStateHub.ts @@ -1,18 +1,16 @@ -import { useEffect, useRef } from 'react' -import { CoreStateHub } from '.' +import { useEffect, useState } from 'react' +import { coreStateHub } from '.' +import type { CoreStateStore, CoreStateStoreKey } from './types' /** * Hook to initialize the CoreStateHub once the Core is ready. */ -export function useCoreStateHub(isCoreReady: boolean): void { - const coreStateHubRef = useRef(undefined) +export function useCoreStateHub(key: K): CoreStateStore[K]['state'] { + const [state, setState] = useState(undefined) useEffect(() => { - if (!isCoreReady || coreStateHubRef.current) { - return - } + coreStateHub.addListener(key, setState) + }, [key]) - coreStateHubRef.current = new CoreStateHub() - coreStateHubRef.current.init() - }, [isCoreReady]) + return state } diff --git a/src/screens/Dashboard/index.tsx b/src/screens/Dashboard/index.tsx index 1b1e9c2e..540dd318 100644 --- a/src/screens/Dashboard/index.tsx +++ b/src/screens/Dashboard/index.tsx @@ -24,7 +24,7 @@ export function Dashboard() { }, []) useEffect(() => { - invoke('get_dashboard_state') + // invoke('get_dashboard_state') listen('dashboard:state', event => { setState(event.payload) diff --git a/src/screens/Scanner/Component.tsx b/src/screens/Scanner/Component.tsx index 2f823155..5aca975b 100644 --- a/src/screens/Scanner/Component.tsx +++ b/src/screens/Scanner/Component.tsx @@ -2,8 +2,7 @@ import { Card } from '@components/Card' import type { CardAction } from '@components/Card/types' import { FileExplorer } from '@components/FileExplorer' import type { FileManager } from '@core/FileManager/types' -import type { Scanner } from '@core/Scanner/types' -import { Core } from '@core/types' +import { Scanner } from '@core/Scanner/types' import { ScanningSpinner } from '@elements/ScanningSpinner' import { ScreenBox } from '@layouts/ScreenBox' import { MdClose, MdVerifiedUser } from 'react-icons/md' @@ -28,7 +27,10 @@ export function ScannerScreenComponent({ onScanStop, scannerState, }: ScannerScreenComponentProps) { - if (scannerState?.module_status === Core.ModuleStatus.Running) { + if ( + scannerState && + [Scanner.ScannerStatusStep.Counting, Scanner.ScannerStatusStep.Running].includes(scannerState.step) + ) { return ( @@ -37,7 +39,7 @@ export function ScannerScreenComponent({ Scanning files... - {scannerState.currently_scanned_file_path} + {scannerState.current_path} ) } diff --git a/src/screens/Scanner/index.tsx b/src/screens/Scanner/index.tsx index 6aac07ad..6c5dc3e1 100644 --- a/src/screens/Scanner/index.tsx +++ b/src/screens/Scanner/index.tsx @@ -1,10 +1,13 @@ import { FileManagerModule } from '@core/FileManager' import type { FileManager } from '@core/FileManager/types' -import { noop } from '@utils/noop' import { useCallback, useEffect, useState } from 'react' import { ScannerScreenComponent } from './Component' +import { ScannerModule } from '@core/Scanner' +import { useCoreStateHub } from '@libs/CoreStateHub/useCoreStateHub' export function Scanner() { + const scannerState = useCoreStateHub('scanner') + const [fileExplorerRootFilePaths, setFileExplorerRootFilePaths] = useState( undefined, ) @@ -20,6 +23,14 @@ export function Scanner() { setFileExplorerRootFilePaths(coreFilePaths) }, [getDirectoryFilePaths]) + const startScanner = useCallback(() => { + ScannerModule.startScanner(fileExplorerSelectedPaths) + }, [fileExplorerSelectedPaths]) + + const stopScanner = useCallback(() => { + ScannerModule.stopScanner() + }, []) + useEffect(() => { initialize() }, [initialize]) @@ -30,9 +41,9 @@ export function Scanner() { fileExplorerRootPaths={fileExplorerRootFilePaths} onFileExporerChange={setFileExplorerSelectedPaths} onFileExporerExpand={getDirectoryFilePaths} - onScanStart={noop} - onScanStop={noop} - scannerState={undefined} + onScanStart={startScanner} + onScanStop={stopScanner} + scannerState={scannerState} /> ) }