@@ -13,11 +13,16 @@ use tauri::{
1313use tokio:: sync:: mpsc;
1414use tokio:: sync:: Mutex ;
1515
16+ /// Application state shared across all Tauri commands.
1617struct AppState {
18+ /// Iroh instance for peer-to-peer file transfers
1719 pub iroh : IrohInstance ,
20+ /// Channel sender for internal event communication
1821 inner : Mutex < mpsc:: Sender < Event > > ,
19- // Store active send bubble to keep it alive
22+ /// Active send bubble to keep it alive during transfers
2023 active_send_bubble : Arc < Mutex < Option < SendFilesBubble > > > ,
24+ /// Custom download directory set by user
25+ custom_download_dir : Mutex < Option < PathBuf > > ,
2126}
2227
2328enum Event {
@@ -30,6 +35,7 @@ impl AppState {
3035 iroh,
3136 inner : Mutex :: new ( async_proc_input_tx) ,
3237 active_send_bubble : Arc :: new ( Mutex :: new ( None ) ) ,
38+ custom_download_dir : Mutex :: new ( None ) ,
3339 }
3440 }
3541}
@@ -84,6 +90,8 @@ pub fn run() {
8490 . invoke_handler ( generate_handler ! [
8591 generate_ticket,
8692 receive_files,
93+ set_download_directory,
94+ get_download_directory,
8795 open_directory,
8896 is_valid_ticket,
8997 get_env
@@ -112,11 +120,28 @@ fn event_handler(message: Event, manager: &AppHandle) {
112120 }
113121}
114122
123+ /// Gets an environment variable value.
124+ ///
125+ /// # Arguments
126+ /// * `key` - The environment variable name
127+ ///
128+ /// # Returns
129+ /// The value of the environment variable, or an empty string if not found
115130#[ tauri:: command]
116131fn get_env ( key : & str ) -> String {
117132 std:: env:: var ( String :: from ( key) ) . unwrap_or ( String :: from ( "" ) )
118133}
119134
135+ /// Generates a ticket for sending files.
136+ ///
137+ /// Creates a ticket that encodes the file paths and connection information,
138+ /// which can be shared with a receiver to initiate a file transfer.
139+ ///
140+ /// # Arguments
141+ /// * `paths` - List of file paths to include in the transfer
142+ ///
143+ /// # Returns
144+ /// A BlobTicket containing the transfer information
120145#[ tauri:: command]
121146async fn generate_ticket (
122147 state : tauri:: State < ' _ , AppState > ,
@@ -171,6 +196,16 @@ async fn generate_ticket(
171196 Ok ( ticket)
172197}
173198
199+ /// Receives files using a transfer ticket.
200+ ///
201+ /// Downloads files from a sender using the provided ticket and saves them to
202+ /// the configured download directory.
203+ ///
204+ /// # Arguments
205+ /// * `ticket` - The transfer ticket string from the sender
206+ ///
207+ /// # Returns
208+ /// The path to the directory where files were saved
174209#[ tauri:: command]
175210async fn receive_files (
176211 state : tauri:: State < ' _ , AppState > ,
@@ -187,12 +222,12 @@ async fn receive_files(
187222 }
188223 } ) ;
189224
190- // Determine output directory
191- let output_dir = if let Some ( path ) = dirs :: download_dir ( ) {
192- path
193- } else {
194- // Android download path
195- PathBuf :: from ( "/storage/emulated/0/Download/" )
225+ let output_dir = {
226+ let custom_dir = state . custom_download_dir . lock ( ) . await ;
227+ custom_dir
228+ . clone ( )
229+ . or_else ( || dirs :: download_dir ( ) )
230+ . unwrap_or_else ( || PathBuf :: from ( "/storage/emulated/0/Download/" ) )
196231 } ;
197232
198233 // Receive files with proper file writing
@@ -206,14 +241,80 @@ async fn receive_files(
206241 Ok ( output_dir)
207242}
208243
244+ /// Sets a custom download directory for received files.
245+ ///
246+ /// # Arguments
247+ /// * `path` - The filesystem path to the directory
248+ ///
249+ /// # Errors
250+ /// Returns an error if the path doesn't exist or is not a directory
251+ #[ tauri:: command]
252+ async fn set_download_directory (
253+ state : tauri:: State < ' _ , AppState > ,
254+ path : String ,
255+ ) -> Result < ( ) , InvokeError > {
256+ let path_buf = PathBuf :: from ( & path) ;
257+
258+ if !path_buf. exists ( ) {
259+ return Err ( InvokeError :: from_anyhow ( anyhow ! (
260+ "Directory does not exist: {}" ,
261+ path
262+ ) ) ) ;
263+ }
264+
265+ if !path_buf. is_dir ( ) {
266+ return Err ( InvokeError :: from_anyhow ( anyhow ! (
267+ "Path is not a directory: {}" ,
268+ path
269+ ) ) ) ;
270+ }
271+
272+ let mut custom_dir = state. custom_download_dir . lock ( ) . await ;
273+ * custom_dir = Some ( path_buf) ;
274+
275+ Ok ( ( ) )
276+ }
277+
278+ /// Gets the current download directory.
279+ ///
280+ /// Returns the custom directory if set, otherwise returns the system default
281+ /// download directory, or the Android download path as a fallback.
282+ ///
283+ /// # Returns
284+ /// The absolute path to the download directory as a string
285+ #[ tauri:: command]
286+ async fn get_download_directory ( state : tauri:: State < ' _ , AppState > ) -> Result < String , InvokeError > {
287+ let custom_dir = state. custom_download_dir . lock ( ) . await ;
288+
289+ let output_dir = custom_dir
290+ . clone ( )
291+ . or_else ( || dirs:: download_dir ( ) )
292+ . unwrap_or_else ( || PathBuf :: from ( "/storage/emulated/0/Download/" ) ) ;
293+
294+ Ok ( output_dir. to_string_lossy ( ) . to_string ( ) )
295+ }
296+
297+ /// Opens a directory in the system's file manager.
298+ ///
299+ /// # Arguments
300+ /// * `directory` - Path to the directory to open
301+ ///
302+ /// # Errors
303+ /// Returns an error if the directory cannot be opened
209304#[ tauri:: command]
210305fn open_directory ( directory : PathBuf ) -> Result < ( ) , InvokeError > {
211306 open:: that ( directory) . map_err ( |e| InvokeError :: from_anyhow ( anyhow ! ( e) ) )
212307}
213308
309+ /// Validates a transfer ticket format.
310+ ///
311+ /// # Arguments
312+ /// * `ticket` - The ticket string to validate
313+ ///
314+ /// # Returns
315+ /// `true` if the ticket is valid, `false` otherwise
214316#[ tauri:: command]
215317fn is_valid_ticket ( ticket : String ) -> Result < bool , InvokeError > {
216- // With ark-core, we can simply try to parse the ticket
217318 match BlobTicket :: parse ( & ticket) {
218319 Ok ( _) => Ok ( true ) ,
219320 Err ( _) => Ok ( false ) ,
0 commit comments