Files
mail-hub/lib/core/storage/database.dart
T
hz4th_coder 6c1225f588 MailHub v1.0.0: 跨平台邮箱客户端(纯 Dart IMAP/SMTP/MIME 协议栈)
- 登录: QQ/163/126/189/搜狐/Gmail/Outlook/Yahoo/iCloud/企业邮/自定义, 自动识别+测试连接
- 收信: IMAP 多文件夹同步/未读/星标/搜索(本地+服务器)
- 读信: HTML渲染/CID内嵌图/原文查看/附件下载
- 写信: 收件人/抄送/密送/附件/富文本/草稿/回复转发
- 存储: SQLite + 系统安全存储(DPAPI/Keychain/Keystore)
- 测试: 单元测试 + Mock IMAP/SMTP 服务器集成测试(23项全过)
- 可迁移: Windows/Android/iOS/macOS/Linux 一套代码
2026-08-18 01:10:11 +08:00

135 lines
3.8 KiB
Dart

import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart' show openDatabase, Database, OpenDatabaseOptions;
import 'package:sqflite_common_ffi/sqflite_ffi.dart'
show sqfliteFfiInit, databaseFactoryFfi;
/// 数据库单例 —— 桌面端用 FFI, 移动端用原生 sqflite
class AppDatabase {
AppDatabase._();
static final AppDatabase instance = AppDatabase._();
static Database? _db;
static bool _isDesktop() =>
Platform.isWindows || Platform.isLinux || Platform.isMacOS;
Future<Database> get database async {
if (_db != null) return _db!;
final dir = await getApplicationSupportDirectory();
final path = p.join(dir.path, 'mail_hub.db');
if (_isDesktop()) {
sqfliteFfiInit();
_db = await databaseFactoryFfi.openDatabase(
path,
options: OpenDatabaseOptions(
version: 1,
onCreate: (db, version) async {
await _createTables(db);
},
),
);
} else {
_db = await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await _createTables(db);
},
);
}
return _db!;
}
static Future<void> _createTables(Database db) async {
await db.execute('''
CREATE TABLE accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
display_name TEXT DEFAULT '',
provider TEXT DEFAULT 'custom',
imap_host TEXT NOT NULL,
imap_port INTEGER NOT NULL,
imap_ssl INTEGER DEFAULT 1,
smtp_host TEXT NOT NULL,
smtp_port INTEGER NOT NULL,
smtp_ssl INTEGER DEFAULT 1,
username TEXT DEFAULT '',
signature TEXT DEFAULT '',
enabled INTEGER DEFAULT 1,
last_sync_at INTEGER,
color INTEGER DEFAULT 4278190051
)
''');
await db.execute('''
CREATE TABLE folders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
name TEXT NOT NULL,
delim TEXT DEFAULT '/',
attrs TEXT DEFAULT '',
subscribed INTEGER DEFAULT 1,
UNIQUE(account_id, name)
)
''');
await db.execute('''
CREATE TABLE emails (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
folder TEXT NOT NULL,
uid INTEGER NOT NULL,
message_id TEXT DEFAULT '',
in_reply_to TEXT DEFAULT '',
references TEXT DEFAULT '',
subject TEXT DEFAULT '',
from_name TEXT DEFAULT '',
from_addr TEXT DEFAULT '',
to_json TEXT DEFAULT '',
cc_json TEXT DEFAULT '',
bcc_json TEXT DEFAULT '',
reply_to_json TEXT DEFAULT '',
date INTEGER,
size INTEGER DEFAULT 0,
seen INTEGER DEFAULT 1,
flagged INTEGER DEFAULT 0,
answered INTEGER DEFAULT 0,
deleted INTEGER DEFAULT 0,
draft INTEGER DEFAULT 0,
snippet TEXT DEFAULT '',
has_attachments INTEGER DEFAULT 0,
body_html TEXT,
body_text TEXT,
attachments_json TEXT DEFAULT '',
headers_json TEXT DEFAULT '',
fetched INTEGER DEFAULT 0,
UNIQUE(account_id, folder, uid)
)
''');
await db.execute(
'CREATE INDEX idx_emails_folder ON emails(account_id, folder, date DESC)');
await db.execute('''
CREATE TABLE contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER,
email TEXT NOT NULL,
name TEXT DEFAULT '',
last_used INTEGER DEFAULT 0,
UNIQUE(account_id, email)
)
''');
await db.execute('''
CREATE TABLE prefs (
key TEXT PRIMARY KEY,
value TEXT
)
''');
}
Future<void> close() async {
await _db?.close();
_db = null;
}
}