🎨 Refactoring the extension service and the keybinding service
This commit is contained in:
4
frontend/bindings/database/sql/index.ts
Normal file
4
frontend/bindings/database/sql/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export * from "./models.js";
|
37
frontend/bindings/database/sql/models.ts
Normal file
37
frontend/bindings/database/sql/models.ts
Normal file
@@ -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<DB> = {}) {
|
||||||
|
|
||||||
|
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<DB>);
|
||||||
|
}
|
||||||
|
}
|
@@ -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<sql$0.DB | null> & { 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<void> & { 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);
|
@@ -78,14 +78,6 @@ export function ListDeletedDocumentsMeta(): Promise<(models$0.Document | null)[]
|
|||||||
return $typingPromise;
|
return $typingPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* OnDataPathChanged handles data path changes
|
|
||||||
*/
|
|
||||||
export function OnDataPathChanged(): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(269349439) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RestoreDocument restores a deleted document
|
* RestoreDocument restores a deleted document
|
||||||
*/
|
*/
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
import * as ConfigService from "./configservice.js";
|
import * as ConfigService from "./configservice.js";
|
||||||
|
import * as DatabaseService from "./databaseservice.js";
|
||||||
import * as DialogService from "./dialogservice.js";
|
import * as DialogService from "./dialogservice.js";
|
||||||
import * as DocumentService from "./documentservice.js";
|
import * as DocumentService from "./documentservice.js";
|
||||||
import * as ExtensionService from "./extensionservice.js";
|
import * as ExtensionService from "./extensionservice.js";
|
||||||
@@ -15,6 +16,7 @@ import * as TranslationService from "./translationservice.js";
|
|||||||
import * as TrayService from "./trayservice.js";
|
import * as TrayService from "./trayservice.js";
|
||||||
export {
|
export {
|
||||||
ConfigService,
|
ConfigService,
|
||||||
|
DatabaseService,
|
||||||
DialogService,
|
DialogService,
|
||||||
DocumentService,
|
DocumentService,
|
||||||
ExtensionService,
|
ExtensionService,
|
||||||
|
@@ -107,14 +107,7 @@ func NewDefaultExtensions() []Extension {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
IsDefault: true,
|
IsDefault: true,
|
||||||
Config: ExtensionConfig{
|
Config: ExtensionConfig{
|
||||||
"defaultSourceLang": "auto",
|
|
||||||
"defaultTargetLang": "zh",
|
|
||||||
"defaultTranslator": "bing",
|
"defaultTranslator": "bing",
|
||||||
"showTranslateButton": true,
|
|
||||||
"showButtonOnSelect": true,
|
|
||||||
"buttonDisplayDelay": 300,
|
|
||||||
"tooltipTimeout": 0,
|
|
||||||
"maxTooltipWidth": 300,
|
|
||||||
"minSelectionLength": 2,
|
"minSelectionLength": 2,
|
||||||
"maxTranslationLength": 5000,
|
"maxTranslationLength": 5000,
|
||||||
},
|
},
|
||||||
|
@@ -44,7 +44,7 @@ type Migratable interface {
|
|||||||
// ConfigMigrationService 配置迁移服务
|
// ConfigMigrationService 配置迁移服务
|
||||||
type ConfigMigrationService[T Migratable] struct {
|
type ConfigMigrationService[T Migratable] struct {
|
||||||
logger *log.LoggerService
|
logger *log.LoggerService
|
||||||
pathManager *PathManager
|
configDir string
|
||||||
configName string
|
configName string
|
||||||
targetVersion string
|
targetVersion string
|
||||||
configPath string
|
configPath string
|
||||||
@@ -60,12 +60,12 @@ type MigrationResult struct {
|
|||||||
// NewConfigMigrationService 创建配置迁移服务
|
// NewConfigMigrationService 创建配置迁移服务
|
||||||
func NewConfigMigrationService[T Migratable](
|
func NewConfigMigrationService[T Migratable](
|
||||||
logger *log.LoggerService,
|
logger *log.LoggerService,
|
||||||
pathManager *PathManager,
|
configDir string,
|
||||||
configName, targetVersion, configPath string,
|
configName, targetVersion, configPath string,
|
||||||
) *ConfigMigrationService[T] {
|
) *ConfigMigrationService[T] {
|
||||||
return &ConfigMigrationService[T]{
|
return &ConfigMigrationService[T]{
|
||||||
logger: orDefault(logger, log.New()),
|
logger: orDefault(logger, log.New()),
|
||||||
pathManager: orDefault(pathManager, NewPathManager()),
|
configDir: configDir,
|
||||||
configName: configName,
|
configName: configName,
|
||||||
targetVersion: targetVersion,
|
targetVersion: targetVersion,
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
@@ -138,7 +138,7 @@ func (cms *ConfigMigrationService[T]) createBackupOptimized() (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
configDir := cms.pathManager.GetConfigDir()
|
configDir := cms.configDir
|
||||||
timestamp := time.Now().Format("20060102150405")
|
timestamp := time.Now().Format("20060102150405")
|
||||||
newBackupPath := filepath.Join(configDir, fmt.Sprintf(BackupFilePattern, cms.configName, timestamp))
|
newBackupPath := filepath.Join(configDir, fmt.Sprintf(BackupFilePattern, cms.configName, timestamp))
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ func (cms *ConfigMigrationService[T]) tryQuickRecovery(existingConfig *koanf.Koa
|
|||||||
|
|
||||||
// findLatestBackupQuick 快速查找最新备份(优化排序)
|
// findLatestBackupQuick 快速查找最新备份(优化排序)
|
||||||
func (cms *ConfigMigrationService[T]) findLatestBackupQuick() string {
|
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)
|
matches, err := filepath.Glob(pattern)
|
||||||
if err != nil || len(matches) == 0 {
|
if err != nil || len(matches) == 0 {
|
||||||
return ""
|
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](
|
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](
|
return NewConfigMigrationService[*models.KeyBindingConfig](
|
||||||
logger, pathManager, "keybindings", CurrentKeyBindingConfigVersion, pathManager.GetKeybindsPath())
|
logger, configDir, "keybindings", CurrentKeyBindingConfigVersion, keybindsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExtensionMigrationService 创建扩展配置迁移服务
|
// 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](
|
return NewConfigMigrationService[*models.ExtensionSettings](
|
||||||
logger, pathManager, "extensions", CurrentExtensionConfigVersion, pathManager.GetExtensionsPath())
|
logger, configDir, "extensions", CurrentExtensionConfigVersion, extensionsPath)
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
@@ -19,7 +20,8 @@ import (
|
|||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
koanf *koanf.Koanf // koanf 实例
|
koanf *koanf.Koanf // koanf 实例
|
||||||
logger *log.LoggerService // 日志服务
|
logger *log.LoggerService // 日志服务
|
||||||
pathManager *PathManager // 路径管理器
|
configDir string // 配置目录
|
||||||
|
settingsPath string // 设置文件路径
|
||||||
mu sync.RWMutex // 读写锁
|
mu sync.RWMutex // 读写锁
|
||||||
fileProvider *file.File // 文件提供器,用于监听
|
fileProvider *file.File // 文件提供器,用于监听
|
||||||
|
|
||||||
@@ -53,41 +55,34 @@ func (e *ConfigError) Is(target error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewConfigService 创建新的配置服务实例
|
// NewConfigService 创建新的配置服务实例
|
||||||
func NewConfigService(logger *log.LoggerService, pathManager *PathManager) *ConfigService {
|
func NewConfigService(logger *log.LoggerService) *ConfigService {
|
||||||
// 设置日志服务
|
// 获取用户主目录
|
||||||
if logger == nil {
|
homeDir, err := os.UserHomeDir()
|
||||||
logger = log.New()
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("unable to get the user's home directory: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置路径管理器
|
// 设置配置目录和设置文件路径
|
||||||
if pathManager == nil {
|
configDir := filepath.Join(homeDir, ".voidraft", "config")
|
||||||
pathManager = NewPathManager()
|
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)
|
cs.initConfig()
|
||||||
migrationService := NewAppConfigMigrationService(logger, pathManager)
|
|
||||||
|
|
||||||
// 构造配置服务实例
|
|
||||||
service := &ConfigService{
|
|
||||||
koanf: k,
|
|
||||||
logger: logger,
|
|
||||||
pathManager: pathManager,
|
|
||||||
notificationService: notificationService,
|
|
||||||
migrationService: migrationService,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化配置
|
|
||||||
if err := service.initConfig(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动配置文件监听
|
// 启动配置文件监听
|
||||||
service.startWatching()
|
cs.startWatching()
|
||||||
|
|
||||||
return service
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaults 设置默认配置
|
// setDefaults 设置默认配置
|
||||||
@@ -107,13 +102,12 @@ func (cs *ConfigService) initConfig() error {
|
|||||||
defer cs.mu.Unlock()
|
defer cs.mu.Unlock()
|
||||||
|
|
||||||
// 检查配置文件是否存在
|
// 检查配置文件是否存在
|
||||||
configPath := cs.pathManager.GetSettingsPath()
|
if _, err := os.Stat(cs.settingsPath); os.IsNotExist(err) {
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
|
||||||
return cs.createDefaultConfig()
|
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 {
|
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
|
||||||
return &ConfigError{Operation: "load_config_file", Err: err}
|
return &ConfigError{Operation: "load_config_file", Err: err}
|
||||||
}
|
}
|
||||||
@@ -127,7 +121,7 @@ func (cs *ConfigService) initConfig() error {
|
|||||||
|
|
||||||
if result.Migrated && result.ConfigUpdated {
|
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 创建默认配置文件
|
// createDefaultConfig 创建默认配置文件
|
||||||
func (cs *ConfigService) createDefaultConfig() error {
|
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}
|
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 {
|
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
|
||||||
return &ConfigError{Operation: "load_config_file", Err: err}
|
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 {
|
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}
|
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}
|
return &ConfigError{Operation: "write_config_file", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
211
internal/services/database_service.go
Normal file
211
internal/services/database_service.go
Normal file
@@ -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()
|
||||||
|
}
|
@@ -5,8 +5,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
@@ -16,32 +14,8 @@ import (
|
|||||||
_ "modernc.org/sqlite" // SQLite driver
|
_ "modernc.org/sqlite" // SQLite driver
|
||||||
)
|
)
|
||||||
|
|
||||||
// SQL constants for database operations
|
// SQL constants for document operations
|
||||||
const (
|
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
|
// Document operations
|
||||||
sqlGetDocumentByID = `
|
sqlGetDocumentByID = `
|
||||||
@@ -95,107 +69,31 @@ SELECT id FROM documents WHERE is_deleted = 0 ORDER BY id LIMIT 1`
|
|||||||
|
|
||||||
// DocumentService provides document management functionality
|
// DocumentService provides document management functionality
|
||||||
type DocumentService struct {
|
type DocumentService struct {
|
||||||
configService *ConfigService
|
databaseService *DatabaseService
|
||||||
logger *log.LoggerService
|
logger *log.LoggerService
|
||||||
db *sql.DB
|
mu sync.RWMutex
|
||||||
mu sync.RWMutex
|
ctx context.Context
|
||||||
ctx context.Context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDocumentService creates a new document service
|
// 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 {
|
if logger == nil {
|
||||||
logger = log.New()
|
logger = log.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DocumentService{
|
return &DocumentService{
|
||||||
configService: configService,
|
databaseService: databaseService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStartup initializes the service when the application starts
|
// OnStartup initializes the service when the application starts
|
||||||
func (ds *DocumentService) OnStartup(ctx context.Context, _ application.ServiceOptions) error {
|
func (ds *DocumentService) OnStartup(ctx context.Context, _ application.ServiceOptions) error {
|
||||||
ds.ctx = ctx
|
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
|
// Ensure default document exists
|
||||||
if err := ds.ensureDefaultDocument(); err != nil {
|
if err := ds.ensureDefaultDocument(); err != nil {
|
||||||
return fmt.Errorf("failed to ensure default document: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +101,8 @@ func (ds *DocumentService) createIndexes() error {
|
|||||||
func (ds *DocumentService) ensureDefaultDocument() error {
|
func (ds *DocumentService) ensureDefaultDocument() error {
|
||||||
// Check if any document exists
|
// Check if any document exists
|
||||||
var count int
|
var count int
|
||||||
err := ds.db.QueryRow(sqlCountDocuments).Scan(&count)
|
db := ds.databaseService.GetDB()
|
||||||
|
err := db.QueryRow(sqlCountDocuments).Scan(&count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -224,7 +123,8 @@ func (ds *DocumentService) GetDocumentByID(id int64) (*models.Document, error) {
|
|||||||
|
|
||||||
var doc models.Document
|
var doc models.Document
|
||||||
var isDeletedInt int
|
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)
|
err := row.Scan(&doc.ID, &doc.Title, &doc.Content, &doc.CreatedAt, &doc.UpdatedAt, &isDeletedInt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
@@ -252,7 +152,8 @@ func (ds *DocumentService) CreateDocument(title string) (*models.Document, error
|
|||||||
IsDeleted: false,
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create document: %w", err)
|
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()
|
ds.mu.Lock()
|
||||||
defer ds.mu.Unlock()
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update document content: %w", err)
|
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()
|
ds.mu.Lock()
|
||||||
defer ds.mu.Unlock()
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update document title: %w", err)
|
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")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to mark document as deleted: %w", err)
|
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()
|
ds.mu.Lock()
|
||||||
defer ds.mu.Unlock()
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to restore document: %w", err)
|
return fmt.Errorf("failed to restore document: %w", err)
|
||||||
}
|
}
|
||||||
@@ -326,7 +231,8 @@ func (ds *DocumentService) ListAllDocumentsMeta() ([]*models.Document, error) {
|
|||||||
ds.mu.RLock()
|
ds.mu.RLock()
|
||||||
defer ds.mu.RUnlock()
|
defer ds.mu.RUnlock()
|
||||||
|
|
||||||
rows, err := ds.db.Query(sqlListAllDocumentsMeta)
|
db := ds.databaseService.GetDB()
|
||||||
|
rows, err := db.Query(sqlListAllDocumentsMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list document meta: %w", err)
|
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()
|
ds.mu.RLock()
|
||||||
defer ds.mu.RUnlock()
|
defer ds.mu.RUnlock()
|
||||||
|
|
||||||
rows, err := ds.db.Query(sqlListDeletedDocumentsMeta)
|
db := ds.databaseService.GetDB()
|
||||||
|
rows, err := db.Query(sqlListDeletedDocumentsMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list deleted document meta: %w", err)
|
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()
|
ds.mu.RLock()
|
||||||
defer ds.mu.RUnlock()
|
defer ds.mu.RUnlock()
|
||||||
|
|
||||||
|
db := ds.databaseService.GetDB()
|
||||||
var id int64
|
var id int64
|
||||||
err := ds.db.QueryRow(sqlGetFirstDocumentID).Scan(&id)
|
err := db.QueryRow(sqlGetFirstDocumentID).Scan(&id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return 0, nil // No documents exist
|
return 0, nil // No documents exist
|
||||||
@@ -386,22 +294,3 @@ func (ds *DocumentService) GetFirstDocumentID() (int64, error) {
|
|||||||
}
|
}
|
||||||
return id, nil
|
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()
|
|
||||||
}
|
|
||||||
|
@@ -2,33 +2,51 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
|
|
||||||
jsonparser "github.com/knadh/koanf/parsers/json"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
"github.com/knadh/koanf/providers/file"
|
|
||||||
"github.com/knadh/koanf/providers/structs"
|
|
||||||
"github.com/knadh/koanf/v2"
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
"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 扩展管理服务
|
// ExtensionService 扩展管理服务
|
||||||
type ExtensionService struct {
|
type ExtensionService struct {
|
||||||
koanf *koanf.Koanf
|
databaseService *DatabaseService
|
||||||
logger *log.LoggerService
|
logger *log.LoggerService
|
||||||
pathManager *PathManager
|
|
||||||
fileProvider *file.File
|
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
|
|
||||||
// 配置迁移服务
|
|
||||||
migrationService *ConfigMigrationService[*models.ExtensionSettings]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtensionError 扩展错误
|
// ExtensionError 扩展错误
|
||||||
@@ -55,127 +73,120 @@ func (e *ExtensionError) Is(target error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewExtensionService 创建扩展服务实例
|
// NewExtensionService 创建扩展服务实例
|
||||||
func NewExtensionService(logger *log.LoggerService, pathManager *PathManager) *ExtensionService {
|
func NewExtensionService(databaseService *DatabaseService, logger *log.LoggerService) *ExtensionService {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = log.New()
|
logger = log.New()
|
||||||
}
|
}
|
||||||
if pathManager == nil {
|
|
||||||
pathManager = NewPathManager()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
k := koanf.New(".")
|
|
||||||
|
|
||||||
migrationService := NewExtensionMigrationService(logger, pathManager)
|
|
||||||
|
|
||||||
service := &ExtensionService{
|
service := &ExtensionService{
|
||||||
koanf: k,
|
databaseService: databaseService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
pathManager: pathManager,
|
ctx: ctx,
|
||||||
ctx: ctx,
|
cancel: cancel,
|
||||||
cancel: cancel,
|
|
||||||
migrationService: migrationService,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步初始化
|
|
||||||
go service.initialize()
|
|
||||||
|
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize 初始化配置
|
// initialize 初始化配置
|
||||||
func (es *ExtensionService) initialize() {
|
func (es *ExtensionService) initialize() {
|
||||||
es.initOnce.Do(func() {
|
es.initOnce.Do(func() {
|
||||||
if err := es.initConfig(); err != nil {
|
if err := es.initDatabase(); err != nil {
|
||||||
es.logger.Error("failed to initialize extension config", "error", err)
|
es.logger.Error("failed to initialize extension database", "error", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaults 设置默认值
|
// initDatabase 初始化数据库数据
|
||||||
func (es *ExtensionService) setDefaults() error {
|
func (es *ExtensionService) initDatabase() 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 {
|
|
||||||
es.mu.Lock()
|
es.mu.Lock()
|
||||||
defer es.mu.Unlock()
|
defer es.mu.Unlock()
|
||||||
|
|
||||||
// 检查配置文件是否存在
|
// 检查是否已有扩展数据
|
||||||
configPath := es.pathManager.GetExtensionsPath()
|
db := es.databaseService.GetDB()
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
if db == nil {
|
||||||
return es.createDefaultConfig()
|
return &ExtensionError{"get_database", "", fmt.Errorf("database connection is nil")}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置文件存在,先加载现有配置
|
var count int
|
||||||
es.fileProvider = file.Provider(configPath)
|
err := db.QueryRow("SELECT COUNT(*) FROM extensions").Scan(&count)
|
||||||
if err := es.koanf.Load(es.fileProvider, jsonparser.Parser()); err != nil {
|
if err != nil {
|
||||||
return &ExtensionError{"load_config_file", "", err}
|
return &ExtensionError{"check_extensions_count", "", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查并执行配置迁移
|
es.logger.Info("Extension database check", "existing_count", count)
|
||||||
if es.migrationService != nil {
|
|
||||||
result, err := es.migrationService.MigrateConfig(es.koanf)
|
// 如果没有数据,插入默认配置
|
||||||
|
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 {
|
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 {
|
_, err = db.Exec(sqlInsertExtension,
|
||||||
// 迁移完成且配置已更新,重新创建文件提供器以监听新文件
|
string(ext.ID),
|
||||||
es.fileProvider = file.Provider(configPath)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDefaultConfig 创建默认配置文件
|
// OnStartup 启动时调用
|
||||||
func (es *ExtensionService) createDefaultConfig() error {
|
func (es *ExtensionService) OnStartup(ctx context.Context, _ application.ServiceOptions) error {
|
||||||
if err := es.pathManager.EnsureConfigDir(); err != nil {
|
es.ctx = ctx
|
||||||
return &ExtensionError{"create_config_dir", "", err}
|
es.logger.Info("Extension service starting up")
|
||||||
}
|
|
||||||
|
|
||||||
if err := es.setDefaults(); err != nil {
|
// 初始化数据库
|
||||||
return err
|
var initErr error
|
||||||
}
|
es.initOnce.Do(func() {
|
||||||
|
es.logger.Info("Initializing extension database...")
|
||||||
configBytes, err := es.koanf.Marshal(jsonparser.Parser())
|
if err := es.initDatabase(); err != nil {
|
||||||
if err != nil {
|
es.logger.Error("failed to initialize extension database", "error", err)
|
||||||
return &ExtensionError{"marshal_config", "", err}
|
initErr = err
|
||||||
}
|
} else {
|
||||||
|
es.logger.Info("Extension database initialized successfully")
|
||||||
if err := os.WriteFile(es.pathManager.GetExtensionsPath(), configBytes, 0644); err != nil {
|
}
|
||||||
return &ExtensionError{"write_config", "", err}
|
})
|
||||||
}
|
return initErr
|
||||||
|
|
||||||
// 创建文件提供器
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllExtensions 获取所有扩展配置
|
// GetAllExtensions 获取所有扩展配置
|
||||||
@@ -183,11 +194,31 @@ func (es *ExtensionService) GetAllExtensions() ([]models.Extension, error) {
|
|||||||
es.mu.RLock()
|
es.mu.RLock()
|
||||||
defer es.mu.RUnlock()
|
defer es.mu.RUnlock()
|
||||||
|
|
||||||
var settings models.ExtensionSettings
|
db := es.databaseService.GetDB()
|
||||||
if err := es.koanf.Unmarshal("", &settings); err != nil {
|
rows, err := db.Query(sqlGetAllExtensions)
|
||||||
return nil, &ExtensionError{"unmarshal_config", "", err}
|
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 更新扩展启用状态
|
// UpdateExtensionEnabled 更新扩展启用状态
|
||||||
@@ -200,25 +231,28 @@ func (es *ExtensionService) UpdateExtensionState(id models.ExtensionID, enabled
|
|||||||
es.mu.Lock()
|
es.mu.Lock()
|
||||||
defer es.mu.Unlock()
|
defer es.mu.Unlock()
|
||||||
|
|
||||||
// 获取当前配置
|
db := es.databaseService.GetDB()
|
||||||
var settings models.ExtensionSettings
|
var configJSON []byte
|
||||||
if err := es.koanf.Unmarshal("", &settings); err != nil {
|
var err error
|
||||||
return &ExtensionError{"unmarshal_config", string(id), err}
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新扩展状态
|
_, err = db.Exec(sqlUpdateExtension, enabled, string(configJSON), time.Now(), string(id))
|
||||||
if !settings.UpdateExtension(id, enabled, config) {
|
if err != nil {
|
||||||
return &ExtensionError{"extension_not_found", string(id), nil}
|
return &ExtensionError{"update_extension", string(id), err}
|
||||||
}
|
|
||||||
|
|
||||||
// 重新加载到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}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
es.logger.Info("extension state updated", "id", id, "enabled", enabled)
|
es.logger.Info("extension state updated", "id", id, "enabled", enabled)
|
||||||
@@ -242,23 +276,18 @@ func (es *ExtensionService) ResetAllExtensionsToDefault() error {
|
|||||||
es.mu.Lock()
|
es.mu.Lock()
|
||||||
defer es.mu.Unlock()
|
defer es.mu.Unlock()
|
||||||
|
|
||||||
// 加载默认配置
|
// 删除所有现有扩展
|
||||||
defaultSettings := models.NewDefaultExtensionSettings()
|
db := es.databaseService.GetDB()
|
||||||
if err := es.koanf.Load(structs.Provider(defaultSettings, "json"), nil); err != nil {
|
_, err := db.Exec(sqlDeleteAllExtensions)
|
||||||
return &ExtensionError{"load_defaults", "", err}
|
if err != nil {
|
||||||
|
return &ExtensionError{"delete_all_extensions", "", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存到文件
|
// 插入默认扩展配置
|
||||||
if err := es.saveExtensionConfig(); err != nil {
|
if err := es.insertDefaultExtensions(); err != nil {
|
||||||
return &ExtensionError{"save_config", "", err}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
es.logger.Info("all extensions reset to default")
|
es.logger.Info("all extensions reset to default")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnShutdown 关闭服务
|
|
||||||
func (es *ExtensionService) OnShutdown() error {
|
|
||||||
es.cancel()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -4,31 +4,59 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
|
|
||||||
jsonparser "github.com/knadh/koanf/parsers/json"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
"github.com/knadh/koanf/providers/file"
|
|
||||||
"github.com/knadh/koanf/providers/structs"
|
|
||||||
"github.com/knadh/koanf/v2"
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
"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 快捷键管理服务
|
// KeyBindingService 快捷键管理服务
|
||||||
type KeyBindingService struct {
|
type KeyBindingService struct {
|
||||||
koanf *koanf.Koanf
|
databaseService *DatabaseService
|
||||||
logger *log.LoggerService
|
logger *log.LoggerService
|
||||||
pathManager *PathManager
|
|
||||||
fileProvider *file.File
|
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
|
|
||||||
// 配置迁移服务
|
|
||||||
migrationService *ConfigMigrationService[*models.KeyBindingConfig]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyBindingError 快捷键错误
|
// KeyBindingError 快捷键错误
|
||||||
@@ -55,138 +83,145 @@ func (e *KeyBindingError) Is(target error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyBindingService 创建快捷键服务实例
|
// NewKeyBindingService 创建快捷键服务实例
|
||||||
func NewKeyBindingService(logger *log.LoggerService, pathManager *PathManager) *KeyBindingService {
|
func NewKeyBindingService(databaseService *DatabaseService, logger *log.LoggerService) *KeyBindingService {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = log.New()
|
logger = log.New()
|
||||||
}
|
}
|
||||||
if pathManager == nil {
|
|
||||||
pathManager = NewPathManager()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
k := koanf.New(".")
|
|
||||||
|
|
||||||
migrationService := NewKeyBindingMigrationService(logger, pathManager)
|
|
||||||
|
|
||||||
service := &KeyBindingService{
|
service := &KeyBindingService{
|
||||||
koanf: k,
|
databaseService: databaseService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
pathManager: pathManager,
|
ctx: ctx,
|
||||||
ctx: ctx,
|
cancel: cancel,
|
||||||
cancel: cancel,
|
|
||||||
migrationService: migrationService,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步初始化
|
|
||||||
go service.initialize()
|
|
||||||
|
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize 初始化配置
|
// initDatabase 初始化数据库数据
|
||||||
func (kbs *KeyBindingService) initialize() {
|
func (kbs *KeyBindingService) initDatabase() error {
|
||||||
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 {
|
|
||||||
kbs.mu.Lock()
|
kbs.mu.Lock()
|
||||||
defer kbs.mu.Unlock()
|
defer kbs.mu.Unlock()
|
||||||
|
|
||||||
// 检查配置文件是否存在
|
// 检查是否已有快捷键数据
|
||||||
configPath := kbs.pathManager.GetKeybindsPath()
|
db := kbs.databaseService.GetDB()
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
if db == nil {
|
||||||
return kbs.createDefaultConfig()
|
return &KeyBindingError{"get_database", "", fmt.Errorf("database connection is nil")}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置文件存在,先加载现有配置
|
var count int
|
||||||
kbs.fileProvider = file.Provider(configPath)
|
err := db.QueryRow("SELECT COUNT(*) FROM key_bindings").Scan(&count)
|
||||||
if err := kbs.koanf.Load(kbs.fileProvider, jsonparser.Parser()); err != nil {
|
if err != nil {
|
||||||
return &KeyBindingError{"load_config_file", "", err}
|
return &KeyBindingError{"check_keybindings_count", "", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查并执行配置迁移
|
kbs.logger.Info("KeyBinding database check", "existing_count", count)
|
||||||
if kbs.migrationService != nil {
|
|
||||||
result, err := kbs.migrationService.MigrateConfig(kbs.koanf)
|
|
||||||
if err != nil {
|
|
||||||
return &KeyBindingError{"migrate_config", "", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Migrated && result.ConfigUpdated {
|
// 如果没有数据,插入默认配置
|
||||||
// 迁移完成且配置已更新,重新创建文件提供器以监听新文件
|
if count == 0 {
|
||||||
kbs.fileProvider = file.Provider(configPath)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDefaultConfig 创建默认配置文件
|
// insertDefaultKeyBindings 插入默认快捷键配置
|
||||||
func (kbs *KeyBindingService) createDefaultConfig() error {
|
func (kbs *KeyBindingService) insertDefaultKeyBindings() error {
|
||||||
if err := kbs.pathManager.EnsureConfigDir(); err != nil {
|
defaultConfig := models.NewDefaultKeyBindingConfig()
|
||||||
return &KeyBindingError{"create_config_dir", "", err}
|
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 {
|
kbs.logger.Info("Completed inserting all default key bindings")
|
||||||
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
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyBindingConfig 获取完整快捷键配置
|
// GetKeyBindingConfig 获取完整快捷键配置
|
||||||
func (kbs *KeyBindingService) GetKeyBindingConfig() (*models.KeyBindingConfig, error) {
|
func (kbs *KeyBindingService) GetKeyBindingConfig() (*models.KeyBindingConfig, error) {
|
||||||
kbs.mu.RLock()
|
keyBindings, err := kbs.GetAllKeyBindings()
|
||||||
defer kbs.mu.RUnlock()
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
var config models.KeyBindingConfig
|
|
||||||
if err := kbs.koanf.Unmarshal("", &config); err != nil {
|
|
||||||
return nil, &KeyBindingError{"unmarshal_config", "", err}
|
|
||||||
}
|
}
|
||||||
return &config, nil
|
|
||||||
|
config := &models.KeyBindingConfig{
|
||||||
|
KeyBindings: keyBindings,
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllKeyBindings 获取所有快捷键配置
|
// GetAllKeyBindings 获取所有快捷键配置
|
||||||
func (kbs *KeyBindingService) GetAllKeyBindings() ([]models.KeyBinding, error) {
|
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 {
|
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 关闭服务
|
// OnStartup 启动时调用
|
||||||
func (kbs *KeyBindingService) OnShutdown() error {
|
func (kbs *KeyBindingService) OnStartup(ctx context.Context, _ application.ServiceOptions) error {
|
||||||
kbs.cancel()
|
kbs.ctx = ctx
|
||||||
return nil
|
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
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
|
||||||
}
|
|
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
// ServiceManager 服务管理器,负责协调各个服务
|
// ServiceManager 服务管理器,负责协调各个服务
|
||||||
type ServiceManager struct {
|
type ServiceManager struct {
|
||||||
pathManager *PathManager
|
|
||||||
configService *ConfigService
|
configService *ConfigService
|
||||||
|
databaseService *DatabaseService
|
||||||
documentService *DocumentService
|
documentService *DocumentService
|
||||||
migrationService *MigrationService
|
migrationService *MigrationService
|
||||||
systemService *SystemService
|
systemService *SystemService
|
||||||
@@ -30,17 +30,17 @@ func NewServiceManager() *ServiceManager {
|
|||||||
// 初始化日志服务
|
// 初始化日志服务
|
||||||
logger := log.New()
|
logger := log.New()
|
||||||
|
|
||||||
// 初始化路径管理器
|
|
||||||
pathManager := NewPathManager()
|
|
||||||
|
|
||||||
// 初始化配置服务
|
// 初始化配置服务
|
||||||
configService := NewConfigService(logger, pathManager)
|
configService := NewConfigService(logger)
|
||||||
|
|
||||||
|
// 初始化数据库服务
|
||||||
|
databaseService := NewDatabaseService(configService, logger)
|
||||||
|
|
||||||
// 初始化迁移服务
|
// 初始化迁移服务
|
||||||
migrationService := NewMigrationService(logger)
|
migrationService := NewMigrationService(logger)
|
||||||
|
|
||||||
// 初始化文档服务
|
// 初始化文档服务
|
||||||
documentService := NewDocumentService(configService, logger)
|
documentService := NewDocumentService(databaseService, logger)
|
||||||
|
|
||||||
// 初始化系统服务
|
// 初始化系统服务
|
||||||
systemService := NewSystemService(logger)
|
systemService := NewSystemService(logger)
|
||||||
@@ -55,10 +55,10 @@ func NewServiceManager() *ServiceManager {
|
|||||||
trayService := NewTrayService(logger, configService)
|
trayService := NewTrayService(logger, configService)
|
||||||
|
|
||||||
// 初始化快捷键服务
|
// 初始化快捷键服务
|
||||||
keyBindingService := NewKeyBindingService(logger, pathManager)
|
keyBindingService := NewKeyBindingService(databaseService, logger)
|
||||||
|
|
||||||
// 初始化扩展服务
|
// 初始化扩展服务
|
||||||
extensionService := NewExtensionService(logger, pathManager)
|
extensionService := NewExtensionService(databaseService, logger)
|
||||||
|
|
||||||
// 初始化开机启动服务
|
// 初始化开机启动服务
|
||||||
startupService := NewStartupService(configService, logger)
|
startupService := NewStartupService(configService, logger)
|
||||||
@@ -82,7 +82,7 @@ func NewServiceManager() *ServiceManager {
|
|||||||
|
|
||||||
// 设置数据路径变更监听,处理配置重置和路径变更
|
// 设置数据路径变更监听,处理配置重置和路径变更
|
||||||
err = configService.SetDataPathChangeCallback(func() error {
|
err = configService.SetDataPathChangeCallback(func() error {
|
||||||
return documentService.OnDataPathChanged()
|
return databaseService.OnDataPathChanged()
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -90,6 +90,7 @@ func NewServiceManager() *ServiceManager {
|
|||||||
|
|
||||||
return &ServiceManager{
|
return &ServiceManager{
|
||||||
configService: configService,
|
configService: configService,
|
||||||
|
databaseService: databaseService,
|
||||||
documentService: documentService,
|
documentService: documentService,
|
||||||
migrationService: migrationService,
|
migrationService: migrationService,
|
||||||
systemService: systemService,
|
systemService: systemService,
|
||||||
@@ -106,17 +107,19 @@ func NewServiceManager() *ServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetServices 获取所有wails服务列表
|
// GetServices 获取所有wails服务列表
|
||||||
|
// 注意:服务启动顺序很重要,DatabaseService 必须在依赖数据库的服务之前启动
|
||||||
func (sm *ServiceManager) GetServices() []application.Service {
|
func (sm *ServiceManager) GetServices() []application.Service {
|
||||||
services := []application.Service{
|
services := []application.Service{
|
||||||
application.NewService(sm.configService),
|
application.NewService(sm.configService),
|
||||||
|
application.NewService(sm.databaseService),
|
||||||
application.NewService(sm.documentService),
|
application.NewService(sm.documentService),
|
||||||
|
application.NewService(sm.keyBindingService),
|
||||||
|
application.NewService(sm.extensionService),
|
||||||
application.NewService(sm.migrationService),
|
application.NewService(sm.migrationService),
|
||||||
application.NewService(sm.systemService),
|
application.NewService(sm.systemService),
|
||||||
application.NewService(sm.hotkeyService),
|
application.NewService(sm.hotkeyService),
|
||||||
application.NewService(sm.dialogService),
|
application.NewService(sm.dialogService),
|
||||||
application.NewService(sm.trayService),
|
application.NewService(sm.trayService),
|
||||||
application.NewService(sm.keyBindingService),
|
|
||||||
application.NewService(sm.extensionService),
|
|
||||||
application.NewService(sm.startupService),
|
application.NewService(sm.startupService),
|
||||||
application.NewService(sm.selfUpdateService),
|
application.NewService(sm.selfUpdateService),
|
||||||
application.NewService(sm.translationService),
|
application.NewService(sm.translationService),
|
||||||
@@ -173,3 +176,8 @@ func (sm *ServiceManager) GetSelfUpdateService() *SelfUpdateService {
|
|||||||
func (sm *ServiceManager) GetTranslationService() *TranslationService {
|
func (sm *ServiceManager) GetTranslationService() *TranslationService {
|
||||||
return sm.translationService
|
return sm.translationService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDatabaseService 获取数据库服务实例
|
||||||
|
func (sm *ServiceManager) GetDatabaseService() *DatabaseService {
|
||||||
|
return sm.databaseService
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user