mirror of https://github.com/Wox-launcher/Wox
refactor(setting): refactor settings management to use a unified store interface and migrate from JSON to SQLite database
- Introduced a new `Store` struct for managing Wox settings and plugin settings with GORM. - Implemented migration logic to transfer settings from old JSON files to the new SQLite database. - Updated the `Value` type to handle lazy loading and persistence of setting values. - Refactored existing settings access in the UI and other components to utilize the new store. - Added migration tests to ensure data integrity during the transition. - Enhanced logging for better traceability during migration and settings updates.
This commit is contained in:
parent
247c970ed2
commit
405346cc09
|
@ -69,6 +69,9 @@ call_lefthook()
|
|||
elif mise exec -- lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
mise exec -- lefthook "$@"
|
||||
elif devbox run lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
devbox run lefthook "$@"
|
||||
else
|
||||
echo "Can't find lefthook in PATH"
|
||||
fi
|
||||
|
|
|
@ -69,6 +69,9 @@ call_lefthook()
|
|||
elif mise exec -- lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
mise exec -- lefthook "$@"
|
||||
elif devbox run lefthook -h >/dev/null 2>&1
|
||||
then
|
||||
devbox run lefthook "$@"
|
||||
else
|
||||
echo "Can't find lefthook in PATH"
|
||||
fi
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"wox/common"
|
||||
"wox/util"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
|
@ -12,69 +11,19 @@ import (
|
|||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
once sync.Once
|
||||
)
|
||||
var db *gorm.DB
|
||||
|
||||
const dbFileName = "wox.db"
|
||||
|
||||
// Models
|
||||
|
||||
type Setting struct {
|
||||
type WoxSetting struct {
|
||||
Key string `gorm:"primaryKey"`
|
||||
Value string
|
||||
}
|
||||
|
||||
type Hotkey struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
Hotkey string `gorm:"unique"`
|
||||
Query string
|
||||
IsSilentExecution bool
|
||||
}
|
||||
|
||||
type QueryShortcut struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
Shortcut string `gorm:"unique"`
|
||||
Query string
|
||||
}
|
||||
|
||||
type AIProvider struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
Name common.ProviderName
|
||||
ApiKey string
|
||||
Host string
|
||||
}
|
||||
|
||||
type QueryHistory struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
Query string
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
type FavoriteResult struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
PluginID string `gorm:"uniqueIndex:idx_fav"`
|
||||
Title string `gorm:"uniqueIndex:idx_fav"`
|
||||
Subtitle string `gorm:"uniqueIndex:idx_fav"`
|
||||
}
|
||||
|
||||
type PluginSetting struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
PluginID string `gorm:"uniqueIndex:idx_plugin_setting"`
|
||||
Key string `gorm:"uniqueIndex:idx_plugin_setting"`
|
||||
PluginID string `gorm:"primaryKey"`
|
||||
Key string `gorm:"primaryKey"`
|
||||
Value string
|
||||
}
|
||||
|
||||
type ActionedResult struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
PluginID string
|
||||
Title string
|
||||
Subtitle string
|
||||
Timestamp int64
|
||||
Query string
|
||||
}
|
||||
|
||||
type Oplog struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
EntityType string
|
||||
|
@ -86,47 +35,30 @@ type Oplog struct {
|
|||
SyncedToCloud bool `gorm:"default:false"`
|
||||
}
|
||||
|
||||
// Init initializes the database connection and migrates the schema.
|
||||
func Init() error {
|
||||
func Init(ctx context.Context) error {
|
||||
dbPath := filepath.Join(util.GetLocation().GetUserDataDirectory(), "wox.db")
|
||||
|
||||
var err error
|
||||
once.Do(func() {
|
||||
dbPath := filepath.Join(util.GetLocation().GetUserDataDirectory(), dbFileName)
|
||||
|
||||
db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to connect to database: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// AutoMigrate will create tables, columns, and indexes, but not delete them.
|
||||
err = migrateSchema()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to migrate database schema: %w", err)
|
||||
return
|
||||
}
|
||||
db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.AutoMigrate(
|
||||
&WoxSetting{},
|
||||
&PluginSetting{},
|
||||
&Oplog{},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to migrate database schema: %w", err)
|
||||
}
|
||||
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("database initialized at %s", dbPath))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDB returns the GORM database instance.
|
||||
func GetDB() *gorm.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
// migrateSchema runs GORM's AutoMigrate function.
|
||||
func migrateSchema() error {
|
||||
return db.AutoMigrate(
|
||||
&Setting{},
|
||||
&Hotkey{},
|
||||
&QueryShortcut{},
|
||||
&AIProvider{},
|
||||
&QueryHistory{},
|
||||
&FavoriteResult{},
|
||||
&PluginSetting{},
|
||||
&ActionedResult{},
|
||||
&Oplog{},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"wox/database"
|
||||
"wox/migration"
|
||||
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -51,11 +54,21 @@ func main() {
|
|||
|
||||
ctx := util.NewTraceContext()
|
||||
util.GetLogger().Info(ctx, "------------------------------")
|
||||
util.GetLogger().Info(ctx, "Wox starting")
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("Wox starting: %s", updater.CURRENT_VERSION))
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("golang version: %s", strings.ReplaceAll(runtime.Version(), "go", "")))
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("wox data location: %s", util.GetLocation().GetWoxDataDirectory()))
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("user data location: %s", util.GetLocation().GetUserDataDirectory()))
|
||||
|
||||
if err := database.Init(ctx); err != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("failed to initialize database: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := migration.Run(ctx); err != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("failed to run migration: %s", err.Error()))
|
||||
// In some cases, we might want to exit if migration fails, but for now we just log it.
|
||||
}
|
||||
|
||||
serverPort := 34987
|
||||
if util.IsProd() {
|
||||
availablePort, portErr := util.GetAvailableTcpPort(ctx)
|
||||
|
@ -120,11 +133,15 @@ func main() {
|
|||
return
|
||||
}
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
util.UpdateHTTPProxy(ctx, woxSetting.HttpProxyUrl.Get())
|
||||
|
||||
langErr := i18n.GetI18nManager().UpdateLang(ctx, woxSetting.LangCode)
|
||||
// update proxy
|
||||
if woxSetting.HttpProxyEnabled.Get() {
|
||||
util.UpdateHTTPProxy(ctx, woxSetting.HttpProxyUrl.Get())
|
||||
}
|
||||
|
||||
langErr := i18n.GetI18nManager().UpdateLang(ctx, woxSetting.LangCode.Get())
|
||||
if langErr != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("failed to initialize lang(%s): %s", woxSetting.LangCode, langErr.Error()))
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("failed to initialize lang(%s): %s", woxSetting.LangCode.Get(), langErr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -134,7 +151,7 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
if woxSetting.ShowTray {
|
||||
if woxSetting.ShowTray.Get() {
|
||||
ui.GetUIManager().ShowTray()
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"wox/common"
|
||||
"wox/database"
|
||||
"wox/i18n"
|
||||
"wox/setting"
|
||||
"wox/util"
|
||||
"wox/util/locale"
|
||||
)
|
||||
|
||||
// This file contains the logic for a one-time migration from the old JSON-based settings
|
||||
// to the new SQLite database. It is designed to be self-contained.
|
||||
|
||||
// oldPlatformSettingValue mirrors the old PlatformSettingValue[T] generic struct.
|
||||
// We define it locally to avoid dependencies on the old setting structure.
|
||||
type oldPlatformSettingValue[T any] struct {
|
||||
WinValue T `json:"WinValue"`
|
||||
MacValue T `json:"MacValue"`
|
||||
LinuxValue T `json:"LinuxValue"`
|
||||
}
|
||||
|
||||
func (p *oldPlatformSettingValue[T]) Get() T {
|
||||
// This is a simplified Get method for migration purposes.
|
||||
// It doesn't represent the full platform-specific logic of the original.
|
||||
// It is kept here for reference but should not be used for migration.
|
||||
// The entire object should be marshalled to JSON instead.
|
||||
if util.IsMacOS() {
|
||||
return p.MacValue
|
||||
}
|
||||
if util.IsWindows() {
|
||||
return p.WinValue
|
||||
}
|
||||
if util.IsLinux() {
|
||||
return p.LinuxValue
|
||||
}
|
||||
|
||||
// Default to Mac value as a fallback
|
||||
return p.MacValue
|
||||
}
|
||||
|
||||
// oldWoxSetting is a snapshot of the old WoxSetting struct.
|
||||
type oldWoxSetting struct {
|
||||
EnableAutostart oldPlatformSettingValue[bool]
|
||||
MainHotkey oldPlatformSettingValue[string]
|
||||
SelectionHotkey oldPlatformSettingValue[string]
|
||||
UsePinYin bool
|
||||
SwitchInputMethodABC bool
|
||||
HideOnStart bool
|
||||
HideOnLostFocus bool
|
||||
ShowTray bool
|
||||
LangCode i18n.LangCode
|
||||
QueryHotkeys oldPlatformSettingValue[[]oldQueryHotkey]
|
||||
QueryShortcuts []oldQueryShortcut
|
||||
LastQueryMode string
|
||||
ShowPosition string
|
||||
AIProviders []oldAIProvider
|
||||
EnableAutoBackup bool
|
||||
EnableAutoUpdate bool
|
||||
CustomPythonPath oldPlatformSettingValue[string]
|
||||
CustomNodejsPath oldPlatformSettingValue[string]
|
||||
HttpProxyEnabled oldPlatformSettingValue[bool]
|
||||
HttpProxyUrl oldPlatformSettingValue[string]
|
||||
AppWidth int
|
||||
MaxResultCount int
|
||||
ThemeId string
|
||||
LastWindowX int
|
||||
LastWindowY int
|
||||
}
|
||||
|
||||
// oldQueryHotkey is a snapshot of the old QueryHotkey struct.
|
||||
type oldQueryHotkey struct {
|
||||
Hotkey string
|
||||
Query string
|
||||
IsSilentExecution bool
|
||||
}
|
||||
|
||||
// oldQueryShortcut is a snapshot of the old QueryShortcut struct.
|
||||
type oldQueryShortcut struct {
|
||||
Shortcut string
|
||||
Query string
|
||||
}
|
||||
|
||||
// oldAIProvider is a snapshot of the old AIProvider struct.
|
||||
type oldAIProvider struct {
|
||||
Name common.ProviderName
|
||||
ApiKey string
|
||||
Host string
|
||||
}
|
||||
|
||||
// oldQueryHistory is a snapshot of the old QueryHistory struct.
|
||||
type oldQueryHistory struct {
|
||||
Query common.PlainQuery
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
// oldWoxAppData is a snapshot of the old WoxAppData struct.
|
||||
type oldWoxAppData struct {
|
||||
QueryHistories []oldQueryHistory
|
||||
FavoriteResults *util.HashMap[string, bool]
|
||||
}
|
||||
|
||||
func getOldDefaultWoxSetting() oldWoxSetting {
|
||||
usePinYin := false
|
||||
langCode := i18n.LangCodeEnUs
|
||||
switchInputMethodABC := false
|
||||
if locale.IsZhCN() {
|
||||
usePinYin = true
|
||||
switchInputMethodABC = true
|
||||
langCode = i18n.LangCodeZhCn
|
||||
}
|
||||
|
||||
return oldWoxSetting{
|
||||
MainHotkey: oldPlatformSettingValue[string]{WinValue: "alt+space", MacValue: "command+space", LinuxValue: "ctrl+ctrl"},
|
||||
SelectionHotkey: oldPlatformSettingValue[string]{WinValue: "win+alt+space", MacValue: "command+option+space", LinuxValue: "ctrl+shift+j"},
|
||||
UsePinYin: usePinYin,
|
||||
SwitchInputMethodABC: switchInputMethodABC,
|
||||
ShowTray: true,
|
||||
HideOnLostFocus: true,
|
||||
LangCode: langCode,
|
||||
LastQueryMode: "empty",
|
||||
ShowPosition: "mouse_screen",
|
||||
AppWidth: 800,
|
||||
MaxResultCount: 10,
|
||||
ThemeId: "e4006bd3-6bfe-4020-8d1c-4c32a8e567e5",
|
||||
EnableAutostart: oldPlatformSettingValue[bool]{WinValue: false, MacValue: false, LinuxValue: false},
|
||||
HttpProxyEnabled: oldPlatformSettingValue[bool]{WinValue: false, MacValue: false, LinuxValue: false},
|
||||
HttpProxyUrl: oldPlatformSettingValue[string]{WinValue: "", MacValue: "", LinuxValue: ""},
|
||||
CustomPythonPath: oldPlatformSettingValue[string]{WinValue: "", MacValue: "", LinuxValue: ""},
|
||||
CustomNodejsPath: oldPlatformSettingValue[string]{WinValue: "", MacValue: "", LinuxValue: ""},
|
||||
EnableAutoBackup: true,
|
||||
EnableAutoUpdate: true,
|
||||
LastWindowX: -1,
|
||||
LastWindowY: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func Run(ctx context.Context) error {
|
||||
// if database exists, no need to migrate
|
||||
if _, err := os.Stat(util.GetLocation().GetUserDataDirectory() + "wox.db"); err == nil {
|
||||
util.GetLogger().Info(ctx, "database found, skip for migrate.")
|
||||
return nil
|
||||
}
|
||||
|
||||
util.GetLogger().Info(ctx, "database not found. Checking for old configuration files to migrate.")
|
||||
|
||||
oldSettingPath := util.GetLocation().GetWoxSettingPath()
|
||||
oldAppDataPath := util.GetLocation().GetWoxAppDataPath()
|
||||
|
||||
_, settingStatErr := os.Stat(oldSettingPath)
|
||||
_, appDataStatErr := os.Stat(oldAppDataPath)
|
||||
|
||||
if os.IsNotExist(settingStatErr) && os.IsNotExist(appDataStatErr) {
|
||||
util.GetLogger().Info(ctx, "no old configuration files found. Skipping migration.")
|
||||
return nil
|
||||
}
|
||||
|
||||
util.GetLogger().Info(ctx, "old configuration files found. Starting migration process.")
|
||||
|
||||
migrateDB := database.GetDB()
|
||||
|
||||
// Load old settings
|
||||
oldSettings := getOldDefaultWoxSetting()
|
||||
if _, err := os.Stat(oldSettingPath); err == nil {
|
||||
fileContent, readErr := os.ReadFile(oldSettingPath)
|
||||
if readErr == nil && len(fileContent) > 0 {
|
||||
if unmarshalErr := json.Unmarshal(fileContent, &oldSettings); unmarshalErr != nil {
|
||||
util.GetLogger().Warn(ctx, fmt.Sprintf("failed to unmarshal old wox.setting.json: %v, will use defaults for migration.", unmarshalErr))
|
||||
} else {
|
||||
util.GetLogger().Info(ctx, "successfully loaded old wox.setting.json for migration.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load old app data
|
||||
var oldAppData oldWoxAppData
|
||||
if oldAppData.QueryHistories == nil {
|
||||
oldAppData.QueryHistories = []oldQueryHistory{}
|
||||
}
|
||||
if _, err := os.Stat(oldAppDataPath); err == nil {
|
||||
fileContent, readErr := os.ReadFile(oldAppDataPath)
|
||||
if readErr == nil && len(fileContent) > 0 {
|
||||
if json.Unmarshal(fileContent, &oldAppData) != nil {
|
||||
util.GetLogger().Warn(ctx, "failed to unmarshal old wox.app.data.json, will use defaults for migration.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx := migrateDB.Begin()
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
panic(r)
|
||||
} else if err := tx.Error; err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
store := setting.NewStore(tx)
|
||||
|
||||
// Migrate simple settings
|
||||
settingsToMigrate := map[string]interface{}{
|
||||
"EnableAutostart": oldSettings.EnableAutostart,
|
||||
"MainHotkey": oldSettings.MainHotkey,
|
||||
"SelectionHotkey": oldSettings.SelectionHotkey,
|
||||
"UsePinYin": oldSettings.UsePinYin,
|
||||
"SwitchInputMethodABC": oldSettings.SwitchInputMethodABC,
|
||||
"HideOnStart": oldSettings.HideOnStart,
|
||||
"HideOnLostFocus": oldSettings.HideOnLostFocus,
|
||||
"ShowTray": oldSettings.ShowTray,
|
||||
"LangCode": oldSettings.LangCode,
|
||||
"LastQueryMode": oldSettings.LastQueryMode,
|
||||
"ShowPosition": oldSettings.ShowPosition,
|
||||
"EnableAutoBackup": oldSettings.EnableAutoBackup,
|
||||
"EnableAutoUpdate": oldSettings.EnableAutoUpdate,
|
||||
"CustomPythonPath": oldSettings.CustomPythonPath,
|
||||
"CustomNodejsPath": oldSettings.CustomNodejsPath,
|
||||
"HttpProxyEnabled": oldSettings.HttpProxyEnabled,
|
||||
"HttpProxyUrl": oldSettings.HttpProxyUrl,
|
||||
"AppWidth": oldSettings.AppWidth,
|
||||
"MaxResultCount": oldSettings.MaxResultCount,
|
||||
"ThemeId": oldSettings.ThemeId,
|
||||
"LastWindowX": oldSettings.LastWindowX,
|
||||
"LastWindowY": oldSettings.LastWindowY,
|
||||
|
||||
"QueryHotkeys": oldSettings.QueryHotkeys,
|
||||
"QueryShortcuts": oldSettings.QueryShortcuts,
|
||||
"AIProviders": oldSettings.AIProviders,
|
||||
}
|
||||
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("migrating %d core settings", len(settingsToMigrate)))
|
||||
for key, value := range settingsToMigrate {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("migrating setting %s", key))
|
||||
if err := store.Set(key, value); err != nil {
|
||||
return fmt.Errorf("failed to migrate setting %s: %w", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate plugin settings
|
||||
pluginDir := util.GetLocation().GetPluginSettingDirectory()
|
||||
dirs, err := os.ReadDir(pluginDir)
|
||||
if err == nil {
|
||||
for _, file := range dirs {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(file.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file.Name(), "wox") {
|
||||
continue
|
||||
}
|
||||
|
||||
pluginId := strings.TrimSuffix(file.Name(), ".json")
|
||||
pluginJsonPath := path.Join(pluginDir, file.Name())
|
||||
if _, err := os.Stat(pluginJsonPath); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(pluginJsonPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var setting struct {
|
||||
Name string `json:"Name"`
|
||||
Settings map[string]string `json:"Settings"`
|
||||
}
|
||||
if err := json.Unmarshal(content, &setting); err != nil {
|
||||
continue
|
||||
}
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("migrating plugin settings for %s (%s)", setting.Name, pluginId))
|
||||
|
||||
counter := 0
|
||||
for key, value := range setting.Settings {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if err := store.SetPluginSetting(pluginId, key, value); err != nil {
|
||||
util.GetLogger().Warn(ctx, fmt.Sprintf("failed to migrate plugin setting %s for %s: %v", key, pluginId, err))
|
||||
continue
|
||||
}
|
||||
counter++
|
||||
}
|
||||
if err := os.Rename(pluginJsonPath, pluginJsonPath+".bak"); err != nil {
|
||||
util.GetLogger().Warn(ctx, fmt.Sprintf("failed to rename old plugin setting file to .bak for %s: %v", pluginId, err))
|
||||
}
|
||||
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("migrated %d plugin settings for %s", counter, setting.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate query history
|
||||
if len(oldAppData.QueryHistories) > 0 {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("migrating %d query histories", len(oldAppData.QueryHistories)))
|
||||
if err := store.Set("QueryHistories", oldAppData.QueryHistories); err != nil {
|
||||
util.GetLogger().Warn(ctx, fmt.Sprintf("failed to migrate query histories: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate favorite results
|
||||
if oldAppData.FavoriteResults != nil {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("migrating %d favorite results", oldAppData.FavoriteResults.Len()))
|
||||
if err := store.Set("FavoriteResults", oldAppData.FavoriteResults); err != nil {
|
||||
util.GetLogger().Warn(ctx, fmt.Sprintf("failed to migrate favorite results: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return fmt.Errorf("failed to commit migration transaction: %w", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(oldSettingPath); err == nil {
|
||||
if err := os.Rename(oldSettingPath, oldSettingPath+".bak"); err != nil {
|
||||
util.GetLogger().Warn(ctx, fmt.Sprintf("Failed to rename old setting file to .bak: %v", err))
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(oldAppDataPath); err == nil {
|
||||
if err := os.Rename(oldAppDataPath, oldAppDataPath+".bak"); err != nil {
|
||||
util.GetLogger().Warn(ctx, fmt.Sprintf("Failed to rename old app data file to .bak: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
util.GetLogger().Info(ctx, "Successfully migrated old configuration to the new database.")
|
||||
return nil
|
||||
}
|
|
@ -179,7 +179,7 @@ func (m *Manager) loadPlugins(ctx context.Context) error {
|
|||
}
|
||||
|
||||
for _, metadata := range metaDataList {
|
||||
if strings.ToUpper(metadata.Metadata.Runtime) != strings.ToUpper(string(host.GetRuntime(newCtx))) {
|
||||
if !strings.EqualFold(metadata.Metadata.Runtime, string(host.GetRuntime(newCtx))) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -281,7 +281,7 @@ func (m *Manager) loadHostPlugin(ctx context.Context, host Host, metadata Metada
|
|||
DevPluginDirectory: metadata.DevPluginDirectory,
|
||||
}
|
||||
instance.API = NewAPI(instance)
|
||||
pluginSetting, settingErr := setting.GetSettingManager().LoadPluginSetting(ctx, metadata.Metadata.Id, metadata.Metadata.Name, metadata.Metadata.SettingDefinitions)
|
||||
pluginSetting, settingErr := setting.GetSettingManager().LoadPluginSetting(ctx, metadata.Metadata.Id, metadata.Metadata.Name, metadata.Metadata.SettingDefinitions.ToMap())
|
||||
if settingErr != nil {
|
||||
instance.API.Log(ctx, LogLevelError, fmt.Errorf("[SYS] failed to load plugin[%s] setting: %w", metadata.Metadata.Name, settingErr).Error())
|
||||
return settingErr
|
||||
|
@ -357,7 +357,7 @@ func (m *Manager) loadSystemPlugins(ctx context.Context) {
|
|||
instance.API = NewAPI(instance)
|
||||
|
||||
startTimestamp := util.GetSystemTimestamp()
|
||||
pluginSetting, settingErr := setting.GetSettingManager().LoadPluginSetting(ctx, metadata.Id, metadata.Name, metadata.SettingDefinitions)
|
||||
pluginSetting, settingErr := setting.GetSettingManager().LoadPluginSetting(ctx, metadata.Id, metadata.Name, metadata.SettingDefinitions.ToMap())
|
||||
if settingErr != nil {
|
||||
errMsg := fmt.Sprintf("failed to load system plugin[%s] setting, use default plugin setting. err: %s", metadata.Name, settingErr.Error())
|
||||
logger.Error(ctx, errMsg)
|
||||
|
@ -760,7 +760,7 @@ func (m *Manager) queryForPlugin(ctx context.Context, pluginInstance *Instance,
|
|||
if query.Type == QueryTypeSelection && query.Search != "" {
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
results = lo.Filter(results, func(item QueryResult, _ int) bool {
|
||||
match, _ := util.IsStringMatchScore(item.Title, query.Search, woxSetting.UsePinYin)
|
||||
match, _ := util.IsStringMatchScore(item.Title, query.Search, woxSetting.UsePinYin.Get())
|
||||
return match
|
||||
})
|
||||
}
|
||||
|
@ -995,8 +995,8 @@ func (m *Manager) calculateResultScore(ctx context.Context, pluginId, title, sub
|
|||
var score int64 = 0
|
||||
|
||||
resultHash := setting.NewResultHash(pluginId, title, subTitle)
|
||||
woxAppData := setting.GetSettingManager().GetWoxAppData(ctx)
|
||||
actionResults, ok := woxAppData.ActionedResults.Load(resultHash)
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
actionResults, ok := woxSetting.ActionedResults.Get().Load(resultHash)
|
||||
if !ok {
|
||||
return score
|
||||
}
|
||||
|
@ -1294,9 +1294,9 @@ func (m *Manager) NewQuery(ctx context.Context, plainQuery common.PlainQuery) (Q
|
|||
if plainQuery.QueryType == QueryTypeInput {
|
||||
newQuery := plainQuery.QueryText
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
if len(woxSetting.QueryShortcuts) > 0 {
|
||||
if len(woxSetting.QueryShortcuts.Get()) > 0 {
|
||||
originQuery := plainQuery.QueryText
|
||||
expandedQuery := m.expandQueryShortcut(ctx, plainQuery.QueryText, woxSetting.QueryShortcuts)
|
||||
expandedQuery := m.expandQueryShortcut(ctx, plainQuery.QueryText, woxSetting.QueryShortcuts.Get())
|
||||
if originQuery != expandedQuery {
|
||||
logger.Info(ctx, fmt.Sprintf("expand query shortcut: %s -> %s", originQuery, expandedQuery))
|
||||
newQuery = expandedQuery
|
||||
|
@ -1578,7 +1578,7 @@ func (m *Manager) GetAIProvider(ctx context.Context, provider common.ProviderNam
|
|||
}
|
||||
|
||||
//check if provider has setting
|
||||
aiProviderSettings := setting.GetSettingManager().GetWoxSetting(ctx).AIProviders
|
||||
aiProviderSettings := setting.GetSettingManager().GetWoxSetting(ctx).AIProviders.Get()
|
||||
providerSetting, providerSettingExist := lo.Find(aiProviderSettings, func(item setting.AIProvider) bool {
|
||||
return item.Name == provider
|
||||
})
|
||||
|
|
|
@ -51,7 +51,7 @@ func (i *QueryHistoryPlugin) Init(ctx context.Context, initParams plugin.InitPar
|
|||
}
|
||||
|
||||
func (i *QueryHistoryPlugin) Query(ctx context.Context, query plugin.Query) (results []plugin.QueryResult) {
|
||||
queryHistories := setting.GetSettingManager().GetWoxAppData(ctx).QueryHistories
|
||||
queryHistories := setting.GetSettingManager().GetWoxSetting(ctx).QueryHistories.Get()
|
||||
|
||||
maxResultCount := 0
|
||||
for k := len(queryHistories) - 1; k >= 0; k-- {
|
||||
|
|
|
@ -120,7 +120,7 @@ func (c *ThemePlugin) Query(ctx context.Context, query plugin.Query) []plugin.Qu
|
|||
},
|
||||
})
|
||||
}
|
||||
currentThemeId := setting.GetSettingManager().GetWoxSetting(ctx).ThemeId
|
||||
currentThemeId := setting.GetSettingManager().GetWoxSetting(ctx).ThemeId.Get()
|
||||
if currentThemeId == theme.ThemeId {
|
||||
result.Group = "Current"
|
||||
result.GroupScore = 100
|
||||
|
|
|
@ -36,15 +36,15 @@ var windowIconCache = util.NewHashMap[string, common.WoxImage]()
|
|||
|
||||
func IsStringMatchScore(ctx context.Context, term string, subTerm string) (bool, int64) {
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
if woxSetting.UsePinYin {
|
||||
if woxSetting.UsePinYin.Get() {
|
||||
key := term + subTerm
|
||||
if result, ok := pinyinMatchCache.Load(key); ok {
|
||||
return result.match, result.score
|
||||
}
|
||||
}
|
||||
|
||||
match, score := util.IsStringMatchScore(term, subTerm, woxSetting.UsePinYin)
|
||||
if woxSetting.UsePinYin {
|
||||
match, score := util.IsStringMatchScore(term, subTerm, woxSetting.UsePinYin.Get())
|
||||
if woxSetting.UsePinYin.Get() {
|
||||
key := term + subTerm
|
||||
pinyinMatchCache.Store(key, cacheResult{match, score})
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func (m *Manager) StartAutoBackup(ctx context.Context) {
|
|||
continue
|
||||
}
|
||||
|
||||
if !settings.EnableAutoBackup {
|
||||
if !settings.EnableAutoBackup.Get() {
|
||||
logger.Info(ctx, "auto backup is disabled, skipping")
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -132,6 +132,16 @@ func (n *PluginSettingDefinitionItem) UnmarshalJSON(b []byte) error {
|
|||
|
||||
type PluginSettingDefinitions []PluginSettingDefinitionItem
|
||||
|
||||
func (c PluginSettingDefinitions) ToMap() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, item := range c {
|
||||
if item.Value != nil {
|
||||
m[item.Value.GetKey()] = item.Value.GetDefaultValue()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c PluginSettingDefinitions) GetDefaultValue(key string) (string, bool) {
|
||||
for _, item := range c {
|
||||
if item.Value.GetKey() == key {
|
||||
|
|
|
@ -2,6 +2,7 @@ package definition
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
|
@ -24,5 +25,4 @@ func (p *PluginSettingValueHead) GetDefaultValue() string {
|
|||
}
|
||||
|
||||
func (p *PluginSettingValueHead) Translate(translator func(ctx context.Context, key string) string) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -2,21 +2,12 @@ package setting
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"sync"
|
||||
"wox/common"
|
||||
"wox/database"
|
||||
"wox/i18n"
|
||||
"wox/setting/definition"
|
||||
"wox/util"
|
||||
"wox/util/autostart"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var managerInstance *Manager
|
||||
|
@ -25,40 +16,32 @@ var logger *util.Log
|
|||
|
||||
type Manager struct {
|
||||
woxSetting *WoxSetting
|
||||
woxAppData *WoxAppData
|
||||
store *Store
|
||||
}
|
||||
|
||||
func GetSettingManager() *Manager {
|
||||
managerOnce.Do(func() {
|
||||
managerInstance = &Manager{
|
||||
woxSetting: &WoxSetting{},
|
||||
woxAppData: &WoxAppData{},
|
||||
}
|
||||
logger = util.GetLogger()
|
||||
db := database.GetDB()
|
||||
if db == nil {
|
||||
logger.Error(context.Background(), "Database not initialized, cannot create Setting Manager")
|
||||
panic("database not initialized")
|
||||
}
|
||||
|
||||
store := NewStore(db)
|
||||
managerInstance = &Manager{
|
||||
store: store,
|
||||
}
|
||||
managerInstance.woxSetting = NewWoxSetting(store)
|
||||
})
|
||||
return managerInstance
|
||||
}
|
||||
|
||||
func (m *Manager) Init(ctx context.Context) error {
|
||||
// Step 1: Check if a migration is needed and perform it *before* initializing the main DB connection.
|
||||
if err := m.migrateDataIfNeeded(ctx); err != nil {
|
||||
// Log the error but don't block startup, as we can proceed with default settings.
|
||||
logger.Error(ctx, fmt.Sprintf("failed to perform data migration: %v. Proceeding with default settings.", err))
|
||||
}
|
||||
|
||||
// Step 2: Initialize the database. This will now either open the existing DB or create a new one.
|
||||
if err := database.Init(); err != nil {
|
||||
return fmt.Errorf("failed to initialize database: %w", err)
|
||||
}
|
||||
|
||||
// Step 3: Load settings from the database into the manager's struct.
|
||||
if err := m.loadSettingsFromDB(ctx); err != nil {
|
||||
return fmt.Errorf("failed to load settings from database: %w", err)
|
||||
}
|
||||
|
||||
// Initialization is now handled by GetSettingManager and lazy-loading in Value[T].
|
||||
// We just need to kick off any background processes.
|
||||
m.StartAutoBackup(ctx)
|
||||
|
||||
// Step 4: Perform post-load checks (like autostart)
|
||||
if err := m.checkAutostart(ctx); err != nil {
|
||||
logger.Error(ctx, fmt.Sprintf("failed to check autostart status: %v", err))
|
||||
}
|
||||
|
@ -66,315 +49,6 @@ func (m *Manager) Init(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) migrateDataIfNeeded(ctx context.Context) error {
|
||||
dbPath := path.Join(util.GetLocation().GetUserDataDirectory(), "wox.db")
|
||||
if _, err := os.Stat(dbPath); !os.IsNotExist(err) {
|
||||
// Database already exists, no migration needed.
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Info(ctx, "Database not found. Checking for old configuration files to migrate.")
|
||||
|
||||
oldSettingPath := util.GetLocation().GetWoxSettingPath()
|
||||
oldAppDataPath := util.GetLocation().GetWoxAppDataPath()
|
||||
|
||||
_, settingStatErr := os.Stat(oldSettingPath)
|
||||
_, appDataStatErr := os.Stat(oldAppDataPath)
|
||||
|
||||
if os.IsNotExist(settingStatErr) && os.IsNotExist(appDataStatErr) {
|
||||
logger.Info(ctx, "No old configuration files found. Skipping migration.")
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Info(ctx, "Old configuration files found. Starting migration process.")
|
||||
|
||||
// Temporarily connect to the database to perform migration.
|
||||
migrateDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open database for migration: %w", err)
|
||||
}
|
||||
|
||||
// Get the underlying SQL DB connection to close it later.
|
||||
sqlDB, err := migrateDB.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
|
||||
// Manually create schema
|
||||
if err := migrateDB.AutoMigrate(&database.Setting{}, &database.Hotkey{}, &database.QueryShortcut{}, &database.AIProvider{}, &database.QueryHistory{}, &database.FavoriteResult{}, &database.PluginSetting{}, &database.ActionedResult{}, &database.Oplog{}); err != nil {
|
||||
return fmt.Errorf("failed to create schema during migration: %w", err)
|
||||
}
|
||||
|
||||
// Load old settings
|
||||
oldWoxSetting := GetDefaultWoxSetting(ctx)
|
||||
if _, err := os.Stat(oldSettingPath); err == nil {
|
||||
fileContent, readErr := os.ReadFile(oldSettingPath)
|
||||
if readErr == nil && len(fileContent) > 0 {
|
||||
if json.Unmarshal(fileContent, &oldWoxSetting) != nil {
|
||||
logger.Warn(ctx, "Failed to unmarshal old wox.setting.json, will use defaults for migration.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load old app data
|
||||
oldWoxAppData := GetDefaultWoxAppData(ctx)
|
||||
if _, err := os.Stat(oldAppDataPath); err == nil {
|
||||
fileContent, readErr := os.ReadFile(oldAppDataPath)
|
||||
if readErr == nil && len(fileContent) > 0 {
|
||||
if json.Unmarshal(fileContent, &oldWoxAppData) != nil {
|
||||
logger.Warn(ctx, "Failed to unmarshal old wox.app.data.json, will use defaults for migration.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the migration in a single transaction
|
||||
tx := migrateDB.Begin()
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
// Defer a rollback in case of panic or error
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
panic(r)
|
||||
} else if err := tx.Error; err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// ... (rest of the migration logic is the same)
|
||||
settings := map[string]string{
|
||||
"EnableAutostart": strconv.FormatBool(oldWoxSetting.EnableAutostart.Get()),
|
||||
"MainHotkey": oldWoxSetting.MainHotkey.Get(),
|
||||
"SelectionHotkey": oldWoxSetting.SelectionHotkey.Get(),
|
||||
"UsePinYin": strconv.FormatBool(oldWoxSetting.UsePinYin),
|
||||
"SwitchInputMethodABC": strconv.FormatBool(oldWoxSetting.SwitchInputMethodABC),
|
||||
"HideOnStart": strconv.FormatBool(oldWoxSetting.HideOnStart),
|
||||
"HideOnLostFocus": strconv.FormatBool(oldWoxSetting.HideOnLostFocus),
|
||||
"ShowTray": strconv.FormatBool(oldWoxSetting.ShowTray),
|
||||
"LangCode": string(oldWoxSetting.LangCode),
|
||||
"LastQueryMode": oldWoxSetting.LastQueryMode,
|
||||
"ShowPosition": string(oldWoxSetting.ShowPosition),
|
||||
"EnableAutoBackup": strconv.FormatBool(oldWoxSetting.EnableAutoBackup),
|
||||
"EnableAutoUpdate": strconv.FormatBool(oldWoxSetting.EnableAutoUpdate),
|
||||
"CustomPythonPath": oldWoxSetting.CustomPythonPath.Get(),
|
||||
"CustomNodejsPath": oldWoxSetting.CustomNodejsPath.Get(),
|
||||
"HttpProxyEnabled": strconv.FormatBool(oldWoxSetting.HttpProxyEnabled.Get()),
|
||||
"HttpProxyUrl": oldWoxSetting.HttpProxyUrl.Get(),
|
||||
"AppWidth": strconv.Itoa(oldWoxSetting.AppWidth),
|
||||
"MaxResultCount": strconv.Itoa(oldWoxSetting.MaxResultCount),
|
||||
"ThemeId": oldWoxSetting.ThemeId,
|
||||
"LastWindowX": strconv.Itoa(oldWoxSetting.LastWindowX),
|
||||
"LastWindowY": strconv.Itoa(oldWoxSetting.LastWindowY),
|
||||
}
|
||||
|
||||
for key, value := range settings {
|
||||
if err := tx.Create(&database.Setting{Key: key, Value: value}).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate setting %s: %w", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate complex types
|
||||
for _, hotkey := range oldWoxSetting.QueryHotkeys.Get() {
|
||||
if err := tx.Create(&database.Hotkey{Hotkey: hotkey.Hotkey, Query: hotkey.Query, IsSilentExecution: hotkey.IsSilentExecution}).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate hotkey: %w", err)
|
||||
}
|
||||
}
|
||||
for _, shortcut := range oldWoxSetting.QueryShortcuts {
|
||||
if err := tx.Create(&database.QueryShortcut{Shortcut: shortcut.Shortcut, Query: shortcut.Query}).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate shortcut: %w", err)
|
||||
}
|
||||
}
|
||||
for _, provider := range oldWoxSetting.AIProviders {
|
||||
if err := tx.Create(&database.AIProvider{Name: provider.Name, ApiKey: provider.ApiKey, Host: provider.Host}).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate AI provider: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate App Data
|
||||
for _, history := range oldWoxAppData.QueryHistories {
|
||||
if err := tx.Create(&database.QueryHistory{Query: history.Query.String(), Timestamp: history.Timestamp}).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate query history: %w", err)
|
||||
}
|
||||
}
|
||||
// NOTE: FavoriteResults cannot be migrated due to the one-way hash nature of ResultHash.
|
||||
// Users will need to re-favorite items after this update.
|
||||
logger.Warn(ctx, "Favorite results cannot be migrated and will be reset.")
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return fmt.Errorf("failed to commit migration transaction: %w", err)
|
||||
}
|
||||
|
||||
// Rename old files to .bak on successful migration
|
||||
if _, err := os.Stat(oldSettingPath); err == nil {
|
||||
if err := os.Rename(oldSettingPath, oldSettingPath+".bak"); err != nil {
|
||||
logger.Warn(ctx, fmt.Sprintf("Failed to rename old setting file to .bak: %v", err))
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(oldAppDataPath); err == nil {
|
||||
if err := os.Rename(oldAppDataPath, oldAppDataPath+".bak"); err != nil {
|
||||
logger.Warn(ctx, fmt.Sprintf("Failed to rename old app data file to .bak: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info(ctx, "Successfully migrated old configuration to the new database.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) loadSettingsFromDB(ctx context.Context) error {
|
||||
logger.Info(ctx, "Loading settings from database...")
|
||||
db := database.GetDB()
|
||||
|
||||
// Start with default settings, then overwrite with values from DB
|
||||
defaultWoxSetting := GetDefaultWoxSetting(ctx)
|
||||
m.woxSetting = &defaultWoxSetting
|
||||
defaultWoxAppData := GetDefaultWoxAppData(ctx)
|
||||
m.woxAppData = &defaultWoxAppData
|
||||
|
||||
// Load simple K/V settings
|
||||
var settings []database.Setting
|
||||
if err := db.Find(&settings).Error; err != nil {
|
||||
return fmt.Errorf("failed to load settings: %w", err)
|
||||
}
|
||||
|
||||
settingsMap := make(map[string]string)
|
||||
for _, s := range settings {
|
||||
settingsMap[s.Key] = s.Value
|
||||
}
|
||||
|
||||
// Populate m.woxSetting from settingsMap
|
||||
m.populateWoxSettingFromMap(settingsMap)
|
||||
|
||||
// Load complex types
|
||||
var hotkeys []database.Hotkey
|
||||
if err := db.Find(&hotkeys).Error; err == nil {
|
||||
queryHotkeys := make([]QueryHotkey, len(hotkeys))
|
||||
for i, h := range hotkeys {
|
||||
queryHotkeys[i] = QueryHotkey{Hotkey: h.Hotkey, Query: h.Query, IsSilentExecution: h.IsSilentExecution}
|
||||
}
|
||||
m.woxSetting.QueryHotkeys.Set(queryHotkeys)
|
||||
} else {
|
||||
logger.Warn(ctx, fmt.Sprintf("Could not load hotkeys: %v", err))
|
||||
}
|
||||
|
||||
var shortcuts []database.QueryShortcut
|
||||
if err := db.Find(&shortcuts).Error; err == nil {
|
||||
queryShortcuts := make([]QueryShortcut, len(shortcuts))
|
||||
for i, s := range shortcuts {
|
||||
queryShortcuts[i] = QueryShortcut{Shortcut: s.Shortcut, Query: s.Query}
|
||||
}
|
||||
m.woxSetting.QueryShortcuts = queryShortcuts
|
||||
} else {
|
||||
logger.Warn(ctx, fmt.Sprintf("Could not load query shortcuts: %v", err))
|
||||
}
|
||||
|
||||
var providers []database.AIProvider
|
||||
if err := db.Find(&providers).Error; err == nil {
|
||||
m.woxSetting.AIProviders = make([]AIProvider, len(providers))
|
||||
for i, p := range providers {
|
||||
m.woxSetting.AIProviders[i] = AIProvider{Name: p.Name, ApiKey: p.ApiKey, Host: p.Host}
|
||||
}
|
||||
} else {
|
||||
logger.Warn(ctx, fmt.Sprintf("Could not load AI providers: %v", err))
|
||||
}
|
||||
|
||||
// Load App Data
|
||||
var history []database.QueryHistory
|
||||
if err := db.Order("timestamp asc").Find(&history).Error; err == nil {
|
||||
m.woxAppData.QueryHistories = make([]QueryHistory, len(history))
|
||||
for i, h := range history {
|
||||
m.woxAppData.QueryHistories[i] = QueryHistory{Query: common.PlainQuery{QueryText: h.Query}, Timestamp: h.Timestamp}
|
||||
}
|
||||
} else {
|
||||
logger.Warn(ctx, fmt.Sprintf("Could not load query history: %v", err))
|
||||
}
|
||||
|
||||
var favorites []database.FavoriteResult
|
||||
if err := db.Find(&favorites).Error; err == nil {
|
||||
m.woxAppData.FavoriteResults = util.NewHashMap[ResultHash, bool]()
|
||||
for _, f := range favorites {
|
||||
hash := NewResultHash(f.PluginID, f.Title, f.Subtitle)
|
||||
m.woxAppData.FavoriteResults.Store(hash, true)
|
||||
}
|
||||
} else {
|
||||
logger.Warn(ctx, fmt.Sprintf("Could not load favorite results: %v", err))
|
||||
}
|
||||
|
||||
logger.Info(ctx, "Successfully loaded settings from database.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) populateWoxSettingFromMap(settingsMap map[string]string) {
|
||||
if val, ok := settingsMap["EnableAutostart"]; ok {
|
||||
m.woxSetting.EnableAutostart.Set(val == "true")
|
||||
}
|
||||
if val, ok := settingsMap["MainHotkey"]; ok {
|
||||
m.woxSetting.MainHotkey.Set(val)
|
||||
}
|
||||
if val, ok := settingsMap["SelectionHotkey"]; ok {
|
||||
m.woxSetting.SelectionHotkey.Set(val)
|
||||
}
|
||||
if val, ok := settingsMap["UsePinYin"]; ok {
|
||||
m.woxSetting.UsePinYin = val == "true"
|
||||
}
|
||||
if val, ok := settingsMap["SwitchInputMethodABC"]; ok {
|
||||
m.woxSetting.SwitchInputMethodABC = val == "true"
|
||||
}
|
||||
if val, ok := settingsMap["HideOnStart"]; ok {
|
||||
m.woxSetting.HideOnStart = val == "true"
|
||||
}
|
||||
if val, ok := settingsMap["HideOnLostFocus"]; ok {
|
||||
m.woxSetting.HideOnLostFocus = val == "true"
|
||||
}
|
||||
if val, ok := settingsMap["ShowTray"]; ok {
|
||||
m.woxSetting.ShowTray = val == "true"
|
||||
}
|
||||
if val, ok := settingsMap["LangCode"]; ok {
|
||||
m.woxSetting.LangCode = i18n.LangCode(val)
|
||||
}
|
||||
if val, ok := settingsMap["LastQueryMode"]; ok {
|
||||
m.woxSetting.LastQueryMode = val
|
||||
}
|
||||
if val, ok := settingsMap["ShowPosition"]; ok {
|
||||
m.woxSetting.ShowPosition = PositionType(val)
|
||||
}
|
||||
if val, ok := settingsMap["EnableAutoBackup"]; ok {
|
||||
m.woxSetting.EnableAutoBackup = val == "true"
|
||||
}
|
||||
if val, ok := settingsMap["EnableAutoUpdate"]; ok {
|
||||
m.woxSetting.EnableAutoUpdate = val == "true"
|
||||
}
|
||||
if val, ok := settingsMap["CustomPythonPath"]; ok {
|
||||
m.woxSetting.CustomPythonPath.Set(val)
|
||||
}
|
||||
if val, ok := settingsMap["CustomNodejsPath"]; ok {
|
||||
m.woxSetting.CustomNodejsPath.Set(val)
|
||||
}
|
||||
if val, ok := settingsMap["HttpProxyEnabled"]; ok {
|
||||
m.woxSetting.HttpProxyEnabled.Set(val == "true")
|
||||
}
|
||||
if val, ok := settingsMap["HttpProxyUrl"]; ok {
|
||||
m.woxSetting.HttpProxyUrl.Set(val)
|
||||
}
|
||||
if val, ok := settingsMap["ThemeId"]; ok {
|
||||
m.woxSetting.ThemeId = val
|
||||
}
|
||||
if val, ok := settingsMap["AppWidth"]; ok {
|
||||
m.woxSetting.AppWidth, _ = strconv.Atoi(val)
|
||||
}
|
||||
if val, ok := settingsMap["MaxResultCount"]; ok {
|
||||
m.woxSetting.MaxResultCount, _ = strconv.Atoi(val)
|
||||
}
|
||||
if val, ok := settingsMap["LastWindowX"]; ok {
|
||||
m.woxSetting.LastWindowX, _ = strconv.Atoi(val)
|
||||
}
|
||||
if val, ok := settingsMap["LastWindowY"]; ok {
|
||||
m.woxSetting.LastWindowY, _ = strconv.Atoi(val)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) checkAutostart(ctx context.Context) error {
|
||||
actualAutostart, err := autostart.IsAutostart(ctx)
|
||||
if err != nil {
|
||||
|
@ -400,9 +74,6 @@ func (m *Manager) checkAutostart(ctx context.Context) error {
|
|||
m.woxSetting.EnableAutostart.Set(true) // Revert setting if action fails
|
||||
}
|
||||
}
|
||||
|
||||
// Save the updated setting
|
||||
return m.SaveWoxSetting(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -411,242 +82,53 @@ func (m *Manager) GetWoxSetting(ctx context.Context) *WoxSetting {
|
|||
return m.woxSetting
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateWoxSetting(ctx context.Context, key, value string) error {
|
||||
db := database.GetDB()
|
||||
func (m *Manager) GetLatestQueryHistory(ctx context.Context, limit int) []common.PlainQuery {
|
||||
histories := m.woxSetting.QueryHistories.Get()
|
||||
|
||||
// Use a map for easy lookup and update
|
||||
updateMap := map[string]interface{}{
|
||||
"EnableAutostart": func() { m.woxSetting.EnableAutostart.Set(value == "true") },
|
||||
"MainHotkey": func() { m.woxSetting.MainHotkey.Set(value) },
|
||||
"SelectionHotkey": func() { m.woxSetting.SelectionHotkey.Set(value) },
|
||||
"UsePinYin": func() { m.woxSetting.UsePinYin = value == "true" },
|
||||
"SwitchInputMethodABC": func() { m.woxSetting.SwitchInputMethodABC = value == "true" },
|
||||
"HideOnStart": func() { m.woxSetting.HideOnStart = value == "true" },
|
||||
"HideOnLostFocus": func() { m.woxSetting.HideOnLostFocus = value == "true" },
|
||||
"ShowTray": func() { m.woxSetting.ShowTray = value == "true" },
|
||||
"LangCode": func() { m.woxSetting.LangCode = i18n.LangCode(value) },
|
||||
"LastQueryMode": func() { m.woxSetting.LastQueryMode = value },
|
||||
"ThemeId": func() { m.woxSetting.ThemeId = value },
|
||||
"ShowPosition": func() { m.woxSetting.ShowPosition = PositionType(value) },
|
||||
"EnableAutoBackup": func() { m.woxSetting.EnableAutoBackup = value == "true" },
|
||||
"EnableAutoUpdate": func() { m.woxSetting.EnableAutoUpdate = value == "true" },
|
||||
"CustomPythonPath": func() { m.woxSetting.CustomPythonPath.Set(value) },
|
||||
"CustomNodejsPath": func() { m.woxSetting.CustomNodejsPath.Set(value) },
|
||||
"HttpProxyEnabled": func() {
|
||||
m.woxSetting.HttpProxyEnabled.Set(value == "true")
|
||||
if m.woxSetting.HttpProxyUrl.Get() != "" && m.woxSetting.HttpProxyEnabled.Get() {
|
||||
m.onUpdateProxy(ctx, m.woxSetting.HttpProxyUrl.Get())
|
||||
} else {
|
||||
m.onUpdateProxy(ctx, "")
|
||||
}
|
||||
},
|
||||
"HttpProxyUrl": func() {
|
||||
m.woxSetting.HttpProxyUrl.Set(value)
|
||||
if m.woxSetting.HttpProxyEnabled.Get() && value != "" {
|
||||
m.onUpdateProxy(ctx, m.woxSetting.HttpProxyUrl.Get())
|
||||
} else {
|
||||
m.onUpdateProxy(ctx, "")
|
||||
}
|
||||
},
|
||||
"AppWidth": func() {
|
||||
appWidth, _ := strconv.Atoi(value)
|
||||
m.woxSetting.AppWidth = appWidth
|
||||
},
|
||||
"MaxResultCount": func() {
|
||||
maxResultCount, _ := strconv.Atoi(value)
|
||||
m.woxSetting.MaxResultCount = maxResultCount
|
||||
},
|
||||
"QueryHotkeys": func() {
|
||||
var queryHotkeys []QueryHotkey
|
||||
if json.Unmarshal([]byte(value), &queryHotkeys) == nil {
|
||||
m.woxSetting.QueryHotkeys.Set(queryHotkeys)
|
||||
db.Delete(&database.Hotkey{}, "1 = 1") // Clear existing
|
||||
for _, h := range queryHotkeys {
|
||||
db.Create(&database.Hotkey{Hotkey: h.Hotkey, Query: h.Query, IsSilentExecution: h.IsSilentExecution})
|
||||
}
|
||||
}
|
||||
},
|
||||
"QueryShortcuts": func() {
|
||||
var queryShortcuts []QueryShortcut
|
||||
if json.Unmarshal([]byte(value), &queryShortcuts) == nil {
|
||||
m.woxSetting.QueryShortcuts = queryShortcuts
|
||||
db.Delete(&database.QueryShortcut{}, "1 = 1") // Clear existing
|
||||
for _, s := range queryShortcuts {
|
||||
db.Create(&database.QueryShortcut{Shortcut: s.Shortcut, Query: s.Query})
|
||||
}
|
||||
}
|
||||
},
|
||||
"AIProviders": func() {
|
||||
var aiProviders []AIProvider
|
||||
if json.Unmarshal([]byte(value), &aiProviders) == nil {
|
||||
m.woxSetting.AIProviders = aiProviders
|
||||
db.Delete(&database.AIProvider{}, "1 = 1") // Clear existing
|
||||
for _, p := range aiProviders {
|
||||
db.Create(&database.AIProvider{Name: p.Name, ApiKey: p.ApiKey, Host: p.Host})
|
||||
}
|
||||
}
|
||||
},
|
||||
// Sort by timestamp descending and limit results
|
||||
var result []common.PlainQuery
|
||||
count := 0
|
||||
for i := len(histories) - 1; i >= 0 && count < limit; i-- {
|
||||
result = append(result, histories[i].Query)
|
||||
count++
|
||||
}
|
||||
|
||||
if updateFunc, ok := updateMap[key]; ok {
|
||||
// For complex types, the update is handled within the function itself.
|
||||
if key != "QueryHotkeys" && key != "QueryShortcuts" && key != "AIProviders" {
|
||||
result := db.Model(&database.Setting{}).Where("key = ?", key).Update("value", value)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
// If no rows were affected, it means the key doesn't exist, so create it.
|
||||
if err := db.Create(&database.Setting{Key: key, Value: value}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update in-memory struct
|
||||
updateFunc.(func())()
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown key: %s", key)
|
||||
}
|
||||
|
||||
func (m *Manager) onUpdateProxy(ctx context.Context, url string) {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("updating HTTP proxy, url: %s", url))
|
||||
|
||||
if url != "" {
|
||||
util.UpdateHTTPProxy(ctx, url)
|
||||
} else {
|
||||
util.UpdateHTTPProxy(ctx, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) GetWoxAppData(ctx context.Context) *WoxAppData {
|
||||
return m.woxAppData
|
||||
}
|
||||
|
||||
func (m *Manager) SaveWoxSetting(ctx context.Context) error {
|
||||
// This method is now a convenience wrapper. The primary update logic is in UpdateWoxSetting.
|
||||
// It can be used to persist the entire in-memory setting state to the database if needed.
|
||||
logger.Info(ctx, "Persisting all settings to database.")
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
|
||||
// This is a simplified version. A full implementation would iterate through all settings
|
||||
// and update them, which is complex. The per-key update in UpdateWoxSetting is more efficient.
|
||||
|
||||
// For now, we just log that this is happening.
|
||||
// The actual saving happens in UpdateWoxSetting.
|
||||
|
||||
tx.Commit()
|
||||
logger.Info(ctx, "Wox setting state persisted.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) AddQueryHistory(ctx context.Context, query common.PlainQuery) {
|
||||
if query.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug(ctx, fmt.Sprintf("add query history: %s", query.String()))
|
||||
historyEntry := QueryHistory{
|
||||
Query: query,
|
||||
Timestamp: util.GetSystemTimestamp(),
|
||||
}
|
||||
m.woxAppData.QueryHistories = append(m.woxAppData.QueryHistories, historyEntry)
|
||||
|
||||
// Persist to DB
|
||||
database.GetDB().Create(&database.QueryHistory{Query: query.String(), Timestamp: historyEntry.Timestamp})
|
||||
|
||||
// Trim in-memory and DB history
|
||||
if len(m.woxAppData.QueryHistories) > 100 {
|
||||
toDeleteCount := len(m.woxAppData.QueryHistories) - 100
|
||||
m.woxAppData.QueryHistories = m.woxAppData.QueryHistories[toDeleteCount:]
|
||||
|
||||
var oldestEntries []database.QueryHistory
|
||||
database.GetDB().Order("timestamp asc").Limit(toDeleteCount).Find(&oldestEntries)
|
||||
if len(oldestEntries) > 0 {
|
||||
database.GetDB().Delete(&oldestEntries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) GetLatestQueryHistory(ctx context.Context, n int) []QueryHistory {
|
||||
if n <= 0 {
|
||||
return []QueryHistory{}
|
||||
}
|
||||
|
||||
if n > len(m.woxAppData.QueryHistories) {
|
||||
n = len(m.woxAppData.QueryHistories)
|
||||
}
|
||||
|
||||
histories := m.woxAppData.QueryHistories[len(m.woxAppData.QueryHistories)-n:]
|
||||
|
||||
// copy to new list and order by time desc
|
||||
result := make([]QueryHistory, n)
|
||||
for i := 0; i < n; i++ {
|
||||
result[i] = histories[n-i-1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) AddFavoriteResult(ctx context.Context, pluginId string, resultTitle string, resultSubTitle string) {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("add favorite result: %s, %s", resultTitle, resultSubTitle))
|
||||
resultHash := NewResultHash(pluginId, resultTitle, resultSubTitle)
|
||||
m.woxAppData.FavoriteResults.Store(resultHash, true)
|
||||
|
||||
fav := database.FavoriteResult{PluginID: pluginId, Title: resultTitle, Subtitle: resultSubTitle}
|
||||
database.GetDB().Create(&fav)
|
||||
}
|
||||
|
||||
func (m *Manager) IsFavoriteResult(ctx context.Context, pluginId string, resultTitle string, resultSubTitle string) bool {
|
||||
resultHash := NewResultHash(pluginId, resultTitle, resultSubTitle)
|
||||
return m.woxAppData.FavoriteResults.Exist(resultHash)
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveFavoriteResult(ctx context.Context, pluginId string, resultTitle string, resultSubTitle string) {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("remove favorite result: %s, %s", resultTitle, resultSubTitle))
|
||||
resultHash := NewResultHash(pluginId, resultTitle, resultSubTitle)
|
||||
m.woxAppData.FavoriteResults.Delete(resultHash)
|
||||
|
||||
database.GetDB().Where("plugin_id = ? AND title = ? AND subtitle = ?", pluginId, resultTitle, resultSubTitle).Delete(&database.FavoriteResult{})
|
||||
}
|
||||
|
||||
func (m *Manager) LoadPluginSetting(ctx context.Context, pluginId string, pluginName string, defaultSettings definition.PluginSettingDefinitions) (*PluginSetting, error) {
|
||||
db := database.GetDB()
|
||||
func (m *Manager) LoadPluginSetting(ctx context.Context, pluginId string, pluginName string, defaultSettings map[string]string) (*PluginSetting, error) {
|
||||
pluginSetting := &PluginSetting{
|
||||
Name: pluginName,
|
||||
Settings: defaultSettings.GetAllDefaults(),
|
||||
Settings: util.NewHashMap[string, string](),
|
||||
}
|
||||
|
||||
var settings []database.PluginSetting
|
||||
db.Where("plugin_id = ?", pluginId).Find(&settings)
|
||||
// Load default settings first
|
||||
for key, value := range defaultSettings {
|
||||
pluginSetting.Settings.Store(key, value)
|
||||
}
|
||||
|
||||
for _, s := range settings {
|
||||
pluginSetting.Settings.Store(s.Key, s.Value)
|
||||
actualSettings, err := m.store.GetAllPluginSettings(pluginId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load plugin settings: %w", err)
|
||||
}
|
||||
|
||||
// Override defaults with actual settings
|
||||
for key, value := range actualSettings {
|
||||
pluginSetting.Settings.Store(key, value)
|
||||
}
|
||||
|
||||
return pluginSetting, nil
|
||||
}
|
||||
|
||||
func (m *Manager) SavePluginSetting(ctx context.Context, pluginId string, pluginSetting *PluginSetting) error {
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
settings := make(map[string]string)
|
||||
|
||||
pluginSetting.Settings.Range(func(key string, value string) bool {
|
||||
var existing database.PluginSetting
|
||||
result := tx.Where("plugin_id = ? AND key = ?", pluginId, key).First(&existing)
|
||||
|
||||
if result.Error == nil {
|
||||
// Update
|
||||
tx.Model(&existing).Update("value", value)
|
||||
} else {
|
||||
// Create
|
||||
tx.Create(&database.PluginSetting{PluginID: pluginId, Key: key, Value: value})
|
||||
}
|
||||
settings[key] = value
|
||||
return true
|
||||
})
|
||||
|
||||
return tx.Commit().Error
|
||||
return m.store.SetAllPluginSettings(pluginId, settings)
|
||||
}
|
||||
|
||||
func (m *Manager) AddActionedResult(ctx context.Context, pluginId string, resultTitle string, resultSubTitle string, query string) {
|
||||
|
@ -656,31 +138,36 @@ func (m *Manager) AddActionedResult(ctx context.Context, pluginId string, result
|
|||
Query: query,
|
||||
}
|
||||
|
||||
if v, ok := m.woxAppData.ActionedResults.Load(resultHash); ok {
|
||||
actionedResults := m.woxSetting.ActionedResults.Get()
|
||||
if v, ok := actionedResults.Load(resultHash); ok {
|
||||
v = append(v, actionedResult)
|
||||
if len(v) > 100 {
|
||||
v = v[len(v)-100:]
|
||||
}
|
||||
m.woxAppData.ActionedResults.Store(resultHash, v)
|
||||
actionedResults.Store(resultHash, v)
|
||||
} else {
|
||||
m.woxAppData.ActionedResults.Store(resultHash, []ActionedResult{actionedResult})
|
||||
actionedResults.Store(resultHash, []ActionedResult{actionedResult})
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
db.Create(&database.ActionedResult{
|
||||
PluginID: pluginId,
|
||||
Title: resultTitle,
|
||||
Subtitle: resultSubTitle,
|
||||
Timestamp: actionedResult.Timestamp,
|
||||
Query: actionedResult.Query,
|
||||
})
|
||||
m.woxSetting.ActionedResults.Set(actionedResults)
|
||||
}
|
||||
|
||||
func (m *Manager) SaveWindowPosition(ctx context.Context, x, y int) error {
|
||||
m.woxSetting.LastWindowX = x
|
||||
m.woxSetting.LastWindowY = y
|
||||
db := database.GetDB()
|
||||
db.Model(&database.Setting{}).Where("key = ?", "LastWindowX").Update("value", strconv.Itoa(x))
|
||||
db.Model(&database.Setting{}).Where("key = ?", "LastWindowY").Update("value", strconv.Itoa(y))
|
||||
return nil
|
||||
func (m *Manager) AddFavoriteResult(ctx context.Context, pluginId string, resultTitle string, resultSubTitle string) {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("add favorite result: %s, %s", resultTitle, resultSubTitle))
|
||||
resultHash := NewResultHash(pluginId, resultTitle, resultSubTitle)
|
||||
favoriteResults := m.woxSetting.FavoriteResults.Get()
|
||||
favoriteResults.Store(resultHash, true)
|
||||
m.woxSetting.FavoriteResults.Set(favoriteResults)
|
||||
}
|
||||
|
||||
func (m *Manager) IsFavoriteResult(ctx context.Context, pluginId string, resultTitle string, resultSubTitle string) bool {
|
||||
resultHash := NewResultHash(pluginId, resultTitle, resultSubTitle)
|
||||
return m.woxSetting.FavoriteResults.Get().Exist(resultHash)
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveFavoriteResult(ctx context.Context, pluginId string, resultTitle string, resultSubTitle string) {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("remove favorite result: %s, %s", resultTitle, resultSubTitle))
|
||||
resultHash := NewResultHash(pluginId, resultTitle, resultSubTitle)
|
||||
favoriteResults := m.woxSetting.FavoriteResults.Get()
|
||||
favoriteResults.Delete(resultHash)
|
||||
m.woxSetting.FavoriteResults.Set(favoriteResults)
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"wox/util"
|
||||
)
|
||||
|
||||
// platform specific setting value. Don't set this value directly, use get,set instead
|
||||
type PlatformSettingValue[T any] struct {
|
||||
MacValue T
|
||||
WinValue T
|
||||
LinuxValue T
|
||||
}
|
||||
|
||||
func (p *PlatformSettingValue[T]) Get() T {
|
||||
if util.IsWindows() {
|
||||
return p.WinValue
|
||||
} else if util.IsMacOS() {
|
||||
return p.MacValue
|
||||
} else if util.IsLinux() {
|
||||
return p.LinuxValue
|
||||
}
|
||||
|
||||
panic("unknown platform")
|
||||
}
|
||||
|
||||
func (p *PlatformSettingValue[T]) Set(t T) {
|
||||
if util.IsWindows() {
|
||||
p.WinValue = t
|
||||
return
|
||||
} else if util.IsMacOS() {
|
||||
p.MacValue = t
|
||||
return
|
||||
} else if util.IsLinux() {
|
||||
p.LinuxValue = t
|
||||
return
|
||||
}
|
||||
|
||||
panic("unknown platform")
|
||||
}
|
||||
|
||||
func NewPlatformSettingValue[T any](t T) PlatformSettingValue[T] {
|
||||
if util.IsWindows() {
|
||||
return PlatformSettingValue[T]{
|
||||
WinValue: t,
|
||||
}
|
||||
} else if util.IsMacOS() {
|
||||
return PlatformSettingValue[T]{
|
||||
MacValue: t,
|
||||
}
|
||||
} else if util.IsLinux() {
|
||||
return PlatformSettingValue[T]{
|
||||
LinuxValue: t,
|
||||
}
|
||||
}
|
||||
|
||||
panic("unknown platform")
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"wox/database"
|
||||
"wox/util"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// WoxSettingStore defines the unified interface for reading and writing settings
|
||||
type WoxSettingStore interface {
|
||||
Get(key string, target interface{}) error
|
||||
Set(key string, value interface{}) error
|
||||
LogOplog(key string, value interface{}) error
|
||||
}
|
||||
|
||||
// PluginSettingStore defines the interface for plugin settings
|
||||
type PluginSettingStore interface {
|
||||
GetPluginSetting(pluginId, key string, target interface{}) error
|
||||
SetPluginSetting(pluginId, key string, value interface{}) error
|
||||
GetAllPluginSettings(pluginId string) (map[string]string, error)
|
||||
SetAllPluginSettings(pluginId string, settings map[string]string) error
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewStore(db *gorm.DB) *Store {
|
||||
return &Store{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) Get(key string, target interface{}) error {
|
||||
var setting database.WoxSetting
|
||||
if err := s.db.Where("key = ?", key).First(&setting).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// Return the default value (target should already contain it)
|
||||
return nil
|
||||
}
|
||||
logger.Error(context.Background(), fmt.Sprintf("Failed to read setting %s: %v", key, err))
|
||||
return err
|
||||
}
|
||||
|
||||
return s.deserializeValue(setting.Value, target)
|
||||
}
|
||||
|
||||
func (s *Store) Set(key string, value interface{}) error {
|
||||
strValue, err := s.serializeValue(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize value: %w", err)
|
||||
}
|
||||
|
||||
// Use GORM's Save for upsert behavior
|
||||
return s.db.Save(&database.WoxSetting{Key: key, Value: strValue}).Error
|
||||
}
|
||||
|
||||
func (s *Store) LogOplog(key string, value interface{}) error {
|
||||
strValue, err := s.serializeValue(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize value for oplog: %w", err)
|
||||
}
|
||||
|
||||
oplog := database.Oplog{
|
||||
EntityType: "setting",
|
||||
EntityID: key,
|
||||
Operation: "update",
|
||||
Value: strValue,
|
||||
Timestamp: util.GetSystemTimestamp(),
|
||||
}
|
||||
|
||||
return s.db.Create(&oplog).Error
|
||||
}
|
||||
|
||||
func (s *Store) GetPluginSetting(pluginId, key string, target interface{}) error {
|
||||
var setting database.PluginSetting
|
||||
if err := s.db.Where("plugin_id = ? AND key = ?", pluginId, key).First(&setting).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// Return the default value (target should already contain it)
|
||||
return nil
|
||||
}
|
||||
logger.Error(context.Background(), fmt.Sprintf("Failed to read plugin setting %s.%s: %v", pluginId, key, err))
|
||||
return err
|
||||
}
|
||||
|
||||
return s.deserializeValue(setting.Value, target)
|
||||
}
|
||||
|
||||
func (s *Store) SetPluginSetting(pluginId, key string, value interface{}) error {
|
||||
strValue, err := s.serializeValue(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize plugin setting value: %w", err)
|
||||
}
|
||||
|
||||
// Use GORM's Save for upsert behavior
|
||||
return s.db.Save(&database.PluginSetting{PluginID: pluginId, Key: key, Value: strValue}).Error
|
||||
}
|
||||
|
||||
func (s *Store) GetAllPluginSettings(pluginId string) (map[string]string, error) {
|
||||
var settings []database.PluginSetting
|
||||
if err := s.db.Where("plugin_id = ?", pluginId).Find(&settings).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to read plugin settings for %s: %w", pluginId, err)
|
||||
}
|
||||
|
||||
result := make(map[string]string)
|
||||
for _, setting := range settings {
|
||||
result[setting.Key] = setting.Value
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Store) SetAllPluginSettings(pluginId string, settings map[string]string) error {
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// Clear existing settings for this plugin
|
||||
if err := tx.Where("plugin_id = ?", pluginId).Delete(&database.PluginSetting{}).Error; err != nil {
|
||||
return fmt.Errorf("failed to clear existing plugin settings: %w", err)
|
||||
}
|
||||
|
||||
// Insert new settings
|
||||
for key, value := range settings {
|
||||
if err := tx.Create(&database.PluginSetting{
|
||||
PluginID: pluginId,
|
||||
Key: key,
|
||||
Value: value,
|
||||
}).Error; err != nil {
|
||||
return fmt.Errorf("failed to save plugin setting %s.%s: %w", pluginId, key, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) serializeValue(value interface{}) (string, error) {
|
||||
if value == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return v, nil
|
||||
case int:
|
||||
return strconv.Itoa(v), nil
|
||||
case bool:
|
||||
return strconv.FormatBool(v), nil
|
||||
default:
|
||||
// For complex types, marshal to JSON
|
||||
bytes, err := json.Marshal(v)
|
||||
return string(bytes), err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) deserializeValue(strValue string, target interface{}) error {
|
||||
rv := reflect.ValueOf(target)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("target must be a pointer")
|
||||
}
|
||||
|
||||
elem := rv.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.String:
|
||||
elem.SetString(strValue)
|
||||
return nil
|
||||
case reflect.Int:
|
||||
i, err := strconv.Atoi(strValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse int: %w", err)
|
||||
}
|
||||
elem.SetInt(int64(i))
|
||||
return nil
|
||||
case reflect.Bool:
|
||||
b, err := strconv.ParseBool(strValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse bool: %w", err)
|
||||
}
|
||||
elem.SetBool(b)
|
||||
return nil
|
||||
default:
|
||||
// For complex types, unmarshal from JSON
|
||||
if elem.Type().Kind() == reflect.String {
|
||||
// Custom string-based types (like LangCode)
|
||||
elem.Set(reflect.ValueOf(strValue).Convert(elem.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try JSON unmarshaling for complex types
|
||||
return json.Unmarshal([]byte(strValue), target)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"wox/util"
|
||||
)
|
||||
|
||||
// ValidatorFunc is a function type for validating setting values
|
||||
type ValidatorFunc[T any] func(T) bool
|
||||
|
||||
// Value is a generic type that represents a single, observable setting.
|
||||
// It handles lazy loading and persisting of its value.
|
||||
type Value[T any] struct {
|
||||
key string
|
||||
defaultValue T
|
||||
value T
|
||||
isLoaded bool
|
||||
store WoxSettingStore
|
||||
validator ValidatorFunc[T]
|
||||
syncable bool
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// platform specific setting value. Don't set this value directly, use get,set instead
|
||||
type PlatformValue[T any] struct {
|
||||
*Value[struct {
|
||||
MacValue T
|
||||
WinValue T
|
||||
LinuxValue T
|
||||
}]
|
||||
}
|
||||
|
||||
func (p *PlatformValue[T]) Get() T {
|
||||
if util.IsWindows() {
|
||||
return p.Value.Get().WinValue
|
||||
} else if util.IsMacOS() {
|
||||
return p.Value.Get().MacValue
|
||||
} else if util.IsLinux() {
|
||||
return p.Value.Get().LinuxValue
|
||||
}
|
||||
|
||||
panic("unknown platform")
|
||||
}
|
||||
|
||||
func (p *PlatformValue[T]) Set(t T) {
|
||||
if util.IsWindows() {
|
||||
p.value.WinValue = t
|
||||
p.Value.Set(p.value)
|
||||
return
|
||||
} else if util.IsMacOS() {
|
||||
p.value.MacValue = t
|
||||
p.Value.Set(p.value)
|
||||
return
|
||||
} else if util.IsLinux() {
|
||||
p.value.LinuxValue = t
|
||||
p.Value.Set(p.value)
|
||||
return
|
||||
}
|
||||
|
||||
panic("unknown platform")
|
||||
}
|
||||
|
||||
// NewValue creates a new setting value using the unified store interface.
|
||||
func NewValue[T any](store WoxSettingStore, key string, defaultValue T) *Value[T] {
|
||||
return &Value[T]{
|
||||
store: store,
|
||||
key: key,
|
||||
defaultValue: defaultValue,
|
||||
}
|
||||
}
|
||||
|
||||
// NewValueWithValidator creates a new setting value with a validator function using the unified store interface.
|
||||
func NewValueWithValidator[T any](store WoxSettingStore, key string, defaultValue T, validator ValidatorFunc[T]) *Value[T] {
|
||||
return &Value[T]{
|
||||
store: store,
|
||||
key: key,
|
||||
defaultValue: defaultValue,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
func NewPlatformValue[T any](store WoxSettingStore, key string, winValue T, macValue T, linuxValue T) *PlatformValue[T] {
|
||||
return &PlatformValue[T]{
|
||||
Value: NewValue(store, key, struct {
|
||||
MacValue T
|
||||
WinValue T
|
||||
LinuxValue T
|
||||
}{
|
||||
MacValue: macValue,
|
||||
WinValue: winValue,
|
||||
LinuxValue: linuxValue,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value of the setting, loading it from the store if necessary.
|
||||
func (v *Value[T]) Get() T {
|
||||
v.mu.RLock()
|
||||
if v.isLoaded {
|
||||
defer v.mu.RUnlock()
|
||||
return v.value
|
||||
}
|
||||
v.mu.RUnlock()
|
||||
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
// Double-check in case another goroutine loaded it while we were waiting for the lock.
|
||||
if v.isLoaded {
|
||||
return v.value
|
||||
}
|
||||
|
||||
// Load from unified store
|
||||
v.value = v.defaultValue // Start with default value
|
||||
if v.store != nil {
|
||||
if err := v.store.Get(v.key, &v.value); err != nil {
|
||||
// Log error and keep default value
|
||||
v.value = v.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// Apply validation if provided
|
||||
if v.validator != nil && !v.validator(v.value) {
|
||||
v.value = v.defaultValue
|
||||
}
|
||||
|
||||
v.isLoaded = true
|
||||
return v.value
|
||||
}
|
||||
|
||||
// Set updates the value of the setting and persists it to the store.
|
||||
func (v *Value[T]) Set(newValue T) error {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
var err error
|
||||
if v.store != nil {
|
||||
err = v.store.Set(v.key, newValue)
|
||||
} else {
|
||||
return fmt.Errorf("no store available")
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
v.value = newValue
|
||||
v.isLoaded = true
|
||||
|
||||
if v.syncable {
|
||||
return v.store.LogOplog(v.key, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"wox/common"
|
||||
"wox/util"
|
||||
)
|
||||
|
||||
type ResultHash string
|
||||
|
||||
type WoxAppData struct {
|
||||
QueryHistories []QueryHistory
|
||||
ActionedResults *util.HashMap[ResultHash, []ActionedResult]
|
||||
FavoriteResults *util.HashMap[ResultHash, bool]
|
||||
}
|
||||
|
||||
type QueryHistory struct {
|
||||
Query common.PlainQuery
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
type ActionedResult struct {
|
||||
Timestamp int64
|
||||
Query string // Record the raw query text when the user performs action on this result
|
||||
}
|
||||
|
||||
func NewResultHash(pluginId string, title, subTitle string) ResultHash {
|
||||
return ResultHash(util.Md5([]byte(fmt.Sprintf("%s%s%s", pluginId, title, subTitle))))
|
||||
}
|
||||
|
||||
func GetDefaultWoxAppData(ctx context.Context) WoxAppData {
|
||||
return WoxAppData{
|
||||
QueryHistories: []QueryHistory{},
|
||||
ActionedResults: util.NewHashMap[ResultHash, []ActionedResult](),
|
||||
FavoriteResults: util.NewHashMap[ResultHash, bool](),
|
||||
}
|
||||
}
|
|
@ -1,46 +1,52 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"wox/common"
|
||||
"wox/i18n"
|
||||
"wox/util"
|
||||
"wox/util/locale"
|
||||
)
|
||||
|
||||
type WoxSetting struct {
|
||||
EnableAutostart PlatformSettingValue[bool]
|
||||
MainHotkey PlatformSettingValue[string]
|
||||
SelectionHotkey PlatformSettingValue[string]
|
||||
UsePinYin bool
|
||||
SwitchInputMethodABC bool
|
||||
HideOnStart bool
|
||||
HideOnLostFocus bool
|
||||
ShowTray bool
|
||||
LangCode i18n.LangCode
|
||||
QueryHotkeys PlatformSettingValue[[]QueryHotkey]
|
||||
QueryShortcuts []QueryShortcut
|
||||
LastQueryMode LastQueryMode
|
||||
ShowPosition PositionType
|
||||
AIProviders []AIProvider
|
||||
EnableAutoBackup bool // Enable automatic data backup
|
||||
EnableAutoUpdate bool // Enable automatic update check and download
|
||||
CustomPythonPath PlatformSettingValue[string] // Custom Python executable path
|
||||
CustomNodejsPath PlatformSettingValue[string] // Custom Node.js executable path
|
||||
EnableAutostart *PlatformValue[bool]
|
||||
MainHotkey *PlatformValue[string]
|
||||
SelectionHotkey *PlatformValue[string]
|
||||
UsePinYin *Value[bool]
|
||||
SwitchInputMethodABC *Value[bool]
|
||||
HideOnStart *Value[bool]
|
||||
HideOnLostFocus *Value[bool]
|
||||
ShowTray *Value[bool]
|
||||
LangCode *Value[i18n.LangCode]
|
||||
QueryHotkeys *PlatformValue[[]QueryHotkey]
|
||||
QueryShortcuts *Value[[]QueryShortcut]
|
||||
LastQueryMode *Value[LastQueryMode]
|
||||
ShowPosition *Value[PositionType]
|
||||
AIProviders *Value[[]AIProvider]
|
||||
EnableAutoBackup *Value[bool]
|
||||
EnableAutoUpdate *Value[bool]
|
||||
CustomPythonPath *PlatformValue[string]
|
||||
CustomNodejsPath *PlatformValue[string]
|
||||
|
||||
// HTTP proxy settings
|
||||
HttpProxyEnabled PlatformSettingValue[bool]
|
||||
HttpProxyUrl PlatformSettingValue[string]
|
||||
HttpProxyEnabled *PlatformValue[bool]
|
||||
HttpProxyUrl *PlatformValue[string]
|
||||
|
||||
// UI related
|
||||
AppWidth int
|
||||
MaxResultCount int
|
||||
ThemeId string
|
||||
AppWidth *Value[int]
|
||||
MaxResultCount *Value[int]
|
||||
ThemeId *Value[string]
|
||||
|
||||
// Window position for last location mode
|
||||
LastWindowX int
|
||||
LastWindowY int
|
||||
LastWindowX *Value[int]
|
||||
LastWindowY *Value[int]
|
||||
|
||||
// Data that was previously in WoxAppData
|
||||
QueryHistories *Value[[]QueryHistory]
|
||||
FavoriteResults *Value[*util.HashMap[ResultHash, bool]]
|
||||
ActionedResults *Value[*util.HashMap[ResultHash, []ActionedResult]]
|
||||
}
|
||||
|
||||
type LastQueryMode = string
|
||||
|
@ -87,65 +93,66 @@ type QueryHotkey struct {
|
|||
IsSilentExecution bool // If true, the query will be executed without showing the query in the input box
|
||||
}
|
||||
|
||||
func GetDefaultWoxSetting(ctx context.Context) WoxSetting {
|
||||
// ResultHash is a unique identifier for a result.
|
||||
// It is used to store actioned results and favorite results.
|
||||
type ResultHash string
|
||||
|
||||
func NewResultHash(pluginId, title, subTitle string) ResultHash {
|
||||
return ResultHash(util.Md5([]byte(fmt.Sprintf("%s%s%s", pluginId, title, subTitle))))
|
||||
}
|
||||
|
||||
// ActionedResult stores the information of an actioned result.
|
||||
type ActionedResult struct {
|
||||
Timestamp int64
|
||||
Query string // Record the raw query text when the user performs action on this result
|
||||
}
|
||||
|
||||
// QueryHistory stores the information of a query history.
|
||||
type QueryHistory struct {
|
||||
Query common.PlainQuery
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
func NewWoxSetting(store WoxSettingStore) *WoxSetting {
|
||||
usePinYin := false
|
||||
langCode := i18n.LangCodeEnUs
|
||||
defaultLangCode := i18n.LangCodeEnUs
|
||||
switchInputMethodABC := false
|
||||
if locale.IsZhCN() {
|
||||
usePinYin = true
|
||||
switchInputMethodABC = true
|
||||
langCode = i18n.LangCodeZhCn
|
||||
defaultLangCode = i18n.LangCodeZhCn
|
||||
}
|
||||
|
||||
return WoxSetting{
|
||||
MainHotkey: PlatformSettingValue[string]{
|
||||
WinValue: "alt+space",
|
||||
MacValue: "command+space",
|
||||
LinuxValue: "ctrl+ctrl",
|
||||
},
|
||||
SelectionHotkey: PlatformSettingValue[string]{
|
||||
WinValue: "win+alt+space",
|
||||
MacValue: "command+option+space",
|
||||
LinuxValue: "ctrl+shift+j",
|
||||
},
|
||||
UsePinYin: usePinYin,
|
||||
SwitchInputMethodABC: switchInputMethodABC,
|
||||
ShowTray: true,
|
||||
HideOnLostFocus: true,
|
||||
LangCode: langCode,
|
||||
LastQueryMode: LastQueryModeEmpty,
|
||||
ShowPosition: PositionTypeMouseScreen,
|
||||
AppWidth: 800,
|
||||
MaxResultCount: 10,
|
||||
ThemeId: DefaultThemeId,
|
||||
EnableAutostart: PlatformSettingValue[bool]{
|
||||
WinValue: false,
|
||||
MacValue: false,
|
||||
LinuxValue: false,
|
||||
},
|
||||
HttpProxyEnabled: PlatformSettingValue[bool]{
|
||||
WinValue: false,
|
||||
MacValue: false,
|
||||
LinuxValue: false,
|
||||
},
|
||||
HttpProxyUrl: PlatformSettingValue[string]{
|
||||
WinValue: "",
|
||||
MacValue: "",
|
||||
LinuxValue: "",
|
||||
},
|
||||
CustomPythonPath: PlatformSettingValue[string]{
|
||||
WinValue: "",
|
||||
MacValue: "",
|
||||
LinuxValue: "",
|
||||
},
|
||||
CustomNodejsPath: PlatformSettingValue[string]{
|
||||
WinValue: "",
|
||||
MacValue: "",
|
||||
LinuxValue: "",
|
||||
},
|
||||
EnableAutoBackup: true,
|
||||
EnableAutoUpdate: true,
|
||||
LastWindowX: -1, // -1 indicates no saved position
|
||||
LastWindowY: -1,
|
||||
return &WoxSetting{
|
||||
MainHotkey: NewPlatformValue(store, "MainHotkey", "alt+space", "option+space", "ctrl+space"),
|
||||
SelectionHotkey: NewPlatformValue(store, "SelectionHotkey", "ctrl+alt+space", "command+option+space", "ctrl+shift+j"),
|
||||
UsePinYin: NewValue(store, "UsePinYin", usePinYin),
|
||||
SwitchInputMethodABC: NewValue(store, "SwitchInputMethodABC", switchInputMethodABC),
|
||||
ShowTray: NewValue(store, "ShowTray", true),
|
||||
HideOnLostFocus: NewValue(store, "HideOnLostFocus", true),
|
||||
HideOnStart: NewValue(store, "HideOnStart", false),
|
||||
LangCode: NewValueWithValidator(store, "LangCode", defaultLangCode, func(code i18n.LangCode) bool {
|
||||
return i18n.IsSupportedLangCode(string(code))
|
||||
}),
|
||||
LastQueryMode: NewValue(store, "LastQueryMode", LastQueryModeEmpty),
|
||||
ShowPosition: NewValue(store, "ShowPosition", PositionTypeMouseScreen),
|
||||
AppWidth: NewValue(store, "AppWidth", 800),
|
||||
MaxResultCount: NewValue(store, "MaxResultCount", 10),
|
||||
ThemeId: NewValue(store, "ThemeId", DefaultThemeId),
|
||||
EnableAutostart: NewPlatformValue(store, "EnableAutostart", false, false, false),
|
||||
HttpProxyEnabled: NewPlatformValue(store, "HttpProxyEnabled", false, false, false),
|
||||
HttpProxyUrl: NewPlatformValue(store, "HttpProxyUrl", "", "", ""),
|
||||
CustomPythonPath: NewPlatformValue(store, "CustomPythonPath", "", "", ""),
|
||||
CustomNodejsPath: NewPlatformValue(store, "CustomNodejsPath", "", "", ""),
|
||||
EnableAutoBackup: NewValue(store, "EnableAutoBackup", true),
|
||||
EnableAutoUpdate: NewValue(store, "EnableAutoUpdate", true),
|
||||
LastWindowX: NewValue(store, "LastWindowX", -1),
|
||||
LastWindowY: NewValue(store, "LastWindowY", -1),
|
||||
QueryHotkeys: NewPlatformValue(store, "QueryHotkeys", []QueryHotkey{}, []QueryHotkey{}, []QueryHotkey{}),
|
||||
QueryShortcuts: NewValue(store, "QueryShortcuts", []QueryShortcut{}),
|
||||
AIProviders: NewValue(store, "AIProviders", []AIProvider{}),
|
||||
QueryHistories: NewValue(store, "QueryHistories", []QueryHistory{}),
|
||||
FavoriteResults: NewValue(store, "FavoriteResults", util.NewHashMap[ResultHash, bool]()),
|
||||
ActionedResults: NewValue(store, "ActionedResults", util.NewHashMap[ResultHash, []ActionedResult]()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -343,21 +343,19 @@ func (suite *ConfigBlackboxTestSuite) validateLoadedWoxSetting(t *testing.T, wox
|
|||
return
|
||||
}
|
||||
|
||||
defaultSetting := setting.GetDefaultWoxSetting(context.Background())
|
||||
|
||||
// Check critical fields have reasonable values
|
||||
if expectDefaults {
|
||||
// When expecting defaults, missing fields should be filled with default values
|
||||
if woxSetting.AppWidth == 0 {
|
||||
if woxSetting.AppWidth.Get() == 0 {
|
||||
t.Errorf("AppWidth should have default value, got 0 in test %s", testName)
|
||||
}
|
||||
if woxSetting.MaxResultCount == 0 {
|
||||
if woxSetting.MaxResultCount.Get() == 0 {
|
||||
t.Errorf("MaxResultCount should have default value, got 0 in test %s", testName)
|
||||
}
|
||||
if woxSetting.ThemeId == "" {
|
||||
if woxSetting.ThemeId.Get() == "" {
|
||||
t.Errorf("ThemeId should have default value, got empty string in test %s", testName)
|
||||
}
|
||||
if woxSetting.LangCode == "" {
|
||||
if woxSetting.LangCode.Get() == "" {
|
||||
t.Errorf("LangCode should have default value, got empty string in test %s", testName)
|
||||
}
|
||||
if woxSetting.MainHotkey.Get() == "" {
|
||||
|
@ -367,13 +365,13 @@ func (suite *ConfigBlackboxTestSuite) validateLoadedWoxSetting(t *testing.T, wox
|
|||
|
||||
// Log the loaded values for debugging
|
||||
t.Logf("Loaded setting values for %s:", testName)
|
||||
t.Logf(" AppWidth: %d (default: %d)", woxSetting.AppWidth, defaultSetting.AppWidth)
|
||||
t.Logf(" MaxResultCount: %d (default: %d)", woxSetting.MaxResultCount, defaultSetting.MaxResultCount)
|
||||
t.Logf(" UsePinYin: %t (default: %t)", woxSetting.UsePinYin, defaultSetting.UsePinYin)
|
||||
t.Logf(" ShowTray: %t (default: %t)", woxSetting.ShowTray, defaultSetting.ShowTray)
|
||||
t.Logf(" LangCode: %s (default: %s)", woxSetting.LangCode, defaultSetting.LangCode)
|
||||
t.Logf(" ThemeId: %s (default: %s)", woxSetting.ThemeId, defaultSetting.ThemeId)
|
||||
t.Logf(" MainHotkey: %s (default: %s)", woxSetting.MainHotkey.Get(), defaultSetting.MainHotkey.Get())
|
||||
t.Logf(" AppWidth: %d", woxSetting.AppWidth.Get())
|
||||
t.Logf(" MaxResultCount: %d", woxSetting.MaxResultCount.Get())
|
||||
t.Logf(" UsePinYin: %t", woxSetting.UsePinYin.Get())
|
||||
t.Logf(" ShowTray: %t", woxSetting.ShowTray.Get())
|
||||
t.Logf(" LangCode: %s", woxSetting.LangCode.Get())
|
||||
t.Logf(" ThemeId: %s", woxSetting.ThemeId.Get())
|
||||
t.Logf(" MainHotkey: %s", woxSetting.MainHotkey.Get())
|
||||
}
|
||||
|
||||
// cleanup cleans up test resources
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
"wox/common"
|
||||
"wox/database"
|
||||
"wox/i18n"
|
||||
"wox/plugin"
|
||||
"wox/resource"
|
||||
|
@ -228,6 +229,12 @@ func ensureServicesInitialized(t *testing.T) {
|
|||
t.Fatalf("Failed to extract resources: %v", err)
|
||||
}
|
||||
|
||||
// Initialize database
|
||||
err = database.Init(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to initialize database: %v", err)
|
||||
}
|
||||
|
||||
// Initialize settings
|
||||
err = setting.GetSettingManager().Init(ctx)
|
||||
if err != nil {
|
||||
|
|
|
@ -327,7 +327,7 @@ func (m *Manager) StartUIApp(ctx context.Context) error {
|
|||
|
||||
func (m *Manager) GetCurrentTheme(ctx context.Context) common.Theme {
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
if v, ok := m.themes.Load(woxSetting.ThemeId); ok {
|
||||
if v, ok := m.themes.Load(woxSetting.ThemeId.Get()); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
|
@ -413,7 +413,7 @@ func (m *Manager) PostUIReady(ctx context.Context) {
|
|||
m.isUIReadyHandled = true
|
||||
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
if !woxSetting.HideOnStart {
|
||||
if !woxSetting.HideOnStart.Get() {
|
||||
m.ui.ShowApp(ctx, common.ShowContext{SelectAll: false})
|
||||
}
|
||||
}
|
||||
|
@ -424,7 +424,7 @@ func (m *Manager) PostOnShow(ctx context.Context) {
|
|||
|
||||
func (m *Manager) PostOnQueryBoxFocus(ctx context.Context) {
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
if woxSetting.SwitchInputMethodABC {
|
||||
if woxSetting.SwitchInputMethodABC.Get() {
|
||||
util.GetLogger().Info(ctx, "switch input method to ABC on query box focus")
|
||||
switchErr := ime.SwitchInputMethodABC()
|
||||
if switchErr != nil {
|
||||
|
@ -434,7 +434,7 @@ func (m *Manager) PostOnQueryBoxFocus(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (m *Manager) PostOnHide(ctx context.Context, query common.PlainQuery) {
|
||||
setting.GetSettingManager().AddQueryHistory(ctx, query)
|
||||
// no-op
|
||||
}
|
||||
|
||||
func (m *Manager) IsSystemTheme(id string) bool {
|
||||
|
@ -481,21 +481,19 @@ func (m *Manager) HideTray() {
|
|||
tray.RemoveTray()
|
||||
}
|
||||
|
||||
func (m *Manager) PostSettingUpdate(ctx context.Context, key, value string) {
|
||||
if key == "ShowTray" {
|
||||
if value == "true" {
|
||||
func (m *Manager) PostSettingUpdate(ctx context.Context, key string, value any) {
|
||||
switch key {
|
||||
case "ShowTray":
|
||||
if value.(bool) {
|
||||
m.ShowTray()
|
||||
} else {
|
||||
m.HideTray()
|
||||
}
|
||||
}
|
||||
if key == "MainHotkey" {
|
||||
m.RegisterMainHotkey(ctx, value)
|
||||
}
|
||||
if key == "SelectionHotkey" {
|
||||
m.RegisterSelectionHotkey(ctx, value)
|
||||
}
|
||||
if key == "QueryHotkeys" {
|
||||
case "MainHotkey":
|
||||
m.RegisterMainHotkey(ctx, value.(string))
|
||||
case "SelectionHotkey":
|
||||
m.RegisterSelectionHotkey(ctx, value.(string))
|
||||
case "QueryHotkeys":
|
||||
// unregister previous hotkeys
|
||||
logger.Info(ctx, "post update query hotkeys, unregister previous query hotkeys")
|
||||
for _, hk := range m.queryHotkeys {
|
||||
|
@ -507,18 +505,15 @@ func (m *Manager) PostSettingUpdate(ctx context.Context, key, value string) {
|
|||
for _, queryHotkey := range queryHotkeys {
|
||||
m.RegisterQueryHotkey(ctx, queryHotkey)
|
||||
}
|
||||
}
|
||||
if key == "EnableAutostart" {
|
||||
enabled := value == "true"
|
||||
case "EnableAutostart":
|
||||
enabled := value.(bool)
|
||||
err := autostart.SetAutostart(ctx, enabled)
|
||||
if err != nil {
|
||||
logger.Error(ctx, fmt.Sprintf("failed to set autostart: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
if key == "EnableAutoUpdate" {
|
||||
case "EnableAutoUpdate":
|
||||
updater.CheckForUpdates(ctx)
|
||||
}
|
||||
if key == "AIProviders" {
|
||||
case "AIProviders":
|
||||
plugin.GetPluginManager().GetUI().ReloadChatResources(ctx, "models")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -490,28 +490,38 @@ func handleSettingWox(w http.ResponseWriter, r *http.Request) {
|
|||
woxSetting := setting.GetSettingManager().GetWoxSetting(util.NewTraceContext())
|
||||
|
||||
var settingDto dto.WoxSettingDto
|
||||
copyErr := copier.Copy(&settingDto, &woxSetting)
|
||||
if copyErr != nil {
|
||||
writeErrorResponse(w, copyErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
settingDto.EnableAutostart = woxSetting.EnableAutostart.Get()
|
||||
settingDto.MainHotkey = woxSetting.MainHotkey.Get()
|
||||
settingDto.SelectionHotkey = woxSetting.SelectionHotkey.Get()
|
||||
settingDto.UsePinYin = woxSetting.UsePinYin.Get()
|
||||
settingDto.SwitchInputMethodABC = woxSetting.SwitchInputMethodABC.Get()
|
||||
settingDto.HideOnStart = woxSetting.HideOnStart.Get()
|
||||
settingDto.HideOnLostFocus = woxSetting.HideOnLostFocus.Get()
|
||||
settingDto.ShowTray = woxSetting.ShowTray.Get()
|
||||
settingDto.LangCode = woxSetting.LangCode.Get()
|
||||
settingDto.QueryHotkeys = woxSetting.QueryHotkeys.Get()
|
||||
settingDto.QueryShortcuts = woxSetting.QueryShortcuts.Get()
|
||||
settingDto.LastQueryMode = woxSetting.LastQueryMode.Get()
|
||||
settingDto.AIProviders = woxSetting.AIProviders.Get()
|
||||
settingDto.HttpProxyEnabled = woxSetting.HttpProxyEnabled.Get()
|
||||
settingDto.HttpProxyUrl = woxSetting.HttpProxyUrl.Get()
|
||||
settingDto.ShowPosition = woxSetting.ShowPosition.Get()
|
||||
settingDto.EnableAutoBackup = woxSetting.EnableAutoBackup.Get()
|
||||
settingDto.EnableAutoUpdate = woxSetting.EnableAutoUpdate.Get()
|
||||
settingDto.CustomPythonPath = woxSetting.CustomPythonPath.Get()
|
||||
settingDto.CustomNodejsPath = woxSetting.CustomNodejsPath.Get()
|
||||
|
||||
settingDto.AppWidth = woxSetting.AppWidth.Get()
|
||||
settingDto.MaxResultCount = woxSetting.MaxResultCount.Get()
|
||||
settingDto.ThemeId = woxSetting.ThemeId.Get()
|
||||
|
||||
writeSuccessResponse(w, settingDto)
|
||||
}
|
||||
|
||||
func handleSettingWoxUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
type keyValuePair struct {
|
||||
Key string
|
||||
Value string
|
||||
Value any
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
@ -522,9 +532,52 @@ func handleSettingWoxUpdate(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
updateErr := setting.GetSettingManager().UpdateWoxSetting(util.NewTraceContext(), kv.Key, kv.Value)
|
||||
if updateErr != nil {
|
||||
writeErrorResponse(w, updateErr.Error())
|
||||
ctx := util.NewTraceContext()
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
|
||||
switch kv.Key {
|
||||
case "EnableAutostart":
|
||||
woxSetting.EnableAutostart.Set(kv.Value.(bool))
|
||||
case "MainHotkey":
|
||||
woxSetting.MainHotkey.Set(kv.Value.(string))
|
||||
case "SelectionHotkey":
|
||||
woxSetting.SelectionHotkey.Set(kv.Value.(string))
|
||||
case "UsePinYin":
|
||||
woxSetting.UsePinYin.Set(kv.Value.(bool))
|
||||
case "SwitchInputMethodABC":
|
||||
woxSetting.SwitchInputMethodABC.Set(kv.Value.(bool))
|
||||
case "HideOnStart":
|
||||
woxSetting.HideOnStart.Set(kv.Value.(bool))
|
||||
case "HideOnLostFocus":
|
||||
woxSetting.HideOnLostFocus.Set(kv.Value.(bool))
|
||||
case "ShowTray":
|
||||
woxSetting.ShowTray.Set(kv.Value.(bool))
|
||||
case "LangCode":
|
||||
woxSetting.LangCode.Set(i18n.LangCode(kv.Value.(string)))
|
||||
case "LastQueryMode":
|
||||
woxSetting.LastQueryMode.Set(setting.LastQueryMode(kv.Value.(string)))
|
||||
case "ShowPosition":
|
||||
woxSetting.ShowPosition.Set(setting.PositionType(kv.Value.(string)))
|
||||
case "EnableAutoBackup":
|
||||
woxSetting.EnableAutoBackup.Set(kv.Value.(bool))
|
||||
case "EnableAutoUpdate":
|
||||
woxSetting.EnableAutoUpdate.Set(kv.Value.(bool))
|
||||
case "CustomPythonPath":
|
||||
woxSetting.CustomPythonPath.Set(kv.Value.(string))
|
||||
case "CustomNodejsPath":
|
||||
woxSetting.CustomNodejsPath.Set(kv.Value.(string))
|
||||
case "HttpProxyEnabled":
|
||||
woxSetting.HttpProxyEnabled.Set(kv.Value.(bool))
|
||||
case "HttpProxyUrl":
|
||||
woxSetting.HttpProxyUrl.Set(kv.Value.(string))
|
||||
case "AppWidth":
|
||||
woxSetting.AppWidth.Set(int(kv.Value.(float64)))
|
||||
case "MaxResultCount":
|
||||
woxSetting.MaxResultCount.Set(int(kv.Value.(float64)))
|
||||
case "ThemeId":
|
||||
woxSetting.ThemeId.Set(kv.Value.(string))
|
||||
default:
|
||||
writeErrorResponse(w, "unknown setting key: "+kv.Key)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -611,12 +664,9 @@ func handleSaveWindowPosition(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
logger.Info(ctx, fmt.Sprintf("Received window position save request: x=%d, y=%d", pos.X, pos.Y))
|
||||
|
||||
saveErr := setting.GetSettingManager().SaveWindowPosition(ctx, pos.X, pos.Y)
|
||||
if saveErr != nil {
|
||||
logger.Error(ctx, fmt.Sprintf("Failed to save window position: %s", saveErr.Error()))
|
||||
writeErrorResponse(w, saveErr.Error())
|
||||
return
|
||||
}
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
woxSetting.LastWindowX.Set(pos.X)
|
||||
woxSetting.LastWindowY.Set(pos.Y)
|
||||
|
||||
logger.Info(ctx, fmt.Sprintf("Window position saved successfully: x=%d, y=%d", pos.X, pos.Y))
|
||||
writeSuccessResponse(w, "")
|
||||
|
@ -701,7 +751,7 @@ func handleOnUIReady(w http.ResponseWriter, r *http.Request) {
|
|||
func handleOnFocusLost(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := util.NewTraceContext()
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
if woxSetting.HideOnLostFocus {
|
||||
if woxSetting.HideOnLostFocus.Get() {
|
||||
GetUIManager().GetUI(ctx).HideApp(ctx)
|
||||
}
|
||||
writeSuccessResponse(w, "")
|
||||
|
@ -883,7 +933,7 @@ func handleAIModels(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var results = []common.Model{}
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
for _, providerSetting := range woxSetting.AIProviders {
|
||||
for _, providerSetting := range woxSetting.AIProviders.Get() {
|
||||
provider, err := ai.NewProvider(ctx, providerSetting)
|
||||
if err != nil {
|
||||
logger.Error(ctx, fmt.Sprintf("failed to new ai provider: %s", err.Error()))
|
||||
|
|
|
@ -50,8 +50,7 @@ func (u *uiImpl) GetServerPort(ctx context.Context) int {
|
|||
func (u *uiImpl) ChangeTheme(ctx context.Context, theme common.Theme) {
|
||||
logger.Info(ctx, fmt.Sprintf("change theme: %s", theme.ThemeName))
|
||||
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
woxSetting.ThemeId = theme.ThemeId
|
||||
setting.GetSettingManager().SaveWoxSetting(ctx)
|
||||
woxSetting.ThemeId.Set(theme.ThemeId)
|
||||
u.invokeWebsocketMethod(ctx, "ChangeTheme", theme)
|
||||
}
|
||||
|
||||
|
@ -182,21 +181,21 @@ func getShowAppParams(ctx context.Context, showContext common.ShowContext) map[s
|
|||
var position Position
|
||||
|
||||
// Now we can directly use the ShowPosition as a PositionType
|
||||
switch woxSetting.ShowPosition {
|
||||
switch woxSetting.ShowPosition.Get() {
|
||||
case setting.PositionTypeActiveScreen:
|
||||
position = NewActiveScreenPosition(woxSetting.AppWidth)
|
||||
position = NewActiveScreenPosition(woxSetting.AppWidth.Get())
|
||||
case setting.PositionTypeLastLocation:
|
||||
// Use saved window position if available, otherwise use mouse screen position as fallback
|
||||
if woxSetting.LastWindowX != -1 && woxSetting.LastWindowY != -1 {
|
||||
logger.Info(ctx, fmt.Sprintf("Using saved window position: x=%d, y=%d", woxSetting.LastWindowX, woxSetting.LastWindowY))
|
||||
position = NewLastLocationPosition(woxSetting.LastWindowX, woxSetting.LastWindowY)
|
||||
if woxSetting.LastWindowX.Get() != -1 && woxSetting.LastWindowY.Get() != -1 {
|
||||
logger.Info(ctx, fmt.Sprintf("Using saved window position: x=%d, y=%d", woxSetting.LastWindowX.Get(), woxSetting.LastWindowY.Get()))
|
||||
position = NewLastLocationPosition(woxSetting.LastWindowX.Get(), woxSetting.LastWindowY.Get())
|
||||
} else {
|
||||
logger.Info(ctx, "No saved window position, using mouse screen position as fallback")
|
||||
// No saved position, fallback to mouse screen position
|
||||
position = NewMouseScreenPosition(woxSetting.AppWidth)
|
||||
position = NewMouseScreenPosition(woxSetting.AppWidth.Get())
|
||||
}
|
||||
default: // Default to mouse screen
|
||||
position = NewMouseScreenPosition(woxSetting.AppWidth)
|
||||
position = NewMouseScreenPosition(woxSetting.AppWidth.Get())
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
|
@ -204,7 +203,7 @@ func getShowAppParams(ctx context.Context, showContext common.ShowContext) map[s
|
|||
"AutoFocusToChatInput": showContext.AutoFocusToChatInput,
|
||||
"Position": position,
|
||||
"QueryHistories": setting.GetSettingManager().GetLatestQueryHistory(ctx, 10),
|
||||
"LastQueryMode": woxSetting.LastQueryMode,
|
||||
"LastQueryMode": woxSetting.LastQueryMode.Get(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ func CheckForUpdates(ctx context.Context) {
|
|||
util.GetLogger().Info(ctx, "start checking for updates")
|
||||
|
||||
setting := setting.GetSettingManager().GetWoxSetting(ctx)
|
||||
if setting != nil && !setting.EnableAutoUpdate {
|
||||
if setting != nil && !setting.EnableAutoUpdate.Get() {
|
||||
util.GetLogger().Info(ctx, "auto update is disabled, skipping")
|
||||
currentUpdateInfo.Status = UpdateStatusNone
|
||||
currentUpdateInfo.HasUpdate = false
|
||||
|
|
|
@ -14,8 +14,13 @@ var locationInstance *Location
|
|||
var locationOnce sync.Once
|
||||
|
||||
type Location struct {
|
||||
woxDataDirectory string
|
||||
userDataDirectory string
|
||||
// wox data directory is the directory that contains all wox data, including logs, hosts, etc.
|
||||
woxDataDirectory string
|
||||
|
||||
// user data directory is the directory that contains all user data, including plugins, settings, etc.
|
||||
// user may change the user data directory to another location, E.g. icloud, google drive, etc.
|
||||
userDataDirectory string
|
||||
|
||||
userDataDirectoryShortcutPath string // A file named .wox.location that contains the user data directory path
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue