@@ -18,6 +18,7 @@ pub mod volume;
1818pub ( crate ) mod watcher;
1919pub ( crate ) mod write_operations;
2020
21+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
2122use std:: sync:: { Arc , LazyLock } ;
2223
2324// Re-export public types from the listing module
@@ -66,6 +67,20 @@ pub use write_operations::{
6667/// Global volume manager instance
6768static VOLUME_MANAGER : LazyLock < VolumeManager > = LazyLock :: new ( VolumeManager :: new) ;
6869
70+ /// Whether to auto-upgrade SMB mounts to direct smb2 connections.
71+ /// Set from the `network.directSmbConnection` setting at startup.
72+ static DIRECT_SMB_ENABLED : AtomicBool = AtomicBool :: new ( true ) ;
73+
74+ /// Sets the direct SMB connection preference. Call from app setup after loading settings.
75+ pub fn set_direct_smb_enabled ( enabled : bool ) {
76+ DIRECT_SMB_ENABLED . store ( enabled, Ordering :: Relaxed ) ;
77+ }
78+
79+ /// Returns whether direct SMB connection is enabled.
80+ pub fn is_direct_smb_enabled ( ) -> bool {
81+ DIRECT_SMB_ENABLED . load ( Ordering :: Relaxed )
82+ }
83+
6984/// Initializes the global volume manager with all discovered volumes.
7085///
7186/// This should be called during app startup (after init_watcher_manager).
@@ -126,5 +141,112 @@ pub fn get_volume_manager() -> &'static VolumeManager {
126141 & VOLUME_MANAGER
127142}
128143
144+ /// Upgrades all existing SMB mounts to direct smb2 connections (background task).
145+ ///
146+ /// Scans all registered volumes, finds those on `smbfs`, and tries to establish
147+ /// a parallel smb2 session for each. Non-blocking: failures are logged and skipped.
148+ #[ cfg( any( target_os = "macos" , target_os = "linux" ) ) ]
149+ pub fn upgrade_existing_smb_mounts ( ) {
150+ if !is_direct_smb_enabled ( ) {
151+ log:: debug!( "Direct SMB connections disabled, skipping startup upgrade" ) ;
152+ return ;
153+ }
154+
155+ // Collect SMB volume paths to upgrade (don't hold the manager lock during async work)
156+ let volumes_to_upgrade: Vec < ( String , String ) > = {
157+ let all_volumes = VOLUME_MANAGER . list_volumes ( ) ;
158+ all_volumes
159+ . into_iter ( )
160+ . map ( |( id, _name) | id)
161+ . filter_map ( |id| {
162+ let vol = VOLUME_MANAGER . get ( & id) ?;
163+ // Skip volumes that are already SmbVolume
164+ if vol. smb_connection_state ( ) . is_some ( ) {
165+ return None ;
166+ }
167+ let path = vol. root ( ) . to_string_lossy ( ) . to_string ( ) ;
168+ // Check if it's an SMB mount
169+ let info = crate :: volumes:: get_smb_mount_info ( & path) ?;
170+ let _ = info; // We just need to know it's SMB
171+ Some ( ( id, path) )
172+ } )
173+ . collect ( )
174+ } ;
175+
176+ if volumes_to_upgrade. is_empty ( ) {
177+ log:: debug!( "No SMB mounts to upgrade at startup" ) ;
178+ return ;
179+ }
180+
181+ log:: info!(
182+ "Found {} SMB mount(s) to upgrade to direct connections" ,
183+ volumes_to_upgrade. len( )
184+ ) ;
185+
186+ // Use tauri's runtime spawn — this runs during setup() before Tokio is fully available.
187+ // Wait for mDNS discovery to reach Active state (initial burst complete) so hostname
188+ // resolution is available for Keychain lookup.
189+ tauri:: async_runtime:: spawn ( async move {
190+ wait_for_mdns_ready ( ) . await ;
191+
192+ let mut any_upgraded = false ;
193+ for ( _volume_id, mount_path) in volumes_to_upgrade {
194+ let info = match crate :: volumes:: get_smb_mount_info ( & mount_path) {
195+ Some ( info) => info,
196+ None => continue ,
197+ } ;
198+
199+ // Resolve hostname from mDNS for Keychain lookup
200+ let hostname = crate :: commands:: network:: resolve_ip_to_hostname ( & info. server ) ;
201+
202+ // Try Keychain creds
203+ let creds =
204+ crate :: commands:: network:: get_keychain_password ( & info. server , hostname. as_deref ( ) , & info. share ) . await ;
205+
206+ let ( username, password) = match & creds {
207+ Some ( ( u, p) ) => ( Some ( u. as_str ( ) ) , Some ( p. as_str ( ) ) ) ,
208+ None => ( None , None ) ,
209+ } ;
210+
211+ crate :: commands:: network:: register_smb_volume (
212+ & info. server ,
213+ & info. share ,
214+ & mount_path,
215+ username,
216+ password,
217+ 445 ,
218+ )
219+ . await ;
220+ any_upgraded = true ;
221+ }
222+
223+ // Notify frontend to refresh volume list so indicators update from yellow to green
224+ if any_upgraded {
225+ crate :: volume_broadcast:: emit_volumes_changed ( ) ;
226+ }
227+ } ) ;
228+ }
229+
230+ /// Waits until mDNS discovery reaches the `Active` state (initial burst complete).
231+ ///
232+ /// Polls every 500ms for up to 15 seconds. If discovery never reaches Active,
233+ /// proceeds anyway — the upgrade will try without hostname resolution and may
234+ /// fall back to guest access.
235+ #[ cfg( any( target_os = "macos" , target_os = "linux" ) ) ]
236+ async fn wait_for_mdns_ready ( ) {
237+ use crate :: network:: { DiscoveryState , get_discovery_state_value} ;
238+
239+ for _ in 0 ..30 {
240+ match get_discovery_state_value ( ) {
241+ DiscoveryState :: Active => {
242+ log:: debug!( "mDNS discovery is Active, proceeding with SMB upgrades" ) ;
243+ return ;
244+ }
245+ _ => tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 500 ) ) . await ,
246+ }
247+ }
248+ log:: debug!( "mDNS discovery didn't reach Active within 15s, proceeding anyway" ) ;
249+ }
250+
129251#[ cfg( test) ]
130252mod watcher_test;
0 commit comments