44) ]
55
66use rand:: RngExt ;
7- use std:: fs ;
7+ use std:: { collections :: HashSet , fmt :: Display , fs } ;
88use unicode_segmentation:: UnicodeSegmentation ;
99
10- fn main ( ) {
11- #[ cfg( target_os = "linux" ) ]
10+ type CommandResult < T > = Result < T , String > ;
11+
12+ #[ cfg( target_os = "linux" ) ]
13+ fn configure_linux_environment ( ) {
14+ // SAFETY: This runs during application startup before any threads are spawned
15+ // and only sets a fixed process-wide environment variable needed on Linux.
1216 unsafe {
1317 std:: env:: set_var ( "__NV_DISABLE_EXPLICIT_SYNC" , "1" ) ;
1418 }
19+ }
20+
21+ fn map_command_result < T , E > ( result : Result < T , E > ) -> CommandResult < T >
22+ where
23+ E : Display ,
24+ {
25+ result. map_err ( |error| error. to_string ( ) )
26+ }
27+
28+ fn as_platform_size ( value : u64 , field_name : & str ) -> CommandResult < usize > {
29+ usize:: try_from ( value) . map_err ( |_| format ! ( "{field_name} is too large for this platform." ) )
30+ }
31+
32+ fn count_unique_passwords ( min_length : usize , max_length : usize , grapheme_count : usize ) -> usize {
33+ let mut total = 0usize ;
34+
35+ for length in min_length..=max_length {
36+ let combinations = u32:: try_from ( length)
37+ . ok ( )
38+ . and_then ( |value| grapheme_count. checked_pow ( value) )
39+ . unwrap_or ( usize:: MAX ) ;
40+
41+ total = total. saturating_add ( combinations) ;
42+ if total == usize:: MAX {
43+ break ;
44+ }
45+ }
46+
47+ total
48+ }
49+
50+ fn main ( ) {
51+ #[ cfg( target_os = "linux" ) ]
52+ configure_linux_environment ( ) ;
1553
1654 tauri:: Builder :: default ( )
1755 . plugin ( tauri_plugin_os:: init ( ) )
@@ -34,76 +72,83 @@ fn exit_app() {
3472}
3573
3674#[ tauri:: command]
37- fn open_website ( website : & str ) -> Result < String , String > {
38- match open:: that ( website) {
39- Ok ( _) => Ok ( String :: from ( "Success" ) ) ,
40- Err ( e) => Err ( e. to_string ( ) ) ,
41- }
75+ fn open_website ( website : & str ) -> CommandResult < ( ) > {
76+ map_command_result ( open:: that ( website) )
4277}
4378
4479#[ tauri:: command]
45- fn save_string_to_disk ( content : & str , path : & str ) -> Result < String , String > {
46- match fs:: write ( path, content) {
47- Ok ( _) => Ok ( String :: from ( "Success" ) ) ,
48- Err ( e) => Err ( e. to_string ( ) ) ,
49- }
80+ fn save_string_to_disk ( content : & str , path : & str ) -> CommandResult < ( ) > {
81+ map_command_result ( fs:: write ( path, content) )
5082}
5183
5284#[ tauri:: command]
53- async fn read_string_from_file ( path : & str ) -> Result < String , String > {
54- match fs:: read_to_string ( path) {
55- Ok ( s) => Ok ( s) ,
56- Err ( e) => Err ( e. to_string ( ) ) ,
57- }
85+ fn read_string_from_file ( path : & str ) -> CommandResult < String > {
86+ map_command_result ( fs:: read_to_string ( path) )
5887}
5988
6089#[ tauri:: command]
61- async fn generate_passwords (
90+ fn generate_passwords (
6291 min_length : u64 ,
6392 max_length : u64 ,
6493 character_set : & str ,
6594 include_symbols : & str ,
6695 amount : u64 ,
6796 allow_duplicates : bool ,
68- ) -> Result < Vec < String > , String > {
69- let mut password_list: Vec < String > = Vec :: new ( ) ;
70- let mut max_count: f64 = 0.0 ;
97+ ) -> CommandResult < Vec < String > > {
98+ let min_length = as_platform_size ( min_length, "Minimum length" ) ?;
99+ let max_length = as_platform_size ( max_length, "Maximum length" ) ?;
100+ let requested_amount = as_platform_size ( amount, "Password amount" ) ?;
71101
72- let mut total_character_set = String :: from ( character_set) ;
73- total_character_set. push_str ( include_symbols) ;
102+ if min_length > max_length {
103+ return Err ( "Minimum length cannot be greater than maximum length." . into ( ) ) ;
104+ }
105+
106+ if requested_amount == 0 {
107+ return Ok ( Vec :: new ( ) ) ;
108+ }
74109
75- let graphemes = total_character_set. graphemes ( true ) ;
76- let char_count = graphemes. clone ( ) . count ( ) ;
110+ let total_character_set = format ! ( "{character_set}{include_symbols}" ) ;
111+ let graphemes = total_character_set. graphemes ( true ) . collect :: < Vec < & str > > ( ) ;
112+ let grapheme_count = graphemes. len ( ) ;
77113
78- if !allow_duplicates {
79- let mut current = min_length;
80- while current <= max_length {
81- max_count += ( char_count as f64 ) . powf ( current as f64 ) ;
82- current += 1 ;
83- }
114+ if grapheme_count == 0 {
115+ return Ok ( Vec :: new ( ) ) ;
84116 }
85117
118+ let target_amount = if allow_duplicates {
119+ requested_amount
120+ } else {
121+ requested_amount. min ( count_unique_passwords (
122+ min_length,
123+ max_length,
124+ grapheme_count,
125+ ) )
126+ } ;
127+
128+ if target_amount == 0 {
129+ return Ok ( Vec :: new ( ) ) ;
130+ }
131+
132+ let mut password_list = Vec :: with_capacity ( target_amount) ;
133+ let mut seen_passwords = ( !allow_duplicates) . then ( || HashSet :: with_capacity ( target_amount) ) ;
86134 let mut rng = rand:: rng ( ) ;
87- let chars = graphemes. collect :: < Vec < & str > > ( ) ;
88- for _n in 0 ..amount {
89- let mut can_continue = false ;
90- while !can_continue {
91- let mut password = String :: from ( "" ) ;
92- let length = rng. random_range ( min_length..( max_length + 1 ) ) ;
93- for _j in 0 ..length {
94- let index = rng. random_range ( 0 ..char_count) ;
95- password. push_str ( chars. clone ( ) [ index] ) ;
96- }
97135
98- if allow_duplicates || !password_list. contains ( & password) {
99- password_list. push ( password) ;
100- can_continue = true ;
101- }
136+ while password_list. len ( ) < target_amount {
137+ let length = rng. random_range ( min_length..=max_length) ;
138+ let mut password = String :: new ( ) ;
139+
140+ for _ in 0 ..length {
141+ let index = rng. random_range ( 0 ..grapheme_count) ;
142+ password. push_str ( graphemes[ index] ) ;
143+ }
102144
103- if !can_continue && !allow_duplicates && password_list. len ( ) as f64 == max_count {
104- return Ok ( password_list) ;
145+ if let Some ( seen_passwords) = seen_passwords. as_mut ( ) {
146+ if !seen_passwords. insert ( password. clone ( ) ) {
147+ continue ;
105148 }
106149 }
150+
151+ password_list. push ( password) ;
107152 }
108153
109154 Ok ( password_list)
0 commit comments