Skip to content

Commit

Permalink
Merge e66ef7e into c6c6566
Browse files Browse the repository at this point in the history
  • Loading branch information
conaticus authored Jun 29, 2023
2 parents c6c6566 + e66ef7e commit daf6898
Show file tree
Hide file tree
Showing 8 changed files with 500 additions and 244 deletions.
419 changes: 255 additions & 164 deletions src-tauri/Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fuzzy-matcher = "*"
rayon = "1.7.0"
dirs = "5.0.1"
notify = "6.0.1"
tokio = { version = "1.28.2", features = ["full"] }

[features]
# this feature is used for production builds or when `devPath` points to the filesystem
Expand Down
156 changes: 156 additions & 0 deletions src-tauri/src/filesystem/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use crate::{AppState, CachedPath, StateSafe, VolumeCache};
use std::{fs};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, MutexGuard};
use std::time::Duration;
use notify::{Event};
use notify::event::{CreateKind, ModifyKind, RenameMode};
use tokio::time;
use crate::filesystem::{DIRECTORY, FILE};

pub const CACHE_FILE_PATH: &str = "./system_cache.json";

/// Handles filesystem events, currently intended for cache invalidation.
pub struct FsEventHandler {
state_mux: StateSafe,
mountpoint: PathBuf,
}

impl FsEventHandler {
pub fn new(state_mux: StateSafe, mountpoint: PathBuf) -> Self {
Self { state_mux, mountpoint }
}

/// Gets the current volume from the cache
fn get_from_cache<'a>(&self, state: &'a mut AppState) -> &'a mut VolumeCache {
let mountpoint = self.mountpoint.to_string_lossy().to_string();

state.system_cache.get_mut(&mountpoint)
.unwrap_or_else(|| panic!("Failed to find mountpoint '{:?}' in cache.", self.mountpoint))
}

pub fn handle_create(&self, kind: CreateKind, path: &Path) {
let state = &mut self.state_mux.lock().unwrap();
let current_volume = self.get_from_cache(state);

let filename = path.file_name().unwrap().to_string_lossy().to_string();
let file_type = match kind {
CreateKind::File => FILE,
CreateKind::Folder => DIRECTORY,
_ => return, // Other options are weird lol
}.to_string();

let file_path = path.to_string_lossy().to_string();
current_volume.entry(filename).or_insert(vec![CachedPath{file_path, file_type}]);
}

pub fn handle_delete(&self, path: &Path) {
let state = &mut self.state_mux.lock().unwrap();
let current_volume = self.get_from_cache(state);

let filename = path.file_name().unwrap().to_string_lossy().to_string();
current_volume.remove(&filename);
}

/// Removes file from cache, when `handle_rename_to` is called a new file is added to the cache in place.
pub fn handle_rename_from(&mut self, old_path: &Path) {
let state = &mut self.state_mux.lock().unwrap();
let current_volume = self.get_from_cache(state);

let old_path_string= old_path.to_string_lossy().to_string();
let old_filename = old_path.file_name().unwrap().to_string_lossy().to_string();

let empty_vec = &mut Vec::new();
let cached_paths = current_volume.get_mut(&old_filename).unwrap_or(empty_vec);

// If there is only one item in the cached paths, this means it can only be the renamed file and therefore it should be removed from the hashmap
if cached_paths.len() <= 1 {
current_volume.remove(&old_filename);
return;
}

cached_paths.retain(|path| path.file_path != old_path_string);
}

/// Adds new file name & path to cache.
pub fn handle_rename_to(&self, new_path: &Path) {
let state = &mut self.state_mux.lock().unwrap();
let current_volume = self.get_from_cache(state);

let filename = new_path.file_name().unwrap().to_string_lossy().to_string();
let file_type = if new_path.is_dir() { DIRECTORY } else { FILE };

let path_string = new_path.to_string_lossy().to_string();
current_volume.entry(filename).or_insert(vec![CachedPath{file_path: path_string, file_type: String::from(file_type)}]);
}

pub fn handle_event(&mut self, event: Event) {
let paths = event.paths;

match event.kind {
notify::EventKind::Modify(modify_kind) => {
if modify_kind == ModifyKind::Name(RenameMode::From) {
self.handle_rename_from(&paths[0]);
} else if modify_kind == ModifyKind::Name(RenameMode::To) {
self.handle_rename_to(&paths[0]);
}
},
notify::EventKind::Create(kind) => self.handle_create(kind, &paths[0]),
notify::EventKind::Remove(_) => self.handle_delete(&paths[0]),
_ => (),
}
}
}

/// Starts a constant interval loop where the cache is updated every ~30 seconds.
pub fn run_cache_interval(state_mux: &StateSafe) {
let state_clone = Arc::clone(state_mux);

tokio::spawn(async move { // We use tokio spawn because async closures with std spawn is unstable
let mut interval = time::interval(Duration::from_secs(30));
interval.tick().await; // Wait 30 seconds before doing first re-cache

loop {
interval.tick().await;

let guard = &mut state_clone.lock().unwrap();
save_to_cache(guard);
}
});
}

/// This takes in an Arc<Mutex<AppState>> and calls `save_to_cache` after locking it.
pub fn save_system_cache(state_mux: &StateSafe) {
let state = &mut state_mux.lock().unwrap();
save_to_cache(state);
}

/// Gets the cache from the state (in memory), encodes and saves it to the cache file path.
/// This needs optimising.
fn save_to_cache(state: &mut MutexGuard<AppState>) {
let serialized_cache = serde_json::to_string(&state.system_cache).unwrap();

let mut file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(CACHE_FILE_PATH)
.unwrap();

file.write_all(serialized_cache.as_bytes()).unwrap();
}

/// Reads and decodes the cache file and stores it in memory for quick access.
/// Returns false if the cache was unable to deserialize.
pub fn load_system_cache(state_mux: &StateSafe) -> bool {
let state = &mut state_mux.lock().unwrap();
let file_contents = fs::read_to_string(CACHE_FILE_PATH).unwrap();

let deserialize_result = serde_json::from_str(&file_contents);
if let Ok(system_cache) = deserialize_result {
state.system_cache = system_cache;
return true;
}

false
}
37 changes: 37 additions & 0 deletions src-tauri/src/filesystem/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
pub mod cache;
pub mod volume;

use std::fs::{read_dir};
use crate::filesystem::volume::DirectoryChild;

pub const DIRECTORY: &str = "directory";
pub const FILE: &str = "file";

pub const fn bytes_to_gb(bytes: u64) -> u16 { (bytes / (1e+9 as u64)) as u16 }

/// Searches and returns the files in a given directory. This is not recursive.
#[tauri::command]
pub fn open_directory(path: String) -> Vec<DirectoryChild> {
let mut dir_children = Vec::new();

let Ok(directory) = read_dir(path) else {
return dir_children;
};

for entry in directory {
let entry = entry.unwrap();

let file_name = entry.file_name().to_str().unwrap().to_string();
let entry_is_file = entry.file_type().unwrap().is_file();
let entry = entry.path().to_str().unwrap().to_string();

if entry_is_file {
dir_children.push(DirectoryChild::File(file_name, entry));
continue;
}

dir_children.push(DirectoryChild::Directory(file_name, entry));
}

dir_children
}
103 changes: 36 additions & 67 deletions src-tauri/src/filesystem.rs → src-tauri/src/filesystem/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ use crate::{CachedPath, StateSafe};
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::fs::{read_dir, File};
use std::io::Write;
use std::path::PathBuf;
use std::{fs, thread};
use std::fs::{File};
use std::path::{PathBuf};
use std::sync::{Arc, Mutex};
use sysinfo::{Disk, DiskExt, System, SystemExt};
use tauri::State;
use tauri::{State};
use walkdir::WalkDir;

const CACHE_FILE_PATH: &str = "./system_cache.json";

const fn bytes_to_gb(bytes: u64) -> u16 {
(bytes / (1e+9 as u64)) as u16
}
use notify::{Watcher, RecursiveMode};
use tokio::task::block_in_place;
use crate::filesystem::{bytes_to_gb, DIRECTORY, FILE};
use crate::filesystem::cache::{CACHE_FILE_PATH, FsEventHandler, load_system_cache, run_cache_interval, save_system_cache};

#[derive(Serialize)]
pub struct Volume {
Expand Down Expand Up @@ -94,7 +91,7 @@ impl Volume {
true => "Local Volume",
false => volume_name,
}
.to_string()
.to_string()
};

let mountpoint = disk.mount_point().to_path_buf();
Expand Down Expand Up @@ -131,12 +128,8 @@ impl Volume {
let file_path = entry.path().to_string_lossy().to_string();

let walkdir_filetype = entry.file_type();
let file_type = if walkdir_filetype.is_dir() {
"directory"
} else {
"file"
}
.to_string();
let file_type = if walkdir_filetype.is_dir() { DIRECTORY } else { FILE }
.to_string();

let cache_guard = &mut system_cache.lock().unwrap();
cache_guard
Expand All @@ -148,6 +141,27 @@ impl Volume {
});
});
}

fn watch_changes(&self, state_mux: &StateSafe) {
let mut fs_event_manager = FsEventHandler::new(state_mux.clone(), self.mountpoint.clone());

let mut watcher = notify::recommended_watcher(move |res| {
match res {
Ok(event) => fs_event_manager.handle_event(event),
Err(e) => panic!("Failed to handle event: {:?}", e),
}
}).unwrap();

let path = self.mountpoint.clone();

thread::spawn(move || {
watcher.watch(&path, RecursiveMode::Recursive).unwrap();

block_in_place(|| loop {
thread::park();
})
});
}
}

#[derive(Serialize, Deserialize, Clone)]
Expand All @@ -156,26 +170,6 @@ pub enum DirectoryChild {
Directory(String, String),
}

/// Gets the cache from the state (in memory), encodes and saves it to the cache file path.
/// This needs optimising.
pub fn save_system_cache(state_mux: &StateSafe) {
let state = &mut state_mux.lock().unwrap();
let serialized_cache = serde_json::to_string(&state.system_cache).unwrap();

let mut file = fs::OpenOptions::new()
.write(true)
.open(CACHE_FILE_PATH)
.unwrap();
file.write_all(serialized_cache.as_bytes()).unwrap();
}

/// Reads and decodes the cache file and stores it in memory for quick access.
pub fn load_system_cache(state_mux: &StateSafe) {
let state = &mut state_mux.lock().unwrap();
let file_contents = fs::read_to_string(CACHE_FILE_PATH).unwrap();
state.system_cache = serde_json::from_str(&file_contents).unwrap();
}

/// Gets list of volumes and returns them.
/// If there is a cache stored on volume it is loaded.
/// If there is no cache stored on volume, one is created as well as stored in memory.
Expand All @@ -186,9 +180,9 @@ pub fn get_volumes(state_mux: State<StateSafe>) -> Vec<Volume> {
let mut sys = System::new_all();
sys.refresh_all();

let cache_exists = fs::metadata(CACHE_FILE_PATH).is_ok();
let mut cache_exists = fs::metadata(CACHE_FILE_PATH).is_ok();
if cache_exists {
load_system_cache(&state_mux);
cache_exists = load_system_cache(&state_mux);
} else {
File::create(CACHE_FILE_PATH).unwrap();
}
Expand All @@ -200,37 +194,12 @@ pub fn get_volumes(state_mux: State<StateSafe>) -> Vec<Volume> {
volume.create_cache(&state_mux);
}

volume.watch_changes(&state_mux);
volumes.push(volume);
}

save_system_cache(&state_mux);
run_cache_interval(&state_mux);

volumes
}

/// Searches and returns the files in a given directory. This is not recursive.
#[tauri::command]
pub fn open_directory(path: String) -> Vec<DirectoryChild> {
let mut dir_children = Vec::new();

let Ok(directory) = read_dir(path) else {
return dir_children;
};

for entry in directory {
let entry = entry.unwrap();

let file_name = entry.file_name().to_str().unwrap().to_string();
let entry_is_file = entry.file_type().unwrap().is_file();
let entry = entry.path().to_str().unwrap().to_string();

if entry_is_file {
dir_children.push(DirectoryChild::File(file_name, entry));
continue;
}

dir_children.push(DirectoryChild::Directory(file_name, entry));
}

dir_children
}
9 changes: 5 additions & 4 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
mod filesystem;
mod search;

use filesystem::{get_volumes, open_directory};
use filesystem::open_directory;
use filesystem::volume::get_volumes;
use search::search_directory;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand All @@ -25,7 +26,8 @@ pub struct AppState {

pub type StateSafe = Arc<Mutex<AppState>>;

fn main() {
#[tokio::main]
async fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
get_volumes,
Expand All @@ -35,5 +37,4 @@ fn main() {
.manage(Arc::new(Mutex::new(AppState::default())))
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

}
Loading

0 comments on commit daf6898

Please sign in to comment.