feat(settings): add Rebuild & Install self-update button
Settings panel now shows the current version and a "Rebuild & Install Latest" button that: 1. git pull from the embedded source directory (CARGO_MANIFEST_DIR) 2. makepkg -sf in packaging/ 3. pkexec pacman -U (graphical polkit auth prompt) Reports the installed version on success; surfaces the failing step on error. Update runs off the UI thread so the window stays responsive. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+96
@@ -61,6 +61,9 @@ pub enum Message {
|
|||||||
ServiceInstall,
|
ServiceInstall,
|
||||||
ServiceUninstall,
|
ServiceUninstall,
|
||||||
ServiceActionDone(Result<(), String>),
|
ServiceActionDone(Result<(), String>),
|
||||||
|
// Self-update
|
||||||
|
UpdateApp,
|
||||||
|
UpdateAppDone(Result<String, String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Dashboard {
|
struct Dashboard {
|
||||||
@@ -87,6 +90,8 @@ struct Dashboard {
|
|||||||
proton_versions: Vec<String>,
|
proton_versions: Vec<String>,
|
||||||
service_busy: bool,
|
service_busy: bool,
|
||||||
service_status: String,
|
service_status: String,
|
||||||
|
update_busy: bool,
|
||||||
|
update_status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dashboard {
|
impl Dashboard {
|
||||||
@@ -118,6 +123,8 @@ impl Dashboard {
|
|||||||
proton_versions,
|
proton_versions,
|
||||||
service_busy: false,
|
service_busy: false,
|
||||||
service_status: String::new(),
|
service_status: String::new(),
|
||||||
|
update_busy: false,
|
||||||
|
update_status: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -570,6 +577,22 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
|||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
Message::UpdateApp => {
|
||||||
|
state.update_busy = true;
|
||||||
|
state.update_status = "Pulling latest changes…".into();
|
||||||
|
Task::perform(
|
||||||
|
async_blocking(run_self_update),
|
||||||
|
Message::UpdateAppDone,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Message::UpdateAppDone(res) => {
|
||||||
|
state.update_busy = false;
|
||||||
|
state.update_status = match res {
|
||||||
|
Ok(ver) => format!("✓ Updated to {ver} — restart the app to apply."),
|
||||||
|
Err(e) => format!("Update failed: {e}"),
|
||||||
|
};
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1155,6 +1178,18 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
|||||||
text(svc_status_label).size(13),
|
text(svc_status_label).size(13),
|
||||||
row![svc_install_btn, svc_uninstall_btn,].spacing(10),
|
row![svc_install_btn, svc_uninstall_btn,].spacing(10),
|
||||||
text(&state.service_status).size(12),
|
text(&state.service_status).size(12),
|
||||||
|
iced::widget::horizontal_rule(1),
|
||||||
|
row![
|
||||||
|
text(format!("Version: {}", env!("CARGO_PKG_VERSION"))).size(13),
|
||||||
|
iced::widget::horizontal_space(),
|
||||||
|
]
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
button(
|
||||||
|
text(if state.update_busy { "Updating…" } else { "Rebuild & Install Latest" }).size(13),
|
||||||
|
)
|
||||||
|
.on_press_maybe((!state.update_busy).then_some(Message::UpdateApp))
|
||||||
|
.style(button::secondary),
|
||||||
|
text(&state.update_status).size(12),
|
||||||
]
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(20);
|
.padding(20);
|
||||||
@@ -1165,6 +1200,67 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_self_update() -> Result<String, String> {
|
||||||
|
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
if !src.exists() {
|
||||||
|
return Err(format!("Source directory not found: {}", src.display()));
|
||||||
|
}
|
||||||
|
let pkg_dir = src.join("packaging");
|
||||||
|
|
||||||
|
// 1. git pull
|
||||||
|
let out = std::process::Command::new("git")
|
||||||
|
.args(["-C", src.to_str().unwrap_or("."), "pull"])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("git pull: {e}"))?;
|
||||||
|
if !out.status.success() {
|
||||||
|
return Err(format!("git pull failed: {}", String::from_utf8_lossy(&out.stderr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. makepkg -sf --noconfirm
|
||||||
|
let out = std::process::Command::new("makepkg")
|
||||||
|
.args(["-sf", "--noconfirm"])
|
||||||
|
.current_dir(&pkg_dir)
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("makepkg: {e}"))?;
|
||||||
|
if !out.status.success() {
|
||||||
|
return Err(format!("makepkg failed: {}", String::from_utf8_lossy(&out.stderr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Find the freshly built package
|
||||||
|
let pkg = std::fs::read_dir(&pkg_dir)
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.flatten()
|
||||||
|
.filter_map(|e| {
|
||||||
|
let name = e.file_name();
|
||||||
|
let n = name.to_string_lossy().to_string();
|
||||||
|
if n.ends_with(".pkg.tar.zst") && !n.contains("debug") {
|
||||||
|
Some(e.path())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.max_by_key(|p| p.metadata().and_then(|m| m.modified()).ok())
|
||||||
|
.ok_or_else(|| "No .pkg.tar.zst found after makepkg".to_string())?;
|
||||||
|
|
||||||
|
// 4. Install with pkexec (graphical auth prompt)
|
||||||
|
let out = std::process::Command::new("pkexec")
|
||||||
|
.args(["pacman", "-U", "--noconfirm", pkg.to_str().unwrap_or("")])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("pkexec: {e}"))?;
|
||||||
|
if !out.status.success() {
|
||||||
|
return Err(format!("Install failed: {}", String::from_utf8_lossy(&out.stderr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract version from package filename e.g. umutray-0.1.0-4-x86_64.pkg.tar.zst
|
||||||
|
let ver = pkg
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.and_then(|n| n.split('-').nth(1))
|
||||||
|
.unwrap_or("?")
|
||||||
|
.to_string();
|
||||||
|
Ok(ver)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(config: &Config) -> Result<()> {
|
pub fn run(config: &Config) -> Result<()> {
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
iced::application(|_: &Dashboard| String::from("umutray"), update, view)
|
iced::application(|_: &Dashboard| String::from("umutray"), update, view)
|
||||||
|
|||||||
Reference in New Issue
Block a user