Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b41f63d12 | |||
| 91efff75bc | |||
| fd0393bf7b | |||
| 6b939db37b | |||
| 4743c3eb8e | |||
| 5c99f3eb77 | |||
| 817ecf67be | |||
| 1da3c08bbe | |||
| 5112338c0f | |||
| 960eab965d | |||
| 848f4e7de7 | |||
| f22438d960 | |||
| 174d10772d | |||
| badffe9b8d | |||
| 5a4e11a459 | |||
| 45b878545a | |||
| d5e8fdcaaa | |||
| 38bac5b8e6 | |||
| e07a24c7c8 | |||
| ea2354fad5 | |||
| ba64cb53fa | |||
| 3622243271 | |||
| 67e5c1ed7a | |||
| 218afbb998 | |||
| d2e82caf54 | |||
| 1cde1e63d9 | |||
| cbc3463643 | |||
| 3d349f05b6 | |||
| 36ce1ed451 | |||
| a4a7464354 | |||
| 5e1c10cea3 | |||
| c82c9ad9e9 | |||
| 294420ed65 | |||
| 10cd5c6a1b | |||
| cb205fa9ee | |||
| 9125f7f6f3 | |||
| 4d8e882735 | |||
| d60fd6bb5e | |||
| 557dc6c900 | |||
| fe353e1c5d | |||
| a61f976546 | |||
| 05391ea1fe | |||
| 5884453c42 | |||
| d896bfb720 | |||
| 96832608b0 | |||
| ecb4057e99 | |||
| 1490be467c | |||
| b1fddbbcad | |||
| 7e7887d1f2 | |||
| 665b6fa736 | |||
| 1cf605e5db | |||
| 542a854612 | |||
| f8e7773e4c | |||
| 832176866b | |||
| fab2dfce4a | |||
| d3f32fc053 | |||
| 2aa141b410 | |||
| eea3d2af8a | |||
| f3dafb451d | |||
| 1da69dd44e | |||
| df8606251f | |||
| bfd91104f7 | |||
| 6eb50998fa | |||
| 1372d42350 | |||
| 500fa45c73 | |||
| 0d24ea7795 | |||
| d4ac469ac0 | |||
| f649b8eb9f | |||
| 0ef4c239f8 | |||
| e69c5f7a7c | |||
| abf61f5c32 | |||
| b72398d090 | |||
| a9ccc91090 | |||
| 40f7f12834 | |||
| 870ef2a739 | |||
| 539a570bd0 | |||
| 5b8c9d76cf | |||
| 9e086b8a4b | |||
| ee5de0ff47 | |||
| 2604e35a8c | |||
| 4969d0638d | |||
| 0a200c22bd | |||
| 4a9de759b9 | |||
| 718fd88351 | |||
| 76209c9cad | |||
| 905f5987fd | |||
| 5077b35068 | |||
| 784415834e | |||
| 19e4844a8e | |||
| 7d4a9d9c95 | |||
| c43c2b73ba | |||
| 682ae73ab7 | |||
| 667f0eb1c2 | |||
| d256c008f4 | |||
| e5d1bd8590 |
+24
-19
@@ -29,6 +29,7 @@ pub struct AppImageRecord {
|
||||
pub first_seen: String,
|
||||
pub last_scanned: String,
|
||||
pub file_modified: Option<String>,
|
||||
// Phase 2 fields
|
||||
pub fuse_status: Option<String>,
|
||||
pub wayland_status: Option<String>,
|
||||
pub update_info: Option<String>,
|
||||
@@ -37,17 +38,20 @@ pub struct AppImageRecord {
|
||||
pub update_checked: Option<String>,
|
||||
pub update_url: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
// Phase 3 fields
|
||||
pub sandbox_mode: Option<String>,
|
||||
// Phase 5 fields
|
||||
pub runtime_wayland_status: Option<String>,
|
||||
pub runtime_wayland_checked: Option<String>,
|
||||
// Async analysis pipeline
|
||||
pub analysis_status: Option<String>,
|
||||
// Custom launch arguments
|
||||
pub launch_args: Option<String>,
|
||||
// Phase 6 fields
|
||||
pub tags: Option<String>,
|
||||
pub pinned: bool,
|
||||
pub avg_startup_ms: Option<i64>,
|
||||
// extended metadata
|
||||
// Phase 9 fields - extended metadata
|
||||
pub appstream_id: Option<String>,
|
||||
pub appstream_description: Option<String>,
|
||||
pub generic_name: Option<String>,
|
||||
@@ -65,7 +69,7 @@ pub struct AppImageRecord {
|
||||
pub desktop_actions: Option<String>,
|
||||
pub has_signature: bool,
|
||||
pub screenshot_urls: Option<String>,
|
||||
// system modification tracking
|
||||
// Phase 11 fields - system modification tracking
|
||||
pub previous_version_path: Option<String>,
|
||||
pub source_url: Option<String>,
|
||||
pub autostart: bool,
|
||||
@@ -342,7 +346,7 @@ impl Database {
|
||||
}
|
||||
|
||||
fn init_schema(&self) -> SqlResult<()> {
|
||||
// base tables
|
||||
// Phase 1 base tables
|
||||
self.conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS schema_version (
|
||||
version INTEGER NOT NULL
|
||||
@@ -531,8 +535,8 @@ impl Database {
|
||||
}
|
||||
|
||||
fn migrate_to_v2(&self) -> SqlResult<()> {
|
||||
// Add columns to appimages table
|
||||
let extra_columns = [
|
||||
// Add Phase 2 columns to appimages table
|
||||
let phase2_columns = [
|
||||
"fuse_status TEXT",
|
||||
"wayland_status TEXT",
|
||||
"update_info TEXT",
|
||||
@@ -542,11 +546,12 @@ impl Database {
|
||||
"update_url TEXT",
|
||||
"notes TEXT",
|
||||
];
|
||||
for col in &extra_columns {
|
||||
for col in &phase2_columns {
|
||||
let sql = format!("ALTER TABLE appimages ADD COLUMN {}", col);
|
||||
self.conn.execute_batch(&sql).ok();
|
||||
}
|
||||
|
||||
// Phase 2 tables
|
||||
self.conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS launch_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -590,7 +595,7 @@ impl Database {
|
||||
}
|
||||
|
||||
fn migrate_to_v3(&self) -> SqlResult<()> {
|
||||
// security scanning
|
||||
// Phase 3 tables: security scanning
|
||||
self.conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS bundled_libraries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -1707,7 +1712,7 @@ impl Database {
|
||||
self.conn.query_row("SELECT COUNT(*) FROM appimages", [], |row| row.get(0))
|
||||
}
|
||||
|
||||
// --- Status updates ---
|
||||
// --- Phase 2: Status updates ---
|
||||
|
||||
pub fn update_fuse_status(&self, id: i64, status: &str) -> SqlResult<()> {
|
||||
self.conn.execute(
|
||||
@@ -1806,7 +1811,7 @@ impl Database {
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
// --- Launch tracking ---
|
||||
// --- Phase 2: Launch tracking ---
|
||||
|
||||
pub fn record_launch(&self, appimage_id: i64, source: &str) -> SqlResult<()> {
|
||||
self.conn.execute(
|
||||
@@ -1849,7 +1854,7 @@ impl Database {
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
// --- Update history ---
|
||||
// --- Phase 2: Update history ---
|
||||
|
||||
pub fn record_update(
|
||||
&self,
|
||||
@@ -1869,7 +1874,7 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Security scanning ---
|
||||
// --- Phase 3: Security scanning ---
|
||||
|
||||
pub fn clear_bundled_libraries(&self, appimage_id: i64) -> SqlResult<()> {
|
||||
self.conn.execute(
|
||||
@@ -2021,7 +2026,7 @@ impl Database {
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
// --- App data paths ---
|
||||
// --- Phase 3: App data paths ---
|
||||
|
||||
pub fn insert_app_data_path(
|
||||
&self,
|
||||
@@ -2101,7 +2106,7 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Runtime Wayland ---
|
||||
// --- Phase 5: Runtime Wayland ---
|
||||
|
||||
pub fn update_runtime_wayland_status(&self, id: i64, status: &str) -> SqlResult<()> {
|
||||
self.conn.execute(
|
||||
@@ -2111,7 +2116,7 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Config Backups ---
|
||||
// --- Phase 5: Config Backups ---
|
||||
|
||||
pub fn insert_config_backup(
|
||||
&self,
|
||||
@@ -2181,7 +2186,7 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- CVE Notifications ---
|
||||
// --- Phase 5: CVE Notifications ---
|
||||
|
||||
pub fn has_cve_been_notified(&self, appimage_id: i64, cve_id: &str) -> SqlResult<bool> {
|
||||
let count: i32 = self.conn.query_row(
|
||||
@@ -2206,7 +2211,7 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Sandbox Profiles ---
|
||||
// --- Phase 5: Sandbox Profiles ---
|
||||
|
||||
pub fn insert_sandbox_profile(
|
||||
&self,
|
||||
@@ -2276,7 +2281,7 @@ impl Database {
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
// --- Tags, Pin, Startup Time ---
|
||||
// --- Phase 6: Tags, Pin, Startup Time ---
|
||||
|
||||
pub fn update_tags(&self, id: i64, tags: Option<&str>) -> SqlResult<()> {
|
||||
self.conn.execute(
|
||||
@@ -2350,7 +2355,7 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Runtime Updates ---
|
||||
// --- Phase 5: Runtime Updates ---
|
||||
|
||||
pub fn record_runtime_update(
|
||||
&self,
|
||||
@@ -3254,7 +3259,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_updates() {
|
||||
fn test_phase2_status_updates() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let id = db.upsert_appimage("/path/app.AppImage", "app.AppImage", Some(2), 1000, true, None).unwrap();
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn detect_duplicates(db: &Database) -> Vec<DuplicateGroup> {
|
||||
|
||||
let mut groups = Vec::new();
|
||||
|
||||
// Find exact duplicates by SHA256 hash
|
||||
// Phase 1: Find exact duplicates by SHA256 hash
|
||||
let hash_groups = group_by_hash(&records);
|
||||
for (hash, members) in &hash_groups {
|
||||
if members.len() > 1 {
|
||||
@@ -95,7 +95,7 @@ pub fn detect_duplicates(db: &Database) -> Vec<DuplicateGroup> {
|
||||
}
|
||||
}
|
||||
|
||||
// Find same app name groups (excluding already-found exact dupes)
|
||||
// Phase 2: Find same app name groups (excluding already-found exact dupes)
|
||||
let exact_dupe_ids: std::collections::HashSet<i64> = groups
|
||||
.iter()
|
||||
.flat_map(|g| g.members.iter().map(|m| m.record.id))
|
||||
|
||||
@@ -117,7 +117,7 @@ pub fn show_drop_dialog(
|
||||
let on_complete_ref = on_complete.clone();
|
||||
|
||||
glib::spawn_future_local(async move {
|
||||
// Fast registration (copy + DB upsert only)
|
||||
// Phase 1: Fast registration (copy + DB upsert only)
|
||||
let result = gio::spawn_blocking(move || {
|
||||
register_dropped_files(&files, copy)
|
||||
})
|
||||
@@ -143,7 +143,7 @@ pub fn show_drop_dialog(
|
||||
toast_ref.add_toast(widgets::info_toast(&msg));
|
||||
}
|
||||
|
||||
// Background analysis for each file
|
||||
// Phase 2: Background analysis for each file
|
||||
let on_complete_bg = on_complete_ref.clone();
|
||||
for reg in registered {
|
||||
let on_complete_inner = on_complete_bg.clone();
|
||||
|
||||
+7
-7
@@ -1571,11 +1571,11 @@ impl DriftwoodWindow {
|
||||
let toast_overlay = self.imp().toast_overlay.get().unwrap().clone();
|
||||
let window_weak = self.downgrade();
|
||||
|
||||
// Two-pass scan:
|
||||
// Fast pass: discover files, upsert into DB, mark pending analysis
|
||||
// Background pass: heavy analysis per file
|
||||
// Two-phase scan:
|
||||
// Phase 1 (fast): discover files, upsert into DB, mark pending analysis
|
||||
// Phase 2 (background): heavy analysis per file
|
||||
glib::spawn_future_local(async move {
|
||||
// Fast registration
|
||||
// Phase 1: Fast registration
|
||||
let result = gio::spawn_blocking(move || {
|
||||
let bg_db = Database::open().expect("Failed to open database for scan");
|
||||
let start = Instant::now();
|
||||
@@ -1646,7 +1646,7 @@ impl DriftwoodWindow {
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Scan: {} files, {} new, {} removed, {} skipped (unchanged), took {}ms",
|
||||
"Scan phase 1: {} files, {} new, {} removed, {} skipped (unchanged), took {}ms",
|
||||
total, new_count, removed_count, skipped_count, start.elapsed().as_millis()
|
||||
);
|
||||
|
||||
@@ -1665,7 +1665,7 @@ impl DriftwoodWindow {
|
||||
.await;
|
||||
|
||||
if let Ok((total, new_count, needs_analysis)) = result {
|
||||
// Dismiss the "Scanning..." toast now that fast scan is done
|
||||
// Dismiss the "Scanning..." toast now that Phase 1 is done
|
||||
if let Some(ref toast) = scan_toast {
|
||||
toast.dismiss();
|
||||
}
|
||||
@@ -1689,7 +1689,7 @@ impl DriftwoodWindow {
|
||||
};
|
||||
toast_overlay.add_toast(widgets::info_toast(&msg));
|
||||
|
||||
// Background analysis per file with debounced UI refresh
|
||||
// Phase 2: Background analysis per file with debounced UI refresh
|
||||
let running = analysis::running_count();
|
||||
if running > 0 {
|
||||
log::info!("Analyzing {} AppImage(s) in background ({} already running)", needs_analysis.len(), running);
|
||||
|
||||
Reference in New Issue
Block a user