mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 16:56:16 +08:00
feat: add skill update detection via SHA-256 content hashing
- Add content_hash and updated_at fields to skills table (DB migration v6→v7) - Compute directory content hash on install/import/restore for version tracking - Add check_updates command: downloads repos, compares hashes, returns update list - Add update_skill command: backs up old files, re-downloads and replaces SSOT - Backfill content_hash for existing skills on first update check - Add "Check Updates" button and per-skill update badge/button in UnifiedSkillsPanel - Add i18n keys for zh/en/ja
This commit is contained in:
@@ -22,7 +22,8 @@ impl Database {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT id, name, description, directory, repo_owner, repo_name, repo_branch,
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, installed_at
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode,
|
||||
installed_at, content_hash, updated_at
|
||||
FROM skills ORDER BY name ASC",
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
@@ -45,6 +46,8 @@ impl Database {
|
||||
opencode: row.get(11)?,
|
||||
},
|
||||
installed_at: row.get(12)?,
|
||||
content_hash: row.get(13)?,
|
||||
updated_at: row.get::<_, i64>(14).unwrap_or(0),
|
||||
})
|
||||
})
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
@@ -63,7 +66,8 @@ impl Database {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT id, name, description, directory, repo_owner, repo_name, repo_branch,
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, installed_at
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode,
|
||||
installed_at, content_hash, updated_at
|
||||
FROM skills WHERE id = ?1",
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
@@ -85,6 +89,8 @@ impl Database {
|
||||
opencode: row.get(11)?,
|
||||
},
|
||||
installed_at: row.get(12)?,
|
||||
content_hash: row.get(13)?,
|
||||
updated_at: row.get::<_, i64>(14).unwrap_or(0),
|
||||
})
|
||||
});
|
||||
|
||||
@@ -101,8 +107,9 @@ impl Database {
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO skills
|
||||
(id, name, description, directory, repo_owner, repo_name, repo_branch,
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, installed_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode,
|
||||
installed_at, content_hash, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)",
|
||||
params![
|
||||
skill.id,
|
||||
skill.name,
|
||||
@@ -117,6 +124,8 @@ impl Database {
|
||||
skill.apps.gemini,
|
||||
skill.apps.opencode,
|
||||
skill.installed_at,
|
||||
skill.content_hash,
|
||||
skill.updated_at,
|
||||
],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
@@ -152,6 +161,23 @@ impl Database {
|
||||
Ok(affected > 0)
|
||||
}
|
||||
|
||||
/// 更新 Skill 的内容哈希和更新时间
|
||||
pub fn update_skill_hash(
|
||||
&self,
|
||||
id: &str,
|
||||
content_hash: &str,
|
||||
updated_at: i64,
|
||||
) -> Result<bool, AppError> {
|
||||
let conn = lock_conn!(self.conn);
|
||||
let affected = conn
|
||||
.execute(
|
||||
"UPDATE skills SET content_hash = ?1, updated_at = ?2 WHERE id = ?3",
|
||||
params![content_hash, updated_at, id],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
Ok(affected > 0)
|
||||
}
|
||||
|
||||
// ========== SkillRepo CRUD(保持原有) ==========
|
||||
|
||||
/// 获取所有 Skill 仓库
|
||||
|
||||
@@ -44,7 +44,7 @@ use std::sync::Mutex;
|
||||
|
||||
/// 当前 Schema 版本号
|
||||
/// 每次修改表结构时递增,并在 schema.rs 中添加相应的迁移逻辑
|
||||
pub(crate) const SCHEMA_VERSION: i32 = 6;
|
||||
pub(crate) const SCHEMA_VERSION: i32 = 7;
|
||||
|
||||
/// 安全地序列化 JSON,避免 unwrap panic
|
||||
pub(crate) fn to_json_string<T: Serialize>(value: &T) -> Result<String, AppError> {
|
||||
|
||||
@@ -93,7 +93,9 @@ impl Database {
|
||||
enabled_codex BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_gemini BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_opencode BOOLEAN NOT NULL DEFAULT 0,
|
||||
installed_at INTEGER NOT NULL DEFAULT 0
|
||||
installed_at INTEGER NOT NULL DEFAULT 0,
|
||||
content_hash TEXT,
|
||||
updated_at INTEGER NOT NULL DEFAULT 0
|
||||
)",
|
||||
[],
|
||||
)
|
||||
@@ -393,6 +395,11 @@ impl Database {
|
||||
Self::migrate_v5_to_v6(conn)?;
|
||||
Self::set_user_version(conn, 6)?;
|
||||
}
|
||||
6 => {
|
||||
log::info!("迁移数据库从 v6 到 v7(Skills 更新检测支持)");
|
||||
Self::migrate_v6_to_v7(conn)?;
|
||||
Self::set_user_version(conn, 7)?;
|
||||
}
|
||||
_ => {
|
||||
return Err(AppError::Database(format!(
|
||||
"未知的数据库版本 {version},无法迁移到 {SCHEMA_VERSION}"
|
||||
@@ -1045,6 +1052,14 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// v6 -> v7: Skills 更新检测支持(content_hash + updated_at)
|
||||
fn migrate_v6_to_v7(conn: &Connection) -> Result<(), AppError> {
|
||||
Self::add_column_if_missing(conn, "skills", "content_hash", "TEXT")?;
|
||||
Self::add_column_if_missing(conn, "skills", "updated_at", "INTEGER NOT NULL DEFAULT 0")?;
|
||||
log::info!("v6 -> v7 迁移完成:已添加 content_hash 和 updated_at 列");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 插入默认模型定价数据
|
||||
/// 格式: (model_id, display_name, input, output, cache_read, cache_creation)
|
||||
/// 注意: model_id 使用短横线格式(如 claude-haiku-4-5),与 API 返回的模型名称标准化后一致
|
||||
|
||||
Reference in New Issue
Block a user