feat: 移植截图中心后端(索引/分组/编辑保存)
Sync source to CNB / Sync Git ref (push) Has been cancelled

This commit is contained in:
AxTps committed 2026-10-01 21:08:57 +08:00
1 parent e646598078
commit ead5d96b78
13 files changed
+1829 -1

No files matched your search

@@ -12,6 +12,34 @@ use chrono::{DateTime, TimeZone, Utc};
use serde::de::DeserializeOwned;
use sqlx::{Executor, Sqlite, SqlitePool, Transaction};
use uuid::Uuid;
#[derive(Clone, Debug, sqlx::FromRow)]
pub(crate) struct InstanceScreenshotSource {
pub id: String,
pub name: String,
pub path: String,
}
pub(crate) async fn get_instance_screenshot_source(
instance_id: &str,
pool: &SqlitePool,
) -> crate::Result<Option<InstanceScreenshotSource>> {
Ok(sqlx::query_as::<_, InstanceScreenshotSource>(
"SELECT id, name, path FROM instances WHERE id = ?",
)
.bind(instance_id)
.fetch_optional(pool)
.await?)
}
pub(crate) async fn list_screenshot_sources(
pool: &SqlitePool,
) -> crate::Result<Vec<InstanceScreenshotSource>> {
Ok(sqlx::query_as::<_, InstanceScreenshotSource>(
"SELECT id, name, path FROM instances ORDER BY name, id",
)
.fetch_all(pool)
.await?)
}
#[derive(Debug, sqlx::FromRow)]
pub(crate) struct InstanceRow {
@@ -2,3 +2,4 @@ pub(crate) mod config_sync_rows;
pub(crate) mod content_rows;
pub(crate) mod instance_rows;
pub(crate) mod loader_component_rows;
pub(crate) mod screenshot_rows;
@@ -0,0 +1,236 @@
use sqlx::{Sqlite, SqlitePool, Transaction};
#[derive(Clone, Debug, sqlx::FromRow)]
pub(crate) struct ScreenshotRow {
pub id: String,
pub instance_id: String,
pub file_name: String,
pub content_hash: String,
pub file_size: i64,
pub modified_at: i64,
pub created_at: i64,
pub group_id: Option<String>,
}
#[derive(Clone, Debug, sqlx::FromRow)]
pub(crate) struct ScreenshotGroupRow {
pub id: String,
pub name: String,
}
pub(crate) async fn list_screenshots(
instance_id: &str,
pool: &SqlitePool,
) -> crate::Result<Vec<ScreenshotRow>> {
Ok(sqlx::query_as!(
ScreenshotRow,
r#"
SELECT
screenshots.id,
screenshots.instance_id,
screenshots.file_name,
screenshots.content_hash,
screenshots.file_size,
screenshots.modified_at,
screenshots.created_at,
memberships.group_id AS "group_id?"
FROM screenshots
LEFT JOIN screenshot_group_memberships memberships
ON memberships.screenshot_id = screenshots.id
WHERE screenshots.instance_id = ?
ORDER BY screenshots.file_name, screenshots.id
"#,
instance_id,
)
.fetch_all(pool)
.await?)
}
pub(crate) async fn get_screenshot_by_key(
instance_id: &str,
file_name: &str,
pool: &SqlitePool,
) -> crate::Result<Option<ScreenshotRow>> {
Ok(sqlx::query_as!(
ScreenshotRow,
r#"
SELECT
screenshots.id,
screenshots.instance_id,
screenshots.file_name,
screenshots.content_hash,
screenshots.file_size,
screenshots.modified_at,
screenshots.created_at,
memberships.group_id AS "group_id?"
FROM screenshots
LEFT JOIN screenshot_group_memberships memberships
ON memberships.screenshot_id = screenshots.id
WHERE screenshots.instance_id = ? AND screenshots.file_name = ?
"#,
instance_id,
file_name,
)
.fetch_optional(pool)
.await?)
}
pub(crate) async fn copy_group_membership(
source_id: &str,
edited_id: &str,
tx: &mut Transaction<'_, Sqlite>,
) -> crate::Result<()> {
sqlx::query!(
r#"
INSERT INTO screenshot_group_memberships (screenshot_id, group_id)
SELECT ?, group_id
FROM screenshot_group_memberships
WHERE screenshot_id = ?
ON CONFLICT (screenshot_id) DO UPDATE SET group_id = excluded.group_id
"#,
edited_id,
source_id,
)
.execute(&mut **tx)
.await?;
Ok(())
}
pub(crate) async fn insert_screenshot(
row: &ScreenshotRow,
tx: &mut Transaction<'_, Sqlite>,
) -> crate::Result<()> {
sqlx::query!(
"
INSERT INTO screenshots (
id,
instance_id,
file_name,
content_hash,
file_size,
modified_at,
created_at
)
VALUES (?, ?, ?, ?, ?, ?, ?)
",
row.id,
row.instance_id,
row.file_name,
row.content_hash,
row.file_size,
row.modified_at,
row.created_at,
)
.execute(&mut **tx)
.await?;
Ok(())
}
pub(crate) async fn update_screenshot(
row: &ScreenshotRow,
tx: &mut Transaction<'_, Sqlite>,
) -> crate::Result<()> {
sqlx::query!(
"
UPDATE screenshots
SET
instance_id = ?,
file_name = ?,
content_hash = ?,
file_size = ?,
modified_at = ?,
created_at = ?
WHERE id = ?
",
row.instance_id,
row.file_name,
row.content_hash,
row.file_size,
row.modified_at,
row.created_at,
row.id,
)
.execute(&mut **tx)
.await?;
Ok(())
}
pub(crate) async fn delete_screenshot(
id: &str,
tx: &mut Transaction<'_, Sqlite>,
) -> crate::Result<()> {
sqlx::query!("DELETE FROM screenshots WHERE id = ?", id)
.execute(&mut **tx)
.await?;
Ok(())
}
pub(crate) async fn move_screenshot(
source_instance_id: &str,
source_file_name: &str,
target_instance_id: &str,
target_file_name: &str,
pool: &SqlitePool,
) -> crate::Result<()> {
let mut tx = pool.begin().await?;
sqlx::query!(
"DELETE FROM screenshots WHERE instance_id = ? AND file_name = ?",
target_instance_id,
target_file_name,
)
.execute(&mut *tx)
.await?;
sqlx::query!(
"
UPDATE screenshots
SET instance_id = ?, file_name = ?
WHERE instance_id = ? AND file_name = ?
",
target_instance_id,
target_file_name,
source_instance_id,
source_file_name,
)
.execute(&mut *tx)
.await?;
tx.commit().await?;
Ok(())
}
pub(crate) async fn delete_screenshot_by_key(
instance_id: &str,
file_name: &str,
pool: &SqlitePool,
) -> crate::Result<()> {
sqlx::query!(
"DELETE FROM screenshots WHERE instance_id = ? AND file_name = ?",
instance_id,
file_name,
)
.execute(pool)
.await?;
Ok(())
}
pub(crate) async fn list_groups(
pool: &SqlitePool,
) -> crate::Result<Vec<ScreenshotGroupRow>> {
Ok(sqlx::query_as!(
ScreenshotGroupRow,
"
SELECT id, name
FROM screenshot_groups
ORDER BY display_order, name, id
",
)
.fetch_all(pool)
.await?)
}
+15
View File
@@ -120,6 +120,7 @@ pub struct State {
pub(crate) install_job_cancellations: DashMap<Uuid, CancellationToken>,
pub(crate) install_job_operation_locks:
DashMap<Uuid, Arc<AsyncMutex<InstallJobOperationState>>>,
pub(crate) screenshot_locks: DashMap<String, Arc<AsyncMutex<()>>>,
/// Process manager
pub process_manager: ProcessManager,
@@ -904,6 +905,7 @@ impl State {
install_db_semaphore: Semaphore::new(1),
install_job_cancellations: DashMap::new(),
install_job_operation_locks: DashMap::new(),
screenshot_locks: DashMap::new(),
process_manager,
friends_socket,
restart_after_pending_update: AtomicBool::new(false),
@@ -931,6 +933,18 @@ impl State {
) -> InstanceLockGuard {
self.instance_locks.lock_exclusive(instance_id).await
}
pub(crate) async fn lock_instance_screenshots(
&self,
instance_id: &str,
) -> tokio::sync::OwnedMutexGuard<()> {
self.screenshot_locks
.entry(instance_id.to_string())
.or_insert_with(|| Arc::new(AsyncMutex::new(())))
.clone()
.lock_owned()
.await
}
}
#[cfg(test)]
@@ -971,6 +985,7 @@ pub(crate) async fn test_state(
install_db_semaphore: Semaphore::new(1),
install_job_cancellations: DashMap::new(),
install_job_operation_locks: DashMap::new(),
screenshot_locks: DashMap::new(),
process_manager: ProcessManager::new(),
friends_socket: FriendsSocket::new(),
restart_after_pending_update: AtomicBool::new(false),