1+ use crate :: { AppState , CachedPath , StateSafe , VolumeCache } ;
2+ use std:: { fs} ;
3+ use std:: io:: Write ;
4+ use std:: path:: { Path , PathBuf } ;
5+ use std:: sync:: { Arc , MutexGuard } ;
6+ use std:: time:: Duration ;
7+ use notify:: { Event } ;
8+ use notify:: event:: { CreateKind , ModifyKind , RenameMode } ;
9+ use tokio:: time;
10+ use crate :: filesystem:: { DIRECTORY , FILE } ;
11+
12+ pub const CACHE_FILE_PATH : & str = "./system_cache.json" ;
13+
14+ /// Handles filesystem events, currently intended for cache invalidation.
15+ pub struct FsEventHandler {
16+ state_mux : StateSafe ,
17+ mountpoint : PathBuf ,
18+ }
19+
20+ impl FsEventHandler {
21+ pub fn new ( state_mux : StateSafe , mountpoint : PathBuf ) -> Self {
22+ Self { state_mux, mountpoint }
23+ }
24+
25+ /// Gets the current volume from the cache
26+ fn get_from_cache < ' a > ( & self , state : & ' a mut AppState ) -> & ' a mut VolumeCache {
27+ let mountpoint = self . mountpoint . to_string_lossy ( ) . to_string ( ) ;
28+
29+ state. system_cache . get_mut ( & mountpoint)
30+ . unwrap_or_else ( || panic ! ( "Failed to find mountpoint '{:?}' in cache." , self . mountpoint) )
31+ }
32+
33+ pub fn handle_create ( & self , kind : CreateKind , path : & Path ) {
34+ let state = & mut self . state_mux . lock ( ) . unwrap ( ) ;
35+ let current_volume = self . get_from_cache ( state) ;
36+
37+ let filename = path. file_name ( ) . unwrap ( ) . to_string_lossy ( ) . to_string ( ) ;
38+ let file_type = match kind {
39+ CreateKind :: File => FILE ,
40+ CreateKind :: Folder => DIRECTORY ,
41+ _ => return , // Other options are weird lol
42+ } . to_string ( ) ;
43+
44+ let file_path = path. to_string_lossy ( ) . to_string ( ) ;
45+ current_volume. entry ( filename) . or_insert ( vec ! [ CachedPath { file_path, file_type} ] ) ;
46+ }
47+
48+ pub fn handle_delete ( & self , path : & Path ) {
49+ let state = & mut self . state_mux . lock ( ) . unwrap ( ) ;
50+ let current_volume = self . get_from_cache ( state) ;
51+
52+ let filename = path. file_name ( ) . unwrap ( ) . to_string_lossy ( ) . to_string ( ) ;
53+ current_volume. remove ( & filename) ;
54+ }
55+
56+ /// Removes file from cache, when `handle_rename_to` is called a new file is added to the cache in place.
57+ pub fn handle_rename_from ( & mut self , old_path : & Path ) {
58+ let state = & mut self . state_mux . lock ( ) . unwrap ( ) ;
59+ let current_volume = self . get_from_cache ( state) ;
60+
61+ let old_path_string= old_path. to_string_lossy ( ) . to_string ( ) ;
62+ let old_filename = old_path. file_name ( ) . unwrap ( ) . to_string_lossy ( ) . to_string ( ) ;
63+
64+ let empty_vec = & mut Vec :: new ( ) ;
65+ let cached_paths = current_volume. get_mut ( & old_filename) . unwrap_or ( empty_vec) ;
66+
67+ // 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
68+ if cached_paths. len ( ) <= 1 {
69+ current_volume. remove ( & old_filename) ;
70+ return ;
71+ }
72+
73+ cached_paths. retain ( |path| path. file_path != old_path_string) ;
74+ }
75+
76+ /// Adds new file name & path to cache.
77+ pub fn handle_rename_to ( & self , new_path : & Path ) {
78+ let state = & mut self . state_mux . lock ( ) . unwrap ( ) ;
79+ let current_volume = self . get_from_cache ( state) ;
80+
81+ let filename = new_path. file_name ( ) . unwrap ( ) . to_string_lossy ( ) . to_string ( ) ;
82+ let file_type = if new_path. is_dir ( ) { DIRECTORY } else { FILE } ;
83+
84+ let path_string = new_path. to_string_lossy ( ) . to_string ( ) ;
85+ current_volume. entry ( filename) . or_insert ( vec ! [ CachedPath { file_path: path_string, file_type: String :: from( file_type) } ] ) ;
86+ }
87+
88+ pub fn handle_event ( & mut self , event : Event ) {
89+ let paths = event. paths ;
90+
91+ match event. kind {
92+ notify:: EventKind :: Modify ( modify_kind) => {
93+ if modify_kind == ModifyKind :: Name ( RenameMode :: From ) {
94+ self . handle_rename_from ( & paths[ 0 ] ) ;
95+ } else if modify_kind == ModifyKind :: Name ( RenameMode :: To ) {
96+ self . handle_rename_to ( & paths[ 0 ] ) ;
97+ }
98+ } ,
99+ notify:: EventKind :: Create ( kind) => self . handle_create ( kind, & paths[ 0 ] ) ,
100+ notify:: EventKind :: Remove ( _) => self . handle_delete ( & paths[ 0 ] ) ,
101+ _ => ( ) ,
102+ }
103+ }
104+ }
105+
106+ /// Starts a constant interval loop where the cache is updated every ~30 seconds.
107+ pub fn run_cache_interval ( state_mux : & StateSafe ) {
108+ let state_clone = Arc :: clone ( state_mux) ;
109+
110+ tokio:: spawn ( async move { // We use tokio spawn because async closures with std spawn is unstable
111+ let mut interval = time:: interval ( Duration :: from_secs ( 30 ) ) ;
112+ interval. tick ( ) . await ; // Wait 30 seconds before doing first re-cache
113+
114+ loop {
115+ interval. tick ( ) . await ;
116+
117+ let guard = & mut state_clone. lock ( ) . unwrap ( ) ;
118+ save_to_cache ( guard) ;
119+ }
120+ } ) ;
121+ }
122+
123+ /// This takes in an Arc<Mutex<AppState>> and calls `save_to_cache` after locking it.
124+ pub fn save_system_cache ( state_mux : & StateSafe ) {
125+ let state = & mut state_mux. lock ( ) . unwrap ( ) ;
126+ save_to_cache ( state) ;
127+ }
128+
129+ /// Gets the cache from the state (in memory), encodes and saves it to the cache file path.
130+ /// This needs optimising.
131+ fn save_to_cache ( state : & mut MutexGuard < AppState > ) {
132+ let serialized_cache = serde_json:: to_string ( & state. system_cache ) . unwrap ( ) ;
133+
134+ let mut file = fs:: OpenOptions :: new ( )
135+ . write ( true )
136+ . truncate ( true )
137+ . open ( CACHE_FILE_PATH )
138+ . unwrap ( ) ;
139+
140+ file. write_all ( serialized_cache. as_bytes ( ) ) . unwrap ( ) ;
141+ }
142+
143+ /// Reads and decodes the cache file and stores it in memory for quick access.
144+ /// Returns false if the cache was unable to deserialize.
145+ pub fn load_system_cache ( state_mux : & StateSafe ) -> bool {
146+ let state = & mut state_mux. lock ( ) . unwrap ( ) ;
147+ let file_contents = fs:: read_to_string ( CACHE_FILE_PATH ) . unwrap ( ) ;
148+
149+ let deserialize_result = serde_json:: from_str ( & file_contents) ;
150+ if let Ok ( system_cache) = deserialize_result {
151+ state. system_cache = system_cache;
152+ return true ;
153+ }
154+
155+ false
156+ }
0 commit comments