1
- use std:: io:: { self , Write } ;
1
+ use std:: io:: { self , BufRead , BufReader , Write } ;
2
2
use std:: process:: { Command , Stdio } ;
3
- use std:: thread;
3
+ use std:: sync:: { Arc , Mutex } ;
4
+ use std:: thread:: { self , JoinHandle } ;
4
5
use std:: time:: Duration ;
5
6
use colored:: * ;
6
7
use indicatif:: { ProgressBar , ProgressStyle , MultiProgress } ;
@@ -11,15 +12,16 @@ use crossterm::{
11
12
} ;
12
13
13
14
// Constants for styling
14
- const HEADER_WIDTH : usize = 60 ;
15
- const SPINNER_TICK_CHARS : & str = "⣾⣷⣯⣟⡿⢿⣻⣽ " ;
16
- const PROGRESS_BAR_CHARS : & str = "█▉▊▋▌▍▎▏ " ;
15
+ const HEADER_WIDTH : usize = 80 ;
16
+ const SPINNER_TICK_CHARS : & str = "⠁⠂⠄⠈⠐⠠⢀⡀ " ;
17
+ const PROGRESS_BAR_CHARS : & str = "█▓▒░ " ;
17
18
18
19
// Structure to hold command details
19
20
struct CommandInfo {
20
21
name : & ' static str ,
21
22
cmd : & ' static str ,
22
23
color : Color ,
24
+ list_cmd : Option < & ' static str > ,
23
25
}
24
26
25
27
// Structure to manage update sections
@@ -43,23 +45,27 @@ fn main() -> io::Result<()> {
43
45
commands: vec![
44
46
CommandInfo {
45
47
name: "APT Update" ,
46
- cmd: "sudo /usr/lib/HackerOS/ apt update" ,
48
+ cmd: "sudo apt update" ,
47
49
color: Color :: BrightMagenta ,
50
+ list_cmd: None ,
48
51
} ,
49
52
CommandInfo {
50
53
name: "APT Upgrade" ,
51
- cmd: "sudo /usr/lib/HackerOS/ apt upgrade -y" ,
54
+ cmd: "sudo apt upgrade -y" ,
52
55
color: Color :: BrightMagenta ,
56
+ list_cmd: Some ( "apt list --upgradable" ) ,
53
57
} ,
54
58
CommandInfo {
55
59
name: "APT Autoremove" ,
56
- cmd: "sudo /usr/lib/HackerOS/ apt autoremove -y" ,
60
+ cmd: "sudo apt autoremove -y" ,
57
61
color: Color :: BrightMagenta ,
62
+ list_cmd: None ,
58
63
} ,
59
64
CommandInfo {
60
65
name: "APT Autoclean" ,
61
- cmd: "sudo /usr/lib/HackerOS/ apt autoclean" ,
66
+ cmd: "sudo apt autoclean" ,
62
67
color: Color :: BrightMagenta ,
68
+ list_cmd: None ,
63
69
} ,
64
70
] ,
65
71
} ,
@@ -70,6 +76,7 @@ fn main() -> io::Result<()> {
70
76
name: "Flatpak Update" ,
71
77
cmd: "flatpak update -y" ,
72
78
color: Color :: BrightYellow ,
79
+ list_cmd: Some ( "flatpak remote-ls --updates flathub" ) ,
73
80
} ,
74
81
] ,
75
82
} ,
@@ -80,6 +87,7 @@ fn main() -> io::Result<()> {
80
87
name: "Snap Refresh" ,
81
88
cmd: "sudo snap refresh" ,
82
89
color: Color :: BrightBlue ,
90
+ list_cmd: Some ( "snap refresh --list" ) ,
83
91
} ,
84
92
] ,
85
93
} ,
@@ -90,11 +98,13 @@ fn main() -> io::Result<()> {
90
98
name: "Firmware Refresh" ,
91
99
cmd: "sudo fwupdmgr refresh" ,
92
100
color: Color :: BrightGreen ,
101
+ list_cmd: None ,
93
102
} ,
94
103
CommandInfo {
95
104
name: "Firmware Update" ,
96
105
cmd: "sudo fwupdmgr update" ,
97
106
color: Color :: BrightGreen ,
107
+ list_cmd: Some ( "fwupdmgr get-updates" ) ,
98
108
} ,
99
109
] ,
100
110
} ,
@@ -105,6 +115,7 @@ fn main() -> io::Result<()> {
105
115
name: "HackerOS Script" ,
106
116
cmd: "/usr/share/HackerOS/Scripts/Bin/Update-usrshare.sh" ,
107
117
color: Color :: Magenta ,
118
+ list_cmd: None ,
108
119
} ,
109
120
] ,
110
121
} ,
@@ -116,59 +127,137 @@ fn main() -> io::Result<()> {
116
127
// Process each update section
117
128
for section in update_sections {
118
129
print_section_header ( & section. name ) ;
119
- let total_steps = section. commands . len ( ) as u64 * 100 ;
130
+ let total_steps = section. commands . len ( ) as u64 ;
120
131
let pb = multi_pb. add ( ProgressBar :: new ( total_steps) ) ;
121
132
pb. set_style (
122
133
ProgressStyle :: with_template (
123
- "{prefix:.bold.dim} {spinner:.cyan/blue} [{bar:40 .cyan/blue}] {percent}% | {msg} | ETA: {eta_precise }"
134
+ "{prefix:.bold.dim} {spinner:.cyan/blue} [{wide_bar: .cyan/blue}] {pos}/{len} | {msg:.white.bold } | ETA: {eta }"
124
135
)
125
136
. unwrap ( )
126
137
. progress_chars ( PROGRESS_BAR_CHARS )
127
138
. tick_chars ( SPINNER_TICK_CHARS ) ,
128
139
) ;
129
- pb. set_prefix ( format ! ( "{} " , section. name) ) ;
140
+ pb. set_prefix ( format ! ( "{:<30} " , section. name. bright_cyan ( ) ) ) ;
130
141
pb. enable_steady_tick ( Duration :: from_millis ( 50 ) ) ;
131
142
132
143
for cmd_info in section. commands {
133
- pb. set_message ( format ! ( "{}" , cmd_info. name. bright_white( ) . bold( ) ) ) ;
134
- // Simulate progress
135
- for _ in 0 ..100 {
136
- pb. inc ( 1 ) ;
137
- thread:: sleep ( Duration :: from_millis ( 20 ) ) ;
144
+ pb. set_message ( format ! ( "{}" , cmd_info. name) ) ;
145
+ if let Some ( list_cmd) = cmd_info. list_cmd {
146
+ let list_spinner = multi_pb. add ( ProgressBar :: new_spinner ( ) ) ;
147
+ list_spinner. set_style (
148
+ ProgressStyle :: with_template ( "{spinner:.green} {msg:.white}" )
149
+ . unwrap ( )
150
+ . tick_chars ( SPINNER_TICK_CHARS ) ,
151
+ ) ;
152
+ list_spinner. set_message ( format ! ( "Listing updates for {}..." , cmd_info. name) ) ;
153
+ list_spinner. enable_steady_tick ( Duration :: from_millis ( 40 ) ) ;
154
+
155
+ let list_output = Command :: new ( "sh" )
156
+ . arg ( "-c" )
157
+ . arg ( list_cmd)
158
+ . stdout ( Stdio :: piped ( ) )
159
+ . stderr ( Stdio :: piped ( ) )
160
+ . output ( ) ;
161
+
162
+ list_spinner. finish_and_clear ( ) ;
163
+ match list_output {
164
+ Ok ( o) => {
165
+ let list_stdout = String :: from_utf8_lossy ( & o. stdout ) . to_string ( ) ;
166
+ let list_stderr = String :: from_utf8_lossy ( & o. stderr ) . to_string ( ) ;
167
+ if !list_stdout. trim ( ) . is_empty ( ) && o. status . success ( ) {
168
+ println ! (
169
+ "{}" ,
170
+ format!( "Updates available for {}:" , cmd_info. name) . bold( ) . color( cmd_info. color)
171
+ ) ;
172
+ let mut count = 0 ;
173
+ for line in list_stdout. lines ( ) . filter ( |l| !l. trim ( ) . is_empty ( ) && !l. contains ( "Listing..." ) && !l. contains ( "All snaps up to date." ) ) {
174
+ println ! ( " {:2}. {}" , count + 1 , line. bright_white( ) ) ;
175
+ count += 1 ;
176
+ }
177
+ if count == 0 {
178
+ println ! ( "{}" , " None" . bright_white( ) ) ;
179
+ }
180
+ } else if !list_stderr. is_empty ( ) {
181
+ println ! ( "{}" , format!( "Error listing updates: {}" , list_stderr) . bright_red( ) ) ;
182
+ } else {
183
+ println ! (
184
+ "{}" ,
185
+ format!( "No updates available for {}." , cmd_info. name) . bright_green( )
186
+ ) ;
187
+ }
188
+ }
189
+ Err ( e) => {
190
+ println ! ( "{}" , format!( "Failed to list updates: {}" , e) . bright_red( ) ) ;
191
+ }
192
+ }
193
+ println ! ( ) ;
138
194
}
195
+
139
196
let spinner = multi_pb. add ( ProgressBar :: new_spinner ( ) ) ;
140
197
spinner. set_style (
141
- ProgressStyle :: with_template ( "{spinner:.green} {msg}" )
198
+ ProgressStyle :: with_template ( "{spinner:.green} {msg:.white }" )
142
199
. unwrap ( )
143
200
. tick_chars ( SPINNER_TICK_CHARS ) ,
144
201
) ;
145
202
spinner. set_message ( format ! ( "Executing: {}" , cmd_info. name) ) ;
146
203
spinner. enable_steady_tick ( Duration :: from_millis ( 40 ) ) ;
147
204
148
- let output = Command :: new ( "sh" )
149
- . arg ( "-c" )
150
- . arg ( cmd_info. cmd )
151
- . stdout ( Stdio :: piped ( ) )
152
- . stderr ( Stdio :: piped ( ) )
153
- . output ( ) ;
205
+ // Spawn the command
206
+ let mut child = Command :: new ( "sh" )
207
+ . arg ( "-c" )
208
+ . arg ( cmd_info. cmd )
209
+ . stdout ( Stdio :: piped ( ) )
210
+ . stderr ( Stdio :: piped ( ) )
211
+ . spawn ( ) ?;
154
212
155
- spinner. finish_and_clear ( ) ;
156
- match output {
157
- Ok ( output) => {
158
- let stdout = String :: from_utf8_lossy ( & output. stdout ) . to_string ( ) ;
159
- let stderr = String :: from_utf8_lossy ( & output. stderr ) . to_string ( ) ;
160
- let success = output. status . success ( ) ;
161
- logs. push ( ( cmd_info. name . to_string ( ) , stdout. clone ( ) , true ) ) ;
162
- if !stderr. is_empty ( ) {
163
- logs. push ( ( cmd_info. name . to_string ( ) , stderr. clone ( ) , false ) ) ;
164
- }
165
- print_command_result ( & cmd_info. name , success, cmd_info. color ) ;
213
+ let stdout_reader = BufReader :: new ( child. stdout . take ( ) . unwrap ( ) ) ;
214
+ let stderr_reader = BufReader :: new ( child. stderr . take ( ) . unwrap ( ) ) ;
215
+
216
+ let stdout_lines = Arc :: new ( Mutex :: new ( Vec :: < String > :: new ( ) ) ) ;
217
+ let stderr_lines = Arc :: new ( Mutex :: new ( Vec :: < String > :: new ( ) ) ) ;
218
+
219
+ let stdout_lines_clone = Arc :: clone ( & stdout_lines) ;
220
+ let stderr_lines_clone = Arc :: clone ( & stderr_lines) ;
221
+ let cmd_color = cmd_info. color ;
222
+
223
+ // Threads to read stdout and stderr
224
+ let stdout_handle: JoinHandle < ( ) > = thread:: spawn ( move || {
225
+ for line in stdout_reader. lines ( ) . flatten ( ) {
226
+ println ! ( "{}" , line. color( cmd_color) ) ;
227
+ stdout_lines_clone. lock ( ) . unwrap ( ) . push ( line) ;
166
228
}
167
- Err ( e) => {
168
- logs. push ( ( cmd_info. name . to_string ( ) , format ! ( "Failed: {}" , e) , false ) ) ;
169
- print_command_result ( & cmd_info. name , false , cmd_info. color ) ;
229
+ } ) ;
230
+
231
+ let stderr_handle: JoinHandle < ( ) > = thread:: spawn ( move || {
232
+ for line in stderr_reader. lines ( ) . flatten ( ) {
233
+ println ! ( "{}" , line. bright_red( ) ) ;
234
+ stderr_lines_clone. lock ( ) . unwrap ( ) . push ( line) ;
170
235
}
236
+ } ) ;
237
+
238
+ // Wait for threads to finish
239
+ stdout_handle. join ( ) . unwrap ( ) ;
240
+ stderr_handle. join ( ) . unwrap ( ) ;
241
+
242
+ // Wait for child and get status
243
+ let status = child. wait ( ) ?;
244
+ let success = status. success ( ) ;
245
+
246
+ let stdout = stdout_lines. lock ( ) . unwrap ( ) . join ( "\n " ) ;
247
+ let stderr = stderr_lines. lock ( ) . unwrap ( ) . join ( "\n " ) ;
248
+
249
+ spinner. finish_with_message ( format ! (
250
+ "{}: {}" ,
251
+ cmd_info. name,
252
+ if success { "Completed" . bright_green( ) . bold( ) } else { "Failed" . bright_red( ) . bold( ) }
253
+ ) ) ;
254
+ println ! ( ) ;
255
+
256
+ logs. push ( ( cmd_info. name . to_string ( ) , stdout. clone ( ) , true ) ) ;
257
+ if !stderr. is_empty ( ) {
258
+ logs. push ( ( cmd_info. name . to_string ( ) , stderr. clone ( ) , false ) ) ;
171
259
}
260
+ pb. inc ( 1 ) ;
172
261
}
173
262
pb. finish_with_message ( format ! ( "{} completed" , section. name. bright_green( ) . bold( ) ) ) ;
174
263
println ! ( ) ;
@@ -225,66 +314,58 @@ fn main() -> io::Result<()> {
225
314
226
315
// Helper functions
227
316
fn print_header ( ) {
228
- let title = "HackerOS Update Utility" ;
229
- let _padding = ( HEADER_WIDTH - title. len ( ) ) / 2 ; // Unused but kept for potential future use
230
- println ! ( "{}" , "═" . repeat( HEADER_WIDTH ) . bright_green( ) . bold( ) ) ;
231
- println ! ( "{}" , format!( "║{:^width$}║" , title. bright_cyan( ) . bold( ) , width = HEADER_WIDTH - 2 ) . on_bright_black( ) ) ;
232
- println ! ( "{}" , "═" . repeat( HEADER_WIDTH ) . bright_green( ) . bold( ) ) ;
233
- println ! ( "{}" , "Initializing updates..." . bright_blue( ) . italic( ) . bold( ) ) ;
317
+ let title = "HackerOS Update Utility v0.0.1" ;
318
+ println ! ( "{}" , "┏" . to_string( ) + & "━" . repeat( HEADER_WIDTH - 2 ) + & "┓" . bright_green( ) . bold( ) ) ;
319
+ println ! ( "{}" , format!( "┃{:^width$}┃" , title. bright_cyan( ) . bold( ) , width = HEADER_WIDTH - 2 ) . on_bright_black( ) ) ;
320
+ println ! ( "{}" , "┗" . to_string( ) + & "━" . repeat( HEADER_WIDTH - 2 ) + & "┛" . bright_green( ) . bold( ) ) ;
321
+ println ! ( "{}" , format!( "{:^width$}" , "Initializing updates..." . bright_blue( ) . italic( ) . bold( ) , width = HEADER_WIDTH ) ) ;
234
322
println ! ( ) ;
235
323
}
236
324
237
325
fn print_section_header ( name : & str ) {
238
326
let padding = ( HEADER_WIDTH - name. len ( ) - 4 ) / 2 ;
239
327
println ! (
240
328
"{}" ,
241
- format!( "╠ {} {} {}{}╣ " , "═ " . repeat( padding) , name, "═ " . repeat( padding) , if ( name. len( ) + 4 ) % 2 == 1 { "═ " } else { "" } )
242
- . white ( ) . bold ( ) . on_color( get_section_color( name) )
329
+ format!( "┣ {} {} {}{}┫ " , "━ " . repeat( padding) , name. bright_white ( ) . bold ( ) , "━ " . repeat( padding) , if ( name. len( ) + 4 ) % 2 == 1 { "━ " } else { "" } )
330
+ . on_color( get_section_color( name) )
243
331
) ;
244
332
println ! ( ) ;
245
333
}
246
334
247
- fn print_command_result ( name : & str , success : bool , color : Color ) {
248
- let status = if success { "Completed" } else { "Failed" } ;
249
- let _status_color = if success { Color :: BrightGreen } else { Color :: BrightRed } ; // Unused but kept for potential future use
250
- println ! (
251
- "{}" ,
252
- format!( "╠═ {} {} ═╝" , status, name) . white( ) . bold( ) . on_color( color)
253
- ) ;
254
- }
255
-
256
335
fn print_menu ( ) {
257
- println ! ( "{}" , format!( "{}" , "╒" . to_string( ) + & "═" . repeat( HEADER_WIDTH - 2 ) + & "╕" . bright_cyan( ) . bold( ) ) ) ;
258
- println ! ( "{}" , format!( "│{:^width$}│" , "Update Process Completed" , width = HEADER_WIDTH - 2 ) . white( ) . bold( ) . on_bright_black( ) ) ;
259
- println ! ( "{}" , format!( "{}" , "├" . to_string( ) + & "─" . repeat( HEADER_WIDTH - 2 ) + & "┤" . bright_cyan( ) . bold( ) ) ) ;
260
- println ! ( "{}" , format!( "│{:^width$}│" , "(E)xit (S)hutdown (R)eboot" , width = HEADER_WIDTH - 2 ) . bright_yellow( ) . bold( ) ) ;
261
- println ! ( "{}" , format!( "│{:^width$}│" , "(L)og Out (T)ry Again (H) Show Logs" , width = HEADER_WIDTH - 2 ) . bright_yellow( ) . bold( ) ) ;
262
- println ! ( "{}" , format!( "{}" , "╘" . to_string( ) + & "═" . repeat( HEADER_WIDTH - 2 ) + & "╛" . bright_cyan( ) . bold( ) ) ) ;
263
- println ! ( "{}" , "Select an option:" . white( ) . italic( ) . bold( ) ) ;
336
+ println ! ( "{}" , format!( "{}" , "┏" . to_string( ) + & "━" . repeat( HEADER_WIDTH - 2 ) + & "┓" . bright_cyan( ) . bold( ) ) ) ;
337
+ println ! ( "{}" , format!( "┃{:^width$}┃" , "Update Process Completed" . bright_green( ) . bold( ) , width = HEADER_WIDTH - 2 ) . on_bright_black( ) ) ;
338
+ println ! ( "{}" , format!( "{}" , "┣" . to_string( ) + & "━" . repeat( HEADER_WIDTH - 2 ) + & "┫" . bright_cyan( ) . bold( ) ) ) ;
339
+ println ! ( "{}" , format!( "┃{:^width$}┃" , "Choose an action:" , width = HEADER_WIDTH - 2 ) . bright_white( ) . bold( ) ) ;
340
+ println ! ( "{}" , format!( "┃{:^width$}┃" , "(E)xit (S)hutdown (R)eboot" , width = HEADER_WIDTH - 2 ) . bright_yellow( ) ) ;
341
+ println ! ( "{}" , format!( "┃{:^width$}┃" , "(L)og Out (T)ry Again (H) Show Logs" , width = HEADER_WIDTH - 2 ) . bright_yellow( ) ) ;
342
+ println ! ( "{}" , format!( "{}" , "┗" . to_string( ) + & "━" . repeat( HEADER_WIDTH - 2 ) + & "┛" . bright_cyan( ) . bold( ) ) ) ;
343
+ println ! ( "{}" , format!( "{:^width$}" , "Select an option:" . white( ) . italic( ) . bold( ) , width = HEADER_WIDTH ) ) ;
264
344
}
265
345
266
346
fn print_logs ( logs : & [ ( String , String , bool ) ] ) {
267
- println ! ( "{}" , format!( "{}" , "╒ " . to_string( ) + & "═ " . repeat( HEADER_WIDTH - 2 ) + & "╕ " . bright_cyan( ) . bold( ) ) ) ;
268
- println ! ( "{}" , format!( "│ {:^width$}│ " , "Update Logs" , width = HEADER_WIDTH - 2 ) . white( ) . bold( ) . on_bright_cyan( ) ) ;
269
- println ! ( "{}" , format!( "{}" , "├ " . to_string( ) + & "─ " . repeat( HEADER_WIDTH - 2 ) + & "┤ " . bright_cyan( ) . bold( ) ) ) ;
347
+ println ! ( "{}" , format!( "{}" , "┏ " . to_string( ) + & "━ " . repeat( HEADER_WIDTH - 2 ) + & "┓ " . bright_cyan( ) . bold( ) ) ) ;
348
+ println ! ( "{}" , format!( "┃ {:^width$}┃ " , "Update Logs" , width = HEADER_WIDTH - 2 ) . white( ) . bold( ) . on_bright_cyan( ) ) ;
349
+ println ! ( "{}" , format!( "{}" , "┣ " . to_string( ) + & "━ " . repeat( HEADER_WIDTH - 2 ) + & "┫ " . bright_cyan( ) . bold( ) ) ) ;
270
350
for ( name, log, is_stdout) in logs {
271
351
let log_type = if * is_stdout { "Output" } else { "Error" } ;
272
- let log_color = if * is_stdout { Color :: White } else { Color :: BrightRed } ;
273
- let max_len = HEADER_WIDTH - 10 ;
274
- let truncated_log = if log. len ( ) > max_len { & log[ ..max_len] } else { log } ;
275
- println ! (
276
- "{}" ,
277
- format!( "│ {}: {} {}" , log_type, name, truncated_log) . color( log_color) . on_bright_black( )
278
- ) ;
352
+ let log_color = if * is_stdout { Color :: BrightWhite } else { Color :: BrightRed } ;
353
+ if !log. trim ( ) . is_empty ( ) {
354
+ println ! ( "{}" , format!( "┃ {} for {}:" , log_type, name) . bold( ) . color( log_color) ) ;
355
+ for line in log. lines ( ) {
356
+ println ! ( "{}" , format!( "┃ {}" , line) . color( log_color) ) ;
357
+ }
358
+ println ! ( "{}" , format!( "{}" , "┣" . to_string( ) + & "━" . repeat( HEADER_WIDTH - 2 ) + & "┫" . bright_black( ) ) ) ;
359
+ }
279
360
}
280
- println ! ( "{}" , format!( "{}" , "╘ " . to_string( ) + & "═ " . repeat( HEADER_WIDTH - 2 ) + & "╛ " . bright_cyan( ) . bold( ) ) ) ;
361
+ println ! ( "{}" , format!( "{}" , "┗ " . to_string( ) + & "━ " . repeat( HEADER_WIDTH - 2 ) + & "┛ " . bright_cyan( ) . bold( ) ) ) ;
281
362
println ! ( ) ;
282
363
}
283
364
284
365
fn print_action ( message : & str , color : Color ) {
285
366
println ! (
286
367
"{}" ,
287
- format!( "╠═ {} ═╝ " , message) . white( ) . bold( ) . on_color( color)
368
+ format!( "┣━ {} ━┫ " , message) . white( ) . bold( ) . on_color( color)
288
369
) ;
289
370
thread:: sleep ( Duration :: from_millis ( 200 ) ) ;
290
371
}
0 commit comments