- 登录: QQ/163/126/189/搜狐/Gmail/Outlook/Yahoo/iCloud/企业邮/自定义, 自动识别+测试连接 - 收信: IMAP 多文件夹同步/未读/星标/搜索(本地+服务器) - 读信: HTML渲染/CID内嵌图/原文查看/附件下载 - 写信: 收件人/抄送/密送/附件/富文本/草稿/回复转发 - 存储: SQLite + 系统安全存储(DPAPI/Keychain/Keystore) - 测试: 单元测试 + Mock IMAP/SMTP 服务器集成测试(23项全过) - 可迁移: Windows/Android/iOS/macOS/Linux 一套代码
135 lines
3.8 KiB
Dart
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;
|
|
}
|
|
}
|