@@ -21,8 +21,22 @@ pub struct VersionManager {
21
21
impl VersionManager {
22
22
/// Create a new version manager
23
23
pub fn new ( ) -> Result < Self > {
24
- let user_dirs = UserDirs :: new ( ) . ok_or ( DigstoreError :: HomeDirectoryNotFound ) ?;
25
- let versions_dir = user_dirs. home_dir ( ) . join ( ".digstore-versions" ) ;
24
+ // Try system directory first, fall back to user directory if no admin privileges
25
+ let program_files = std:: env:: var ( "ProgramFiles(x86)" )
26
+ . or_else ( |_| std:: env:: var ( "ProgramFiles" ) )
27
+ . unwrap_or_else ( |_| "C:\\ Program Files" . to_string ( ) ) ;
28
+
29
+ let system_versions_dir = PathBuf :: from ( program_files) . join ( "dig-network" ) ;
30
+
31
+ // Test if we can write to system directory
32
+ let versions_dir = if Self :: can_write_to_directory ( & system_versions_dir) {
33
+ // Use system directory (preferred)
34
+ system_versions_dir
35
+ } else {
36
+ // Fall back to user directory
37
+ let user_dirs = UserDirs :: new ( ) . ok_or ( DigstoreError :: HomeDirectoryNotFound ) ?;
38
+ user_dirs. home_dir ( ) . join ( ".digstore-versions" )
39
+ } ;
26
40
27
41
// Create versions directory if it doesn't exist
28
42
if !versions_dir. exists ( ) {
@@ -37,6 +51,19 @@ impl VersionManager {
37
51
} )
38
52
}
39
53
54
+ /// Test if we can write to a directory
55
+ fn can_write_to_directory ( dir : & Path ) -> bool {
56
+ // Try to create the directory and write a test file
57
+ if fs:: create_dir_all ( dir) . is_ok ( ) {
58
+ let test_file = dir. join ( "access_test.tmp" ) ;
59
+ if fs:: write ( & test_file, "test" ) . is_ok ( ) {
60
+ let _ = fs:: remove_file ( & test_file) ;
61
+ return true ;
62
+ }
63
+ }
64
+ false
65
+ }
66
+
40
67
/// Install a new version from a binary path
41
68
pub fn install_version ( & mut self , version : & str , binary_path : & Path ) -> Result < ( ) > {
42
69
println ! (
@@ -73,7 +100,7 @@ impl VersionManager {
73
100
Ok ( ( ) )
74
101
}
75
102
76
- /// Set the active version and update PATH/symlinks
103
+ /// Set the active version and update PATH
77
104
pub fn set_active_version ( & mut self , version : & str ) -> Result < ( ) > {
78
105
let version_dir = self . get_version_dir ( version) ;
79
106
let binary_path = version_dir. join ( self . get_binary_name ( ) ) ;
@@ -84,16 +111,13 @@ impl VersionManager {
84
111
} ) ;
85
112
}
86
113
87
- // Update the active symlink/shortcut
88
- self . update_active_link ( & binary_path ) ?;
114
+ // Update PATH to point to this version
115
+ self . update_path_to_version ( version ) ?;
89
116
90
117
// Save active version info
91
118
self . save_active_version ( version) ?;
92
119
self . active_version = Some ( version. to_string ( ) ) ;
93
120
94
- // Refresh current environment PATH
95
- self . refresh_current_environment ( ) ?;
96
-
97
121
println ! (
98
122
" {} Active version set to: {}" ,
99
123
"✓" . green( ) ,
@@ -269,7 +293,7 @@ impl VersionManager {
269
293
270
294
/// Get the directory for a specific version
271
295
pub fn get_version_dir ( & self , version : & str ) -> PathBuf {
272
- self . versions_dir . join ( version)
296
+ self . versions_dir . join ( format ! ( "v{}" , version) )
273
297
}
274
298
275
299
/// Get the binary name for the current platform
@@ -281,32 +305,10 @@ impl VersionManager {
281
305
}
282
306
}
283
307
284
- /// Update the active symlink or shortcut
308
+ /// Update the active symlink or shortcut (no longer needed with direct PATH approach)
285
309
fn update_active_link ( & self , binary_path : & Path ) -> Result < ( ) > {
286
- let link_path = self . get_active_link_path ( ) ?;
287
-
288
- // Remove existing link/shortcut
289
- if link_path. exists ( ) {
290
- fs:: remove_file ( & link_path) ?;
291
- }
292
-
293
- // Create new link/shortcut
294
- #[ cfg( windows) ]
295
- {
296
- // On Windows, create a batch file that calls the active version
297
- let batch_content = format ! (
298
- "@echo off\n \" {}\" %*\n " ,
299
- binary_path. display( )
300
- ) ;
301
- fs:: write ( & link_path, batch_content) ?;
302
- }
303
-
304
- #[ cfg( unix) ]
305
- {
306
- // On Unix, create a symlink
307
- std:: os:: unix:: fs:: symlink ( binary_path, & link_path) ?;
308
- }
309
-
310
+ // With the new system versioned approach, we update PATH directly instead of using batch files
311
+ // This method is kept for compatibility but does nothing
310
312
Ok ( ( ) )
311
313
}
312
314
@@ -349,21 +351,9 @@ impl VersionManager {
349
351
}
350
352
}
351
353
352
- /// Get the system-wide installation directory for a version
354
+ /// Get the system-wide installation directory for a version (now same as get_version_dir)
353
355
pub fn get_system_install_dir ( & self , version : & str ) -> PathBuf {
354
- #[ cfg( windows) ]
355
- {
356
- let program_files = std:: env:: var ( "ProgramFiles(x86)" )
357
- . or_else ( |_| std:: env:: var ( "ProgramFiles" ) )
358
- . unwrap_or_else ( |_| "C:\\ Program Files" . to_string ( ) ) ;
359
-
360
- PathBuf :: from ( program_files) . join ( "dig-network" ) . join ( format ! ( "v{}" , version) )
361
- }
362
-
363
- #[ cfg( not( windows) ) ]
364
- {
365
- PathBuf :: from ( "/usr/local/lib/digstore" ) . join ( format ! ( "v{}" , version) )
366
- }
356
+ self . get_version_dir ( version)
367
357
}
368
358
369
359
/// Save the active version to a config file
@@ -428,69 +418,77 @@ impl VersionManager {
428
418
self . install_from_msi_user_level ( version, msi_path)
429
419
}
430
420
431
- /// Install from MSI directly to user versioned directory
421
+ /// Install from MSI to system versioned directory
432
422
fn install_from_msi_user_level ( & mut self , version : & str , msi_path : & Path ) -> Result < ( ) > {
433
- println ! ( " {} Installing MSI directly to versioned directory" , "•" . cyan( ) ) ;
423
+ println ! ( " {} Installing MSI to system versioned directory" , "•" . cyan( ) ) ;
434
424
435
- let user_install_dir = self . get_version_dir ( version) ;
436
- fs:: create_dir_all ( & user_install_dir ) ?;
425
+ let version_dir = self . get_version_dir ( version) ;
426
+ fs:: create_dir_all ( & version_dir ) ?;
437
427
438
- // Install MSI directly to the versioned directory (not system location )
439
- println ! ( " {} Installing to: {} " , "•" . cyan( ) , user_install_dir . display ( ) ) ;
428
+ // First, install MSI to the default location (where it wants to go )
429
+ println ! ( " {} Installing MSI to system location... " , "•" . cyan( ) ) ;
440
430
441
431
let install_output = Command :: new ( "msiexec" )
442
432
. args ( & [
443
433
"/i" , msi_path. to_str ( ) . unwrap ( ) , // Install the MSI
444
434
"/quiet" , "/norestart" , // Silent installation
445
- & format ! ( "INSTALLDIR={}" , user_install_dir. display( ) ) , // Target directory
446
- & format ! ( "TARGETDIR={}" , user_install_dir. display( ) ) , // Alternative target property
447
- "ALLUSERS=0" , // User-level installation
448
- "MSIINSTALLPERUSER=1" , // Per-user installation
449
435
] )
450
436
. output ( )
451
437
. map_err ( |e| DigstoreError :: ConfigurationError {
452
438
reason : format ! ( "Failed to run MSI installation: {}" , e) ,
453
439
} ) ?;
454
440
455
- let binary_path = user_install_dir. join ( self . get_binary_name ( ) ) ;
456
- let mut found = false ;
441
+ if !install_output. status . success ( ) {
442
+ let stderr = String :: from_utf8_lossy ( & install_output. stderr ) ;
443
+ let stdout = String :: from_utf8_lossy ( & install_output. stdout ) ;
444
+
445
+ return Err ( DigstoreError :: ConfigurationError {
446
+ reason : format ! ( "MSI installation failed. Stderr: {}, Stdout: {}" , stderr, stdout) ,
447
+ } ) ;
448
+ }
457
449
458
- if install_output. status . success ( ) {
459
- // Check if binary was installed to the target directory
460
- if binary_path. exists ( ) {
461
- found = true ;
462
- } else {
463
- // Check common subdirectories within the install dir
464
- let subdirs = [ "bin" , "." , "digstore" ] ;
465
- for subdir in & subdirs {
466
- let alt_path = user_install_dir. join ( subdir) . join ( self . get_binary_name ( ) ) ;
467
- if alt_path. exists ( ) {
468
- fs:: copy ( & alt_path, & binary_path) ?;
469
- found = true ;
470
- break ;
450
+ // Now move the installed binary to the versioned directory
451
+ println ! ( " {} Moving installation to versioned directory..." , "•" . cyan( ) ) ;
452
+
453
+ let base_install_dir = self . versions_dir . clone ( ) ; // C:\Program Files (x86)\dig-network\
454
+ let source_binary = base_install_dir. join ( self . get_binary_name ( ) ) ;
455
+ let target_binary = version_dir. join ( self . get_binary_name ( ) ) ;
456
+
457
+ // Check if binary was installed to base directory
458
+ if source_binary. exists ( ) {
459
+ // Move the binary to the versioned directory
460
+ fs:: copy ( & source_binary, & target_binary) ?;
461
+
462
+ // Remove from base directory
463
+ let _ = fs:: remove_file ( & source_binary) ;
464
+
465
+ // Also move any other files (like DIG.ico)
466
+ if let Ok ( entries) = fs:: read_dir ( & base_install_dir) {
467
+ for entry in entries. flatten ( ) {
468
+ let entry_path = entry. path ( ) ;
469
+ if entry_path. is_file ( ) && entry_path != source_binary {
470
+ let filename = entry_path. file_name ( ) . unwrap ( ) ;
471
+ let target_path = version_dir. join ( filename) ;
472
+ let _ = fs:: copy ( & entry_path, & target_path) ;
473
+ let _ = fs:: remove_file ( & entry_path) ;
471
474
}
472
475
}
473
476
}
474
- }
475
-
476
- if !found {
477
- let stderr = String :: from_utf8_lossy ( & install_output. stderr ) ;
478
- let stdout = String :: from_utf8_lossy ( & install_output. stdout ) ;
479
477
480
- // Fallback: Try extraction method if direct installation failed
481
- println ! ( " {} Direct installation failed, trying extraction..." , "!" . yellow( ) ) ;
482
- return self . fallback_msi_extraction ( version, msi_path) ;
478
+ println ! (
479
+ " {} Version {} installed to: {}" ,
480
+ "✓" . green( ) ,
481
+ version. bright_cyan( ) ,
482
+ version_dir. display( ) . to_string( ) . dimmed( )
483
+ ) ;
484
+ } else {
485
+ return Err ( DigstoreError :: ConfigurationError {
486
+ reason : format ! ( "MSI installation succeeded but binary not found at: {}" , source_binary. display( ) ) ,
487
+ } ) ;
483
488
}
484
489
485
- println ! (
486
- " {} Version {} installed to: {}" ,
487
- "✓" . green( ) ,
488
- version. bright_cyan( ) ,
489
- user_install_dir. display( ) . to_string( ) . dimmed( )
490
- ) ;
491
-
492
- // Clean up any system installations and PATH entries
493
- self . cleanup_system_installations ( ) ?;
490
+ // Update PATH to point to the versioned directory
491
+ self . update_path_to_version ( version) ?;
494
492
495
493
// Set as active version
496
494
self . set_active_version ( version) ?;
@@ -718,6 +716,54 @@ impl VersionManager {
718
716
Ok ( ( ) )
719
717
}
720
718
719
+ /// Update PATH to point to a specific version directory
720
+ fn update_path_to_version ( & self , version : & str ) -> Result < ( ) > {
721
+ let version_dir = self . get_version_dir ( version) ;
722
+
723
+ println ! ( " {} Updating PATH to: {}" , "•" . cyan( ) , version_dir. display( ) ) ;
724
+
725
+ // Get current PATH
726
+ let current_path = std:: env:: var ( "PATH" ) . unwrap_or_default ( ) ;
727
+ let path_entries: Vec < & str > = current_path. split ( ';' ) . collect ( ) ;
728
+
729
+ // Remove any existing dig-network entries
730
+ let base_dir_str = self . versions_dir . to_string_lossy ( ) ;
731
+ let version_dir_str = version_dir. to_string_lossy ( ) ;
732
+
733
+ let filtered_entries: Vec < & str > = path_entries
734
+ . into_iter ( )
735
+ . filter ( |entry| {
736
+ let entry_trimmed = entry. trim ( ) ;
737
+ // Remove old dig-network entries (including versioned ones)
738
+ !entry_trimmed. starts_with ( & base_dir_str. to_string ( ) )
739
+ } )
740
+ . collect ( ) ;
741
+
742
+ // Add the new version directory to the front of PATH
743
+ let new_path = format ! ( "{};{}" , version_dir_str, filtered_entries. join( ";" ) ) ;
744
+
745
+ // Update PATH
746
+ let output = Command :: new ( "setx" )
747
+ . args ( & [ "PATH" , & new_path] )
748
+ . output ( )
749
+ . map_err ( |e| DigstoreError :: ConfigurationError {
750
+ reason : format ! ( "Failed to update PATH: {}" , e) ,
751
+ } ) ?;
752
+
753
+ if output. status . success ( ) {
754
+ println ! ( " {} Updated PATH to use version {}" , "✓" . green( ) , version. bright_cyan( ) ) ;
755
+
756
+ // Also update current environment
757
+ std:: env:: set_var ( "PATH" , & new_path) ;
758
+ } else {
759
+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
760
+ println ! ( " {} Could not update PATH automatically: {}" , "!" . yellow( ) , stderr) ;
761
+ println ! ( " {} Manually add to PATH: {}" , "→" . cyan( ) , version_dir. display( ) ) ;
762
+ }
763
+
764
+ Ok ( ( ) )
765
+ }
766
+
721
767
/// Clean up system PATH entries that point to old installation locations
722
768
fn cleanup_system_path_entries ( & self ) -> Result < ( ) > {
723
769
println ! ( " {} Cleaning up old PATH entries..." , "•" . cyan( ) ) ;
0 commit comments