Add tags, export/import, and changelog features

- Tag editor in detail view with add/remove pill chips
- Tag filter chips in library view for filtering by tag
- Shared backup module for app list export/import (JSON v2)
- CLI export/import refactored to use shared module
- GUI export/import via file picker dialogs in hamburger menu
- GitHub release history enrichment for catalog apps
- Changelog preview in updates view with expandable rows
- DB migration v19 for catalog release_history column
This commit is contained in:
lashman
2026-03-01 01:01:43 +02:00
parent 79519c500a
commit abb69dc753
9 changed files with 901 additions and 215 deletions

View File

@@ -154,6 +154,7 @@ pub struct CatalogApp {
pub ocs_arch: Option<String>,
pub ocs_md5sum: Option<String>,
pub ocs_comments: Option<i64>,
pub release_history: Option<String>,
}
#[derive(Debug, Clone)]
@@ -484,6 +485,10 @@ impl Database {
self.migrate_to_v18()?;
}
if current_version < 19 {
self.migrate_to_v19()?;
}
// Ensure all expected columns exist (repairs DBs where a migration
// was updated after it had already run on this database)
self.ensure_columns()?;
@@ -1050,6 +1055,18 @@ impl Database {
Ok(())
}
fn migrate_to_v19(&self) -> SqlResult<()> {
self.conn.execute(
"ALTER TABLE catalog_apps ADD COLUMN release_history TEXT",
[],
).ok();
self.conn.execute(
"UPDATE schema_version SET version = ?1",
params![19],
)?;
Ok(())
}
pub fn upsert_appimage(
&self,
path: &str,
@@ -1221,7 +1238,8 @@ impl Database {
github_enriched_at, github_download_url, github_release_assets, github_description, github_readme,
ocs_id, ocs_downloads, ocs_score, ocs_typename, ocs_personid, ocs_description, ocs_summary,
ocs_version, ocs_tags, ocs_changed, ocs_preview_url,
ocs_detailpage, ocs_created, ocs_downloadname, ocs_downloadsize, ocs_arch, ocs_md5sum, ocs_comments";
ocs_detailpage, ocs_created, ocs_downloadname, ocs_downloadsize, ocs_arch, ocs_md5sum, ocs_comments,
release_history";
/// SQL filter that deduplicates catalog apps by lowercase name.
/// Keeps the OCS entry when both OCS and secondary source entries exist for the same name.
@@ -1277,6 +1295,7 @@ impl Database {
ocs_arch: row.get(35).unwrap_or(None),
ocs_md5sum: row.get(36).unwrap_or(None),
ocs_comments: row.get(37).unwrap_or(None),
release_history: row.get(38).unwrap_or(None),
})
}
@@ -2066,6 +2085,27 @@ impl Database {
Ok(())
}
/// Get all distinct tags used across all installed apps.
/// Returns a sorted, deduplicated list of tag strings.
pub fn get_all_tags(&self) -> SqlResult<Vec<String>> {
let mut stmt = self.conn.prepare(
"SELECT tags FROM appimages WHERE tags IS NOT NULL AND tags != ''"
)?;
let rows = stmt.query_map([], |row| row.get::<_, String>(0))?;
let mut tag_set = std::collections::BTreeSet::new();
for row in rows {
if let Ok(tags_str) = row {
for tag in tags_str.split(',') {
let trimmed = tag.trim();
if !trimmed.is_empty() {
tag_set.insert(trimmed.to_string());
}
}
}
}
Ok(tag_set.into_iter().collect())
}
pub fn set_pinned(&self, id: i64, pinned: bool) -> SqlResult<()> {
self.conn.execute(
"UPDATE appimages SET pinned = ?2 WHERE id = ?1",
@@ -2479,6 +2519,23 @@ impl Database {
}
}
/// Look up release history from catalog for an installed app (by name).
/// Returns the release_history JSON string if a matching catalog app has one.
pub fn get_catalog_release_history_by_name(&self, app_name: &str) -> SqlResult<Option<String>> {
let result = self.conn.query_row(
"SELECT release_history FROM catalog_apps
WHERE LOWER(name) = LOWER(?1) AND release_history IS NOT NULL
LIMIT 1",
params![app_name],
|row| row.get::<_, Option<String>>(0),
);
match result {
Ok(history) => Ok(history),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e),
}
}
/// Get featured catalog apps. Apps with enrichment data (stars or OCS downloads)
/// sort first by combined popularity, then unenriched apps get a deterministic
/// shuffle that rotates every 15 minutes.
@@ -2860,6 +2917,18 @@ impl Database {
)?;
Ok(())
}
pub fn update_catalog_app_release_history(
&self,
app_id: i64,
history_json: &str,
) -> SqlResult<()> {
self.conn.execute(
"UPDATE catalog_apps SET release_history = ?2 WHERE id = ?1",
params![app_id, history_json],
)?;
Ok(())
}
}
#[cfg(test)]