Add feature roadmap design for 25 new features
Comprehensive design doc covering discovery-to-uninstall lifecycle: - System modification tracking for clean reversible uninstalls - Sorted by complexity from quick wins to major features - Covers FUSE wizard, autostart, MIME types, catalog browser, portable mode, batch operations, and more
This commit is contained in:
679
docs/plans/2026-02-27-feature-roadmap-design.md
Normal file
679
docs/plans/2026-02-27-feature-roadmap-design.md
Normal file
@@ -0,0 +1,679 @@
|
||||
# Driftwood Feature Roadmap - Design Document
|
||||
|
||||
**Goal:** Add 26 features to make Driftwood the definitive AppImage manager for Linux newcomers coming from Windows, covering the full lifecycle from discovery to clean uninstall.
|
||||
|
||||
**Architecture:** All features build on the existing GTK4/libadwaita/Rust stack. Every system modification (desktop files, icons, MIME associations, autostart entries) is tracked in a central `system_modifications` table and fully reversed on app removal. Features are ordered from simplest to most complex.
|
||||
|
||||
**Tech Stack:** Rust, gtk4-rs, libadwaita-rs, rusqlite, gio, notify crate, pkexec/polkit for privileged operations, XDG specs (Desktop Entry, Autostart, MIME Applications), AppImage feed.json catalog.
|
||||
|
||||
---
|
||||
|
||||
## Core Architecture: System Modification Tracking
|
||||
|
||||
Every feature that touches system files MUST use this tracking system. No exceptions.
|
||||
|
||||
### New Database Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS system_modifications (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
appimage_id INTEGER REFERENCES appimages(id) ON DELETE CASCADE,
|
||||
mod_type TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
previous_value TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_system_mods_appimage
|
||||
ON system_modifications(appimage_id);
|
||||
```
|
||||
|
||||
**mod_type values:** `desktop_file`, `icon`, `autostart`, `mime_default`, `mime_entry`, `system_desktop`, `system_icon`, `system_binary`
|
||||
|
||||
### Core Functions (src/core/integrator.rs)
|
||||
|
||||
```rust
|
||||
pub fn register_modification(db: &Database, appimage_id: i64, mod_type: &str, file_path: &str, previous_value: Option<&str>) -> Result<()>
|
||||
pub fn undo_modification(db: &Database, mod_id: i64) -> Result<()>
|
||||
pub fn undo_all_modifications(db: &Database, appimage_id: i64) -> Result<()>
|
||||
```
|
||||
|
||||
### Uninstall Flow
|
||||
|
||||
1. Query `system_modifications` for the appimage_id
|
||||
2. For each modification (in reverse order):
|
||||
- `desktop_file` / `autostart` / `icon`: delete the file
|
||||
- `mime_default`: restore previous_value via `xdg-mime default {previous_value} {mime_type}`
|
||||
- `system_*`: delete via `pkexec rm`
|
||||
3. Run `update-desktop-database ~/.local/share/applications/`
|
||||
4. Run `gtk-update-icon-cache ~/.local/share/icons/hicolor/` (if icons were removed)
|
||||
5. Optionally delete app data paths (with user confirmation)
|
||||
6. Delete the AppImage file
|
||||
7. Delete DB record (CASCADE handles system_modifications rows)
|
||||
|
||||
### CLI Purge Command
|
||||
|
||||
`driftwood purge` - removes ALL system modifications for ALL managed apps, for when Driftwood itself is being removed from the system.
|
||||
|
||||
---
|
||||
|
||||
## Feature Specifications (Sorted: Easiest to Most Complex)
|
||||
|
||||
---
|
||||
|
||||
### F1. Auto-Set Executable Permission on Existing Files
|
||||
|
||||
**Complexity:** Trivial (15 min)
|
||||
**Problem:** Files already in scan directories skip chmod during drag-and-drop. Non-executable AppImages found during scan aren't fixed.
|
||||
**Files:** `src/ui/drop_dialog.rs`, `src/core/discovery.rs`
|
||||
|
||||
**Changes:**
|
||||
- `drop_dialog.rs`: In the `in_scan_dir` branch of `register_dropped_files()`, add permission check and fix:
|
||||
```rust
|
||||
if !std::os::unix::fs::PermissionsExt::mode(&metadata.permissions()) & 0o111 != 0 {
|
||||
std::fs::set_permissions(&final_path, std::fs::Permissions::from_mode(0o755))?;
|
||||
}
|
||||
```
|
||||
- `discovery.rs`: After scan finds a non-executable AppImage, auto-fix permissions and log it
|
||||
|
||||
---
|
||||
|
||||
### F2. Move-or-Copy Option for Drag-and-Drop (+ Keep in Place)
|
||||
|
||||
**Complexity:** Easy (30 min)
|
||||
**Problem:** Always copies file, wasting disk. No option to keep in place.
|
||||
**Files:** `src/ui/drop_dialog.rs`
|
||||
|
||||
**Changes:**
|
||||
- Replace 3-button dialog with 4 response options:
|
||||
- `"cancel"` - Cancel
|
||||
- `"keep-in-place"` - Keep in place (register at current location, set executable, no copy)
|
||||
- `"copy-only"` - Copy to Applications (current "Just add" behavior)
|
||||
- `"copy-and-integrate"` - Copy & add to menu (current "Add to app menu", suggested/default)
|
||||
- `register_dropped_files()` receives a new `copy_mode` enum: `KeepInPlace`, `CopyOnly`, `CopyAndIntegrate`
|
||||
- `KeepInPlace`: set executable on original path, register in DB at original location, optionally integrate
|
||||
- Dialog text updated: "Where should this AppImage live?"
|
||||
|
||||
---
|
||||
|
||||
### F3. Version Rollback
|
||||
|
||||
**Complexity:** Easy (1 hr)
|
||||
**Problem:** No way to go back if an update breaks things.
|
||||
**Files:** `src/core/updater.rs`, `src/ui/detail_view.rs`, `src/core/database.rs`
|
||||
|
||||
**Changes:**
|
||||
- New DB column: `previous_version_path TEXT` on appimages
|
||||
- In `updater.rs` update flow: before replacing, rename old to `{path}.prev` and store path in `previous_version_path`
|
||||
- Detail view system tab: "Rollback to previous version" button (visible only when `previous_version_path` is set)
|
||||
- Rollback: swap current and .prev files, update DB fields, re-run analysis
|
||||
- Cleanup: delete .prev file when user explicitly confirms or after configurable retention
|
||||
|
||||
---
|
||||
|
||||
### F4. Source Tracking
|
||||
|
||||
**Complexity:** Easy (1 hr)
|
||||
**Problem:** Users can't tell where an AppImage was downloaded from.
|
||||
**Files:** `src/ui/detail_view.rs`, `src/core/database.rs`, `src/ui/drop_dialog.rs`
|
||||
|
||||
**Changes:**
|
||||
- New DB column: `source_url TEXT` on appimages
|
||||
- Auto-detect from `update_info` field (already stored): parse GitHub/GitLab URLs
|
||||
- Display "Source: github.com/obsidianmd/obsidian" on detail view overview tab
|
||||
- Drop dialog: optional "Where did you download this?" text field (pre-filled if detected)
|
||||
- Catalog installs (F26): automatically set source_url from catalog entry
|
||||
|
||||
---
|
||||
|
||||
### F5. Launch Statistics Dashboard
|
||||
|
||||
**Complexity:** Easy (1-2 hrs)
|
||||
**Problem:** Launch data is tracked but never shown.
|
||||
**Files:** `src/ui/dashboard.rs`, `src/core/database.rs`
|
||||
|
||||
**Changes:**
|
||||
- New DB queries:
|
||||
- `get_top_launched(limit: i32) -> Vec<(String, u64)>` - most launched apps
|
||||
- `get_launch_count_since(since: &str) -> u64` - total launches since date
|
||||
- `get_recent_launches(limit: i32) -> Vec<(String, String)>` - recent launch events with timestamps
|
||||
- Dashboard: new "Activity" section showing:
|
||||
- Top 5 most-launched apps with launch counts
|
||||
- "X launches this week" summary stat
|
||||
- "Last launched: AppName, 2 hours ago"
|
||||
|
||||
---
|
||||
|
||||
### F6. Batch Operations
|
||||
|
||||
**Complexity:** Medium (2-3 hrs)
|
||||
**Problem:** Can't select multiple AppImages for bulk actions.
|
||||
**Files:** `src/ui/library_view.rs`, `src/ui/app_card.rs`, `src/window.rs`
|
||||
|
||||
**Changes:**
|
||||
- Library view header: "Select" toggle button
|
||||
- When active: app cards show checkboxes, bottom action bar slides up
|
||||
- Action bar buttons: "Integrate" / "Remove Integration" / "Delete" / "Export"
|
||||
- Each action confirms with count: "Integrate 5 AppImages?"
|
||||
- Delete uses the full uninstall flow (F14/system_modifications cleanup)
|
||||
- Selection state stored in a `HashSet<i64>` of record IDs on the LibraryView
|
||||
|
||||
---
|
||||
|
||||
### F7. Automatic Desktop Integration on Scan
|
||||
|
||||
**Complexity:** Easy (30 min)
|
||||
**Problem:** Users forget to integrate after scanning.
|
||||
**Files:** `src/window.rs`, `src/ui/preferences.rs`
|
||||
|
||||
**Changes:**
|
||||
- GSettings key `auto-integrate` already exists (default false)
|
||||
- Wire it up: after scan completes in `window.rs`, if setting is true, iterate newly discovered apps and call `integrator::integrate()` for each
|
||||
- Register all created files via `register_modification()`
|
||||
- Preferences: add toggle in Behavior page (already may be there, verify)
|
||||
|
||||
---
|
||||
|
||||
### F8. Autostart Manager
|
||||
|
||||
**Complexity:** Medium (2 hrs)
|
||||
**Problem:** No way to set AppImages to start at login.
|
||||
**Spec:** XDG Autostart - `.desktop` file in `~/.config/autostart/`
|
||||
**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs`, `src/core/database.rs`
|
||||
|
||||
**Changes:**
|
||||
- New DB column: `autostart INTEGER NOT NULL DEFAULT 0`
|
||||
- New functions in `integrator.rs`:
|
||||
```rust
|
||||
pub fn enable_autostart(db: &Database, record: &AppImageRecord) -> Result<PathBuf>
|
||||
pub fn disable_autostart(db: &Database, record_id: i64) -> Result<()>
|
||||
```
|
||||
- `enable_autostart`: creates `~/.config/autostart/driftwood-{id}.desktop` with:
|
||||
```ini
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name={app_name}
|
||||
Exec={appimage_path}
|
||||
Icon={icon_path}
|
||||
X-GNOME-Autostart-enabled=true
|
||||
X-Driftwood-AppImage-ID={id}
|
||||
```
|
||||
- Registers modification with mod_type `autostart`
|
||||
- Detail view system tab: "Start at login" switch row
|
||||
- `disable_autostart`: deletes the file, removes from system_modifications
|
||||
- Uninstall flow: handled by `undo_all_modifications()`
|
||||
|
||||
---
|
||||
|
||||
### F9. System Notification Integration
|
||||
|
||||
**Complexity:** Medium (2 hrs)
|
||||
**Problem:** Toasts vanish, important events get missed.
|
||||
**Files:** `src/core/notification.rs`, `src/window.rs`
|
||||
|
||||
**Changes:**
|
||||
- Use `gio::Application::send_notification(id, notification)` for:
|
||||
- App crash on launch (high priority)
|
||||
- Updates available after background check (normal priority)
|
||||
- Security vulnerabilities found (high priority if critical/high severity)
|
||||
- Keep toasts for minor confirmations (copied, integrated, etc.)
|
||||
- Notification click opens Driftwood and navigates to relevant view
|
||||
- `notification.rs` already has the logic for CVE notifications - extend to use gio::Notification instead of/in addition to libnotify
|
||||
|
||||
---
|
||||
|
||||
### F10. Storage Dashboard per App
|
||||
|
||||
**Complexity:** Medium (2 hrs)
|
||||
**Problem:** Users don't know total disk usage per app.
|
||||
**Files:** `src/ui/detail_view.rs`, `src/core/footprint.rs`, `src/core/database.rs`
|
||||
|
||||
**Changes:**
|
||||
- `footprint.rs`: new function `get_total_footprint(db: &Database, record_id: i64) -> FootprintSummary`
|
||||
```rust
|
||||
pub struct FootprintSummary {
|
||||
pub binary_size: u64,
|
||||
pub config_size: u64,
|
||||
pub cache_size: u64,
|
||||
pub data_size: u64,
|
||||
pub state_size: u64,
|
||||
pub total_size: u64,
|
||||
}
|
||||
```
|
||||
- Detail view storage tab: visual breakdown with labeled size bars
|
||||
- Each category shows path and size: "Config (~/.config/MyApp) - 12 MB"
|
||||
- "Clean cache" button per category (deletes cache paths only)
|
||||
- Library list view: optional "Total size" column
|
||||
|
||||
---
|
||||
|
||||
### F11. Background Update Checks
|
||||
|
||||
**Complexity:** Medium (2-3 hrs)
|
||||
**Problem:** No automatic update awareness.
|
||||
**Files:** `src/window.rs`, `src/ui/dashboard.rs`, `src/ui/library_view.rs`, GSettings schema
|
||||
|
||||
**Changes:**
|
||||
- New GSettings key: `update-check-interval-hours` type i, default 24, range 1-168
|
||||
- New GSettings key: `last-update-check` type s, default ''
|
||||
- On startup: if `auto-check-updates` is true and enough time has passed, spawn background check
|
||||
- Background check: iterate all apps with update_info, call `check_appimage_for_update()` per app
|
||||
- Results: update `update_available` column in DB, send gio::Notification if updates found
|
||||
- Dashboard: show "X updates available" with timestamp "Last checked: 2h ago"
|
||||
- Library view: badge on apps with updates
|
||||
- Preferences: toggle + interval dropdown
|
||||
|
||||
---
|
||||
|
||||
### F12. One-Click Update All
|
||||
|
||||
**Complexity:** Medium (3-4 hrs)
|
||||
**Problem:** Can only update one app at a time.
|
||||
**Files:** `src/ui/dashboard.rs`, new `src/ui/batch_update_dialog.rs`, `src/core/updater.rs`
|
||||
|
||||
**Changes:**
|
||||
- Dashboard: "Update All (N)" button when updates are available
|
||||
- Opens batch update dialog showing list of apps to update with checkboxes
|
||||
- Progress: per-app progress bar, overall progress bar
|
||||
- Each update: download new version, save old as .prev (F3 rollback), replace, re-analyze
|
||||
- On completion: summary toast "Updated 5 apps successfully, 1 failed"
|
||||
- Failed updates: show error per app, keep old version
|
||||
- Cancel: stops remaining updates, already-updated apps stay updated
|
||||
|
||||
---
|
||||
|
||||
### F13. Full Uninstall with Data Cleanup
|
||||
|
||||
**Complexity:** Medium (3 hrs)
|
||||
**Problem:** Deleting AppImage leaves config/cache/data behind.
|
||||
**Files:** `src/ui/detail_view.rs`, new confirmation dialog logic, `src/core/footprint.rs`
|
||||
|
||||
**Changes:**
|
||||
- Delete button in detail view triggers new uninstall flow:
|
||||
1. Show dialog with FootprintSummary (from F10):
|
||||
- "Delete MyApp?" with breakdown:
|
||||
- [x] AppImage file (245 MB)
|
||||
- [x] Configuration (~/.config/MyApp) - 12 MB
|
||||
- [x] Cache (~/.cache/MyApp) - 89 MB
|
||||
- [x] Data (~/.local/share/MyApp) - 1.2 GB
|
||||
- Total: 1.5 GB will be freed
|
||||
2. All checked by default, user can uncheck to keep data
|
||||
3. On confirm:
|
||||
- Call `undo_all_modifications()` (removes .desktop, icons, autostart, MIME defaults)
|
||||
- Delete selected data paths
|
||||
- Delete the AppImage file
|
||||
- Remove DB record
|
||||
- Batch delete (F6) uses same flow with aggregated summary
|
||||
|
||||
---
|
||||
|
||||
### F14. Theme/Icon Preview in Drop Dialog
|
||||
|
||||
**Complexity:** Medium (2 hrs)
|
||||
**Problem:** Users don't see what the app looks like before integrating.
|
||||
**Files:** `src/ui/drop_dialog.rs`, `src/core/inspector.rs`
|
||||
|
||||
**Changes:**
|
||||
- New function in inspector: `extract_icon_fast(path: &Path) -> Option<PathBuf>`
|
||||
- Runs `unsquashfs -l` to find icon file, then extracts just that one file
|
||||
- Much faster than full inspection
|
||||
- Drop dialog: after registration, show preview card:
|
||||
- App icon (from fast extraction)
|
||||
- App name (from filename initially, updated after full analysis)
|
||||
- "This is how it will appear in your app menu"
|
||||
- If icon extraction fails, show default AppImage icon
|
||||
|
||||
---
|
||||
|
||||
### F15. FUSE Fix Wizard
|
||||
|
||||
**Complexity:** Significant (4-5 hrs)
|
||||
**Problem:** #1 support issue - FUSE not installed, AppImages won't run.
|
||||
**Files:** `src/core/fuse.rs`, new `src/ui/fuse_wizard.rs`, `src/ui/dashboard.rs`, new `data/app.driftwood.Driftwood.policy`
|
||||
|
||||
**Changes:**
|
||||
- New distro detection in `fuse.rs`:
|
||||
```rust
|
||||
pub struct DistroInfo {
|
||||
pub id: String, // "ubuntu", "fedora", "arch", etc.
|
||||
pub id_like: Vec<String>,
|
||||
pub version_id: String,
|
||||
}
|
||||
pub fn detect_distro() -> Option<DistroInfo> // parses /etc/os-release
|
||||
pub fn get_fuse_install_command(distro: &DistroInfo) -> Option<String>
|
||||
```
|
||||
- Install commands by distro family:
|
||||
- Debian/Ubuntu: `apt install -y libfuse2t64` (24.04+) or `apt install -y libfuse2` (older)
|
||||
- Fedora/RHEL: `dnf install -y fuse-libs`
|
||||
- Arch/Manjaro: `pacman -S --noconfirm fuse2`
|
||||
- openSUSE: `zypper install -y libfuse2`
|
||||
- Polkit policy file (`data/app.driftwood.Driftwood.policy`):
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
<action id="app.driftwood.Driftwood.install-fuse">
|
||||
<description>Install FUSE library for AppImage support</description>
|
||||
<message>Authentication is required to install the FUSE library</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>auth_admin</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
||||
```
|
||||
- Wizard dialog (NavigationPage-style multi-step):
|
||||
1. "FUSE is not installed" - explanation of what FUSE is and why it's needed
|
||||
2. "We detected {distro}. Install with: `{command}`" - shows exact command
|
||||
3. "Install now" button runs `pkexec sh -c "{command}"` and shows output
|
||||
4. Re-check: calls `detect_system_fuse()` and shows success/failure
|
||||
- Dashboard: yellow banner when FUSE is missing with "Fix now" button
|
||||
|
||||
---
|
||||
|
||||
### F16. File Type Association Manager
|
||||
|
||||
**Complexity:** Significant (4 hrs)
|
||||
**Problem:** AppImages don't register MIME types, so files don't open with them.
|
||||
**Spec:** XDG MIME Applications - `~/.local/share/applications/mimeapps.list`
|
||||
**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs`
|
||||
|
||||
**Changes:**
|
||||
- `inspector.rs` already extracts desktop entry content including MimeType= field
|
||||
- New function: `parse_mime_types(desktop_entry: &str) -> Vec<String>` - extracts MimeType= values
|
||||
- When integrating: include `MimeType=` in generated .desktop file
|
||||
- Detail view overview tab: "Supported file types" section listing MIME types
|
||||
- Per-type toggle: "Set as default for .png files"
|
||||
- Setting default:
|
||||
1. Query current default: `xdg-mime query default {mime_type}`
|
||||
2. Store in `system_modifications` with `previous_value` = current default
|
||||
3. Set new: `xdg-mime default driftwood-{id}.desktop {mime_type}`
|
||||
- Removing: restore previous default from `previous_value`
|
||||
- Uninstall: handled by `undo_all_modifications()`
|
||||
|
||||
---
|
||||
|
||||
### F17. Taskbar/Panel Icon Fix (StartupWMClass)
|
||||
|
||||
**Complexity:** Significant (3 hrs)
|
||||
**Problem:** Running AppImages show wrong/generic icon in taskbar.
|
||||
**Files:** `src/core/integrator.rs`, `src/core/wayland.rs`, `src/ui/detail_view.rs`
|
||||
|
||||
**Changes:**
|
||||
- During inspection: extract `StartupWMClass=` from embedded .desktop entry
|
||||
- Store in new DB column: `startup_wm_class TEXT`
|
||||
- When generating .desktop file for integration, include StartupWMClass if available
|
||||
- After launch + Wayland analysis: read `/proc/{pid}/environ` for `GDK_BACKEND`, check window list via `xdotool` or `xprop` for WM_CLASS
|
||||
- If WM_CLASS doesn't match StartupWMClass: log warning, offer to patch .desktop file
|
||||
- Detail view system tab: show detected WM_CLASS, allow manual override
|
||||
|
||||
---
|
||||
|
||||
### F18. Download Verification Helper
|
||||
|
||||
**Complexity:** Significant (4 hrs)
|
||||
**Problem:** No way to verify AppImage authenticity.
|
||||
**Files:** New `src/core/verification.rs`, `src/ui/detail_view.rs`, `src/ui/drop_dialog.rs`
|
||||
|
||||
**Changes:**
|
||||
- AppImage signature spec: GPG signature embedded at offset stored in ELF section
|
||||
- New module `verification.rs`:
|
||||
```rust
|
||||
pub enum VerificationStatus {
|
||||
SignedValid { signer: String },
|
||||
SignedInvalid { reason: String },
|
||||
Unsigned,
|
||||
ChecksumMatch,
|
||||
ChecksumMismatch,
|
||||
NotChecked,
|
||||
}
|
||||
pub fn check_embedded_signature(path: &Path) -> VerificationStatus
|
||||
pub fn verify_sha256(path: &Path, expected: &str) -> VerificationStatus
|
||||
```
|
||||
- `check_embedded_signature`: runs `unsquashfs -l` to check for `.sha256` or signature files, or uses the AppImage --appimage-signature flag
|
||||
- Drop dialog: after adding, show verification badge
|
||||
- Detail view: "Verification: Signed by ..." or "Unsigned - verify manually"
|
||||
- Manual verification: paste SHA256 hash, compare with computed hash
|
||||
- New DB column: `verification_status TEXT` on appimages
|
||||
|
||||
---
|
||||
|
||||
### F19. First-Run Permission Summary
|
||||
|
||||
**Complexity:** Medium (3 hrs)
|
||||
**Problem:** Users don't know what an AppImage can access before running it.
|
||||
**Files:** New `src/ui/permission_dialog.rs`, `src/ui/detail_view.rs`
|
||||
|
||||
**Changes:**
|
||||
- Before first launch of any AppImage (check `launch_events` count = 0):
|
||||
1. Show permission dialog:
|
||||
- "MyApp will run with full access to your files and system"
|
||||
- Icon + app name
|
||||
- If `has_firejail()`: offer sandbox options
|
||||
- "Run without restrictions" (default for now)
|
||||
- "Run in Firejail sandbox (recommended)"
|
||||
- "Don't show this again" checkbox (stored per-app in DB)
|
||||
2. If user chooses Firejail: set `sandbox_mode = 'firejail'` in DB
|
||||
- New DB column: `first_run_prompted INTEGER NOT NULL DEFAULT 0`
|
||||
- Skip dialog if `first_run_prompted = 1`
|
||||
- Preferences: global toggle to disable first-run prompts
|
||||
|
||||
---
|
||||
|
||||
### F20. Default App Selector
|
||||
|
||||
**Complexity:** Medium (3 hrs)
|
||||
**Problem:** Can't set AppImage as default browser, mail client, etc.
|
||||
**Files:** `src/ui/detail_view.rs`, `src/core/integrator.rs`
|
||||
|
||||
**Changes:**
|
||||
- Detect app capabilities from categories:
|
||||
- `WebBrowser` -> can be default web browser
|
||||
- `Email` -> can be default email client
|
||||
- `FileManager` -> can be default file manager
|
||||
- `TerminalEmulator` -> can be default terminal
|
||||
- Detail view overview tab: "Set as default" section (only shown for applicable categories)
|
||||
- Setting defaults:
|
||||
- Browser: `xdg-settings set default-web-browser driftwood-{id}.desktop`
|
||||
- Email: `xdg-mime default driftwood-{id}.desktop x-scheme-handler/mailto`
|
||||
- File manager: `xdg-mime default driftwood-{id}.desktop inode/directory`
|
||||
- Before setting: query current default, store in `system_modifications` with `previous_value`
|
||||
- Uninstall: restore previous defaults via `undo_all_modifications()`
|
||||
- Requirements: app must be integrated first (needs .desktop file)
|
||||
|
||||
---
|
||||
|
||||
### F21. Multi-User / System-Wide Install
|
||||
|
||||
**Complexity:** Significant (4 hrs)
|
||||
**Problem:** Can't install for all users on a shared computer.
|
||||
**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs`, polkit policy
|
||||
|
||||
**Changes:**
|
||||
- Reuses polkit policy from F15 (add new action `app.driftwood.Driftwood.system-install`)
|
||||
- "Install system-wide" option in detail view (requires app to be integrated first)
|
||||
- Flow:
|
||||
1. `pkexec cp {appimage} /opt/driftwood-apps/{filename}`
|
||||
2. `pkexec chmod 755 /opt/driftwood-apps/{filename}`
|
||||
3. Generate system .desktop in `/usr/share/applications/driftwood-{id}.desktop`
|
||||
4. `pkexec cp {desktop_file} /usr/share/applications/`
|
||||
5. Copy icon to `/usr/share/icons/hicolor/` via pkexec
|
||||
6. `pkexec update-desktop-database /usr/share/applications/`
|
||||
- Register all paths as `system_desktop`, `system_icon`, `system_binary` in system_modifications
|
||||
- Uninstall system-wide: `pkexec rm` for each tracked path
|
||||
- DB flag: `system_wide INTEGER NOT NULL DEFAULT 0`
|
||||
|
||||
---
|
||||
|
||||
### F22. CLI Enhancements
|
||||
|
||||
**Complexity:** Medium (3 hrs)
|
||||
**Problem:** Missing install-from-URL and update-all commands.
|
||||
**Files:** `src/cli.rs`
|
||||
|
||||
**New commands:**
|
||||
- `driftwood install <url>` - Download from URL, validate, move to ~/Applications, set executable, register, optionally integrate
|
||||
- `driftwood update --all` - Check all apps, download and apply available updates
|
||||
- `driftwood autostart <path> --enable/--disable` - Toggle autostart for an AppImage
|
||||
- `driftwood purge` - Remove ALL system modifications for all managed apps (for Driftwood removal)
|
||||
- `driftwood verify <path>` - Check embedded signature or compare SHA256
|
||||
|
||||
---
|
||||
|
||||
### F23. Portable Mode / USB Drive Support
|
||||
|
||||
**Complexity:** Major (6-8 hrs)
|
||||
**Problem:** Can't manage AppImages on removable media.
|
||||
**Files:** New `src/core/portable.rs`, `src/ui/library_view.rs`, `src/window.rs`, `src/core/database.rs`
|
||||
|
||||
**Changes:**
|
||||
- New GSettings key: `watch-removable-media` type b, default false
|
||||
- `portable.rs`:
|
||||
```rust
|
||||
pub fn detect_removable_mounts() -> Vec<MountInfo>
|
||||
pub fn scan_mount_for_appimages(mount: &MountInfo) -> Vec<DiscoveredAppImage>
|
||||
pub fn is_path_on_removable(path: &Path) -> bool
|
||||
```
|
||||
- Detection: parse `/proc/mounts` for removable media (type vfat, exfat, ntfs on /media/ or /run/media/)
|
||||
- Alternative: use `gio::VolumeMonitor` to watch for mount/unmount events
|
||||
- DB changes:
|
||||
- New column: `is_portable INTEGER DEFAULT 0`
|
||||
- New column: `mount_point TEXT`
|
||||
- Library view: "Portable" filter/section showing apps on removable media
|
||||
- When drive unmounts: grey out those apps, mark as unavailable
|
||||
- When drive mounts: re-scan and refresh
|
||||
- Portable apps skip the "copy to ~/Applications" step - they stay on the drive
|
||||
- Integration: .desktop files use the full path (may break when unmounted - show warning)
|
||||
|
||||
---
|
||||
|
||||
### F24. "Similar to..." Recommendations
|
||||
|
||||
**Complexity:** Medium (3 hrs, depends on F26)
|
||||
**Problem:** No app discovery within the tool.
|
||||
**Files:** `src/ui/detail_view.rs`, `src/core/database.rs`
|
||||
|
||||
**Changes:**
|
||||
- Requires catalog data from F26 (catalog_apps table populated)
|
||||
- New DB query: `find_similar_apps(categories: &[String], exclude_id: i64, limit: i32) -> Vec<CatalogApp>`
|
||||
- Matches on shared categories, weighted by specificity
|
||||
- Detail view overview tab: "You might also like" section at the bottom
|
||||
- Shows up to 5 catalog apps with icon, name, one-line description
|
||||
- Click opens catalog detail page (F26)
|
||||
- Falls back to nothing if catalog is empty
|
||||
|
||||
---
|
||||
|
||||
### F25. AppImageHub In-App Catalog Browser
|
||||
|
||||
**Complexity:** Major (8-12 hrs)
|
||||
**Problem:** No way to discover and install new AppImages from within the app.
|
||||
**Data source:** `https://appimage.github.io/feed.json` (~1500 apps)
|
||||
**Files:** New `src/ui/catalog_view.rs`, new `src/ui/catalog_detail.rs`, `src/core/database.rs`, `src/window.rs`
|
||||
|
||||
**Architecture:**
|
||||
|
||||
#### Data Layer
|
||||
- Fetch `feed.json` and parse into `catalog_apps` table (already exists in DB schema)
|
||||
- Store in `catalog_sources` as source record
|
||||
- Fields mapped from feed.json:
|
||||
- `name` -> `name`
|
||||
- `description` -> `description`
|
||||
- `categories` -> `categories` (joined with ;)
|
||||
- `authors[0].name` -> `author`
|
||||
- `license` -> `license`
|
||||
- `links` (type=GitHub) -> `repository_url`
|
||||
- `links` (type=Download) -> `download_url`
|
||||
- `icons[0]` -> `icon_url` (prefix with `https://appimage.github.io/database/`)
|
||||
- `screenshots` -> `screenshots` (JSON array)
|
||||
- Refresh: on first open, then daily if enabled
|
||||
- New GSettings key: `catalog-last-refreshed` type s, default ''
|
||||
|
||||
#### Catalog Browse View (NavigationPage)
|
||||
- Header: search bar + category filter chips
|
||||
- Categories from feed: AudioVideo, Development, Education, Game, Graphics, Network, Office, Science, System, Utility
|
||||
- Grid of catalog app cards (reuse app_card pattern):
|
||||
- Icon (fetched from URL, cached locally)
|
||||
- App name
|
||||
- Short description (first line)
|
||||
- Category badge
|
||||
- Pagination: load 50 at a time, "Load more" button
|
||||
- Search: filters by name and description (client-side, data is local)
|
||||
|
||||
#### Catalog App Detail (NavigationPage pushed on click)
|
||||
- App icon (large)
|
||||
- Name, author, license
|
||||
- Full description
|
||||
- Screenshots carousel (if available)
|
||||
- "Install" button (suggested style)
|
||||
- Source link (opens GitHub/website in browser)
|
||||
|
||||
#### Install Flow
|
||||
1. User clicks "Install"
|
||||
2. Resolve download URL:
|
||||
- If `download_url` points to GitHub releases page: fetch latest release via GitHub API (`https://api.github.com/repos/{owner}/{repo}/releases/latest`), find .AppImage asset
|
||||
- If direct link: use as-is
|
||||
3. Download with progress bar (reqwest or gio file download)
|
||||
4. Validate: check AppImage magic bytes
|
||||
5. Move to ~/Applications, set executable
|
||||
6. Register in DB with `source_url` set
|
||||
7. Run full analysis pipeline
|
||||
8. Optionally integrate (based on `auto-integrate` setting)
|
||||
9. Navigate to the app's detail view in library
|
||||
|
||||
#### Navigation
|
||||
- Main sidebar/navigation: add "Catalog" entry alongside Dashboard and Library
|
||||
- Or: floating action button on library view "Browse catalog"
|
||||
|
||||
---
|
||||
|
||||
## New GSettings Keys Summary
|
||||
|
||||
| Key | Type | Default | Range/Choices | Feature |
|
||||
|-----|------|---------|---------------|---------|
|
||||
| `update-check-interval-hours` | i | 24 | 1-168 | F11 |
|
||||
| `last-update-check` | s | '' | - | F11 |
|
||||
| `catalog-last-refreshed` | s | '' | - | F25 |
|
||||
| `watch-removable-media` | b | false | - | F23 |
|
||||
| `show-first-run-prompt` | b | true | - | F19 |
|
||||
|
||||
## New Database Columns Summary
|
||||
|
||||
| Table | Column | Type | Default | Feature |
|
||||
|-------|--------|------|---------|---------|
|
||||
| appimages | previous_version_path | TEXT | NULL | F3 |
|
||||
| appimages | source_url | TEXT | NULL | F4 |
|
||||
| appimages | autostart | INTEGER | 0 | F8 |
|
||||
| appimages | startup_wm_class | TEXT | NULL | F17 |
|
||||
| appimages | verification_status | TEXT | NULL | F18 |
|
||||
| appimages | first_run_prompted | INTEGER | 0 | F19 |
|
||||
| appimages | system_wide | INTEGER | 0 | F21 |
|
||||
| appimages | is_portable | INTEGER | 0 | F23 |
|
||||
| appimages | mount_point | TEXT | NULL | F23 |
|
||||
|
||||
## New Files Summary
|
||||
|
||||
| File | Feature | Purpose |
|
||||
|------|---------|---------|
|
||||
| `src/ui/fuse_wizard.rs` | F15 | FUSE installation wizard dialog |
|
||||
| `src/ui/batch_update_dialog.rs` | F12 | Batch update progress dialog |
|
||||
| `src/ui/permission_dialog.rs` | F19 | First-run permission summary |
|
||||
| `src/ui/catalog_view.rs` | F25 | Catalog browse/search page |
|
||||
| `src/ui/catalog_detail.rs` | F25 | Catalog app detail page |
|
||||
| `src/core/verification.rs` | F18 | Signature and checksum verification |
|
||||
| `src/core/portable.rs` | F23 | Removable media detection and management |
|
||||
| `data/app.driftwood.Driftwood.policy` | F15, F21 | Polkit policy for privileged operations |
|
||||
|
||||
## Implementation Order
|
||||
|
||||
The features are numbered F1-F25 in order of complexity. Implement sequentially - some later features depend on earlier ones:
|
||||
|
||||
- F25 (Catalog) depends on nothing but is largest
|
||||
- F24 (Similar to) depends on F25
|
||||
- F12 (Update All) depends on F11 (Background checks)
|
||||
- F13 (Full Uninstall) depends on system_modifications table (core architecture)
|
||||
- F20 (Default App) depends on F16 (MIME associations)
|
||||
|
||||
**Critical path:** Core architecture (system_modifications) -> F1-F7 quick wins -> F8-F14 medium features -> F15-F22 significant features -> F23-F25 major features
|
||||
Reference in New Issue
Block a user