1use crate::config::ClientConfig;
2use crate::shell::get_cache_dir;
3use anyhow::{Context, Result};
4use cloudpub_common::protocol::message::Message;
5use parking_lot::RwLock;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9use tokio::sync::mpsc;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct PlatformDownloads {
13 pub gui: Option<String>,
14 pub cli: Option<String>,
15 pub rpm: Option<String>,
16 pub deb: Option<String>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct OsDownloads {
21 pub name: String,
22 pub platforms: HashMap<String, PlatformDownloads>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct DownloadConfig {
27 pub macos: OsDownloads,
28 pub windows: OsDownloads,
29 pub linux: OsDownloads,
30}
31
32impl DownloadConfig {
33 pub fn new(base_url: &str, version: &str, branch: &str) -> Self {
34 #[cfg(target_os = "macos")]
35 let macos_platforms = {
36 let mut platforms = HashMap::new();
37 platforms.insert(
38 "aarch64".to_string(),
39 PlatformDownloads {
40 gui: Some(format!(
41 "{base_url}cloudpub-{version}-{branch}-macos-aarch64.dmg"
42 )),
43 cli: Some(format!(
44 "{base_url}clo-{version}-{branch}-macos-aarch64.tar.gz"
45 )),
46 rpm: None,
47 deb: None,
48 },
49 );
50 platforms.insert(
51 "x86_64".to_string(),
52 PlatformDownloads {
53 gui: Some(format!(
54 "{base_url}cloudpub-{version}-{branch}-macos-x86_64.dmg"
55 )),
56 cli: Some(format!(
57 "{base_url}clo-{version}-{branch}-macos-x86_64.tar.gz"
58 )),
59 rpm: None,
60 deb: None,
61 },
62 );
63 platforms
64 };
65
66 #[cfg(target_os = "windows")]
67 let windows_platforms = {
68 let mut platforms = HashMap::new();
69 platforms.insert(
70 "x86_64".to_string(),
71 PlatformDownloads {
72 gui: Some(format!(
73 "{base_url}cloudpub-{version}-{branch}-windows-x86_64.msi"
74 )),
75 cli: Some(format!(
76 "{base_url}clo-{version}-{branch}-windows-x86_64.zip"
77 )),
78 rpm: None,
79 deb: None,
80 },
81 );
82 platforms
83 };
84
85 #[cfg(target_os = "linux")]
86 let linux_platforms = {
87 let mut platforms = HashMap::new();
88 platforms.insert(
89 "arm".to_string(),
90 PlatformDownloads {
91 gui: None,
92 cli: Some(format!("{base_url}clo-{version}-{branch}-linux-arm.tar.gz")),
93 rpm: None,
94 deb: None,
95 },
96 );
97 platforms.insert(
98 "armv5te".to_string(),
99 PlatformDownloads {
100 gui: None,
101 cli: Some(format!(
102 "{base_url}clo-{version}-{branch}-linux-armv5te.tar.gz"
103 )),
104 rpm: None,
105 deb: None,
106 },
107 );
108 platforms.insert(
109 "aarch64".to_string(),
110 PlatformDownloads {
111 gui: None,
112 cli: Some(format!(
113 "{base_url}clo-{version}-{branch}-linux-aarch64.tar.gz"
114 )),
115 rpm: None,
116 deb: None,
117 },
118 );
119 platforms.insert(
120 "mips".to_string(),
121 PlatformDownloads {
122 gui: None,
123 cli: Some(format!(
124 "{base_url}clo-{version}-{branch}-linux-mips.tar.gz"
125 )),
126 rpm: None,
127 deb: None,
128 },
129 );
130 platforms.insert(
131 "mipsel".to_string(),
132 PlatformDownloads {
133 gui: None,
134 cli: Some(format!(
135 "{base_url}clo-{version}-{branch}-linux-mipsel.tar.gz"
136 )),
137 rpm: None,
138 deb: None,
139 },
140 );
141 platforms.insert(
142 "i686".to_string(),
143 PlatformDownloads {
144 gui: None,
145 cli: Some(format!(
146 "{base_url}clo-{version}-{branch}-linux-i686.tar.gz"
147 )),
148 rpm: None,
149 deb: None,
150 },
151 );
152 platforms.insert(
153 "x86_64".to_string(),
154 PlatformDownloads {
155 gui: None,
156 cli: Some(format!(
157 "{base_url}clo-{version}-{branch}-linux-x86_64.tar.gz"
158 )),
159 rpm: Some(format!(
160 "{base_url}cloudpub-{version}-{branch}-linux-x86_64.rpm"
161 )),
162 deb: Some(format!(
163 "{base_url}cloudpub-{version}-{branch}-linux-x86_64.deb"
164 )),
165 },
166 );
167 platforms
168 };
169
170 Self {
171 #[cfg(target_os = "macos")]
172 macos: OsDownloads {
173 name: "macOS".to_string(),
174 platforms: macos_platforms,
175 },
176 #[cfg(not(target_os = "macos"))]
177 macos: OsDownloads {
178 name: "macOS".to_string(),
179 platforms: HashMap::new(),
180 },
181 #[cfg(target_os = "windows")]
182 windows: OsDownloads {
183 name: "Windows".to_string(),
184 platforms: windows_platforms,
185 },
186 #[cfg(not(target_os = "windows"))]
187 windows: OsDownloads {
188 name: "Windows".to_string(),
189 platforms: HashMap::new(),
190 },
191 #[cfg(target_os = "linux")]
192 linux: OsDownloads {
193 name: "Linux".to_string(),
194 platforms: linux_platforms,
195 },
196 #[cfg(not(target_os = "linux"))]
197 linux: OsDownloads {
198 name: "Linux".to_string(),
199 platforms: HashMap::new(),
200 },
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub enum DownloadType {
207 Gui,
208 Cli,
209 Rpm,
210 Deb,
211}
212
213pub fn get_current_os() -> String {
214 #[cfg(target_os = "macos")]
215 return "macos".to_string();
216 #[cfg(target_os = "windows")]
217 return "windows".to_string();
218 #[cfg(target_os = "linux")]
219 return "linux".to_string();
220 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
221 return std::env::consts::OS.to_string();
222}
223
224pub fn get_current_arch() -> String {
225 #[cfg(target_arch = "x86_64")]
226 return "x86_64".to_string();
227 #[cfg(target_arch = "aarch64")]
228 return "aarch64".to_string();
229 #[cfg(target_arch = "arm")]
230 return "arm".to_string();
231 #[cfg(target_arch = "x86")]
232 return "i686".to_string();
233 #[cfg(not(any(
234 target_arch = "x86_64",
235 target_arch = "aarch64",
236 target_arch = "arm",
237 target_arch = "x86"
238 )))]
239 return std::env::consts::ARCH.to_string();
240}
241
242#[cfg(target_os = "linux")]
243pub fn get_linux_package_type() -> Result<String> {
244 if std::process::Command::new("which")
246 .arg("dpkg")
247 .output()
248 .map(|output| output.status.success())
249 .unwrap_or(false)
250 {
251 return Ok("deb".to_string());
252 }
253
254 if std::process::Command::new("which")
256 .arg("rpm")
257 .output()
258 .map(|output| output.status.success())
259 .unwrap_or(false)
260 {
261 return Ok("rpm".to_string());
262 }
263
264 Err(anyhow::anyhow!("Could not detect package type"))
265}
266
267pub fn get_download_url(
268 base_url: &str,
269 download_type: &DownloadType,
270 version: &str,
271 branch: &str,
272) -> Result<String> {
273 let base_url = format!("{}download/{}/", base_url, branch);
274 let config = DownloadConfig::new(&base_url, version, branch);
275 let os = get_current_os();
276 let arch = get_current_arch();
277
278 let os_downloads = match os.as_str() {
279 "macos" => &config.macos,
280 "windows" => &config.windows,
281 "linux" => &config.linux,
282 _ => {
283 return Err(anyhow::anyhow!("Unsupported OS: {}", os))
284 .context("Failed to get OS downloads")
285 }
286 };
287
288 let platform_downloads = os_downloads
289 .platforms
290 .get(&arch)
291 .with_context(|| format!("Unsupported architecture {} for OS {}", arch, os))?;
292
293 let download_url = match download_type {
294 DownloadType::Gui => platform_downloads
295 .gui
296 .as_ref()
297 .context("GUI download not available for this platform")?,
298 DownloadType::Cli => platform_downloads
299 .cli
300 .as_ref()
301 .context("CLI download not available for this platform")?,
302 DownloadType::Rpm => platform_downloads
303 .rpm
304 .as_ref()
305 .context("RPM download not available for this platform")?,
306 DownloadType::Deb => platform_downloads
307 .deb
308 .as_ref()
309 .context("DEB download not available for this platform")?,
310 };
311
312 Ok(download_url.clone())
313}
314
315#[cfg(windows)]
316pub fn install_msi_package(msi_path: &std::path::Path) -> Result<()> {
317 use std::os::windows::process::CommandExt;
318
319 let temp_dir = std::env::temp_dir();
320 let batch_script = temp_dir.join("cloudpub_install_msi.bat");
321 let current_exe = std::env::current_exe()?;
322
323 let script_content = format!(
324 r#"@echo off
325timeout /t 3 /nobreak >nul
326echo Installing MSI package...
327echo Requesting administrator privileges...
328powershell -Command "Start-Process msiexec -ArgumentList '/i', '{}', '/quiet', '/norestart' -Verb RunAs -Wait"
329if errorlevel 1 (
330 echo Failed to install MSI package
331 exit /b 1
332)
333echo Installation completed successfully
334del "%~f0"
335start "" "{}"
336"#,
337 msi_path.display(),
338 current_exe.display()
339 );
340
341 std::fs::write(&batch_script, script_content)
342 .context("Failed to create MSI install batch script")?;
343
344 std::process::Command::new("cmd")
346 .args(&["/C", &batch_script.to_string_lossy()])
347 .creation_flags(0x08000000) .spawn()
349 .context("Failed to execute MSI install batch script")?;
350
351 std::process::exit(0);
352}
353
354#[cfg(windows)]
355pub fn replace_and_restart_windows(
356 new_exe_path: &std::path::Path,
357 current_args: Vec<String>,
358) -> Result<()> {
359 use std::os::windows::process::CommandExt;
360
361 let current_exe = std::env::current_exe()?;
362 let temp_dir = std::env::temp_dir();
363 let batch_script = temp_dir.join("cloudpub_update.bat");
364
365 let script_content = format!(
366 r#"@echo off
367timeout /t 3 /nobreak >nul
368move /y "{}" "{}"
369if errorlevel 1 (
370 echo Failed to replace executable
371 exit /b 1
372)
373start "" "{}" {}
374del "%~f0"
375"#,
376 new_exe_path.display(),
377 current_exe.display(),
378 current_exe.display(),
379 current_args.join(" ")
380 );
381
382 std::fs::write(&batch_script, script_content)
383 .context("Failed to create update batch script")?;
384
385 std::process::Command::new("cmd")
387 .args(&["/C", &batch_script.to_string_lossy()])
388 .creation_flags(0x08000000) .spawn()
390 .context("Failed to execute update batch script")?;
391
392 std::process::exit(0);
393}
394
395#[cfg(not(windows))]
396pub fn replace_and_restart_unix(
397 new_exe_path: &std::path::Path,
398 current_args: Vec<String>,
399) -> Result<()> {
400 let current_exe = std::env::current_exe()?;
401 let backup_exe = current_exe.with_extension("old");
402
403 std::fs::rename(¤t_exe, &backup_exe).context("Failed to backup current executable")?;
405
406 std::fs::rename(new_exe_path, ¤t_exe).context("Failed to move new executable")?;
408
409 #[cfg(unix)]
411 {
412 use std::os::unix::fs::PermissionsExt;
413 let mut perms = std::fs::metadata(¤t_exe)?.permissions();
414 perms.set_mode(0o755);
415 std::fs::set_permissions(¤t_exe, perms)?;
416 }
417
418 std::fs::remove_file(&backup_exe).ok();
420
421 std::process::Command::new(¤t_exe)
423 .args(current_args)
424 .spawn()
425 .context("Failed to restart application")?;
426
427 std::process::exit(0);
428}
429
430pub fn apply_update_and_restart(new_exe_path: &std::path::Path) -> Result<()> {
431 let args: Vec<String> = vec!["run".to_string()];
433
434 eprintln!("{}", crate::t!("applying-update"));
435
436 #[cfg(windows)]
437 replace_and_restart_windows(new_exe_path, args)?;
438
439 #[cfg(not(windows))]
440 replace_and_restart_unix(new_exe_path, args)?;
441
442 Ok(())
443}
444
445#[cfg(target_os = "macos")]
446async fn install_dmg_package(package_path: &std::path::Path) -> Result<()> {
447 eprintln!("Installing DMG package...");
448
449 let current_exe = std::env::current_exe()?;
451 let current_args: Vec<String> = std::env::args().skip(1).collect();
452 let temp_dir = std::env::temp_dir();
453 let install_script = temp_dir.join("cloudpub_install_dmg.sh");
454
455 let script_content = format!(
456 r#"#!/bin/bash
457set -e
458
459PACKAGE_PATH="{}"
460CURRENT_EXE="{}"
461CURRENT_ARGS="{}"
462
463echo "Installing DMG package..."
464
465# Mount the DMG
466MOUNT_OUTPUT=$(hdiutil attach -nobrowse -quiet "$PACKAGE_PATH")
467if [ $? -ne 0 ]; then
468 echo "Failed to mount DMG package"
469 exit 1
470fi
471
472# Extract mount point
473MOUNT_POINT=$(echo "$MOUNT_OUTPUT" | grep "/Volumes/" | awk '{{print $NF}}')
474if [ -z "$MOUNT_POINT" ]; then
475 echo "Could not find mount point"
476 exit 1
477fi
478
479# Find .app bundle
480APP_PATH=$(find "$MOUNT_POINT" -name "*.app" -type d | head -n 1)
481if [ -z "$APP_PATH" ]; then
482 hdiutil detach "$MOUNT_POINT" -quiet
483 echo "No .app bundle found in DMG"
484 exit 1
485fi
486
487APP_NAME=$(basename "$APP_PATH")
488DESTINATION="/Applications/$APP_NAME"
489
490# Remove existing app if it exists
491if [ -d "$DESTINATION" ]; then
492 rm -rf "$DESTINATION"
493fi
494
495# Copy app to Applications
496cp -R "$APP_PATH" "/Applications/"
497if [ $? -ne 0 ]; then
498 hdiutil detach "$MOUNT_POINT" -quiet
499 echo "Failed to copy application"
500 exit 1
501fi
502
503# Unmount DMG
504hdiutil detach "$MOUNT_POINT" -quiet
505
506echo "DMG package installed successfully"
507
508# Launch new version
509APP_NAME_NO_EXT=$(basename "$APP_NAME" .app)
510open -a "$APP_NAME_NO_EXT"
511echo "Launched new application version"
512
513# Clean up script
514rm -f "$0"
515"#,
516 package_path.display(),
517 current_exe.display(),
518 current_args.join(" ")
519 );
520
521 std::fs::write(&install_script, script_content)?;
522
523 #[cfg(unix)]
525 {
526 use std::os::unix::fs::PermissionsExt;
527 let mut perms = std::fs::metadata(&install_script)?.permissions();
528 perms.set_mode(0o755);
529 std::fs::set_permissions(&install_script, perms)?;
530 }
531
532 std::process::Command::new("nohup")
534 .arg(&install_script)
535 .spawn()?;
536
537 std::process::exit(0);
538}
539
540#[cfg(target_os = "linux")]
541async fn install_deb_package(package_path: &std::path::Path) -> Result<()> {
542 eprintln!("Installing DEB package...");
543
544 let current_exe = std::env::current_exe()?;
546 let current_args: Vec<String> = std::env::args().skip(1).collect();
547 let temp_dir = std::env::temp_dir();
548 let install_script = temp_dir.join("cloudpub_install_deb.sh");
549
550 let script_content = format!(
551 r#"#!/bin/bash
552set -e
553
554PACKAGE_PATH="{}"
555CURRENT_EXE="{}"
556CURRENT_ARGS="{}"
557
558echo "Installing DEB package..."
559
560# Check if running as root, if not use pkexec
561if [ "$EUID" -ne 0 ]; then
562 echo "Root privileges required for package installation"
563 pkexec dpkg -i "$PACKAGE_PATH"
564else
565 dpkg -i "$PACKAGE_PATH"
566fi
567
568if [ $? -ne 0 ]; then
569 echo "Failed to install DEB package"
570 exit 1
571fi
572
573echo "DEB package installed successfully"
574
575# Wait a moment for the process to exit
576sleep 3
577
578# Restart the application
579exec "$CURRENT_EXE" $CURRENT_ARGS
580"#,
581 package_path.display(),
582 current_exe.display(),
583 current_args.join(" ")
584 );
585
586 std::fs::write(&install_script, script_content)?;
587
588 #[cfg(unix)]
590 {
591 use std::os::unix::fs::PermissionsExt;
592 let mut perms = std::fs::metadata(&install_script)?.permissions();
593 perms.set_mode(0o755);
594 std::fs::set_permissions(&install_script, perms)?;
595 }
596
597 std::process::Command::new("nohup")
599 .arg(&install_script)
600 .spawn()?;
601
602 std::process::exit(0);
603}
604
605#[cfg(target_os = "linux")]
606async fn install_rpm_package(package_path: &std::path::Path) -> Result<()> {
607 eprintln!("Installing RPM package...");
608
609 let current_exe = std::env::current_exe()?;
611 let current_args: Vec<String> = std::env::args().skip(1).collect();
612 let temp_dir = std::env::temp_dir();
613 let install_script = temp_dir.join("cloudpub_install_rpm.sh");
614
615 let script_content = format!(
616 r#"#!/bin/bash
617set -e
618
619PACKAGE_PATH="{}"
620CURRENT_EXE="{}"
621CURRENT_ARGS="{}"
622
623echo "Installing RPM package..."
624
625# Check if running as root, if not use pkexec
626if [ "$EUID" -ne 0 ]; then
627 echo "Root privileges required for package installation"
628 pkexec rpm -Uvh "$PACKAGE_PATH"
629else
630 rpm -Uvh "$PACKAGE_PATH"
631fi
632
633if [ $? -ne 0 ]; then
634 echo "Failed to install RPM package"
635 exit 1
636fi
637
638echo "RPM package installed successfully"
639
640# Wait a moment for the process to exit
641sleep 3
642
643# Restart the application
644exec "$CURRENT_EXE" $CURRENT_ARGS
645"#,
646 package_path.display(),
647 current_exe.display(),
648 current_args.join(" ")
649 );
650
651 std::fs::write(&install_script, script_content)?;
652
653 #[cfg(unix)]
655 {
656 use std::os::unix::fs::PermissionsExt;
657 let mut perms = std::fs::metadata(&install_script)?.permissions();
658 perms.set_mode(0o755);
659 std::fs::set_permissions(&install_script, perms)?;
660 }
661
662 std::process::Command::new("nohup")
664 .arg(&install_script)
665 .spawn()?;
666
667 std::process::exit(0);
668}
669
670async fn handle_cli_update(
671 filename: &str,
672 output_path: &std::path::Path,
673 cache_dir: &std::path::Path,
674 command_rx: &mut mpsc::Receiver<Message>,
675 result_tx: &mpsc::Sender<Message>,
676) -> Result<()> {
677 let unpack_message = "Unpacking update".to_string();
678
679 if filename.ends_with(".zip") {
680 crate::shell::unzip(&unpack_message, output_path, cache_dir, 0, result_tx)
681 .await
682 .context("Failed to unpack zip update")?;
683 } else if filename.ends_with(".tar.gz") {
684 let tar_args = vec![
685 "-xzf".to_string(),
686 output_path.to_string_lossy().to_string(),
687 ];
688
689 let total_files = 1;
690 let progress = Some((unpack_message, result_tx.clone(), total_files));
691
692 crate::shell::execute(
693 std::path::PathBuf::from("tar"),
694 tar_args,
695 Some(cache_dir.to_path_buf()),
696 std::collections::HashMap::new(),
697 progress,
698 command_rx,
699 )
700 .await
701 .context("Failed to unpack tar.gz update")?;
702 } else {
703 return Err(anyhow::anyhow!("Unknown file format: {}", filename));
704 }
705
706 eprintln!(
707 "{}",
708 crate::t!("update-unpacked", "path" => cache_dir.display().to_string())
709 );
710
711 #[cfg(not(windows))]
712 let new_exe_path = cache_dir.join("clo");
713 #[cfg(windows)]
714 let new_exe_path = cache_dir.join("clo.exe");
715
716 apply_update_and_restart(&new_exe_path)?;
717 Ok(())
718}
719
720pub async fn handle_upgrade_download(
721 version: &str,
722 gui: bool,
723 config: Arc<RwLock<ClientConfig>>,
724 command_rx: &mut mpsc::Receiver<Message>,
725 result_tx: &mpsc::Sender<Message>,
726) -> Result<(std::path::PathBuf, DownloadType, String)> {
727 let cache_dir = get_cache_dir("updates").unwrap();
729 std::fs::remove_dir_all(&cache_dir).ok();
730
731 let cache_dir = get_cache_dir("updates").unwrap();
733
734 let download_type = if gui {
735 #[cfg(target_os = "linux")]
736 {
737 match get_linux_package_type() {
739 Ok(pkg_type) if pkg_type == "deb" => DownloadType::Deb,
740 Ok(pkg_type) if pkg_type == "rpm" => DownloadType::Rpm,
741 _ => DownloadType::Cli, }
743 }
744 #[cfg(not(target_os = "linux"))]
745 DownloadType::Gui
746 } else {
747 DownloadType::Cli
748 };
749
750 let base_url = config.read().server.to_string();
752 let download_url = get_download_url(&base_url, &download_type, version, "stable")?;
753
754 let mut file_extension = std::path::Path::new(&download_url)
756 .extension()
757 .and_then(|ext| ext.to_str())
758 .map(|ext| format!(".{}", ext))
759 .unwrap_or_else(|| ".tar.gz".to_string());
760
761 if file_extension == ".gz" {
762 file_extension = ".tar.gz".to_string();
764 }
765
766 let filename = format!("cloudpub-{}{}", version, file_extension);
767 let output_path = cache_dir.join(&filename);
768
769 let message = crate::t!("downloading-update", "path" => output_path.display().to_string());
770
771 crate::shell::download(
772 &message,
773 config.clone(),
774 &download_url,
775 &output_path,
776 command_rx,
777 result_tx,
778 )
779 .await
780 .context("Failed to download update")?;
781
782 eprintln!(
783 "{}",
784 crate::t!("update-downloaded", "path" => output_path.display().to_string())
785 );
786
787 Ok((output_path, download_type, filename))
788}
789
790pub async fn handle_upgrade_install(
791 output_path: std::path::PathBuf,
792 download_type: DownloadType,
793 filename: String,
794 command_rx: &mut mpsc::Receiver<Message>,
795 result_tx: &mpsc::Sender<Message>,
796) -> Result<()> {
797 let cache_dir = output_path.parent().unwrap();
798 std::env::set_current_dir(cache_dir)
800 .context("Failed to change current directory to cache directory")?;
801
802 match download_type {
804 DownloadType::Gui => {
805 #[cfg(target_os = "macos")]
806 {
807 if filename.ends_with(".dmg") {
808 install_dmg_package(&output_path).await?;
809 } else {
810 return Err(anyhow::anyhow!("Unsupported GUI package format"));
811 }
812 }
813 #[cfg(target_os = "windows")]
814 {
815 if filename.ends_with(".msi") {
816 install_msi_package(&output_path)?;
817 } else {
818 return Err(anyhow::anyhow!("Unsupported GUI package format"));
819 }
820 }
821 #[cfg(target_os = "linux")]
822 {
823 return Err(anyhow::anyhow!("GUI packages not supported on Linux"));
824 }
825 }
826 DownloadType::Deb => {
827 #[cfg(target_os = "linux")]
828 {
829 install_deb_package(&output_path).await?;
830 }
831 #[cfg(not(target_os = "linux"))]
832 {
833 return Err(anyhow::anyhow!("DEB packages only supported on Linux"));
834 }
835 }
836 DownloadType::Rpm => {
837 #[cfg(target_os = "linux")]
838 {
839 install_rpm_package(&output_path).await?;
840 }
841 #[cfg(not(target_os = "linux"))]
842 {
843 return Err(anyhow::anyhow!("RPM packages only supported on Linux"));
844 }
845 }
846 _ => {
847 handle_cli_update(&filename, &output_path, cache_dir, command_rx, result_tx).await?;
849 }
850 }
851
852 Ok(())
853}
854
855pub async fn handle_upgrade_available(
856 version: &str,
857 config: Arc<RwLock<ClientConfig>>,
858 gui: bool,
859 command_rx: &mut mpsc::Receiver<Message>,
860 result_tx: &mpsc::Sender<Message>,
861) -> Result<()> {
862 let (output_path, download_type, filename) =
863 handle_upgrade_download(version, gui, config, command_rx, result_tx).await?;
864
865 handle_upgrade_install(output_path, download_type, filename, command_rx, result_tx).await?;
866
867 Ok(())
868}