1- //! Devolutions product information (https://devolutions.net/productinfo.htm ) parser
1+ //! Devolutions product information (https://devolutions.net/productinfo.json ) parser
22
3+ use serde:: { Deserialize , Serialize } ;
34use std:: collections:: HashMap ;
45use std:: str:: FromStr ;
56
67use crate :: updater:: UpdaterError ;
78
9+ /// Information about a product file available for download
10+ #[ derive( Debug , Clone , Deserialize , Serialize ) ]
11+ pub ( crate ) struct ProductFile {
12+ #[ serde( rename = "Arch" ) ]
13+ pub arch : String ,
14+ #[ serde( rename = "Type" ) ]
15+ pub file_type : String ,
16+ #[ serde( rename = "Url" ) ]
17+ pub url : String ,
18+ #[ serde( rename = "Hash" ) ]
19+ pub hash : String ,
20+ }
21+
22+ /// Product information for a specific channel (Current, Beta, Update, Stable)
23+ #[ derive( Debug , Clone , Deserialize , Serialize ) ]
24+ pub ( crate ) struct ChannelData {
25+ #[ serde( rename = "Version" ) ]
26+ pub version : String ,
27+ #[ serde( rename = "Files" ) ]
28+ pub files : Vec < ProductFile > ,
29+ }
30+
31+ /// Product information containing multiple channels
32+ #[ derive( Debug , Clone , Deserialize , Serialize ) ]
33+ pub ( crate ) struct ProductData {
34+ #[ serde( rename = "Current" ) ]
35+ pub current : Option < ChannelData > ,
36+ #[ serde( rename = "Beta" ) ]
37+ pub beta : Option < ChannelData > ,
38+ #[ serde( rename = "Update" ) ]
39+ pub update : Option < ChannelData > ,
40+ #[ serde( rename = "Stable" ) ]
41+ pub stable : Option < ChannelData > ,
42+ }
43+
844#[ derive( Debug , Clone , Default ) ]
945pub ( crate ) struct ProductInfo {
1046 pub version : String ,
@@ -16,32 +52,61 @@ pub(crate) struct ProductInfoDb {
1652 pub records : HashMap < String , ProductInfo > ,
1753}
1854
55+ /// Determine the target architecture at compile time or runtime, defaulting to x64
56+ fn get_target_arch ( ) -> String {
57+ if cfg ! ( target_arch = "x86_64" ) {
58+ "x64" . to_owned ( )
59+ } else if cfg ! ( target_arch = "aarch64" ) {
60+ "arm64" . to_owned ( )
61+ } else {
62+ // Runtime fallback: check the environment, default to x64
63+ match std:: env:: consts:: ARCH {
64+ "x86_64" => "x64" . to_owned ( ) ,
65+ "aarch64" => "arm64" . to_owned ( ) ,
66+ _ => "x64" . to_owned ( ) , // Default to x64 for unknown architectures
67+ }
68+ }
69+ }
70+
71+ /// Select a file from the product files matching the target architecture and type
72+ fn select_file ( files : & [ ProductFile ] , target_arch : & str , file_type : & str ) -> Option < ProductFile > {
73+ files
74+ . iter ( )
75+ . find ( |f| f. arch == target_arch && f. file_type == file_type)
76+ . cloned ( )
77+ }
78+
1979impl FromStr for ProductInfoDb {
2080 type Err = UpdaterError ;
2181
2282 fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
83+ // Parse the JSON content
84+ let json: serde_json:: Value = serde_json:: from_str ( s) . map_err ( |_| UpdaterError :: ProductInfo ) ?;
85+
2386 let mut records = HashMap :: new ( ) ;
87+ let target_arch = get_target_arch ( ) ;
2488
25- for line in s. lines ( ) {
26- if line. is_empty ( ) {
27- continue ;
28- }
89+ // Iterate through products in the JSON object
90+ if let Some ( obj) = json. as_object ( ) {
91+ for ( product_name, product_value) in obj {
92+ // Try to deserialize the product data
93+ let product_data: ProductData =
94+ serde_json:: from_value ( product_value. clone ( ) ) . map_err ( |_| UpdaterError :: ProductInfo ) ?;
2995
30- let ( key, value) = line. split_once ( '=' ) . ok_or ( UpdaterError :: ProductInfo ) ?;
31- let ( product_id, property) = key. split_once ( '.' ) . ok_or ( UpdaterError :: ProductInfo ) ?;
32-
33- let entry = records
34- . entry ( product_id. to_owned ( ) )
35- . or_insert_with ( ProductInfo :: default) ;
36-
37- match property {
38- "Version" => entry. version = value. to_owned ( ) ,
39- "Url" => entry. url = value. to_owned ( ) ,
40- "hash" => entry. hash = Some ( value. to_owned ( ) ) ,
41- _ => {
42- trace ! ( %product_id, %property, "Unknown productinfo property" ) ;
43- continue ;
44- }
96+ // Use Current channel for now (as specified)
97+ let channel = product_data. current . ok_or ( UpdaterError :: ProductInfo ) ?;
98+
99+ // Select the appropriate file based on architecture and type (msi)
100+ let selected_file =
101+ select_file ( & channel. files , & target_arch, "msi" ) . ok_or ( UpdaterError :: ProductInfo ) ?;
102+
103+ let product_info = ProductInfo {
104+ version : channel. version . clone ( ) ,
105+ hash : Some ( selected_file. hash . clone ( ) ) ,
106+ url : selected_file. url . clone ( ) ,
107+ } ;
108+
109+ records. insert ( product_name. clone ( ) , product_info) ;
45110 }
46111 }
47112
@@ -65,63 +130,24 @@ mod tests {
65130 let input = include_str ! ( "../../../test_assets/test_asset_db" ) ;
66131 let db: ProductInfoDb = input. parse ( ) . expect ( "failed to parse product info database" ) ;
67132
68- assert_eq ! ( db. get( "Gatewaybin " ) . expect( "product not found" ) . version, "2024.2.1.0" ) ;
133+ assert_eq ! ( db. get( "Gateway " ) . expect( "product not found" ) . version, "2024.2.1.0" ) ;
69134 assert_eq ! (
70- db. get( "Gatewaybin " ) . expect( "product not found" ) . url,
135+ db. get( "Gateway " ) . expect( "product not found" ) . url,
71136 "https://cdn.devolutions.net/download/DevolutionsGateway-x86_64-2024.2.1.0.msi"
72137 ) ;
73138 assert_eq ! (
74- db. get( "Gatewaybin " ) . expect( "product not found" ) . hash. as_deref( ) ,
139+ db. get( "Gateway " ) . expect( "product not found" ) . hash. as_deref( ) ,
75140 Some ( "BD2805075FCD78AC339126F4C4D9E6773DC3127CBE7DF48256D6910FA0C59C35" )
76141 ) ;
77142
143+ assert_eq ! ( db. get( "HubServices" ) . expect( "product not found" ) . version, "2024.2.1.0" ) ;
78144 assert_eq ! (
79- db. get( "GatewaybinBeta " ) . expect( "product not found" ) . version ,
80- "2024.2.1.0"
145+ db. get( "HubServices " ) . expect( "product not found" ) . url ,
146+ "https://cdn.devolutions.net/download/HubServices-x86_64- 2024.2.1.0.msi "
81147 ) ;
82148 assert_eq ! (
83- db. get( "GatewaybinBeta" ) . expect( "product not found" ) . url,
84- "https://cdn.devolutions.net/download/DevolutionsGateway-x86_64-2024.2.1.0.msi"
85- ) ;
86- assert_eq ! (
87- db. get( "GatewaybinBeta" ) . expect( "product not found" ) . hash. as_deref( ) ,
88- Some ( "BD2805075FCD78AC339126F4C4D9E6773DC3127CBE7DF48256D6910FA0C59C35" )
89- ) ;
90-
91- assert_eq ! (
92- db. get( "GatewaybinDebX64" ) . expect( "product not found" ) . version,
93- "2024.2.1.0"
94- ) ;
95- assert_eq ! (
96- db. get( "GatewaybinDebX64" ) . expect( "product not found" ) . url,
97- "https://cdn.devolutions.net/download/devolutions-gateway_2024.2.1.0_amd64.deb"
98- ) ;
99- assert_eq ! (
100- db. get( "GatewaybinDebX64" ) . expect( "product not found" ) . hash. as_deref( ) ,
101- Some ( "72D7A836A6AF221D4E7631D27B91A358915CF985AA544CC0F7F5612B85E989AA" )
102- ) ;
103-
104- assert_eq ! (
105- db. get( "GatewaybinDebX64Beta" ) . expect( "product not found" ) . version,
106- "2024.2.1.0"
107- ) ;
108- assert_eq ! (
109- db. get( "GatewaybinDebX64Beta" ) . expect( "product not found" ) . url,
110- "https://cdn.devolutions.net/download/devolutions-gateway_2024.2.1.0_amd64.deb"
111- ) ;
112- assert_eq ! (
113- db. get( "GatewaybinDebX64Beta" )
114- . expect( "product not found" )
115- . hash
116- . as_deref( ) ,
149+ db. get( "HubServices" ) . expect( "product not found" ) . hash. as_deref( ) ,
117150 Some ( "72D7A836A6AF221D4E7631D27B91A358915CF985AA544CC0F7F5612B85E989AA" )
118151 ) ;
119-
120- assert_eq ! ( db. get( "DevoCLIbin" ) . expect( "product not found" ) . version, "2023.3.0.0" ) ;
121- assert_eq ! (
122- db. get( "DevoCLIbin" ) . expect( "product not found" ) . url,
123- "https://cdn.devolutions.net/download/DevoCLI.2023.3.0.0.zip"
124- ) ;
125- assert_eq ! ( db. get( "DevoCLIbin" ) . expect( "product not found" ) . hash, None ) ;
126152 }
127153}
0 commit comments