Skip to content

Instantly share code, notes, and snippets.

@looterz
Created July 6, 2025 18:38
Show Gist options
  • Save looterz/73b17d92469db1c42a38ee8290dad886 to your computer and use it in GitHub Desktop.
Save looterz/73b17d92469db1c42a38ee8290dad886 to your computer and use it in GitHub Desktop.
Wails V3 Service issue
// services/configservice.go
package services
import (
"bytes"
"context"
"errors"
"log"
"os"
"path/filepath"
"sort"
"github.com/BurntSushi/toml"
"github.com/wailsapp/wails/v3/pkg/application"
)
// --- Configuration Structs (No Changes) ---
type GlobalSettings struct {
AutoRefresh bool `toml:"auto_refresh"`
ShowNotifications bool `toml:"show_notifications"`
RefreshInterval int `toml:"refresh_interval"`
MaxChanges int `toml:"max_changes"`
}
type Project struct {
Name string `toml:"name"`
Path string `toml:"path"`
}
type AppConfig struct {
Global GlobalSettings `toml:"global"`
Projects map[string]*Project `toml:"projects"`
}
type ConfigService struct {
ctx context.Context
config *AppConfig
filePath string
}
func NewConfigService() *ConfigService {
return &ConfigService{}
}
// ServiceStartup is called by Wails at the beginning of the application lifecycle.
// This is the correct place to initialize our service.
func (s *ConfigService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
s.ctx = ctx
// Get the path of the executable to place the config file next to it.
exePath, err := os.Executable()
if err != nil {
log.Println("FATAL: Failed to get executable path:", err)
return err
}
exeDir := filepath.Dir(exePath)
s.filePath = filepath.Join(exeDir, "ultragamesync.toml")
log.Println("Initializing ConfigService. Config file path is:", s.filePath)
if err := s.Load(); err != nil {
log.Println("FATAL: Failed to load config:", err)
return err
}
return nil
}
// Load reads the config.toml file from disk or creates a default one.
func (s *ConfigService) Load() error {
if _, err := os.Stat(s.filePath); errors.Is(err, os.ErrNotExist) {
log.Println("Config file not found. Creating default config.")
s.config = &AppConfig{
Global: GlobalSettings{
AutoRefresh: true,
ShowNotifications: true,
RefreshInterval: 5,
MaxChanges: 100,
},
Projects: map[string]*Project{
"MyProject": {
},
},
}
return s.save()
}
log.Println("Loading existing config from:", s.filePath)
var config AppConfig
if _, err := toml.DecodeFile(s.filePath, &config); err != nil {
return err
}
s.config = &config
return nil
}
// save is an internal helper to write the current config state to disk.
func (s *ConfigService) save() error {
var buf bytes.Buffer
encoder := toml.NewEncoder(&buf)
if err := encoder.Encode(s.config); err != nil {
return err
}
return os.WriteFile(s.filePath, buf.Bytes(), 0644)
}
// === Bound Methods ===
// GetConfig returns the currently loaded application configuration.
func (s *ConfigService) GetConfig() *AppConfig {
// The lazy-load workaround is no longer needed because ServiceStartup will handle it.
return s.config
}
// SaveConfig receives a new configuration from the frontend and saves it.
func (s *ConfigService) SaveConfig(newConfig AppConfig) error {
s.config = &newConfig
return s.save()
}
// GetProjectNames returns a sorted list of all configured project names.
func (s *ConfigService) GetProjectNames() []string {
names := make([]string, 0, len(s.config.Projects))
for name := range s.config.Projects {
names = append(names, name)
}
sort.Strings(names)
return names
}
// AddProject adds a new project to the configuration and saves it.
func (s *ConfigService) AddProject(newProject Project) error {
if _, exists := s.config.Projects[newProject.Name]; exists {
return errors.New("a project with this name already exists")
}
if s.config.Projects == nil {
s.config.Projects = make(map[string]*Project)
}
s.config.Projects[newProject.Name] = &newProject
return s.save()
}
// UpdateProject updates an existing project's configuration.
func (s *ConfigService) UpdateProject(projectName string, updatedProject Project) error {
if _, exists := s.config.Projects[projectName]; !exists {
return errors.New("project not found")
}
s.config.Projects[projectName] = &updatedProject
return s.save()
}
// DeleteProject removes a project from the configuration.
func (s *ConfigService) DeleteProject(projectName string) error {
if _, exists := s.config.Projects[projectName]; !exists {
return errors.New("project not found")
}
delete(s.config.Projects, projectName)
return s.save()
}
app := application.New(application.Options{
Name: "Test",
Description: "",
Services: []application.Service{
application.NewService(services.NewConfigService()),
},
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment