From 7fcfc5e9920b627aebd2343bd45158a585d95f28 Mon Sep 17 00:00:00 2001 From: landaiqing Date: Mon, 7 Jul 2025 13:19:59 +0800 Subject: [PATCH] :art: Refactoring the extension service and the keybinding service --- frontend/bindings/database/sql/index.ts | 4 + frontend/bindings/database/sql/models.ts | 37 +++ .../internal/services/databaseservice.ts | 39 +++ .../internal/services/documentservice.ts | 8 - .../voidraft/internal/services/index.ts | 2 + internal/models/extensions.go | 7 - internal/services/config_migration_service.go | 22 +- internal/services/config_service.go | 66 ++-- internal/services/database_service.go | 211 +++++++++++++ internal/services/document_service.go | 167 ++-------- internal/services/extension_service.go | 291 ++++++++++-------- internal/services/keybinding_service.go | 243 ++++++++------- internal/services/path_manager.go | 59 ---- internal/services/service_manager.go | 30 +- 14 files changed, 680 insertions(+), 506 deletions(-) create mode 100644 frontend/bindings/database/sql/index.ts create mode 100644 frontend/bindings/database/sql/models.ts create mode 100644 frontend/bindings/voidraft/internal/services/databaseservice.ts create mode 100644 internal/services/database_service.go delete mode 100644 internal/services/path_manager.go diff --git a/frontend/bindings/database/sql/index.ts b/frontend/bindings/database/sql/index.ts new file mode 100644 index 0000000..c9d993a --- /dev/null +++ b/frontend/bindings/database/sql/index.ts @@ -0,0 +1,4 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export * from "./models.js"; diff --git a/frontend/bindings/database/sql/models.ts b/frontend/bindings/database/sql/models.ts new file mode 100644 index 0000000..8a89ddd --- /dev/null +++ b/frontend/bindings/database/sql/models.ts @@ -0,0 +1,37 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Create as $Create} from "@wailsio/runtime"; + +/** + * DB is a database handle representing a pool of zero or more + * underlying connections. It's safe for concurrent use by multiple + * goroutines. + * + * The sql package creates and frees connections automatically; it + * also maintains a free pool of idle connections. If the database has + * a concept of per-connection state, such state can be reliably observed + * within a transaction ([Tx]) or connection ([Conn]). Once [DB.Begin] is called, the + * returned [Tx] is bound to a single connection. Once [Tx.Commit] or + * [Tx.Rollback] is called on the transaction, that transaction's + * connection is returned to [DB]'s idle connection pool. The pool size + * can be controlled with [DB.SetMaxIdleConns]. + */ +export class DB { + + /** Creates a new DB instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new DB instance from a string or object. + */ + static createFrom($$source: any = {}): DB { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new DB($$parsedSource as Partial); + } +} diff --git a/frontend/bindings/voidraft/internal/services/databaseservice.ts b/frontend/bindings/voidraft/internal/services/databaseservice.ts new file mode 100644 index 0000000..54cb61f --- /dev/null +++ b/frontend/bindings/voidraft/internal/services/databaseservice.ts @@ -0,0 +1,39 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * DatabaseService provides shared database functionality + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as sql$0 from "../../../database/sql/models.js"; + +/** + * GetDB returns the database connection + */ +export function GetDB(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(228760371) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType1($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * OnDataPathChanged handles data path changes + */ +export function OnDataPathChanged(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3652863491) as any; + return $resultPromise; +} + +// Private type creation functions +const $$createType0 = sql$0.DB.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/frontend/bindings/voidraft/internal/services/documentservice.ts b/frontend/bindings/voidraft/internal/services/documentservice.ts index 55e766c..d203844 100644 --- a/frontend/bindings/voidraft/internal/services/documentservice.ts +++ b/frontend/bindings/voidraft/internal/services/documentservice.ts @@ -78,14 +78,6 @@ export function ListDeletedDocumentsMeta(): Promise<(models$0.Document | null)[] return $typingPromise; } -/** - * OnDataPathChanged handles data path changes - */ -export function OnDataPathChanged(): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(269349439) as any; - return $resultPromise; -} - /** * RestoreDocument restores a deleted document */ diff --git a/frontend/bindings/voidraft/internal/services/index.ts b/frontend/bindings/voidraft/internal/services/index.ts index 0bca57d..697cd4c 100644 --- a/frontend/bindings/voidraft/internal/services/index.ts +++ b/frontend/bindings/voidraft/internal/services/index.ts @@ -2,6 +2,7 @@ // This file is automatically generated. DO NOT EDIT import * as ConfigService from "./configservice.js"; +import * as DatabaseService from "./databaseservice.js"; import * as DialogService from "./dialogservice.js"; import * as DocumentService from "./documentservice.js"; import * as ExtensionService from "./extensionservice.js"; @@ -15,6 +16,7 @@ import * as TranslationService from "./translationservice.js"; import * as TrayService from "./trayservice.js"; export { ConfigService, + DatabaseService, DialogService, DocumentService, ExtensionService, diff --git a/internal/models/extensions.go b/internal/models/extensions.go index ec0734a..ce73eb0 100644 --- a/internal/models/extensions.go +++ b/internal/models/extensions.go @@ -107,14 +107,7 @@ func NewDefaultExtensions() []Extension { Enabled: true, IsDefault: true, Config: ExtensionConfig{ - "defaultSourceLang": "auto", - "defaultTargetLang": "zh", "defaultTranslator": "bing", - "showTranslateButton": true, - "showButtonOnSelect": true, - "buttonDisplayDelay": 300, - "tooltipTimeout": 0, - "maxTooltipWidth": 300, "minSelectionLength": 2, "maxTranslationLength": 5000, }, diff --git a/internal/services/config_migration_service.go b/internal/services/config_migration_service.go index a0ea2ce..707b533 100644 --- a/internal/services/config_migration_service.go +++ b/internal/services/config_migration_service.go @@ -44,7 +44,7 @@ type Migratable interface { // ConfigMigrationService 配置迁移服务 type ConfigMigrationService[T Migratable] struct { logger *log.LoggerService - pathManager *PathManager + configDir string configName string targetVersion string configPath string @@ -60,12 +60,12 @@ type MigrationResult struct { // NewConfigMigrationService 创建配置迁移服务 func NewConfigMigrationService[T Migratable]( logger *log.LoggerService, - pathManager *PathManager, + configDir string, configName, targetVersion, configPath string, ) *ConfigMigrationService[T] { return &ConfigMigrationService[T]{ logger: orDefault(logger, log.New()), - pathManager: orDefault(pathManager, NewPathManager()), + configDir: configDir, configName: configName, targetVersion: targetVersion, configPath: configPath, @@ -138,7 +138,7 @@ func (cms *ConfigMigrationService[T]) createBackupOptimized() (string, error) { return "", nil } - configDir := cms.pathManager.GetConfigDir() + configDir := cms.configDir timestamp := time.Now().Format("20060102150405") newBackupPath := filepath.Join(configDir, fmt.Sprintf(BackupFilePattern, cms.configName, timestamp)) @@ -172,7 +172,7 @@ func (cms *ConfigMigrationService[T]) tryQuickRecovery(existingConfig *koanf.Koa // findLatestBackupQuick 快速查找最新备份(优化排序) func (cms *ConfigMigrationService[T]) findLatestBackupQuick() string { - pattern := filepath.Join(cms.pathManager.GetConfigDir(), fmt.Sprintf("%s.backup.*.json", cms.configName)) + pattern := filepath.Join(cms.configDir, fmt.Sprintf("%s.backup.*.json", cms.configName)) matches, err := filepath.Glob(pattern) if err != nil || len(matches) == 0 { return "" @@ -317,18 +317,18 @@ func chainLoad(k *koanf.Koanf, loaders ...func() error) error { } // 工厂函数 -func NewAppConfigMigrationService(logger *log.LoggerService, pathManager *PathManager) *ConfigMigrationService[*models.AppConfig] { +func NewAppConfigMigrationService(logger *log.LoggerService, configDir, settingsPath string) *ConfigMigrationService[*models.AppConfig] { return NewConfigMigrationService[*models.AppConfig]( - logger, pathManager, "settings", CurrentAppConfigVersion, pathManager.GetSettingsPath()) + logger, configDir, "settings", CurrentAppConfigVersion, settingsPath) } -func NewKeyBindingMigrationService(logger *log.LoggerService, pathManager *PathManager) *ConfigMigrationService[*models.KeyBindingConfig] { +func NewKeyBindingMigrationService(logger *log.LoggerService, configDir, keybindsPath string) *ConfigMigrationService[*models.KeyBindingConfig] { return NewConfigMigrationService[*models.KeyBindingConfig]( - logger, pathManager, "keybindings", CurrentKeyBindingConfigVersion, pathManager.GetKeybindsPath()) + logger, configDir, "keybindings", CurrentKeyBindingConfigVersion, keybindsPath) } // NewExtensionMigrationService 创建扩展配置迁移服务 -func NewExtensionMigrationService(logger *log.LoggerService, pathManager *PathManager) *ConfigMigrationService[*models.ExtensionSettings] { +func NewExtensionMigrationService(logger *log.LoggerService, configDir, extensionsPath string) *ConfigMigrationService[*models.ExtensionSettings] { return NewConfigMigrationService[*models.ExtensionSettings]( - logger, pathManager, "extensions", CurrentExtensionConfigVersion, pathManager.GetExtensionsPath()) + logger, configDir, "extensions", CurrentExtensionConfigVersion, extensionsPath) } diff --git a/internal/services/config_service.go b/internal/services/config_service.go index d5060ba..e6f125e 100644 --- a/internal/services/config_service.go +++ b/internal/services/config_service.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "sync" "time" "voidraft/internal/models" @@ -19,7 +20,8 @@ import ( type ConfigService struct { koanf *koanf.Koanf // koanf 实例 logger *log.LoggerService // 日志服务 - pathManager *PathManager // 路径管理器 + configDir string // 配置目录 + settingsPath string // 设置文件路径 mu sync.RWMutex // 读写锁 fileProvider *file.File // 文件提供器,用于监听 @@ -53,41 +55,34 @@ func (e *ConfigError) Is(target error) bool { } // NewConfigService 创建新的配置服务实例 -func NewConfigService(logger *log.LoggerService, pathManager *PathManager) *ConfigService { - // 设置日志服务 - if logger == nil { - logger = log.New() +func NewConfigService(logger *log.LoggerService) *ConfigService { + // 获取用户主目录 + homeDir, err := os.UserHomeDir() + if err != nil { + panic(fmt.Errorf("unable to get the user's home directory: %w", err)) } - // 设置路径管理器 - if pathManager == nil { - pathManager = NewPathManager() + // 设置配置目录和设置文件路径 + configDir := filepath.Join(homeDir, ".voidraft", "config") + settingsPath := filepath.Join(configDir, "settings.json") + + cs := &ConfigService{ + logger: logger, + configDir: configDir, + settingsPath: settingsPath, + koanf: koanf.New("."), + migrationService: NewAppConfigMigrationService(logger, configDir, settingsPath), } - // 使用"."作为键路径分隔符 - k := koanf.New(".") + // 初始化配置通知服务 + cs.notificationService = NewConfigNotificationService(cs.koanf, logger) - notificationService := NewConfigNotificationService(k, logger) - migrationService := NewAppConfigMigrationService(logger, pathManager) - - // 构造配置服务实例 - service := &ConfigService{ - koanf: k, - logger: logger, - pathManager: pathManager, - notificationService: notificationService, - migrationService: migrationService, - } - - // 初始化配置 - if err := service.initConfig(); err != nil { - panic(err) - } + cs.initConfig() // 启动配置文件监听 - service.startWatching() + cs.startWatching() - return service + return cs } // setDefaults 设置默认配置 @@ -107,13 +102,12 @@ func (cs *ConfigService) initConfig() error { defer cs.mu.Unlock() // 检查配置文件是否存在 - configPath := cs.pathManager.GetSettingsPath() - if _, err := os.Stat(configPath); os.IsNotExist(err) { + if _, err := os.Stat(cs.settingsPath); os.IsNotExist(err) { return cs.createDefaultConfig() } // 配置文件存在,先加载现有配置 - cs.fileProvider = file.Provider(configPath) + cs.fileProvider = file.Provider(cs.settingsPath) if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil { return &ConfigError{Operation: "load_config_file", Err: err} } @@ -127,7 +121,7 @@ func (cs *ConfigService) initConfig() error { if result.Migrated && result.ConfigUpdated { // 迁移完成且配置已更新,重新创建文件提供器以监听新文件 - cs.fileProvider = file.Provider(configPath) + cs.fileProvider = file.Provider(cs.settingsPath) } } @@ -137,7 +131,7 @@ func (cs *ConfigService) initConfig() error { // createDefaultConfig 创建默认配置文件 func (cs *ConfigService) createDefaultConfig() error { // 确保配置目录存在 - if err := cs.pathManager.EnsureConfigDir(); err != nil { + if err := os.MkdirAll(cs.configDir, 0755); err != nil { return &ConfigError{Operation: "create_config_dir", Err: err} } @@ -150,7 +144,7 @@ func (cs *ConfigService) createDefaultConfig() error { } // 创建文件提供器 - cs.fileProvider = file.Provider(cs.pathManager.GetSettingsPath()) + cs.fileProvider = file.Provider(cs.settingsPath) if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil { return &ConfigError{Operation: "load_config_file", Err: err} @@ -252,7 +246,7 @@ func (cs *ConfigService) ResetConfig() error { } // 重新创建文件提供器 - cs.fileProvider = file.Provider(cs.pathManager.GetSettingsPath()) + cs.fileProvider = file.Provider(cs.settingsPath) // 重新加载配置文件 if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil { @@ -277,7 +271,7 @@ func (cs *ConfigService) writeConfigToFile() error { return &ConfigError{Operation: "marshal_config", Err: err} } - if err := os.WriteFile(cs.pathManager.GetSettingsPath(), configBytes, 0644); err != nil { + if err := os.WriteFile(cs.settingsPath, configBytes, 0644); err != nil { return &ConfigError{Operation: "write_config_file", Err: err} } diff --git a/internal/services/database_service.go b/internal/services/database_service.go new file mode 100644 index 0000000..56400ee --- /dev/null +++ b/internal/services/database_service.go @@ -0,0 +1,211 @@ +package services + +import ( + "context" + "database/sql" + "fmt" + "os" + "path/filepath" + "sync" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/log" + _ "modernc.org/sqlite" // SQLite driver +) + +const ( + dbName = "voidraft.db" + + // SQLite performance optimization settings + sqlOptimizationSettings = ` +PRAGMA journal_mode = WAL; +PRAGMA synchronous = NORMAL; +PRAGMA cache_size = -64000; +PRAGMA temp_store = MEMORY; +PRAGMA foreign_keys = ON;` + + // Documents table + sqlCreateDocumentsTable = ` +CREATE TABLE IF NOT EXISTS documents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT DEFAULT '∞∞∞text-a', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + is_deleted INTEGER DEFAULT 0 +)` + + // Extensions table + sqlCreateExtensionsTable = ` +CREATE TABLE IF NOT EXISTS extensions ( + id TEXT PRIMARY KEY, + enabled INTEGER NOT NULL DEFAULT 1, + is_default INTEGER NOT NULL DEFAULT 0, + config TEXT DEFAULT '{}', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +)` + + // Key bindings table + sqlCreateKeyBindingsTable = ` +CREATE TABLE IF NOT EXISTS key_bindings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + command TEXT NOT NULL, + extension TEXT NOT NULL, + key TEXT NOT NULL, + enabled INTEGER NOT NULL DEFAULT 1, + is_default INTEGER NOT NULL DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(command, extension) +)` +) + +// DatabaseService provides shared database functionality +type DatabaseService struct { + configService *ConfigService + logger *log.LoggerService + db *sql.DB + mu sync.RWMutex + ctx context.Context +} + +// NewDatabaseService creates a new database service +func NewDatabaseService(configService *ConfigService, logger *log.LoggerService) *DatabaseService { + if logger == nil { + logger = log.New() + } + + return &DatabaseService{ + configService: configService, + logger: logger, + } +} + +// OnStartup initializes the service when the application starts +func (ds *DatabaseService) OnStartup(ctx context.Context, _ application.ServiceOptions) error { + ds.ctx = ctx + return ds.initDatabase() +} + +// initDatabase initializes the SQLite database +func (ds *DatabaseService) initDatabase() error { + dbPath, err := ds.getDatabasePath() + if err != nil { + return fmt.Errorf("failed to get database path: %w", err) + } + + // 确保数据库目录存在 + dbDir := filepath.Dir(dbPath) + if err := os.MkdirAll(dbDir, 0755); err != nil { + return fmt.Errorf("failed to create database directory: %w", err) + } + + // 检查数据库文件是否存在,如果不存在则创建 + if _, err := os.Stat(dbPath); os.IsNotExist(err) { + // 创建空文件 + file, err := os.Create(dbPath) + if err != nil { + return fmt.Errorf("failed to create database file: %w", err) + } + file.Close() + } + + db, err := sql.Open("sqlite", dbPath) + if err != nil { + return fmt.Errorf("failed to open database: %w", err) + } + + ds.db = db + + // Apply optimization settings + if _, err := db.Exec(sqlOptimizationSettings); err != nil { + return fmt.Errorf("failed to apply optimization settings: %w", err) + } + + // Create all tables + if err := ds.createTables(); err != nil { + return fmt.Errorf("failed to create tables: %w", err) + } + + // Create indexes + if err := ds.createIndexes(); err != nil { + return fmt.Errorf("failed to create indexes: %w", err) + } + + return nil +} + +// getDatabasePath gets the database file path +func (ds *DatabaseService) getDatabasePath() (string, error) { + config, err := ds.configService.GetConfig() + if err != nil { + return "", err + } + return filepath.Join(config.General.DataPath, dbName), nil +} + +// createTables creates all database tables +func (ds *DatabaseService) createTables() error { + tables := []string{ + sqlCreateDocumentsTable, + sqlCreateExtensionsTable, + sqlCreateKeyBindingsTable, + } + + for _, table := range tables { + if _, err := ds.db.Exec(table); err != nil { + return err + } + } + return nil +} + +// createIndexes creates database indexes +func (ds *DatabaseService) createIndexes() error { + indexes := []string{ + // Documents indexes + `CREATE INDEX IF NOT EXISTS idx_documents_updated_at ON documents(updated_at DESC)`, + `CREATE INDEX IF NOT EXISTS idx_documents_title ON documents(title)`, + `CREATE INDEX IF NOT EXISTS idx_documents_is_deleted ON documents(is_deleted)`, + // Extensions indexes + `CREATE INDEX IF NOT EXISTS idx_extensions_enabled ON extensions(enabled)`, + // Key bindings indexes + `CREATE INDEX IF NOT EXISTS idx_key_bindings_command ON key_bindings(command)`, + `CREATE INDEX IF NOT EXISTS idx_key_bindings_extension ON key_bindings(extension)`, + `CREATE INDEX IF NOT EXISTS idx_key_bindings_enabled ON key_bindings(enabled)`, + } + + for _, index := range indexes { + if _, err := ds.db.Exec(index); err != nil { + return err + } + } + return nil +} + +// GetDB returns the database connection +func (ds *DatabaseService) GetDB() *sql.DB { + ds.mu.RLock() + defer ds.mu.RUnlock() + return ds.db +} + +// OnShutdown shuts down the service when the application closes +func (ds *DatabaseService) OnShutdown() error { + if ds.db != nil { + return ds.db.Close() + } + return nil +} + +// OnDataPathChanged handles data path changes +func (ds *DatabaseService) OnDataPathChanged() error { + // Close existing database + if ds.db != nil { + ds.db.Close() + } + + // Reinitialize with new path + return ds.initDatabase() +} diff --git a/internal/services/document_service.go b/internal/services/document_service.go index 03c724d..7584a32 100644 --- a/internal/services/document_service.go +++ b/internal/services/document_service.go @@ -5,8 +5,6 @@ import ( "database/sql" "errors" "fmt" - "os" - "path/filepath" "sync" "time" "voidraft/internal/models" @@ -16,32 +14,8 @@ import ( _ "modernc.org/sqlite" // SQLite driver ) -// SQL constants for database operations +// SQL constants for document operations const ( - dbName = "voidraft.db" - // Database schema (simplified single table with auto-increment ID) - sqlCreateDocumentsTable = ` -CREATE TABLE IF NOT EXISTS documents ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL, - content TEXT DEFAULT '∞∞∞text-a', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - is_deleted INTEGER DEFAULT 0 -)` - - // Performance optimization indexes - sqlCreateIndexUpdatedAt = `CREATE INDEX IF NOT EXISTS idx_documents_updated_at ON documents(updated_at DESC)` - sqlCreateIndexTitle = `CREATE INDEX IF NOT EXISTS idx_documents_title ON documents(title)` - sqlCreateIndexIsDeleted = `CREATE INDEX IF NOT EXISTS idx_documents_is_deleted ON documents(is_deleted)` - - // SQLite performance optimization settings - sqlOptimizationSettings = ` -PRAGMA journal_mode = WAL; -PRAGMA synchronous = NORMAL; -PRAGMA cache_size = -64000; -PRAGMA temp_store = MEMORY; -PRAGMA foreign_keys = ON;` // Document operations sqlGetDocumentByID = ` @@ -95,107 +69,31 @@ SELECT id FROM documents WHERE is_deleted = 0 ORDER BY id LIMIT 1` // DocumentService provides document management functionality type DocumentService struct { - configService *ConfigService - logger *log.LoggerService - db *sql.DB - mu sync.RWMutex - ctx context.Context + databaseService *DatabaseService + logger *log.LoggerService + mu sync.RWMutex + ctx context.Context } // NewDocumentService creates a new document service -func NewDocumentService(configService *ConfigService, logger *log.LoggerService) *DocumentService { +func NewDocumentService(databaseService *DatabaseService, logger *log.LoggerService) *DocumentService { if logger == nil { logger = log.New() } return &DocumentService{ - configService: configService, - logger: logger, + databaseService: databaseService, + logger: logger, } } // OnStartup initializes the service when the application starts func (ds *DocumentService) OnStartup(ctx context.Context, _ application.ServiceOptions) error { ds.ctx = ctx - return ds.initDatabase() -} - -// initDatabase initializes the SQLite database -func (ds *DocumentService) initDatabase() error { - dbPath, err := ds.getDatabasePath() - if err != nil { - return fmt.Errorf("failed to get database path: %w", err) - } - - // 确保数据库目录存在 - dbDir := filepath.Dir(dbPath) - if err := os.MkdirAll(dbDir, 0755); err != nil { - return fmt.Errorf("failed to create database directory: %w", err) - } - - // 检查数据库文件是否存在,如果不存在则创建 - if _, err := os.Stat(dbPath); os.IsNotExist(err) { - ds.logger.Info("Database file does not exist, creating empty file", "path", dbPath) - // 创建空文件 - file, err := os.Create(dbPath) - if err != nil { - return fmt.Errorf("failed to create database file: %w", err) - } - file.Close() - } - - db, err := sql.Open("sqlite", dbPath) - if err != nil { - return fmt.Errorf("failed to open database: %w", err) - } - - ds.db = db - - // Apply optimization settings - if _, err := db.Exec(sqlOptimizationSettings); err != nil { - return fmt.Errorf("failed to apply optimization settings: %w", err) - } - - // Create table - if _, err := db.Exec(sqlCreateDocumentsTable); err != nil { - return fmt.Errorf("failed to create table: %w", err) - } - - // Create indexes - if err := ds.createIndexes(); err != nil { - return fmt.Errorf("failed to create indexes: %w", err) - } - // Ensure default document exists if err := ds.ensureDefaultDocument(); err != nil { return fmt.Errorf("failed to ensure default document: %w", err) } - - return nil -} - -// getDatabasePath gets the database file path -func (ds *DocumentService) getDatabasePath() (string, error) { - config, err := ds.configService.GetConfig() - if err != nil { - return "", err - } - return filepath.Join(config.General.DataPath, dbName), nil -} - -// createIndexes creates database indexes -func (ds *DocumentService) createIndexes() error { - indexes := []string{ - sqlCreateIndexUpdatedAt, - sqlCreateIndexTitle, - sqlCreateIndexIsDeleted, - } - - for _, index := range indexes { - if _, err := ds.db.Exec(index); err != nil { - return err - } - } return nil } @@ -203,7 +101,8 @@ func (ds *DocumentService) createIndexes() error { func (ds *DocumentService) ensureDefaultDocument() error { // Check if any document exists var count int - err := ds.db.QueryRow(sqlCountDocuments).Scan(&count) + db := ds.databaseService.GetDB() + err := db.QueryRow(sqlCountDocuments).Scan(&count) if err != nil { return err } @@ -224,7 +123,8 @@ func (ds *DocumentService) GetDocumentByID(id int64) (*models.Document, error) { var doc models.Document var isDeletedInt int - row := ds.db.QueryRow(sqlGetDocumentByID, id) + db := ds.databaseService.GetDB() + row := db.QueryRow(sqlGetDocumentByID, id) err := row.Scan(&doc.ID, &doc.Title, &doc.Content, &doc.CreatedAt, &doc.UpdatedAt, &isDeletedInt) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -252,7 +152,8 @@ func (ds *DocumentService) CreateDocument(title string) (*models.Document, error IsDeleted: false, } - result, err := ds.db.Exec(sqlInsertDocument, doc.Title, doc.Content, doc.CreatedAt, doc.UpdatedAt) + db := ds.databaseService.GetDB() + result, err := db.Exec(sqlInsertDocument, doc.Title, doc.Content, doc.CreatedAt, doc.UpdatedAt) if err != nil { return nil, fmt.Errorf("failed to create document: %w", err) } @@ -273,7 +174,8 @@ func (ds *DocumentService) UpdateDocumentContent(id int64, content string) error ds.mu.Lock() defer ds.mu.Unlock() - _, err := ds.db.Exec(sqlUpdateDocumentContent, content, time.Now(), id) + db := ds.databaseService.GetDB() + _, err := db.Exec(sqlUpdateDocumentContent, content, time.Now(), id) if err != nil { return fmt.Errorf("failed to update document content: %w", err) } @@ -285,7 +187,8 @@ func (ds *DocumentService) UpdateDocumentTitle(id int64, title string) error { ds.mu.Lock() defer ds.mu.Unlock() - _, err := ds.db.Exec(sqlUpdateDocumentTitle, title, time.Now(), id) + db := ds.databaseService.GetDB() + _, err := db.Exec(sqlUpdateDocumentTitle, title, time.Now(), id) if err != nil { return fmt.Errorf("failed to update document title: %w", err) } @@ -302,7 +205,8 @@ func (ds *DocumentService) DeleteDocument(id int64) error { return fmt.Errorf("cannot delete the default document") } - _, err := ds.db.Exec(sqlMarkDocumentAsDeleted, time.Now(), id) + db := ds.databaseService.GetDB() + _, err := db.Exec(sqlMarkDocumentAsDeleted, time.Now(), id) if err != nil { return fmt.Errorf("failed to mark document as deleted: %w", err) } @@ -314,7 +218,8 @@ func (ds *DocumentService) RestoreDocument(id int64) error { ds.mu.Lock() defer ds.mu.Unlock() - _, err := ds.db.Exec(sqlRestoreDocument, time.Now(), id) + db := ds.databaseService.GetDB() + _, err := db.Exec(sqlRestoreDocument, time.Now(), id) if err != nil { return fmt.Errorf("failed to restore document: %w", err) } @@ -326,7 +231,8 @@ func (ds *DocumentService) ListAllDocumentsMeta() ([]*models.Document, error) { ds.mu.RLock() defer ds.mu.RUnlock() - rows, err := ds.db.Query(sqlListAllDocumentsMeta) + db := ds.databaseService.GetDB() + rows, err := db.Query(sqlListAllDocumentsMeta) if err != nil { return nil, fmt.Errorf("failed to list document meta: %w", err) } @@ -351,7 +257,8 @@ func (ds *DocumentService) ListDeletedDocumentsMeta() ([]*models.Document, error ds.mu.RLock() defer ds.mu.RUnlock() - rows, err := ds.db.Query(sqlListDeletedDocumentsMeta) + db := ds.databaseService.GetDB() + rows, err := db.Query(sqlListDeletedDocumentsMeta) if err != nil { return nil, fmt.Errorf("failed to list deleted document meta: %w", err) } @@ -376,8 +283,9 @@ func (ds *DocumentService) GetFirstDocumentID() (int64, error) { ds.mu.RLock() defer ds.mu.RUnlock() + db := ds.databaseService.GetDB() var id int64 - err := ds.db.QueryRow(sqlGetFirstDocumentID).Scan(&id) + err := db.QueryRow(sqlGetFirstDocumentID).Scan(&id) if err != nil { if err == sql.ErrNoRows { return 0, nil // No documents exist @@ -386,22 +294,3 @@ func (ds *DocumentService) GetFirstDocumentID() (int64, error) { } return id, nil } - -// OnShutdown shuts down the service when the application closes -func (ds *DocumentService) OnShutdown() error { - if ds.db != nil { - return ds.db.Close() - } - return nil -} - -// OnDataPathChanged handles data path changes -func (ds *DocumentService) OnDataPathChanged() error { - // Close existing database - if ds.db != nil { - ds.db.Close() - } - - // Reinitialize with new path - return ds.initDatabase() -} diff --git a/internal/services/extension_service.go b/internal/services/extension_service.go index e5784e2..e6dfb7d 100644 --- a/internal/services/extension_service.go +++ b/internal/services/extension_service.go @@ -2,33 +2,51 @@ package services import ( "context" + "encoding/json" "errors" "fmt" - "os" "sync" + "time" "voidraft/internal/models" - jsonparser "github.com/knadh/koanf/parsers/json" - "github.com/knadh/koanf/providers/file" - "github.com/knadh/koanf/providers/structs" - "github.com/knadh/koanf/v2" + "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/services/log" ) +// SQL constants for extension operations +const ( + // Extension operations + sqlGetAllExtensions = ` +SELECT id, enabled, is_default, config +FROM extensions +ORDER BY id` + + sqlGetExtensionByID = ` +SELECT id, enabled, is_default, config +FROM extensions +WHERE id = ?` + + sqlInsertExtension = ` +INSERT INTO extensions (id, enabled, is_default, config, created_at, updated_at) +VALUES (?, ?, ?, ?, ?, ?)` + + sqlUpdateExtension = ` +UPDATE extensions +SET enabled = ?, config = ?, updated_at = ? +WHERE id = ?` + + sqlDeleteAllExtensions = `DELETE FROM extensions` +) + // ExtensionService 扩展管理服务 type ExtensionService struct { - koanf *koanf.Koanf - logger *log.LoggerService - pathManager *PathManager - fileProvider *file.File + databaseService *DatabaseService + logger *log.LoggerService mu sync.RWMutex ctx context.Context cancel context.CancelFunc initOnce sync.Once - - // 配置迁移服务 - migrationService *ConfigMigrationService[*models.ExtensionSettings] } // ExtensionError 扩展错误 @@ -55,127 +73,120 @@ func (e *ExtensionError) Is(target error) bool { } // NewExtensionService 创建扩展服务实例 -func NewExtensionService(logger *log.LoggerService, pathManager *PathManager) *ExtensionService { +func NewExtensionService(databaseService *DatabaseService, logger *log.LoggerService) *ExtensionService { if logger == nil { logger = log.New() } - if pathManager == nil { - pathManager = NewPathManager() - } ctx, cancel := context.WithCancel(context.Background()) - k := koanf.New(".") - - migrationService := NewExtensionMigrationService(logger, pathManager) - service := &ExtensionService{ - koanf: k, - logger: logger, - pathManager: pathManager, - ctx: ctx, - cancel: cancel, - migrationService: migrationService, + databaseService: databaseService, + logger: logger, + ctx: ctx, + cancel: cancel, } - // 异步初始化 - go service.initialize() - return service } // initialize 初始化配置 func (es *ExtensionService) initialize() { es.initOnce.Do(func() { - if err := es.initConfig(); err != nil { - es.logger.Error("failed to initialize extension config", "error", err) + if err := es.initDatabase(); err != nil { + es.logger.Error("failed to initialize extension database", "error", err) } }) } -// setDefaults 设置默认值 -func (es *ExtensionService) setDefaults() error { - defaultConfig := models.NewDefaultExtensionSettings() - - if err := es.koanf.Load(structs.Provider(defaultConfig, "json"), nil); err != nil { - return &ExtensionError{"load_defaults", "", err} - } - - return nil -} - -// initConfig 初始化配置 -func (es *ExtensionService) initConfig() error { +// initDatabase 初始化数据库数据 +func (es *ExtensionService) initDatabase() error { es.mu.Lock() defer es.mu.Unlock() - // 检查配置文件是否存在 - configPath := es.pathManager.GetExtensionsPath() - if _, err := os.Stat(configPath); os.IsNotExist(err) { - return es.createDefaultConfig() + // 检查是否已有扩展数据 + db := es.databaseService.GetDB() + if db == nil { + return &ExtensionError{"get_database", "", fmt.Errorf("database connection is nil")} } - // 配置文件存在,先加载现有配置 - es.fileProvider = file.Provider(configPath) - if err := es.koanf.Load(es.fileProvider, jsonparser.Parser()); err != nil { - return &ExtensionError{"load_config_file", "", err} + var count int + err := db.QueryRow("SELECT COUNT(*) FROM extensions").Scan(&count) + if err != nil { + return &ExtensionError{"check_extensions_count", "", err} } - // 检查并执行配置迁移 - if es.migrationService != nil { - result, err := es.migrationService.MigrateConfig(es.koanf) + es.logger.Info("Extension database check", "existing_count", count) + + // 如果没有数据,插入默认配置 + if count == 0 { + es.logger.Info("No extensions found, inserting default extensions...") + if err := es.insertDefaultExtensions(); err != nil { + es.logger.Error("Failed to insert default extensions", "error", err) + return err + } + es.logger.Info("Default extensions inserted successfully") + } else { + es.logger.Info("Extensions already exist, skipping default insertion") + } + + return nil +} + +// insertDefaultExtensions 插入默认扩展配置 +func (es *ExtensionService) insertDefaultExtensions() error { + defaultSettings := models.NewDefaultExtensionSettings() + db := es.databaseService.GetDB() + now := time.Now() + + es.logger.Info("Starting to insert default extensions", "count", len(defaultSettings.Extensions)) + + for i, ext := range defaultSettings.Extensions { + es.logger.Info("Inserting extension", "index", i+1, "id", ext.ID, "enabled", ext.Enabled) + + configJSON, err := json.Marshal(ext.Config) if err != nil { - return &ExtensionError{"migrate_config", "", err} + es.logger.Error("Failed to marshal config", "extension", ext.ID, "error", err) + return &ExtensionError{"marshal_config", string(ext.ID), err} } - if result.Migrated && result.ConfigUpdated { - // 迁移完成且配置已更新,重新创建文件提供器以监听新文件 - es.fileProvider = file.Provider(configPath) + _, err = db.Exec(sqlInsertExtension, + string(ext.ID), + ext.Enabled, + ext.IsDefault, + string(configJSON), + now, + now, + ) + if err != nil { + es.logger.Error("Failed to insert extension", "extension", ext.ID, "error", err) + return &ExtensionError{"insert_extension", string(ext.ID), err} } + + es.logger.Info("Successfully inserted extension", "id", ext.ID) } + es.logger.Info("Completed inserting all default extensions") return nil } -// createDefaultConfig 创建默认配置文件 -func (es *ExtensionService) createDefaultConfig() error { - if err := es.pathManager.EnsureConfigDir(); err != nil { - return &ExtensionError{"create_config_dir", "", err} - } +// OnStartup 启动时调用 +func (es *ExtensionService) OnStartup(ctx context.Context, _ application.ServiceOptions) error { + es.ctx = ctx + es.logger.Info("Extension service starting up") - if err := es.setDefaults(); err != nil { - return err - } - - configBytes, err := es.koanf.Marshal(jsonparser.Parser()) - if err != nil { - return &ExtensionError{"marshal_config", "", err} - } - - if err := os.WriteFile(es.pathManager.GetExtensionsPath(), configBytes, 0644); err != nil { - return &ExtensionError{"write_config", "", err} - } - - // 创建文件提供器 - es.fileProvider = file.Provider(es.pathManager.GetExtensionsPath()) - if err = es.koanf.Load(es.fileProvider, jsonparser.Parser()); err != nil { - return err - } - return nil -} - -// saveConfig 保存配置到文件 -func (es *ExtensionService) saveExtensionConfig() error { - configBytes, err := es.koanf.Marshal(jsonparser.Parser()) - if err != nil { - return &ExtensionError{"marshal_config", "", err} - } - - if err := os.WriteFile(es.pathManager.GetExtensionsPath(), configBytes, 0644); err != nil { - return &ExtensionError{"write_config", "", err} - } - - return nil + // 初始化数据库 + var initErr error + es.initOnce.Do(func() { + es.logger.Info("Initializing extension database...") + if err := es.initDatabase(); err != nil { + es.logger.Error("failed to initialize extension database", "error", err) + initErr = err + } else { + es.logger.Info("Extension database initialized successfully") + } + }) + return initErr } // GetAllExtensions 获取所有扩展配置 @@ -183,11 +194,31 @@ func (es *ExtensionService) GetAllExtensions() ([]models.Extension, error) { es.mu.RLock() defer es.mu.RUnlock() - var settings models.ExtensionSettings - if err := es.koanf.Unmarshal("", &settings); err != nil { - return nil, &ExtensionError{"unmarshal_config", "", err} + db := es.databaseService.GetDB() + rows, err := db.Query(sqlGetAllExtensions) + if err != nil { + return nil, &ExtensionError{"query_extensions", "", err} } - return settings.Extensions, nil + defer rows.Close() + + var extensions []models.Extension + for rows.Next() { + var ext models.Extension + var configJSON string + if err := rows.Scan(&ext.ID, &ext.Enabled, &ext.IsDefault, &configJSON); err != nil { + return nil, &ExtensionError{"scan_extension", "", err} + } + if err := json.Unmarshal([]byte(configJSON), &ext.Config); err != nil { + return nil, &ExtensionError{"unmarshal_config", string(ext.ID), err} + } + extensions = append(extensions, ext) + } + + if err := rows.Err(); err != nil { + return nil, &ExtensionError{"rows_error", "", err} + } + + return extensions, nil } // UpdateExtensionEnabled 更新扩展启用状态 @@ -200,25 +231,28 @@ func (es *ExtensionService) UpdateExtensionState(id models.ExtensionID, enabled es.mu.Lock() defer es.mu.Unlock() - // 获取当前配置 - var settings models.ExtensionSettings - if err := es.koanf.Unmarshal("", &settings); err != nil { - return &ExtensionError{"unmarshal_config", string(id), err} + db := es.databaseService.GetDB() + var configJSON []byte + var err error + + if config != nil { + configJSON, err = json.Marshal(config) + if err != nil { + return &ExtensionError{"marshal_config", string(id), err} + } + } else { + // 如果没有提供配置,保持原有配置 + var currentConfigJSON string + err = db.QueryRow("SELECT config FROM extensions WHERE id = ?", string(id)).Scan(¤tConfigJSON) + if err != nil { + return &ExtensionError{"query_current_config", string(id), err} + } + configJSON = []byte(currentConfigJSON) } - // 更新扩展状态 - if !settings.UpdateExtension(id, enabled, config) { - return &ExtensionError{"extension_not_found", string(id), nil} - } - - // 重新加载到koanf - if err := es.koanf.Load(structs.Provider(&settings, "json"), nil); err != nil { - return &ExtensionError{"reload_config", string(id), err} - } - - // 保存到文件 - if err := es.saveExtensionConfig(); err != nil { - return &ExtensionError{"save_config", string(id), err} + _, err = db.Exec(sqlUpdateExtension, enabled, string(configJSON), time.Now(), string(id)) + if err != nil { + return &ExtensionError{"update_extension", string(id), err} } es.logger.Info("extension state updated", "id", id, "enabled", enabled) @@ -242,23 +276,18 @@ func (es *ExtensionService) ResetAllExtensionsToDefault() error { es.mu.Lock() defer es.mu.Unlock() - // 加载默认配置 - defaultSettings := models.NewDefaultExtensionSettings() - if err := es.koanf.Load(structs.Provider(defaultSettings, "json"), nil); err != nil { - return &ExtensionError{"load_defaults", "", err} + // 删除所有现有扩展 + db := es.databaseService.GetDB() + _, err := db.Exec(sqlDeleteAllExtensions) + if err != nil { + return &ExtensionError{"delete_all_extensions", "", err} } - // 保存到文件 - if err := es.saveExtensionConfig(); err != nil { - return &ExtensionError{"save_config", "", err} + // 插入默认扩展配置 + if err := es.insertDefaultExtensions(); err != nil { + return err } es.logger.Info("all extensions reset to default") return nil } - -// OnShutdown 关闭服务 -func (es *ExtensionService) OnShutdown() error { - es.cancel() - return nil -} diff --git a/internal/services/keybinding_service.go b/internal/services/keybinding_service.go index a0179cb..fab2548 100644 --- a/internal/services/keybinding_service.go +++ b/internal/services/keybinding_service.go @@ -4,31 +4,59 @@ import ( "context" "errors" "fmt" - "os" "sync" + "time" "voidraft/internal/models" - jsonparser "github.com/knadh/koanf/parsers/json" - "github.com/knadh/koanf/providers/file" - "github.com/knadh/koanf/providers/structs" - "github.com/knadh/koanf/v2" + "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/services/log" ) +// SQL 查询语句 +const ( + // 快捷键操作 + sqlGetAllKeyBindings = ` + SELECT command, extension, key, enabled, is_default + FROM key_bindings + ORDER BY command + ` + + sqlGetKeyBindingByCommand = ` + SELECT command, extension, key, enabled, is_default + FROM key_bindings + WHERE command = ? + ` + + sqlInsertKeyBinding = ` + INSERT INTO key_bindings (command, extension, key, enabled, is_default, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?) + ` + + sqlUpdateKeyBinding = ` + UPDATE key_bindings + SET extension = ?, key = ?, enabled = ?, updated_at = ? + WHERE command = ? + ` + + sqlDeleteKeyBinding = ` + DELETE FROM key_bindings + WHERE command = ? + ` + + sqlDeleteAllKeyBindings = ` + DELETE FROM key_bindings + ` +) + // KeyBindingService 快捷键管理服务 type KeyBindingService struct { - koanf *koanf.Koanf - logger *log.LoggerService - pathManager *PathManager - fileProvider *file.File + databaseService *DatabaseService + logger *log.LoggerService mu sync.RWMutex ctx context.Context cancel context.CancelFunc initOnce sync.Once - - // 配置迁移服务 - migrationService *ConfigMigrationService[*models.KeyBindingConfig] } // KeyBindingError 快捷键错误 @@ -55,138 +83,145 @@ func (e *KeyBindingError) Is(target error) bool { } // NewKeyBindingService 创建快捷键服务实例 -func NewKeyBindingService(logger *log.LoggerService, pathManager *PathManager) *KeyBindingService { +func NewKeyBindingService(databaseService *DatabaseService, logger *log.LoggerService) *KeyBindingService { if logger == nil { logger = log.New() } - if pathManager == nil { - pathManager = NewPathManager() - } ctx, cancel := context.WithCancel(context.Background()) - k := koanf.New(".") - - migrationService := NewKeyBindingMigrationService(logger, pathManager) - service := &KeyBindingService{ - koanf: k, - logger: logger, - pathManager: pathManager, - ctx: ctx, - cancel: cancel, - migrationService: migrationService, + databaseService: databaseService, + logger: logger, + ctx: ctx, + cancel: cancel, } - // 异步初始化 - go service.initialize() - return service } -// initialize 初始化配置 -func (kbs *KeyBindingService) initialize() { - kbs.initOnce.Do(func() { - if err := kbs.initConfig(); err != nil { - kbs.logger.Error("failed to initialize keybinding config", "error", err) - } - }) -} - -// setDefaults 设置默认值 -func (kbs *KeyBindingService) setDefaults() error { - defaultConfig := models.NewDefaultKeyBindingConfig() - - if err := kbs.koanf.Load(structs.Provider(defaultConfig, "json"), nil); err != nil { - return &KeyBindingError{"load_defaults", "", err} - } - - return nil -} - -// initConfig 初始化配置 -func (kbs *KeyBindingService) initConfig() error { +// initDatabase 初始化数据库数据 +func (kbs *KeyBindingService) initDatabase() error { kbs.mu.Lock() defer kbs.mu.Unlock() - // 检查配置文件是否存在 - configPath := kbs.pathManager.GetKeybindsPath() - if _, err := os.Stat(configPath); os.IsNotExist(err) { - return kbs.createDefaultConfig() + // 检查是否已有快捷键数据 + db := kbs.databaseService.GetDB() + if db == nil { + return &KeyBindingError{"get_database", "", fmt.Errorf("database connection is nil")} } - // 配置文件存在,先加载现有配置 - kbs.fileProvider = file.Provider(configPath) - if err := kbs.koanf.Load(kbs.fileProvider, jsonparser.Parser()); err != nil { - return &KeyBindingError{"load_config_file", "", err} + var count int + err := db.QueryRow("SELECT COUNT(*) FROM key_bindings").Scan(&count) + if err != nil { + return &KeyBindingError{"check_keybindings_count", "", err} } - // 检查并执行配置迁移 - if kbs.migrationService != nil { - result, err := kbs.migrationService.MigrateConfig(kbs.koanf) - if err != nil { - return &KeyBindingError{"migrate_config", "", err} - } + kbs.logger.Info("KeyBinding database check", "existing_count", count) - if result.Migrated && result.ConfigUpdated { - // 迁移完成且配置已更新,重新创建文件提供器以监听新文件 - kbs.fileProvider = file.Provider(configPath) + // 如果没有数据,插入默认配置 + if count == 0 { + kbs.logger.Info("No key bindings found, inserting default key bindings...") + if err := kbs.insertDefaultKeyBindings(); err != nil { + kbs.logger.Error("Failed to insert default key bindings", "error", err) + return err } + kbs.logger.Info("Default key bindings inserted successfully") + } else { + kbs.logger.Info("Key bindings already exist, skipping default insertion") } return nil } -// createDefaultConfig 创建默认配置文件 -func (kbs *KeyBindingService) createDefaultConfig() error { - if err := kbs.pathManager.EnsureConfigDir(); err != nil { - return &KeyBindingError{"create_config_dir", "", err} +// insertDefaultKeyBindings 插入默认快捷键配置 +func (kbs *KeyBindingService) insertDefaultKeyBindings() error { + defaultConfig := models.NewDefaultKeyBindingConfig() + db := kbs.databaseService.GetDB() + now := time.Now() + + kbs.logger.Info("Starting to insert default key bindings", "count", len(defaultConfig.KeyBindings)) + + for i, kb := range defaultConfig.KeyBindings { + kbs.logger.Info("Inserting key binding", "index", i+1, "command", kb.Command, "key", kb.Key, "extension", kb.Extension) + + _, err := db.Exec(sqlInsertKeyBinding, + kb.Command, + kb.Extension, + kb.Key, + kb.Enabled, + kb.IsDefault, + now, + now, + ) + if err != nil { + kbs.logger.Error("Failed to insert key binding", "command", kb.Command, "error", err) + return &KeyBindingError{"insert_keybinding", string(kb.Command), err} + } + + kbs.logger.Info("Successfully inserted key binding", "command", kb.Command) } - if err := kbs.setDefaults(); err != nil { - return err - } - - configBytes, err := kbs.koanf.Marshal(jsonparser.Parser()) - if err != nil { - return &KeyBindingError{"marshal_config", "", err} - } - - if err := os.WriteFile(kbs.pathManager.GetKeybindsPath(), configBytes, 0644); err != nil { - return &KeyBindingError{"write_config", "", err} - } - - // 创建文件提供器 - kbs.fileProvider = file.Provider(kbs.pathManager.GetKeybindsPath()) - if err = kbs.koanf.Load(kbs.fileProvider, jsonparser.Parser()); err != nil { - return err - } + kbs.logger.Info("Completed inserting all default key bindings") return nil } // GetKeyBindingConfig 获取完整快捷键配置 func (kbs *KeyBindingService) GetKeyBindingConfig() (*models.KeyBindingConfig, error) { - kbs.mu.RLock() - defer kbs.mu.RUnlock() - - var config models.KeyBindingConfig - if err := kbs.koanf.Unmarshal("", &config); err != nil { - return nil, &KeyBindingError{"unmarshal_config", "", err} + keyBindings, err := kbs.GetAllKeyBindings() + if err != nil { + return nil, err } - return &config, nil + + config := &models.KeyBindingConfig{ + KeyBindings: keyBindings, + } + return config, nil } // GetAllKeyBindings 获取所有快捷键配置 func (kbs *KeyBindingService) GetAllKeyBindings() ([]models.KeyBinding, error) { - config, err := kbs.GetKeyBindingConfig() + kbs.mu.RLock() + defer kbs.mu.RUnlock() + + db := kbs.databaseService.GetDB() + rows, err := db.Query(sqlGetAllKeyBindings) if err != nil { - return nil, err + return nil, &KeyBindingError{"query_keybindings", "", err} } - return config.KeyBindings, nil + defer rows.Close() + + var keyBindings []models.KeyBinding + for rows.Next() { + var kb models.KeyBinding + if err := rows.Scan(&kb.Command, &kb.Extension, &kb.Key, &kb.Enabled, &kb.IsDefault); err != nil { + return nil, &KeyBindingError{"scan_keybinding", "", err} + } + keyBindings = append(keyBindings, kb) + } + + if err := rows.Err(); err != nil { + return nil, &KeyBindingError{"rows_error", "", err} + } + + return keyBindings, nil } -// OnShutdown 关闭服务 -func (kbs *KeyBindingService) OnShutdown() error { - kbs.cancel() - return nil +// OnStartup 启动时调用 +func (kbs *KeyBindingService) OnStartup(ctx context.Context, _ application.ServiceOptions) error { + kbs.ctx = ctx + kbs.logger.Info("KeyBinding service starting up") + + // 初始化数据库 + var initErr error + kbs.initOnce.Do(func() { + kbs.logger.Info("Initializing keybinding database...") + if err := kbs.initDatabase(); err != nil { + kbs.logger.Error("failed to initialize keybinding database", "error", err) + initErr = err + } else { + kbs.logger.Info("KeyBinding database initialized successfully") + } + }) + return initErr } diff --git a/internal/services/path_manager.go b/internal/services/path_manager.go deleted file mode 100644 index dc0f102..0000000 --- a/internal/services/path_manager.go +++ /dev/null @@ -1,59 +0,0 @@ -package services - -import ( - "os" - "path/filepath" -) - -// PathManager 路径管理器 -type PathManager struct { - configDir string // 配置目录 - settingsPath string // 设置文件路径 - keybindsPath string // 快捷键配置文件路径 - extensionsPath string // 扩展配置文件路径 -} - -// NewPathManager 创建新的路径管理器 -func NewPathManager() *PathManager { - // 获取用户配置目录 - userConfigDir, err := os.UserHomeDir() - if err != nil { - // 如果获取失败,使用当前目录 - userConfigDir, _ = os.Getwd() - } - - // 设置voidraft配置目录 - configDir := filepath.Join(userConfigDir, ".voidraft", "config") - - return &PathManager{ - configDir: configDir, - settingsPath: filepath.Join(configDir, "settings.json"), - keybindsPath: filepath.Join(configDir, "keybindings.json"), - extensionsPath: filepath.Join(configDir, "extensions.json"), - } -} - -// GetSettingsPath 获取设置文件路径 -func (pm *PathManager) GetSettingsPath() string { - return pm.settingsPath -} - -// GetKeybindsPath 获取快捷键配置文件路径 -func (pm *PathManager) GetKeybindsPath() string { - return pm.keybindsPath -} - -// GetConfigDir 获取配置目录路径 -func (pm *PathManager) GetConfigDir() string { - return pm.configDir -} - -// GetExtensionsPath 获取扩展配置文件路径 -func (pm *PathManager) GetExtensionsPath() string { - return pm.extensionsPath -} - -// EnsureConfigDir 确保配置目录存在 -func (pm *PathManager) EnsureConfigDir() error { - return os.MkdirAll(pm.configDir, 0755) -} diff --git a/internal/services/service_manager.go b/internal/services/service_manager.go index 1311293..33fbd55 100644 --- a/internal/services/service_manager.go +++ b/internal/services/service_manager.go @@ -9,8 +9,8 @@ import ( // ServiceManager 服务管理器,负责协调各个服务 type ServiceManager struct { - pathManager *PathManager configService *ConfigService + databaseService *DatabaseService documentService *DocumentService migrationService *MigrationService systemService *SystemService @@ -30,17 +30,17 @@ func NewServiceManager() *ServiceManager { // 初始化日志服务 logger := log.New() - // 初始化路径管理器 - pathManager := NewPathManager() - // 初始化配置服务 - configService := NewConfigService(logger, pathManager) + configService := NewConfigService(logger) + + // 初始化数据库服务 + databaseService := NewDatabaseService(configService, logger) // 初始化迁移服务 migrationService := NewMigrationService(logger) // 初始化文档服务 - documentService := NewDocumentService(configService, logger) + documentService := NewDocumentService(databaseService, logger) // 初始化系统服务 systemService := NewSystemService(logger) @@ -55,10 +55,10 @@ func NewServiceManager() *ServiceManager { trayService := NewTrayService(logger, configService) // 初始化快捷键服务 - keyBindingService := NewKeyBindingService(logger, pathManager) + keyBindingService := NewKeyBindingService(databaseService, logger) // 初始化扩展服务 - extensionService := NewExtensionService(logger, pathManager) + extensionService := NewExtensionService(databaseService, logger) // 初始化开机启动服务 startupService := NewStartupService(configService, logger) @@ -82,7 +82,7 @@ func NewServiceManager() *ServiceManager { // 设置数据路径变更监听,处理配置重置和路径变更 err = configService.SetDataPathChangeCallback(func() error { - return documentService.OnDataPathChanged() + return databaseService.OnDataPathChanged() }) if err != nil { panic(err) @@ -90,6 +90,7 @@ func NewServiceManager() *ServiceManager { return &ServiceManager{ configService: configService, + databaseService: databaseService, documentService: documentService, migrationService: migrationService, systemService: systemService, @@ -106,17 +107,19 @@ func NewServiceManager() *ServiceManager { } // GetServices 获取所有wails服务列表 +// 注意:服务启动顺序很重要,DatabaseService 必须在依赖数据库的服务之前启动 func (sm *ServiceManager) GetServices() []application.Service { services := []application.Service{ application.NewService(sm.configService), + application.NewService(sm.databaseService), application.NewService(sm.documentService), + application.NewService(sm.keyBindingService), + application.NewService(sm.extensionService), application.NewService(sm.migrationService), application.NewService(sm.systemService), application.NewService(sm.hotkeyService), application.NewService(sm.dialogService), application.NewService(sm.trayService), - application.NewService(sm.keyBindingService), - application.NewService(sm.extensionService), application.NewService(sm.startupService), application.NewService(sm.selfUpdateService), application.NewService(sm.translationService), @@ -173,3 +176,8 @@ func (sm *ServiceManager) GetSelfUpdateService() *SelfUpdateService { func (sm *ServiceManager) GetTranslationService() *TranslationService { return sm.translationService } + +// GetDatabaseService 获取数据库服务实例 +func (sm *ServiceManager) GetDatabaseService() *DatabaseService { + return sm.databaseService +}