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:
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user