@@ -7,6 +7,7 @@ use reqwest::{Client, Error};
7
7
use colored:: * ;
8
8
9
9
use serde_json:: Value ;
10
+ use serde:: Deserialize ;
10
11
use semver:: { Version } ;
11
12
12
13
/// Reusable function that just prints success messages to the console
@@ -31,7 +32,7 @@ fn print_ascii_art() {
31
32
print_info ( art, false ) ;
32
33
print_info ( "\n Welcome to AdGuardian Terminal Edition!" , false ) ;
33
34
print_info ( "Terminal-based, real-time traffic monitoring and statistics for your AdGuard Home instance" , true ) ;
34
- print_info ( "For documentation and support, please visit: https://github.com/lissy93/adguardian-term\n " , true ) ;
35
+ print_info ( "For documentation and support, please visit: https://github.com/lissy93/adguardian-term" , true ) ;
35
36
}
36
37
37
38
/// Print error message, along with (optional) stack trace, then exit
@@ -151,9 +152,83 @@ async fn verify_connection(
151
152
}
152
153
}
153
154
155
+ #[ derive( Deserialize ) ]
156
+ struct CratesIoResponse {
157
+ #[ serde( rename = "crate" ) ]
158
+ krate : Crate ,
159
+ }
160
+
161
+ #[ derive( Deserialize ) ]
162
+ struct Crate {
163
+ max_version : String ,
164
+ }
165
+
166
+ /// Gets the latest version of the crate from crates.io
167
+ async fn get_latest_version ( crate_name : & str ) -> Result < String , Box < dyn std:: error:: Error > > {
168
+ let url = format ! ( "https://crates.io/api/v1/crates/{}" , crate_name) ;
169
+ let client = reqwest:: Client :: new ( ) ;
170
+ let res = client. get ( & url)
171
+ . header ( reqwest:: header:: USER_AGENT , "version_check (adguardian.as93.net)" )
172
+ . send ( )
173
+ . await ?;
174
+
175
+ if res. status ( ) . is_success ( ) {
176
+ let response: CratesIoResponse = res. json ( ) . await ?;
177
+ Ok ( response. krate . max_version )
178
+ } else {
179
+ let status = res. status ( ) ;
180
+ let body = res. text ( ) . await ?;
181
+ Err ( format ! ( "Request failed with status {}: body: {}" , status, body) . into ( ) )
182
+ }
183
+ }
184
+
185
+ /// Checks for updates to the crate, and prints a message if an update is available
186
+ async fn check_for_updates ( ) {
187
+ // Get crate name and version from Cargo.toml
188
+ let crate_name = env ! ( "CARGO_PKG_NAME" ) ;
189
+ let crate_version = env ! ( "CARGO_PKG_VERSION" ) ;
190
+ println ! ( "{}" , "\n Checking for updates..." . blue( ) ) ;
191
+ // Parse the current version, and fetch and parse the latest version
192
+ let current_version = Version :: parse ( crate_version) . unwrap_or_else ( |_| {
193
+ Version :: parse ( "0.0.0" ) . unwrap ( )
194
+ } ) ;
195
+ let latest_version = Version :: parse (
196
+ & get_latest_version ( crate_name) . await . unwrap_or_else ( |_| {
197
+ "0.0.0" . to_string ( )
198
+ } )
199
+ ) . unwrap ( ) ;
200
+
201
+ // Compare the current and latest versions, and print the appropriate message
202
+ if current_version == Version :: parse ( "0.0.0" ) . unwrap ( ) || latest_version == Version :: parse ( "0.0.0" ) . unwrap ( ) {
203
+ println ! ( "{}" , "Unable to check for updates" . yellow( ) ) ;
204
+ } else if current_version < latest_version {
205
+ println ! ( "{}" ,
206
+ format!(
207
+ "A new version of AdGuardian is available.\n Update from {} to {} for the best experience" ,
208
+ current_version. to_string( ) . bold( ) ,
209
+ latest_version. to_string( ) . bold( )
210
+ ) . yellow( )
211
+ ) ;
212
+ } else if current_version == latest_version {
213
+ println ! (
214
+ "{}" ,
215
+ format!( "AdGuardian is up-to-date, running version {}" , current_version. to_string( ) . bold( ) ) . green( )
216
+ ) ;
217
+ } else if current_version > latest_version {
218
+ println ! (
219
+ "{}" ,
220
+ format!( "Running a pre-released edition of AdGuardian, version {}" , current_version. to_string( ) . bold( ) ) . green( )
221
+ ) ;
222
+ } else {
223
+ println ! ( "{}" , "Unable to check for updates" . yellow( ) ) ;
224
+ }
225
+ }
226
+
227
+
154
228
/// Initiate the welcome script
155
229
/// This function will:
156
230
/// - Print the AdGuardian ASCII art
231
+ /// - Check if there's an update available
157
232
/// - Check for the required environmental variables
158
233
/// - Prompt the user to enter any missing variables
159
234
/// - Verify the connection to the AdGuard instance
@@ -162,7 +237,11 @@ async fn verify_connection(
162
237
/// - Then either print a success message, or show instructions to fix and exit
163
238
pub async fn welcome ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
164
239
print_ascii_art ( ) ;
165
- println ! ( "{}" , "Starting initialization checks..." . blue( ) ) ;
240
+
241
+ // Check for updates
242
+ check_for_updates ( ) . await ;
243
+
244
+ println ! ( "{}" , "\n Starting initialization checks..." . blue( ) ) ;
166
245
167
246
let client = Client :: new ( ) ;
168
247
@@ -214,5 +293,7 @@ pub async fn welcome() -> Result<(), Box<dyn std::error::Error>> {
214
293
let password = get_env ( "ADGUARD_PASSWORD" ) ?;
215
294
216
295
// Verify that we can connect, authenticate, and that version is supported (exit on failure)
217
- verify_connection ( & client, ip, port, protocol, username, password) . await
296
+ verify_connection ( & client, ip, port, protocol, username, password) . await ?;
297
+
298
+ Ok ( ( ) )
218
299
}
0 commit comments