# 数据库设计

本文档介绍 Flash Send 的 SQLite 数据库设计。

## 概述

Flash Send 使用 SQLite 作为本地数据库，存储：
- 聊天消息记录
- 文件传输记录
- 已知设备信息
- 应用设置

**数据库文件位置**: `{app_data_dir}/flash_send.db`

## 表结构

### chat_messages

存储聊天消息。

```sql
CREATE TABLE IF NOT EXISTS chat_messages (
    id TEXT PRIMARY KEY,              -- 消息唯一 ID (UUID)
    from_device TEXT NOT NULL,        -- 发送方设备 ID
    to_device TEXT NOT NULL,          -- 接收方设备 ID
    content TEXT NOT NULL,            -- 消息内容
    message_type TEXT NOT NULL,       -- 消息类型 (text/image/file/system)
    timestamp INTEGER NOT NULL,       -- 时间戳 (毫秒)
    is_read INTEGER DEFAULT 0         -- 是否已读 (0/1)
);

-- 索引：按设备和时间查询
CREATE INDEX IF NOT EXISTS idx_messages_devices 
ON chat_messages(from_device, to_device);

CREATE INDEX IF NOT EXISTS idx_messages_timestamp 
ON chat_messages(timestamp);
```

**字段说明**:

| 字段 | 类型 | 说明 |
|------|------|------|
| id | TEXT | 消息 UUID |
| from_device | TEXT | 发送方设备 ID |
| to_device | TEXT | 接收方设备 ID |
| content | TEXT | 消息内容 |
| message_type | TEXT | text/image/file/system |
| timestamp | INTEGER | Unix 时间戳（毫秒） |
| is_read | INTEGER | 0=未读, 1=已读 |

---

### file_transfers

存储文件传输记录。

```sql
CREATE TABLE IF NOT EXISTS file_transfers (
    file_id TEXT PRIMARY KEY,         -- 传输唯一 ID (UUID)
    name TEXT NOT NULL,               -- 文件名
    size INTEGER NOT NULL,            -- 文件大小 (字节)
    progress REAL DEFAULT 0,          -- 传输进度 (0.0-1.0)
    status TEXT NOT NULL,             -- 传输状态 (JSON)
    mime_type TEXT,                   -- MIME 类型
    from_device TEXT NOT NULL,        -- 发送方设备 ID
    to_device TEXT NOT NULL,          -- 接收方设备 ID
    local_path TEXT,                  -- 本地文件路径
    created_at INTEGER NOT NULL,      -- 创建时间
    completed_at INTEGER,             -- 完成时间
    transferred_bytes INTEGER DEFAULT 0  -- 已传输字节数
);

-- 索引：按设备查询
CREATE INDEX IF NOT EXISTS idx_transfers_devices 
ON file_transfers(from_device, to_device);

CREATE INDEX IF NOT EXISTS idx_transfers_created 
ON file_transfers(created_at);
```

**字段说明**:

| 字段 | 类型 | 说明 |
|------|------|------|
| file_id | TEXT | 传输 UUID |
| name | TEXT | 文件名 |
| size | INTEGER | 文件大小（字节） |
| progress | REAL | 进度 0.0-1.0 |
| status | TEXT | JSON: "pending"/"transferring"/"completed"/"failed"/"cancelled" |
| mime_type | TEXT | MIME 类型 |
| from_device | TEXT | 发送方设备 ID |
| to_device | TEXT | 接收方设备 ID |
| local_path | TEXT | 本地存储路径 |
| created_at | INTEGER | 创建时间戳 |
| completed_at | INTEGER | 完成时间戳 |
| transferred_bytes | INTEGER | 已传输字节数 |

**status 字段格式**:
```json
"\"pending\""
"\"transferring\""
"\"completed\""
"\"failed\""
"\"cancelled\""
```

---

### known_devices

存储已知设备信息。

```sql
CREATE TABLE IF NOT EXISTS known_devices (
    device_id TEXT PRIMARY KEY,       -- 设备唯一 ID
    device_name TEXT NOT NULL,        -- 设备名称
    ip TEXT,                          -- 最后已知 IP
    ws_port INTEGER,                  -- WebSocket 端口
    http_port INTEGER,                -- HTTP 端口
    last_seen INTEGER                 -- 最后在线时间
);
```

**字段说明**:

| 字段 | 类型 | 说明 |
|------|------|------|
| device_id | TEXT | 设备 UUID |
| device_name | TEXT | 显示名称 |
| ip | TEXT | IP 地址 |
| ws_port | INTEGER | WebSocket 端口 |
| http_port | INTEGER | HTTP 端口 |
| last_seen | INTEGER | 最后在线时间戳 |

---

### app_settings

存储应用设置。

```sql
CREATE TABLE IF NOT EXISTS app_settings (
    key TEXT PRIMARY KEY,             -- 设置键名
    value TEXT                        -- 设置值
);
```

**常用设置键**:

| 键名 | 说明 | 示例值 |
|------|------|--------|
| theme | 主题模式 | system/light/dark |
| device_name | 设备名称 | My Computer |
| download_dir | 下载目录 | C:\Downloads |
| language | 语言 | zh-CN |

---

## 数据操作

### 消息操作

#### 保存消息

```rust
pub fn save_message(message: &ChatMessage) -> Result<()> {
    conn.execute(
        "INSERT INTO chat_messages 
         (id, from_device, to_device, content, message_type, timestamp, is_read)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
        params![
            message.id,
            message.from_device,
            message.to_device,
            message.content,
            message.message_type.to_string(),
            message.timestamp,
            message.is_read as i32,
        ],
    )?;
    Ok(())
}
```

#### 获取聊天历史

```rust
pub fn get_chat_history(
    device_id: &str,
    local_device_id: &str,
    limit: usize,
    offset: usize,
) -> Result<Vec<ChatMessage>> {
    let mut stmt = conn.prepare(
        "SELECT id, from_device, to_device, content, message_type, timestamp, is_read
         FROM chat_messages
         WHERE (from_device = ?1 AND to_device = ?2) 
            OR (from_device = ?2 AND to_device = ?1)
         ORDER BY timestamp DESC
         LIMIT ?3 OFFSET ?4"
    )?;
    // ...
}
```

#### 删除聊天历史

```rust
pub fn delete_chat_history(device_id: &str, local_device_id: &str) -> Result<()> {
    conn.execute(
        "DELETE FROM chat_messages 
         WHERE (from_device = ?1 AND to_device = ?2) 
            OR (from_device = ?2 AND to_device = ?1)",
        params![device_id, local_device_id],
    )?;
    Ok(())
}
```

---

### 传输操作

#### 保存传输记录

```rust
pub fn save_file_transfer(transfer: &FileTransfer) -> Result<()> {
    conn.execute(
        "INSERT INTO file_transfers 
         (file_id, name, size, progress, status, mime_type, 
          from_device, to_device, local_path, created_at, completed_at, transferred_bytes)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)
         ON CONFLICT(file_id) DO UPDATE SET 
             progress = ?4, status = ?5, local_path = ?9, 
             completed_at = ?11, transferred_bytes = ?12",
        params![...],
    )?;
    Ok(())
}
```

#### 更新传输进度

```rust
pub fn update_transfer_progress(
    file_id: &str,
    progress: f64,
    transferred_bytes: u64,
    status: &str,
) -> Result<()> {
    let completed_at = if status == "completed" || status == "failed" {
        Some(chrono::Utc::now().timestamp())
    } else {
        None
    };
    
    conn.execute(
        "UPDATE file_transfers 
         SET progress = ?1, transferred_bytes = ?2, status = ?3, completed_at = ?4 
         WHERE file_id = ?5",
        params![progress, transferred_bytes, status, completed_at, file_id],
    )?;
    Ok(())
}
```

#### 获取传输历史

```rust
pub fn get_transfer_history(
    device_id: &str,
    local_device_id: &str,
    limit: usize,
) -> Result<Vec<FileTransfer>> {
    let mut stmt = conn.prepare(
        "SELECT file_id, name, size, progress, status, mime_type, 
                from_device, to_device, local_path, created_at, completed_at, transferred_bytes
         FROM file_transfers
         WHERE (from_device = ?1 AND to_device = ?2) 
            OR (from_device = ?2 AND to_device = ?1)
         ORDER BY created_at DESC
         LIMIT ?3"
    )?;
    // ...
}
```

---

### 设备操作

#### 保存已知设备

```rust
pub fn save_known_device(device: &DeviceInfo) -> Result<()> {
    conn.execute(
        "INSERT INTO known_devices 
         (device_id, device_name, ip, ws_port, http_port, last_seen)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6)
         ON CONFLICT(device_id) DO UPDATE SET 
             device_name = ?2, ip = ?3, ws_port = ?4, http_port = ?5, last_seen = ?6",
        params![...],
    )?;
    Ok(())
}
```

---

### 设置操作

#### 获取设置

```rust
pub fn get_setting(key: &str) -> Result<Option<String>> {
    let mut stmt = conn.prepare(
        "SELECT value FROM app_settings WHERE key = ?1"
    )?;
    stmt.query_row(params![key], |row| row.get(0)).optional()
}
```

#### 保存设置

```rust
pub fn set_setting(key: &str, value: &str) -> Result<()> {
    conn.execute(
        "INSERT INTO app_settings (key, value) VALUES (?1, ?2)
         ON CONFLICT(key) DO UPDATE SET value = ?2",
        params![key, value],
    )?;
    Ok(())
}
```

---

## 数据库迁移

### 迁移机制

使用版本号管理数据库迁移：

```rust
pub fn init_database(conn: &Connection) -> Result<()> {
    // 创建版本表
    conn.execute(
        "CREATE TABLE IF NOT EXISTS schema_version (version INTEGER)",
        [],
    )?;
    
    let version: i32 = conn
        .query_row("SELECT version FROM schema_version", [], |row| row.get(0))
        .unwrap_or(0);
    
    // 根据版本执行迁移
    if version < 1 {
        // 创建初始表结构
        create_initial_tables(conn)?;
        update_version(conn, 1)?;
    }
    
    if version < 2 {
        // 添加 transferred_bytes 字段
        add_transferred_bytes_column(conn)?;
        update_version(conn, 2)?;
    }
    
    Ok(())
}
```

### 迁移示例

添加 `transferred_bytes` 字段：

```rust
fn add_transferred_bytes_column(conn: &Connection) -> Result<()> {
    // 检查字段是否存在
    let columns: Vec<String> = conn
        .prepare("PRAGMA table_info(file_transfers)")?
        .query_map([], |row| row.get::<_, String>(1))?
        .collect::<Result<Vec<_>, _>>()?;
    
    if !columns.contains(&"transferred_bytes".to_string()) {
        conn.execute(
            "ALTER TABLE file_transfers ADD COLUMN transferred_bytes INTEGER DEFAULT 0",
            [],
        )?;
    }
    Ok(())
}
```

---

## 数据清理

### 自动清理策略

- **消息**: 保留最近 1000 条/设备
- **传输记录**: 保留最近 100 条/设备
- **离线设备**: 30 天未见自动删除

### 手动清理

```rust
// 清理指定设备的所有数据
pub fn cleanup_device_data(device_id: &str) -> Result<()> {
    conn.execute("DELETE FROM chat_messages WHERE from_device = ?1 OR to_device = ?1", [device_id])?;
    conn.execute("DELETE FROM file_transfers WHERE from_device = ?1 OR to_device = ?1", [device_id])?;
    conn.execute("DELETE FROM known_devices WHERE device_id = ?1", [device_id])?;
    Ok(())
}
```

---

## 性能优化

### 索引策略

- 按设备 ID 查询：`idx_messages_devices`, `idx_transfers_devices`
- 按时间排序：`idx_messages_timestamp`, `idx_transfers_created`

### 连接池

使用全局单例连接，通过 `parking_lot::Mutex` 保护：

```rust
static DB_CONN: OnceCell<Mutex<Connection>> = OnceCell::new();

pub fn conn() -> MutexGuard<'static, Connection> {
    DB_CONN.get().expect("Database not initialized").lock()
}
```

### 事务使用

批量操作使用事务提升性能：

```rust
conn.execute("BEGIN TRANSACTION", [])?;
for message in messages {
    save_message(&message)?;
}
conn.execute("COMMIT", [])?;
```
