Add comprehensive AppImage metadata extraction, display, and bug fixes

- Add AppStream XML parser (quick-xml) to extract rich metadata from bundled
  metainfo/appdata files: description, developer, license, URLs, keywords,
  categories, content rating, release history, and MIME types
- Database migration v9: 16 new columns for extended metadata storage
- Extended inspector to parse AppStream XML, desktop entry extended fields,
  and detect binary signatures without executing AppImages
- Redesigned detail view overview tab with 8 conditional groups: About,
  Description, Links, Release History, Usage, Capabilities, File Info
- Fix crash on exit caused by stale GLib SourceId removal in debounce timers
- Fix wayland.rs executing AppImages directly to detect squashfs offset,
  replaced with safe binary scan via find_squashfs_offset_for()
- Fix scan skipping re-analysis of apps missing new metadata fields
This commit is contained in:
lashman
2026-02-27 18:31:07 +02:00
parent 39b773fed5
commit 1bb7a3bdc0
13 changed files with 1239 additions and 24 deletions

View File

@@ -50,6 +50,23 @@ pub struct AppImageRecord {
pub tags: Option<String>,
pub pinned: bool,
pub avg_startup_ms: Option<i64>,
// Phase 9 fields - comprehensive metadata
pub appstream_id: Option<String>,
pub appstream_description: Option<String>,
pub generic_name: Option<String>,
pub license: Option<String>,
pub homepage_url: Option<String>,
pub bugtracker_url: Option<String>,
pub donation_url: Option<String>,
pub help_url: Option<String>,
pub vcs_url: Option<String>,
pub keywords: Option<String>,
pub mime_types: Option<String>,
pub content_rating: Option<String>,
pub project_group: Option<String>,
pub release_history: Option<String>,
pub desktop_actions: Option<String>,
pub has_signature: bool,
}
#[derive(Debug, Clone)]
@@ -343,6 +360,10 @@ impl Database {
self.migrate_to_v8()?;
}
if current_version < 9 {
self.migrate_to_v9()?;
}
// Ensure all expected columns exist (repairs DBs where a migration
// was updated after it had already run on this database)
self.ensure_columns()?;
@@ -682,6 +703,44 @@ impl Database {
Ok(())
}
fn migrate_to_v9(&self) -> SqlResult<()> {
let new_columns = [
"appstream_id TEXT",
"appstream_description TEXT",
"generic_name TEXT",
"license TEXT",
"homepage_url TEXT",
"bugtracker_url TEXT",
"donation_url TEXT",
"help_url TEXT",
"vcs_url TEXT",
"keywords TEXT",
"mime_types TEXT",
"content_rating TEXT",
"project_group TEXT",
"release_history TEXT",
"desktop_actions TEXT",
"has_signature INTEGER NOT NULL DEFAULT 0",
];
for col in &new_columns {
let sql = format!("ALTER TABLE appimages ADD COLUMN {}", col);
match self.conn.execute(&sql, []) {
Ok(_) => {}
Err(e) => {
let msg = e.to_string();
if !msg.contains("duplicate column") {
return Err(e);
}
}
}
}
self.conn.execute(
"UPDATE schema_version SET version = ?1",
params![9],
)?;
Ok(())
}
pub fn upsert_appimage(
&self,
path: &str,
@@ -747,6 +806,68 @@ impl Database {
Ok(())
}
pub fn update_appstream_metadata(
&self,
id: i64,
appstream_id: Option<&str>,
appstream_description: Option<&str>,
generic_name: Option<&str>,
license: Option<&str>,
homepage_url: Option<&str>,
bugtracker_url: Option<&str>,
donation_url: Option<&str>,
help_url: Option<&str>,
vcs_url: Option<&str>,
keywords: Option<&str>,
mime_types: Option<&str>,
content_rating: Option<&str>,
project_group: Option<&str>,
release_history: Option<&str>,
desktop_actions: Option<&str>,
has_signature: bool,
) -> SqlResult<()> {
self.conn.execute(
"UPDATE appimages SET
appstream_id = ?2,
appstream_description = ?3,
generic_name = ?4,
license = ?5,
homepage_url = ?6,
bugtracker_url = ?7,
donation_url = ?8,
help_url = ?9,
vcs_url = ?10,
keywords = ?11,
mime_types = ?12,
content_rating = ?13,
project_group = ?14,
release_history = ?15,
desktop_actions = ?16,
has_signature = ?17
WHERE id = ?1",
params![
id,
appstream_id,
appstream_description,
generic_name,
license,
homepage_url,
bugtracker_url,
donation_url,
help_url,
vcs_url,
keywords,
mime_types,
content_rating,
project_group,
release_history,
desktop_actions,
has_signature,
],
)?;
Ok(())
}
pub fn update_sha256(&self, id: i64, sha256: &str) -> SqlResult<()> {
self.conn.execute(
"UPDATE appimages SET sha256 = ?2 WHERE id = ?1",
@@ -782,7 +903,11 @@ impl Database {
fuse_status, wayland_status, update_info, update_type,
latest_version, update_checked, update_url, notes, sandbox_mode,
runtime_wayland_status, runtime_wayland_checked, analysis_status,
launch_args, tags, pinned, avg_startup_ms";
launch_args, tags, pinned, avg_startup_ms,
appstream_id, appstream_description, generic_name, license,
homepage_url, bugtracker_url, donation_url, help_url, vcs_url,
keywords, mime_types, content_rating, project_group,
release_history, desktop_actions, has_signature";
fn row_to_record(row: &rusqlite::Row) -> rusqlite::Result<AppImageRecord> {
Ok(AppImageRecord {
@@ -823,6 +948,22 @@ impl Database {
tags: row.get(34).unwrap_or(None),
pinned: row.get::<_, bool>(35).unwrap_or(false),
avg_startup_ms: row.get(36).unwrap_or(None),
appstream_id: row.get(37).unwrap_or(None),
appstream_description: row.get(38).unwrap_or(None),
generic_name: row.get(39).unwrap_or(None),
license: row.get(40).unwrap_or(None),
homepage_url: row.get(41).unwrap_or(None),
bugtracker_url: row.get(42).unwrap_or(None),
donation_url: row.get(43).unwrap_or(None),
help_url: row.get(44).unwrap_or(None),
vcs_url: row.get(45).unwrap_or(None),
keywords: row.get(46).unwrap_or(None),
mime_types: row.get(47).unwrap_or(None),
content_rating: row.get(48).unwrap_or(None),
project_group: row.get(49).unwrap_or(None),
release_history: row.get(50).unwrap_or(None),
desktop_actions: row.get(51).unwrap_or(None),
has_signature: row.get::<_, bool>(52).unwrap_or(false),
})
}
@@ -1696,13 +1837,13 @@ mod tests {
fn test_fresh_database_creates_at_latest_version() {
let db = Database::open_in_memory().unwrap();
// Verify schema_version is at the latest (8)
// Verify schema_version is at the latest (9)
let version: i32 = db.conn.query_row(
"SELECT version FROM schema_version LIMIT 1",
[],
|row| row.get(0),
).unwrap();
assert_eq!(version, 8);
assert_eq!(version, 9);
// All tables that should exist after the full v1-v7 migration chain
let expected_tables = [