Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ impl App {
update_queue: Vec::new(),
release_notes: std::collections::HashMap::new(),
fetching_notes: std::collections::HashSet::new(),
install_status: std::collections::HashMap::new(),
keyboard_cursor: None,
window_size: (
prefs.window_width.unwrap_or(1000.0).clamp(640.0, 7680.0),
Expand All @@ -256,6 +257,7 @@ impl App {
// Cached catalog repos may have icons already on disk: decode them now
// so the offline/pre-fetch grid is not a wall of fallback hexagons.
app.reload_app_icons();
app.refresh_install_status();

set_active_theme(&app.selected_theme, &app.selected_variant);
set_high_contrast(app.high_contrast);
Expand Down
6 changes: 2 additions & 4 deletions src/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,8 @@ fn parse_desktop_file(path: &Path) -> Result<Application> {
// desktop launchers should show them, but Colony's own scan
// must skip them - the app is already represented by its
// store card, a local duplicate would appear twice.
"X-Colony-Managed" => {
if value.eq_ignore_ascii_case("true") {
anyhow::bail!("Colony-managed entry (represented by its store card)");
}
"X-Colony-Managed" if value.eq_ignore_ascii_case("true") => {
anyhow::bail!("Colony-managed entry (represented by its store card)");
}
_ => {}
}
Expand Down
6 changes: 6 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ pub struct App {
std::collections::HashMap<String, (String, Vec<crate::ui::markdown_blocks::DetailBlock>)>,
/// Repos whose release notes are currently being fetched.
pub fetching_notes: std::collections::HashSet<String>,
/// Install status per store repo: (installed, installed version). The
/// grid used to stat the filesystem per card per FRAME; this cache is
/// refreshed only when it can actually change (catalog load, install
/// completion, uninstall).
pub install_status: std::collections::HashMap<String, (bool, Option<String>)>,
/// Keyboard-highlighted grid row, as a stable key ("repo:<name>" or
/// "app:<name>") so catalog refreshes and re-filters cannot silently move
/// the highlight to a different item. Cleared on search/section changes.
Expand Down Expand Up @@ -544,6 +549,7 @@ impl App {
update_queue: Vec::new(),
release_notes: std::collections::HashMap::new(),
fetching_notes: std::collections::HashSet::new(),
install_status: std::collections::HashMap::new(),
keyboard_cursor: None,
window_size: (1000.0, 700.0),
window_save_gen: 0,
Expand Down
8 changes: 6 additions & 2 deletions src/ui/app_grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,19 @@ impl App {
/// Informational (the action lives on the detail page) so the whole card can
/// stay a single button that opens the detail view.
fn repo_status<'a>(&self, repo: &'a ColonyRepo) -> Element<'a, Message> {
let installed = crate::github::installed_app_path(repo).is_some();
let (installed, cached_version) = self
.install_status
.get(&repo.name)
.cloned()
.unwrap_or((false, None));
if !installed {
return text(crate::i18n::t("status_get"))
.size(self.sz(12))
.font(self.app_font())
.color(Palette::ACCENT())
.into();
}
let version = crate::github::load_installed_version(&repo.name);
let version = cached_version;
if let Some(new_tag) = self.available_updates.get(&repo.name) {
let head = row![
text(HEX_FILLED)
Expand Down
6 changes: 5 additions & 1 deletion src/ui/detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,11 @@ impl App {
let mut footer_items: Vec<Element<'_, Message>> = Vec::new();
// Store-basics metadata: the installed version was recorded at install
// time but never shown anywhere on the detail page.
if let Some(installed) = github::load_installed_version(&repo.name) {
if let Some(installed) = self
.install_status
.get(&repo.name)
.and_then(|(_, v)| v.clone())
{
footer_items.push(
text(crate::i18n::t_fmt(
"installed_version",
Expand Down
25 changes: 25 additions & 0 deletions src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ impl App {
}
}

/// Rebuild the per-repo install-status cache (one filesystem pass). The
/// grid and detail views read ONLY this cache - never the disk - so it
/// must be called whenever an install can have changed: catalog load,
/// download completion, uninstall.
pub fn refresh_install_status(&mut self) {
self.install_status = self
.colony_repo_list
.iter()
.map(|repo| {
let installed = github::installed_app_path(repo).is_some();
let version = if installed {
github::load_installed_version(&repo.name)
} else {
None
};
(repo.name.clone(), (installed, version))
})
.collect();
}

/// Pop the next repo queued by "Update all" and start its download; no-op
/// when the queue is empty. Called from BOTH completion arms so one failed
/// install never strands the remaining queue.
Expand Down Expand Up @@ -399,6 +419,7 @@ impl App {
crate::persistence::prune_orphaned_repo_caches(&live);
// Decode any freshly-cached app icons into image handles.
self.reload_app_icons();
self.refresh_install_status();
// New docs may have landed for the repo currently viewed.
self.detail_md_source = None;
self.refresh_detail_markdown();
Expand Down Expand Up @@ -588,6 +609,7 @@ impl App {
// advertising: clear it, or the card keeps showing
// "Update vX -> vX" until the next global check.
self.available_updates.remove(&repo_name);
self.refresh_install_status();
let display_name = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
Expand Down Expand Up @@ -697,6 +719,9 @@ impl App {
} else {
self.status_message =
i18n::t_fmt("uninstalled", &[("name", &repo_name)]);
// AFTER the directory removal, so the cache
// records the app as gone.
self.refresh_install_status();
return Task::perform(
async {
tokio::time::sleep(Duration::from_secs(4)).await;
Expand Down