mirror of https://github.com/Wox-launcher/Wox
763 lines
24 KiB
Go
763 lines
24 KiB
Go
package setting
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"wox/common"
|
|
"wox/i18n"
|
|
"wox/setting/definition"
|
|
"wox/util"
|
|
"wox/util/autostart"
|
|
"wox/util/hotkey"
|
|
|
|
"github.com/tidwall/pretty"
|
|
)
|
|
|
|
var managerInstance *Manager
|
|
var managerOnce sync.Once
|
|
var logger *util.Log
|
|
|
|
type Manager struct {
|
|
woxSetting *WoxSetting
|
|
woxAppData *WoxAppData
|
|
}
|
|
|
|
func GetSettingManager() *Manager {
|
|
managerOnce.Do(func() {
|
|
managerInstance = &Manager{
|
|
woxSetting: &WoxSetting{},
|
|
woxAppData: &WoxAppData{},
|
|
}
|
|
logger = util.GetLogger()
|
|
})
|
|
return managerInstance
|
|
}
|
|
|
|
func (m *Manager) Init(ctx context.Context) error {
|
|
woxSettingErr := m.loadWoxSetting(ctx)
|
|
if woxSettingErr != nil {
|
|
return woxSettingErr
|
|
}
|
|
|
|
woxAppDataErr := m.loadWoxAppData(ctx)
|
|
if woxAppDataErr != nil {
|
|
// wox app data is not essential, so we just log the error and use default value
|
|
logger.Error(ctx, fmt.Sprintf("failed to load wox app data: %s", woxAppDataErr.Error()))
|
|
defaultWoxAppData := GetDefaultWoxAppData(ctx)
|
|
m.woxAppData = &defaultWoxAppData
|
|
}
|
|
|
|
m.StartAutoBackup(ctx)
|
|
|
|
//check autostart status, if not match, update the setting
|
|
actualAutostart, err := autostart.IsAutostart(ctx)
|
|
if err != nil {
|
|
util.GetLogger().Error(ctx, fmt.Sprintf("Failed to check autostart status: %s", err.Error()))
|
|
} else {
|
|
configAutostart := m.woxSetting.EnableAutostart.Get()
|
|
if actualAutostart != configAutostart {
|
|
util.GetLogger().Warn(ctx, fmt.Sprintf("Autostart setting mismatch: config %v, actual %v", configAutostart, actualAutostart))
|
|
|
|
// If config says autostart should be enabled but actual is false,
|
|
// try to re-enable autostart (this will fix broken autostart entries)
|
|
if configAutostart && !actualAutostart {
|
|
util.GetLogger().Info(ctx, "Attempting to fix autostart configuration...")
|
|
fixErr := autostart.SetAutostart(ctx, true)
|
|
if fixErr != nil {
|
|
util.GetLogger().Error(ctx, fmt.Sprintf("Failed to fix autostart: %s", fixErr.Error()))
|
|
// Update config to match actual state
|
|
m.woxSetting.EnableAutostart.Set(false)
|
|
} else {
|
|
util.GetLogger().Info(ctx, "Autostart configuration fixed successfully")
|
|
}
|
|
} else {
|
|
// Update config to match actual state
|
|
m.woxSetting.EnableAutostart.Set(actualAutostart)
|
|
}
|
|
|
|
err := m.SaveWoxSetting(ctx)
|
|
if err != nil {
|
|
util.GetLogger().Error(ctx, fmt.Sprintf("Failed to save updated autostart setting: %s", err.Error()))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) loadWoxSetting(ctx context.Context) error {
|
|
defaultWoxSetting := GetDefaultWoxSetting(ctx)
|
|
|
|
woxSettingPath := util.GetLocation().GetWoxSettingPath()
|
|
if _, statErr := os.Stat(woxSettingPath); os.IsNotExist(statErr) {
|
|
// Create default setting file if not exists
|
|
defaultWoxSettingJson, marshalErr := json.Marshal(defaultWoxSetting)
|
|
if marshalErr != nil {
|
|
return marshalErr
|
|
}
|
|
|
|
writeErr := os.WriteFile(woxSettingPath, pretty.Pretty(defaultWoxSettingJson), 0644)
|
|
if writeErr != nil {
|
|
return writeErr
|
|
}
|
|
m.woxSetting = &defaultWoxSetting
|
|
return nil
|
|
}
|
|
|
|
// Try to load setting with maximum tolerance for errors
|
|
woxSetting, err := m.loadWoxSettingWithFallback(ctx, woxSettingPath, defaultWoxSetting)
|
|
if err != nil {
|
|
// If all attempts fail, log error and use defaults
|
|
logger.Error(ctx, fmt.Sprintf("Failed to load setting file, using defaults: %v", err))
|
|
m.woxSetting = &defaultWoxSetting
|
|
return nil // Don't return error, just use defaults
|
|
}
|
|
|
|
m.woxSetting = woxSetting
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) loadWoxAppData(ctx context.Context) error {
|
|
woxAppDataPath := util.GetLocation().GetWoxAppDataPath()
|
|
if _, statErr := os.Stat(woxAppDataPath); os.IsNotExist(statErr) {
|
|
defaultWoxAppData := GetDefaultWoxAppData(ctx)
|
|
defaultWoxAppDataJson, marshalErr := json.Marshal(defaultWoxAppData)
|
|
if marshalErr != nil {
|
|
return marshalErr
|
|
}
|
|
|
|
writeErr := os.WriteFile(woxAppDataPath, pretty.Pretty(defaultWoxAppDataJson), 0644)
|
|
if writeErr != nil {
|
|
return writeErr
|
|
}
|
|
}
|
|
|
|
woxAppDataFile, openErr := os.Open(woxAppDataPath)
|
|
if openErr != nil {
|
|
return openErr
|
|
}
|
|
defer woxAppDataFile.Close()
|
|
|
|
woxAppData := &WoxAppData{}
|
|
decodeErr := json.NewDecoder(woxAppDataFile).Decode(woxAppData)
|
|
if decodeErr != nil {
|
|
return decodeErr
|
|
}
|
|
if woxAppData.ActionedResults == nil {
|
|
woxAppData.ActionedResults = util.NewHashMap[ResultHash, []ActionedResult]()
|
|
}
|
|
if woxAppData.FavoriteResults == nil {
|
|
woxAppData.FavoriteResults = util.NewHashMap[ResultHash, bool]()
|
|
}
|
|
|
|
// sort query histories by timestamp asc
|
|
slices.SortFunc(woxAppData.QueryHistories, func(i, j QueryHistory) int {
|
|
return int(i.Timestamp - j.Timestamp)
|
|
})
|
|
|
|
m.woxAppData = woxAppData
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) GetWoxSetting(ctx context.Context) *WoxSetting {
|
|
return m.woxSetting
|
|
}
|
|
|
|
func (m *Manager) UpdateWoxSetting(ctx context.Context, key, value string) error {
|
|
if key == "" {
|
|
return fmt.Errorf("key is empty")
|
|
}
|
|
|
|
if key == "HttpProxyEnabled" {
|
|
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, "")
|
|
}
|
|
} else if key == "HttpProxyUrl" {
|
|
m.woxSetting.HttpProxyUrl.Set(value)
|
|
if m.woxSetting.HttpProxyEnabled.Get() && value != "" {
|
|
m.onUpdateProxy(ctx, m.woxSetting.HttpProxyUrl.Get())
|
|
} else {
|
|
m.onUpdateProxy(ctx, "")
|
|
}
|
|
} else if key == "EnableAutostart" {
|
|
m.woxSetting.EnableAutostart.Set(value == "true")
|
|
} else if key == "MainHotkey" {
|
|
if value != "" {
|
|
isAvailable := hotkey.IsHotkeyAvailable(ctx, value)
|
|
if !isAvailable {
|
|
return fmt.Errorf("hotkey is not available: %s", value)
|
|
}
|
|
}
|
|
m.woxSetting.MainHotkey.Set(value)
|
|
} else if key == "SelectionHotkey" {
|
|
isAvailable := hotkey.IsHotkeyAvailable(ctx, value)
|
|
if !isAvailable {
|
|
return fmt.Errorf("hotkey is not available: %s", value)
|
|
}
|
|
m.woxSetting.SelectionHotkey.Set(value)
|
|
} else if key == "UsePinYin" {
|
|
m.woxSetting.UsePinYin = value == "true"
|
|
} else if key == "SwitchInputMethodABC" {
|
|
m.woxSetting.SwitchInputMethodABC = value == "true"
|
|
} else if key == "HideOnStart" {
|
|
m.woxSetting.HideOnStart = value == "true"
|
|
} else if key == "HideOnLostFocus" {
|
|
m.woxSetting.HideOnLostFocus = value == "true"
|
|
} else if key == "ShowTray" {
|
|
m.woxSetting.ShowTray = value == "true"
|
|
} else if key == "LangCode" {
|
|
newLangCode := i18n.LangCode(value)
|
|
langErr := i18n.GetI18nManager().UpdateLang(ctx, newLangCode)
|
|
if langErr != nil {
|
|
return langErr
|
|
}
|
|
m.woxSetting.LangCode = newLangCode
|
|
} else if key == "LastQueryMode" {
|
|
m.woxSetting.LastQueryMode = value
|
|
} else if key == "ThemeId" {
|
|
m.woxSetting.ThemeId = value
|
|
} else if key == "QueryHotkeys" {
|
|
// value is a json string
|
|
var queryHotkeys []QueryHotkey
|
|
if unmarshalErr := json.Unmarshal([]byte(value), &queryHotkeys); unmarshalErr != nil {
|
|
return unmarshalErr
|
|
}
|
|
m.woxSetting.QueryHotkeys.Set(queryHotkeys)
|
|
} else if key == "QueryShortcuts" {
|
|
// value is a json string
|
|
var queryShortcuts []QueryShortcut
|
|
if unmarshalErr := json.Unmarshal([]byte(value), &queryShortcuts); unmarshalErr != nil {
|
|
return unmarshalErr
|
|
}
|
|
|
|
m.woxSetting.QueryShortcuts = queryShortcuts
|
|
} else if key == "AIProviders" {
|
|
// value is a json string
|
|
var aiModels []AIProvider
|
|
if unmarshalErr := json.Unmarshal([]byte(value), &aiModels); unmarshalErr != nil {
|
|
return unmarshalErr
|
|
}
|
|
|
|
m.woxSetting.AIProviders = aiModels
|
|
} else if key == "ShowPosition" {
|
|
m.woxSetting.ShowPosition = PositionType(value)
|
|
} else if key == "EnableAutoBackup" {
|
|
m.woxSetting.EnableAutoBackup = value == "true"
|
|
} else if key == "EnableAutoUpdate" {
|
|
m.woxSetting.EnableAutoUpdate = value == "true"
|
|
} else if key == "AppWidth" {
|
|
appWidth, parseErr := strconv.Atoi(value)
|
|
if parseErr != nil {
|
|
return parseErr
|
|
}
|
|
m.woxSetting.AppWidth = appWidth
|
|
} else if key == "MaxResultCount" {
|
|
maxResultCount, parseErr := strconv.Atoi(value)
|
|
if parseErr != nil {
|
|
return parseErr
|
|
}
|
|
m.woxSetting.MaxResultCount = maxResultCount
|
|
} else if key == "CustomPythonPath" {
|
|
m.woxSetting.CustomPythonPath.Set(value)
|
|
} else if key == "CustomNodejsPath" {
|
|
m.woxSetting.CustomNodejsPath.Set(value)
|
|
} else {
|
|
return fmt.Errorf("unknown key: %s", key)
|
|
}
|
|
|
|
return m.SaveWoxSetting(ctx)
|
|
}
|
|
|
|
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 {
|
|
woxSettingPath := util.GetLocation().GetWoxSettingPath()
|
|
settingJson, marshalErr := json.Marshal(m.woxSetting)
|
|
if marshalErr != nil {
|
|
logger.Error(ctx, marshalErr.Error())
|
|
return marshalErr
|
|
}
|
|
|
|
writeErr := os.WriteFile(woxSettingPath, pretty.Pretty(settingJson), 0644)
|
|
if writeErr != nil {
|
|
logger.Error(ctx, writeErr.Error())
|
|
return writeErr
|
|
}
|
|
|
|
logger.Info(ctx, "Wox setting saved")
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) saveWoxAppData(ctx context.Context, reason string) error {
|
|
woxAppDataPath := util.GetLocation().GetWoxAppDataPath()
|
|
settingJson, marshalErr := json.Marshal(m.woxAppData)
|
|
if marshalErr != nil {
|
|
logger.Error(ctx, marshalErr.Error())
|
|
return marshalErr
|
|
}
|
|
|
|
writeErr := os.WriteFile(woxAppDataPath, pretty.Pretty(settingJson), 0644)
|
|
if writeErr != nil {
|
|
logger.Error(ctx, writeErr.Error())
|
|
return writeErr
|
|
}
|
|
|
|
logger.Info(ctx, fmt.Sprintf("Wox setting saved, reason: %s", reason))
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) LoadPluginSetting(ctx context.Context, pluginId string, pluginName string, defaultSettings definition.PluginSettingDefinitions) (*PluginSetting, error) {
|
|
pluginSettingPath := path.Join(util.GetLocation().GetPluginSettingDirectory(), fmt.Sprintf("%s.json", pluginId))
|
|
if _, statErr := os.Stat(pluginSettingPath); os.IsNotExist(statErr) {
|
|
return &PluginSetting{
|
|
Name: pluginName,
|
|
Settings: defaultSettings.GetAllDefaults(),
|
|
}, nil
|
|
}
|
|
|
|
fileContent, readErr := os.ReadFile(pluginSettingPath)
|
|
if readErr != nil {
|
|
return &PluginSetting{}, readErr
|
|
}
|
|
|
|
var pluginSetting = &PluginSetting{}
|
|
decodeErr := json.Unmarshal(fileContent, pluginSetting)
|
|
if decodeErr != nil {
|
|
return &PluginSetting{}, decodeErr
|
|
}
|
|
if pluginSetting.Settings == nil {
|
|
pluginSetting.Settings = defaultSettings.GetAllDefaults()
|
|
}
|
|
|
|
//check if all default settings are present in the plugin settings
|
|
//plugin author may add new definitions which are not in the user settings
|
|
defaultSettings.GetAllDefaults().Range(func(key string, value string) bool {
|
|
if _, exist := pluginSetting.Settings.Load(key); !exist {
|
|
pluginSetting.Settings.Store(key, value)
|
|
}
|
|
return true
|
|
})
|
|
|
|
pluginSetting.Name = pluginName
|
|
return pluginSetting, nil
|
|
}
|
|
|
|
// loadWoxSettingWithFallback attempts to load setting with multiple fallback strategies
|
|
func (m *Manager) loadWoxSettingWithFallback(ctx context.Context, settingPath string, defaultSetting WoxSetting) (*WoxSetting, error) {
|
|
// Strategy 1: Try normal JSON decoding
|
|
if setting, err := m.tryLoadWoxSetting(settingPath, defaultSetting); err == nil {
|
|
logger.Info(ctx, "Successfully loaded setting with normal JSON decoding")
|
|
return setting, nil
|
|
} else {
|
|
logger.Warn(ctx, fmt.Sprintf("Normal JSON decoding failed: %v", err))
|
|
}
|
|
|
|
// Strategy 2: Try to fix common JSON issues and reload
|
|
if setting, err := m.tryLoadWithJSONRepair(settingPath, defaultSetting); err == nil {
|
|
logger.Info(ctx, "Successfully loaded setting after JSON repair")
|
|
return setting, nil
|
|
} else {
|
|
logger.Warn(ctx, fmt.Sprintf("JSON repair failed: %v", err))
|
|
}
|
|
|
|
// Strategy 3: Try field-by-field parsing to salvage what we can
|
|
if setting, err := m.tryPartialLoad(settingPath, defaultSetting); err == nil {
|
|
logger.Info(ctx, "Successfully loaded setting with partial parsing")
|
|
return setting, nil
|
|
} else {
|
|
logger.Warn(ctx, fmt.Sprintf("Partial parsing failed: %v", err))
|
|
}
|
|
|
|
return nil, fmt.Errorf("all loading strategies failed")
|
|
}
|
|
|
|
// tryLoadWoxSetting attempts normal JSON decoding
|
|
func (m *Manager) tryLoadWoxSetting(settingPath string, defaultSetting WoxSetting) (*WoxSetting, error) {
|
|
fileContent, err := os.ReadFile(settingPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read file: %w", err)
|
|
}
|
|
|
|
// Check for empty file
|
|
if len(fileContent) == 0 {
|
|
return nil, fmt.Errorf("file is empty")
|
|
}
|
|
|
|
woxSetting := &WoxSetting{}
|
|
if err := json.Unmarshal(fileContent, woxSetting); err != nil {
|
|
return nil, fmt.Errorf("JSON decode error: %w", err)
|
|
}
|
|
|
|
// Apply defaults and sanitize values
|
|
m.applyDefaultsToWoxSetting(woxSetting, defaultSetting)
|
|
m.sanitizeWoxSetting(woxSetting, defaultSetting)
|
|
|
|
return woxSetting, nil
|
|
}
|
|
|
|
// tryLoadWithJSONRepair attempts to fix common JSON syntax issues
|
|
func (m *Manager) tryLoadWithJSONRepair(settingPath string, defaultSetting WoxSetting) (*WoxSetting, error) {
|
|
fileContent, err := os.ReadFile(settingPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read file: %w", err)
|
|
}
|
|
|
|
// Try to repair common JSON issues
|
|
repairedContent := m.repairJSONContent(fileContent)
|
|
|
|
woxSetting := &WoxSetting{}
|
|
if err := json.Unmarshal(repairedContent, woxSetting); err != nil {
|
|
return nil, fmt.Errorf("JSON decode error after repair: %w", err)
|
|
}
|
|
|
|
// Apply defaults and sanitize values
|
|
m.applyDefaultsToWoxSetting(woxSetting, defaultSetting)
|
|
m.sanitizeWoxSetting(woxSetting, defaultSetting)
|
|
|
|
return woxSetting, nil
|
|
}
|
|
|
|
// repairJSONContent attempts to fix common JSON syntax issues
|
|
func (m *Manager) repairJSONContent(content []byte) []byte {
|
|
contentStr := string(content)
|
|
|
|
// If completely empty, return empty object
|
|
if len(contentStr) == 0 {
|
|
return []byte("{}")
|
|
}
|
|
|
|
// Remove trailing commas before } or ]
|
|
contentStr = regexp.MustCompile(`,(\s*[}\]])`).ReplaceAllString(contentStr, "$1")
|
|
|
|
// Try to fix missing closing braces/brackets by counting
|
|
openBraces := strings.Count(contentStr, "{")
|
|
closeBraces := strings.Count(contentStr, "}")
|
|
if openBraces > closeBraces {
|
|
for i := 0; i < openBraces-closeBraces; i++ {
|
|
contentStr += "}"
|
|
}
|
|
}
|
|
|
|
openBrackets := strings.Count(contentStr, "[")
|
|
closeBrackets := strings.Count(contentStr, "]")
|
|
if openBrackets > closeBrackets {
|
|
for i := 0; i < openBrackets-closeBrackets; i++ {
|
|
contentStr += "]"
|
|
}
|
|
}
|
|
|
|
return []byte(contentStr)
|
|
}
|
|
|
|
// tryPartialLoad attempts to parse individual fields to salvage what we can
|
|
func (m *Manager) tryPartialLoad(settingPath string, defaultSetting WoxSetting) (*WoxSetting, error) {
|
|
fileContent, err := os.ReadFile(settingPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read file: %w", err)
|
|
}
|
|
|
|
// Start with default setting
|
|
woxSetting := defaultSetting
|
|
|
|
// Try to parse as a generic map to extract individual fields
|
|
var rawData map[string]interface{}
|
|
if err := json.Unmarshal(fileContent, &rawData); err != nil {
|
|
return nil, fmt.Errorf("failed to parse as map: %w", err)
|
|
}
|
|
|
|
// Extract fields one by one with error tolerance
|
|
m.extractFieldSafely(rawData, "UsePinYin", &woxSetting.UsePinYin)
|
|
m.extractFieldSafely(rawData, "SwitchInputMethodABC", &woxSetting.SwitchInputMethodABC)
|
|
m.extractFieldSafely(rawData, "HideOnStart", &woxSetting.HideOnStart)
|
|
m.extractFieldSafely(rawData, "HideOnLostFocus", &woxSetting.HideOnLostFocus)
|
|
m.extractFieldSafely(rawData, "ShowTray", &woxSetting.ShowTray)
|
|
m.extractFieldSafely(rawData, "EnableAutoBackup", &woxSetting.EnableAutoBackup)
|
|
m.extractFieldSafely(rawData, "EnableAutoUpdate", &woxSetting.EnableAutoUpdate)
|
|
|
|
// Extract numeric fields
|
|
m.extractIntFieldSafely(rawData, "AppWidth", &woxSetting.AppWidth)
|
|
m.extractIntFieldSafely(rawData, "MaxResultCount", &woxSetting.MaxResultCount)
|
|
|
|
// Extract string fields
|
|
m.extractStringFieldSafely(rawData, "LangCode", (*string)(&woxSetting.LangCode))
|
|
m.extractStringFieldSafely(rawData, "LastQueryMode", &woxSetting.LastQueryMode)
|
|
m.extractStringFieldSafely(rawData, "ThemeId", &woxSetting.ThemeId)
|
|
|
|
// Extract platform-specific string fields
|
|
m.extractPlatformStringFieldSafely(rawData, "CustomPythonPath", &woxSetting.CustomPythonPath)
|
|
m.extractPlatformStringFieldSafely(rawData, "CustomNodejsPath", &woxSetting.CustomNodejsPath)
|
|
|
|
// Sanitize the loaded values
|
|
m.sanitizeWoxSetting(&woxSetting, defaultSetting)
|
|
|
|
return &woxSetting, nil
|
|
}
|
|
|
|
// extractFieldSafely safely extracts a boolean field from raw JSON data
|
|
func (m *Manager) extractFieldSafely(rawData map[string]interface{}, fieldName string, target *bool) {
|
|
if value, exists := rawData[fieldName]; exists {
|
|
if boolVal, ok := value.(bool); ok {
|
|
*target = boolVal
|
|
}
|
|
}
|
|
}
|
|
|
|
// extractIntFieldSafely safely extracts an integer field from raw JSON data
|
|
func (m *Manager) extractIntFieldSafely(rawData map[string]interface{}, fieldName string, target *int) {
|
|
if value, exists := rawData[fieldName]; exists {
|
|
switch v := value.(type) {
|
|
case float64:
|
|
*target = int(v)
|
|
case int:
|
|
*target = v
|
|
case int64:
|
|
*target = int(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
// extractStringFieldSafely safely extracts a string field from raw JSON data
|
|
func (m *Manager) extractStringFieldSafely(rawData map[string]interface{}, fieldName string, target *string) {
|
|
if value, exists := rawData[fieldName]; exists {
|
|
if strVal, ok := value.(string); ok {
|
|
*target = strVal
|
|
}
|
|
}
|
|
}
|
|
|
|
// extractPlatformStringFieldSafely safely extracts a platform-specific string field from raw JSON data
|
|
func (m *Manager) extractPlatformStringFieldSafely(rawData map[string]interface{}, fieldName string, target *PlatformSettingValue[string]) {
|
|
if value, exists := rawData[fieldName]; exists {
|
|
if platformValue, ok := value.(map[string]interface{}); ok {
|
|
if winVal, exists := platformValue["WinValue"]; exists {
|
|
if strVal, ok := winVal.(string); ok {
|
|
target.WinValue = strVal
|
|
}
|
|
}
|
|
if macVal, exists := platformValue["MacValue"]; exists {
|
|
if strVal, ok := macVal.(string); ok {
|
|
target.MacValue = strVal
|
|
}
|
|
}
|
|
if linuxVal, exists := platformValue["LinuxValue"]; exists {
|
|
if strVal, ok := linuxVal.(string); ok {
|
|
target.LinuxValue = strVal
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// sanitizeWoxSetting ensures all values are within acceptable ranges
|
|
func (m *Manager) sanitizeWoxSetting(setting *WoxSetting, defaultSetting WoxSetting) {
|
|
// Sanitize AppWidth
|
|
if setting.AppWidth <= 0 || setting.AppWidth > 10000 {
|
|
setting.AppWidth = defaultSetting.AppWidth
|
|
}
|
|
|
|
// Sanitize MaxResultCount
|
|
if setting.MaxResultCount <= 0 || setting.MaxResultCount > 1000 {
|
|
setting.MaxResultCount = defaultSetting.MaxResultCount
|
|
}
|
|
|
|
// Sanitize LangCode
|
|
validLangCodes := []string{"en_US", "zh_CN", "zh_TW", "ja_JP", "ko_KR", "fr_FR", "de_DE", "es_ES", "pt_BR", "ru_RU"}
|
|
isValidLang := false
|
|
for _, code := range validLangCodes {
|
|
if string(setting.LangCode) == code {
|
|
isValidLang = true
|
|
break
|
|
}
|
|
}
|
|
if !isValidLang {
|
|
setting.LangCode = defaultSetting.LangCode
|
|
}
|
|
|
|
// Sanitize LastQueryMode
|
|
if setting.LastQueryMode != LastQueryModePreserve && setting.LastQueryMode != LastQueryModeEmpty {
|
|
setting.LastQueryMode = defaultSetting.LastQueryMode
|
|
}
|
|
|
|
// Sanitize ShowPosition
|
|
validPositions := []PositionType{PositionTypeMouseScreen, PositionTypeActiveScreen, PositionTypeLastLocation}
|
|
isValidPosition := false
|
|
for _, pos := range validPositions {
|
|
if setting.ShowPosition == pos {
|
|
isValidPosition = true
|
|
break
|
|
}
|
|
}
|
|
if !isValidPosition {
|
|
setting.ShowPosition = defaultSetting.ShowPosition
|
|
}
|
|
}
|
|
|
|
// applyDefaultsToWoxSetting applies default values for missing or zero-value fields
|
|
func (m *Manager) applyDefaultsToWoxSetting(setting *WoxSetting, defaultSetting WoxSetting) {
|
|
// Apply defaults for hotkeys
|
|
if setting.MainHotkey.Get() == "" {
|
|
setting.MainHotkey.Set(defaultSetting.MainHotkey.Get())
|
|
}
|
|
if setting.SelectionHotkey.Get() == "" {
|
|
setting.SelectionHotkey.Set(defaultSetting.SelectionHotkey.Get())
|
|
}
|
|
|
|
// Apply defaults for string fields
|
|
if setting.LangCode == "" {
|
|
setting.LangCode = defaultSetting.LangCode
|
|
}
|
|
if setting.LastQueryMode == "" {
|
|
setting.LastQueryMode = defaultSetting.LastQueryMode
|
|
}
|
|
if setting.ThemeId == "" {
|
|
setting.ThemeId = defaultSetting.ThemeId
|
|
}
|
|
|
|
// Apply defaults for numeric fields (only if zero)
|
|
if setting.AppWidth == 0 {
|
|
setting.AppWidth = defaultSetting.AppWidth
|
|
}
|
|
if setting.MaxResultCount == 0 {
|
|
setting.MaxResultCount = defaultSetting.MaxResultCount
|
|
}
|
|
|
|
// Apply defaults for position if empty
|
|
if setting.ShowPosition == "" {
|
|
setting.ShowPosition = defaultSetting.ShowPosition
|
|
}
|
|
|
|
// Apply defaults for slices if nil
|
|
if setting.QueryShortcuts == nil {
|
|
setting.QueryShortcuts = defaultSetting.QueryShortcuts
|
|
}
|
|
if setting.AIProviders == nil {
|
|
setting.AIProviders = defaultSetting.AIProviders
|
|
}
|
|
}
|
|
|
|
func (m *Manager) SavePluginSetting(ctx context.Context, pluginId string, pluginSetting *PluginSetting) error {
|
|
pluginSettingPath := path.Join(util.GetLocation().GetPluginSettingDirectory(), fmt.Sprintf("%s.json", pluginId))
|
|
pluginSettingJson, marshalErr := json.Marshal(pluginSetting)
|
|
if marshalErr != nil {
|
|
logger.Error(ctx, marshalErr.Error())
|
|
return marshalErr
|
|
}
|
|
|
|
writeErr := os.WriteFile(pluginSettingPath, pretty.Pretty(pluginSettingJson), 0644)
|
|
if writeErr != nil {
|
|
logger.Error(ctx, writeErr.Error())
|
|
return writeErr
|
|
}
|
|
|
|
logger.Info(ctx, fmt.Sprintf("plugin setting saved: %s", pluginId))
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) SaveWindowPosition(ctx context.Context, x, y int) error {
|
|
m.woxSetting.LastWindowX = x
|
|
m.woxSetting.LastWindowY = y
|
|
return m.SaveWoxSetting(ctx)
|
|
}
|
|
|
|
func (m *Manager) AddQueryHistory(ctx context.Context, query common.PlainQuery) {
|
|
if query.IsEmpty() {
|
|
return
|
|
}
|
|
|
|
logger.Debug(ctx, fmt.Sprintf("add query history: %s", query))
|
|
m.woxAppData.QueryHistories = append(m.woxAppData.QueryHistories, QueryHistory{
|
|
Query: query,
|
|
Timestamp: util.GetSystemTimestamp(),
|
|
})
|
|
|
|
// if query history is more than 100, remove the oldest ones
|
|
if len(m.woxAppData.QueryHistories) > 100 {
|
|
m.woxAppData.QueryHistories = m.woxAppData.QueryHistories[len(m.woxAppData.QueryHistories)-100:]
|
|
}
|
|
|
|
m.saveWoxAppData(ctx, "add query history")
|
|
}
|
|
|
|
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) AddActionedResult(ctx context.Context, pluginId string, resultTitle string, resultSubTitle string, query string) {
|
|
resultHash := NewResultHash(pluginId, resultTitle, resultSubTitle)
|
|
actionedResult := ActionedResult{
|
|
Timestamp: util.GetSystemTimestamp(),
|
|
Query: query,
|
|
}
|
|
|
|
if v, ok := m.woxAppData.ActionedResults.Load(resultHash); ok {
|
|
v = append(v, actionedResult)
|
|
// if current hash actioned results is more than 100, remove the oldest ones
|
|
if len(v) > 100 {
|
|
v = v[len(v)-100:]
|
|
}
|
|
m.woxAppData.ActionedResults.Store(resultHash, v)
|
|
} else {
|
|
m.woxAppData.ActionedResults.Store(resultHash, []ActionedResult{actionedResult})
|
|
}
|
|
|
|
m.saveWoxAppData(ctx, "add actioned 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)
|
|
m.saveWoxAppData(ctx, "add favorite result")
|
|
}
|
|
|
|
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)
|
|
m.saveWoxAppData(ctx, "remove favorite result")
|
|
}
|