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