mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-20 12:53:20 +03:00
feat, update, win, macos (#11618)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
249
src/updater.rs
Normal file
249
src/updater.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
use crate::{common::do_check_software_update, hbbs_http::create_http_client};
|
||||
use hbb_common::{bail, config, log, ResultType};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Mutex,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
enum UpdateMsg {
|
||||
CheckUpdate,
|
||||
Exit,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref TX_MSG : Mutex<Sender<UpdateMsg>> = Mutex::new(start_auto_update_check());
|
||||
}
|
||||
|
||||
static CONTROLLING_SESSION_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
const DUR_ONE_DAY: Duration = Duration::from_secs(60 * 60 * 24);
|
||||
|
||||
pub fn update_controlling_session_count(count: usize) {
|
||||
CONTROLLING_SESSION_COUNT.store(count, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn start_auto_update() {
|
||||
let _sender = TX_MSG.lock().unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn manually_check_update() -> ResultType<()> {
|
||||
let sender = TX_MSG.lock().unwrap();
|
||||
sender.send(UpdateMsg::CheckUpdate)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn stop_auto_update() {
|
||||
let sender = TX_MSG.lock().unwrap();
|
||||
sender.send(UpdateMsg::Exit).unwrap_or_default();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn has_no_active_conns() -> bool {
|
||||
let conns = crate::Connection::alive_conns();
|
||||
conns.is_empty() && has_no_controlling_conns()
|
||||
}
|
||||
|
||||
#[cfg(any(not(target_os = "windows"), feature = "flutter"))]
|
||||
fn has_no_controlling_conns() -> bool {
|
||||
CONTROLLING_SESSION_COUNT.load(Ordering::SeqCst) == 0
|
||||
}
|
||||
|
||||
#[cfg(not(any(not(target_os = "windows"), feature = "flutter")))]
|
||||
fn has_no_controlling_conns() -> bool {
|
||||
let app_exe = format!("{}.exe", crate::get_app_name().to_lowercase());
|
||||
for arg in [
|
||||
"--connect",
|
||||
"--play",
|
||||
"--file-transfer",
|
||||
"--view-camera",
|
||||
"--port-forward",
|
||||
"--rdp",
|
||||
] {
|
||||
if !crate::platform::get_pids_of_process_with_first_arg(&app_exe, arg).is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn start_auto_update_check() -> Sender<UpdateMsg> {
|
||||
let (tx, rx) = channel();
|
||||
std::thread::spawn(move || start_auto_update_check_(rx));
|
||||
return tx;
|
||||
}
|
||||
|
||||
fn start_auto_update_check_(rx_msg: Receiver<UpdateMsg>) {
|
||||
std::thread::sleep(Duration::from_secs(30));
|
||||
if let Err(e) = check_update(false) {
|
||||
log::error!("Error checking for updates: {}", e);
|
||||
}
|
||||
|
||||
const MIN_INTERVAL: Duration = Duration::from_secs(60 * 10);
|
||||
const RETRY_INTERVAL: Duration = Duration::from_secs(60 * 30);
|
||||
let mut last_check_time = Instant::now();
|
||||
let mut check_interval = DUR_ONE_DAY;
|
||||
loop {
|
||||
let recv_res = rx_msg.recv_timeout(check_interval);
|
||||
match &recv_res {
|
||||
Ok(UpdateMsg::CheckUpdate) | Err(_) => {
|
||||
if last_check_time.elapsed() < MIN_INTERVAL {
|
||||
// log::debug!("Update check skipped due to minimum interval.");
|
||||
continue;
|
||||
}
|
||||
// Don't check update if there are alive connections.
|
||||
if !has_no_active_conns() {
|
||||
check_interval = RETRY_INTERVAL;
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = check_update(matches!(recv_res, Ok(UpdateMsg::CheckUpdate))) {
|
||||
log::error!("Error checking for updates: {}", e);
|
||||
check_interval = RETRY_INTERVAL;
|
||||
} else {
|
||||
last_check_time = Instant::now();
|
||||
check_interval = DUR_ONE_DAY;
|
||||
}
|
||||
}
|
||||
Ok(UpdateMsg::Exit) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_update(manually: bool) -> ResultType<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let is_msi = crate::platform::is_msi_installed()?;
|
||||
if !(manually || config::Config::get_bool_option(config::keys::OPTION_ALLOW_AUTO_UPDATE)) {
|
||||
return Ok(());
|
||||
}
|
||||
if !do_check_software_update().is_ok() {
|
||||
// ignore
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let update_url = crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone();
|
||||
if update_url.is_empty() {
|
||||
log::debug!("No update available.");
|
||||
} else {
|
||||
let download_url = update_url.replace("tag", "download");
|
||||
let version = download_url.split('/').last().unwrap_or_default();
|
||||
#[cfg(target_os = "windows")]
|
||||
let download_url = if cfg!(feature = "flutter") {
|
||||
format!(
|
||||
"{}/rustdesk-{}-x86_64.{}",
|
||||
download_url,
|
||||
version,
|
||||
if is_msi { "msi" } else { "exe" }
|
||||
)
|
||||
} else {
|
||||
format!("{}/rustdesk-{}-x86-sciter.exe", download_url, version)
|
||||
};
|
||||
log::debug!("New version available: {}", &version);
|
||||
let client = create_http_client();
|
||||
let Some(file_path) = get_download_file_from_url(&download_url) else {
|
||||
bail!("Failed to get the file path from the URL: {}", download_url);
|
||||
};
|
||||
let mut is_file_exists = false;
|
||||
if file_path.exists() {
|
||||
// Check if the file size is the same as the server file size
|
||||
// If the file size is the same, we don't need to download it again.
|
||||
let file_size = std::fs::metadata(&file_path)?.len();
|
||||
let response = client.head(&download_url).send()?;
|
||||
if !response.status().is_success() {
|
||||
bail!("Failed to get the file size: {}", response.status());
|
||||
}
|
||||
let total_size = response
|
||||
.headers()
|
||||
.get(reqwest::header::CONTENT_LENGTH)
|
||||
.and_then(|ct_len| ct_len.to_str().ok())
|
||||
.and_then(|ct_len| ct_len.parse::<u64>().ok());
|
||||
let Some(total_size) = total_size else {
|
||||
bail!("Failed to get content length");
|
||||
};
|
||||
if file_size == total_size {
|
||||
is_file_exists = true;
|
||||
} else {
|
||||
std::fs::remove_file(&file_path)?;
|
||||
}
|
||||
}
|
||||
if !is_file_exists {
|
||||
let response = client.get(&download_url).send()?;
|
||||
if !response.status().is_success() {
|
||||
bail!(
|
||||
"Failed to download the new version file: {}",
|
||||
response.status()
|
||||
);
|
||||
}
|
||||
let file_data = response.bytes()?;
|
||||
let mut file = std::fs::File::create(&file_path)?;
|
||||
file.write_all(&file_data)?;
|
||||
}
|
||||
// We have checked if the `conns`` is empty before, but we need to check again.
|
||||
// No need to care about the downloaded file here, because it's rare case that the `conns` are empty
|
||||
// before the download, but not empty after the download.
|
||||
if has_no_active_conns() {
|
||||
#[cfg(target_os = "windows")]
|
||||
update_new_version(is_msi, &version, &file_path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn update_new_version(is_msi: bool, version: &str, file_path: &PathBuf) {
|
||||
log::debug!("New version is downloaded, update begin, is msi: {is_msi}, version: {version}, file: {:?}", file_path.to_str());
|
||||
if let Some(p) = file_path.to_str() {
|
||||
if let Some(session_id) = crate::platform::get_current_process_session_id() {
|
||||
if is_msi {
|
||||
match crate::platform::update_me_msi(p, true) {
|
||||
Ok(_) => {
|
||||
log::debug!("New version \"{}\" updated.", version);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to install the new msi version \"{}\": {}",
|
||||
version,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match crate::platform::launch_privileged_process(
|
||||
session_id,
|
||||
&format!("{} --update", p),
|
||||
) {
|
||||
Ok(h) => {
|
||||
if h.is_null() {
|
||||
log::error!("Failed to update to the new version: {}", version);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to run the new version: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to get the current process session id, Error {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// unreachable!()
|
||||
log::error!(
|
||||
"Failed to convert the file path to string: {}",
|
||||
file_path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_download_file_from_url(url: &str) -> Option<PathBuf> {
|
||||
let filename = url.split('/').last()?;
|
||||
Some(std::env::temp_dir().join(filename))
|
||||
}
|
||||
Reference in New Issue
Block a user