@@ -49,6 +49,38 @@ pub struct StrategyOptions {
4949 pub extra : HashMap < String , String > ,
5050}
5151
52+ struct InfoUploaded {
53+ uploaded : bool ,
54+ url : String ,
55+ last_uploaded : Option < Instant > ,
56+ id : String ,
57+ username : Option < String > ,
58+ }
59+
60+ impl Default for InfoUploaded {
61+ fn default ( ) -> Self {
62+ Self {
63+ uploaded : false ,
64+ url : "" . to_owned ( ) ,
65+ last_uploaded : None ,
66+ id : "" . to_owned ( ) ,
67+ username : None ,
68+ }
69+ }
70+ }
71+
72+ impl InfoUploaded {
73+ fn uploaded ( url : String , id : String , username : String ) -> Self {
74+ Self {
75+ uploaded : true ,
76+ url,
77+ last_uploaded : None ,
78+ id,
79+ username : Some ( username) ,
80+ }
81+ }
82+ }
83+
5284#[ cfg( not( any( target_os = "ios" ) ) ) ]
5385#[ tokio:: main( flavor = "current_thread" ) ]
5486async fn start_hbbs_sync_async ( ) {
@@ -57,8 +89,7 @@ async fn start_hbbs_sync_async() {
5789 TIME_CONN ,
5890 ) ) ;
5991 let mut last_sent: Option < Instant > = None ;
60- let mut info_uploaded: ( bool , String , Option < Instant > , String ) =
61- ( false , "" . to_owned ( ) , None , "" . to_owned ( ) ) ;
92+ let mut info_uploaded = InfoUploaded :: default ( ) ;
6293 let mut sysinfo_ver = "" . to_owned ( ) ;
6394 loop {
6495 tokio:: select! {
@@ -73,89 +104,104 @@ async fn start_hbbs_sync_async() {
73104 continue ;
74105 }
75106 let conns = Connection :: alive_conns( ) ;
76- if info_uploaded. 0 && ( url != info_uploaded. 1 || id != info_uploaded. 3 ) {
77- info_uploaded. 0 = false ;
107+ if info_uploaded. uploaded && ( url != info_uploaded. url || id != info_uploaded. id ) {
108+ info_uploaded. uploaded = false ;
78109 * PRO . lock( ) . unwrap( ) = false ;
79110 }
80- if !info_uploaded. 0 && info_uploaded. 2 . map( |x| x. elapsed( ) >= UPLOAD_SYSINFO_TIMEOUT ) . unwrap_or( true ) {
81- let mut v = crate :: get_sysinfo( ) ;
82- // username is empty in login screen of windows, but here we only upload sysinfo once, causing
83- // real user name not uploaded after login screen. https://github.com/rustdesk/rustdesk/discussions/8031
84- if !cfg!( windows) || !v[ "username" ] . as_str( ) . unwrap_or_default( ) . is_empty( ) {
85- v[ "version" ] = json!( crate :: VERSION ) ;
86- v[ "id" ] = json!( id) ;
87- v[ "uuid" ] = json!( crate :: encode64( hbb_common:: get_uuid( ) ) ) ;
88- let ab_name = Config :: get_option( keys:: OPTION_PRESET_ADDRESS_BOOK_NAME ) ;
89- if !ab_name. is_empty( ) {
90- v[ keys:: OPTION_PRESET_ADDRESS_BOOK_NAME ] = json!( ab_name) ;
91- }
92- let ab_tag = Config :: get_option( keys:: OPTION_PRESET_ADDRESS_BOOK_TAG ) ;
93- if !ab_tag. is_empty( ) {
94- v[ keys:: OPTION_PRESET_ADDRESS_BOOK_TAG ] = json!( ab_tag) ;
95- }
96- let username = get_builtin_option( keys:: OPTION_PRESET_USERNAME ) ;
97- if !username. is_empty( ) {
98- v[ keys:: OPTION_PRESET_USERNAME ] = json!( username) ;
99- }
100- let strategy_name = get_builtin_option( keys:: OPTION_PRESET_STRATEGY_NAME ) ;
101- if !strategy_name. is_empty( ) {
102- v[ keys:: OPTION_PRESET_STRATEGY_NAME ] = json!( strategy_name) ;
103- }
104- let device_group_name = get_builtin_option( keys:: OPTION_PRESET_DEVICE_GROUP_NAME ) ;
105- if !device_group_name. is_empty( ) {
106- v[ keys:: OPTION_PRESET_DEVICE_GROUP_NAME ] = json!( device_group_name) ;
107- }
108- let v = v. to_string( ) ;
109- let mut hash = "" . to_owned( ) ;
110- if crate :: is_public( & url) {
111- use sha2:: { Digest , Sha256 } ;
112- let mut hasher = Sha256 :: new( ) ;
113- hasher. update( url. as_bytes( ) ) ;
114- hasher. update( & v. as_bytes( ) ) ;
115- let res = hasher. finalize( ) ;
116- hash = hbb_common:: base64:: encode( & res[ ..] ) ;
117- let old_hash = config:: Status :: get( "sysinfo_hash" ) ;
118- let ver = config:: Status :: get( "sysinfo_ver" ) ; // sysinfo_ver is the version of sysinfo on server's side
119- if hash == old_hash {
120- // When the api doesn't exist, Ok("") will be returned in test.
121- let samever = match crate :: post_request( url. replace( "heartbeat" , "sysinfo_ver" ) , "" . to_owned( ) , "" ) . await {
122- Ok ( x) => {
123- sysinfo_ver = x. clone( ) ;
124- * PRO . lock( ) . unwrap( ) = true ;
125- x == ver
126- }
127- _ => {
128- false // to make sure Pro can be assigned in below post for old
129- // hbbs pro not supporting sysinfo_ver, use false for ensuring
130- }
131- } ;
132- if samever {
133- info_uploaded = ( true , url. clone( ) , None , id. clone( ) ) ;
134- log:: info!( "sysinfo not changed, skip upload" ) ;
135- continue ;
136- }
137- }
138- }
139- match crate :: post_request( url. replace( "heartbeat" , "sysinfo" ) , v, "" ) . await {
140- Ok ( x) => {
141- if x == "SYSINFO_UPDATED" {
142- info_uploaded = ( true , url. clone( ) , None , id. clone( ) ) ;
143- log:: info!( "sysinfo updated" ) ;
144- if !hash. is_empty( ) {
145- config:: Status :: set( "sysinfo_hash" , hash) ;
146- config:: Status :: set( "sysinfo_ver" , sysinfo_ver. clone( ) ) ;
147- }
111+ // For Windows:
112+ // We can't skip uploading sysinfo when the username is empty, because the username may
113+ // always be empty before login. We also need to upload the other sysinfo info.
114+ //
115+ // https://github.com/rustdesk/rustdesk/discussions/8031
116+ // We still need to check the username after uploading sysinfo, because
117+ // 1. The username may be empty when logining in, and it can be fetched after a while.
118+ // In this case, we need to upload sysinfo again.
119+ // 2. The username may be changed after uploading sysinfo, and we need to upload sysinfo again.
120+ //
121+ // The Windows session will switch to the last user session before the restart,
122+ // so it may be able to get the username before login.
123+ // But strangely, sometimes we can get the username before login,
124+ // we may not be able to get the username before login after the next restart.
125+ let mut v = crate :: get_sysinfo( ) ;
126+ let sys_username = v[ "username" ] . as_str( ) . unwrap_or_default( ) . to_string( ) ;
127+ // Though the username comparison is only necessary on Windows,
128+ // we still keep the comparison on other platforms for consistency.
129+ let need_upload = ( !info_uploaded. uploaded || info_uploaded. username. as_ref( ) != Some ( & sys_username) ) &&
130+ info_uploaded. last_uploaded. map( |x| x. elapsed( ) >= UPLOAD_SYSINFO_TIMEOUT ) . unwrap_or( true ) ;
131+ if need_upload {
132+ v[ "version" ] = json!( crate :: VERSION ) ;
133+ v[ "id" ] = json!( id) ;
134+ v[ "uuid" ] = json!( crate :: encode64( hbb_common:: get_uuid( ) ) ) ;
135+ let ab_name = Config :: get_option( keys:: OPTION_PRESET_ADDRESS_BOOK_NAME ) ;
136+ if !ab_name. is_empty( ) {
137+ v[ keys:: OPTION_PRESET_ADDRESS_BOOK_NAME ] = json!( ab_name) ;
138+ }
139+ let ab_tag = Config :: get_option( keys:: OPTION_PRESET_ADDRESS_BOOK_TAG ) ;
140+ if !ab_tag. is_empty( ) {
141+ v[ keys:: OPTION_PRESET_ADDRESS_BOOK_TAG ] = json!( ab_tag) ;
142+ }
143+ let username = get_builtin_option( keys:: OPTION_PRESET_USERNAME ) ;
144+ if !username. is_empty( ) {
145+ v[ keys:: OPTION_PRESET_USERNAME ] = json!( username) ;
146+ }
147+ let strategy_name = get_builtin_option( keys:: OPTION_PRESET_STRATEGY_NAME ) ;
148+ if !strategy_name. is_empty( ) {
149+ v[ keys:: OPTION_PRESET_STRATEGY_NAME ] = json!( strategy_name) ;
150+ }
151+ let device_group_name = get_builtin_option( keys:: OPTION_PRESET_DEVICE_GROUP_NAME ) ;
152+ if !device_group_name. is_empty( ) {
153+ v[ keys:: OPTION_PRESET_DEVICE_GROUP_NAME ] = json!( device_group_name) ;
154+ }
155+ let v = v. to_string( ) ;
156+ let mut hash = "" . to_owned( ) ;
157+ if crate :: is_public( & url) {
158+ use sha2:: { Digest , Sha256 } ;
159+ let mut hasher = Sha256 :: new( ) ;
160+ hasher. update( url. as_bytes( ) ) ;
161+ hasher. update( & v. as_bytes( ) ) ;
162+ let res = hasher. finalize( ) ;
163+ hash = hbb_common:: base64:: encode( & res[ ..] ) ;
164+ let old_hash = config:: Status :: get( "sysinfo_hash" ) ;
165+ let ver = config:: Status :: get( "sysinfo_ver" ) ; // sysinfo_ver is the version of sysinfo on server's side
166+ if hash == old_hash {
167+ // When the api doesn't exist, Ok("") will be returned in test.
168+ let samever = match crate :: post_request( url. replace( "heartbeat" , "sysinfo_ver" ) , "" . to_owned( ) , "" ) . await {
169+ Ok ( x) => {
170+ sysinfo_ver = x. clone( ) ;
148171 * PRO . lock( ) . unwrap( ) = true ;
149- } else if x == "ID_NOT_FOUND" {
150- info_uploaded. 2 = None ; // next heartbeat will upload sysinfo again
151- } else {
152- info_uploaded. 2 = Some ( Instant :: now( ) ) ;
172+ x == ver
173+ }
174+ _ => {
175+ false // to make sure Pro can be assigned in below post for old
176+ // hbbs pro not supporting sysinfo_ver, use false for ensuring
153177 }
178+ } ;
179+ if samever {
180+ info_uploaded = InfoUploaded :: uploaded( url. clone( ) , id. clone( ) , sys_username) ;
181+ log:: info!( "sysinfo not changed, skip upload" ) ;
182+ continue ;
154183 }
155- _ => {
156- info_uploaded. 2 = Some ( Instant :: now( ) ) ;
184+ }
185+ }
186+ match crate :: post_request( url. replace( "heartbeat" , "sysinfo" ) , v, "" ) . await {
187+ Ok ( x) => {
188+ if x == "SYSINFO_UPDATED" {
189+ info_uploaded = InfoUploaded :: uploaded( url. clone( ) , id. clone( ) , sys_username) ;
190+ log:: info!( "sysinfo updated" ) ;
191+ if !hash. is_empty( ) {
192+ config:: Status :: set( "sysinfo_hash" , hash) ;
193+ config:: Status :: set( "sysinfo_ver" , sysinfo_ver. clone( ) ) ;
194+ }
195+ * PRO . lock( ) . unwrap( ) = true ;
196+ } else if x == "ID_NOT_FOUND" {
197+ info_uploaded. last_uploaded = None ; // next heartbeat will upload sysinfo again
198+ } else {
199+ info_uploaded. last_uploaded = Some ( Instant :: now( ) ) ;
157200 }
158201 }
202+ _ => {
203+ info_uploaded. last_uploaded = Some ( Instant :: now( ) ) ;
204+ }
159205 }
160206 }
161207 if conns. is_empty( ) && last_sent. map( |x| x. elapsed( ) < TIME_HEARTBEAT ) . unwrap_or( false ) {
@@ -174,7 +220,7 @@ async fn start_hbbs_sync_async() {
174220 if let Ok ( s) = crate :: post_request( url. clone( ) , v. to_string( ) , "" ) . await {
175221 if let Ok ( mut rsp) = serde_json:: from_str:: <HashMap :: <& str , Value >>( & s) {
176222 if rsp. remove( "sysinfo" ) . is_some( ) {
177- info_uploaded. 0 = false ;
223+ info_uploaded. uploaded = false ;
178224 config:: Status :: set( "sysinfo_hash" , "" . to_owned( ) ) ;
179225 log:: info!( "sysinfo required to forcely update" ) ;
180226 }
0 commit comments