package mining import ( "encoding/json" "fmt" "os" "path/filepath" "sync" "github.com/adrg/xdg" "github.com/google/uuid" ) const profileConfigFileName = "mining_profiles.json" // pm, err := NewProfileManager() // pm.CreateProfile(profile) type ProfileManager struct { mutex sync.RWMutex profiles map[string]*MiningProfile configPath string } // pm, err := NewProfileManager() // if err != nil { return err } func NewProfileManager() (*ProfileManager, error) { configPath, err := xdg.ConfigFile(filepath.Join("lethean-desktop", profileConfigFileName)) if err != nil { return nil, fmt.Errorf("could not resolve config path: %w", err) } pm := &ProfileManager{ profiles: make(map[string]*MiningProfile), configPath: configPath, } if err := pm.loadProfiles(); err != nil { // If the file doesn't exist, that's fine, but any other error is a problem. if !os.IsNotExist(err) { return nil, fmt.Errorf("could not load profiles: %w", err) } } return pm, nil } // pm.loadProfiles() // call after acquiring pm.mutex.Lock() if needed externally func (pm *ProfileManager) loadProfiles() error { pm.mutex.Lock() defer pm.mutex.Unlock() data, err := os.ReadFile(pm.configPath) if err != nil { return err } var profiles []*MiningProfile if err := json.Unmarshal(data, &profiles); err != nil { return err } pm.profiles = make(map[string]*MiningProfile) for _, profile := range profiles { pm.profiles[profile.ID] = profile } return nil } // pm.saveProfiles() // caller must hold pm.mutex before calling; uses atomic write (temp→sync→rename) func (pm *ProfileManager) saveProfiles() error { profileList := make([]*MiningProfile, 0, len(pm.profiles)) for _, profile := range pm.profiles { profileList = append(profileList, profile) } data, err := json.MarshalIndent(profileList, "", " ") if err != nil { return err } return AtomicWriteFile(pm.configPath, data, 0600) } // created, err := pm.CreateProfile(&MiningProfile{Name: "XMRig CPU", MinerType: "xmrig"}) func (pm *ProfileManager) CreateProfile(profile *MiningProfile) (*MiningProfile, error) { pm.mutex.Lock() defer pm.mutex.Unlock() profile.ID = uuid.New().String() pm.profiles[profile.ID] = profile if err := pm.saveProfiles(); err != nil { // Rollback delete(pm.profiles, profile.ID) return nil, err } return profile, nil } // profile, ok := pm.GetProfile("abc-123") // if !ok { return errors.New("profile not found") } func (pm *ProfileManager) GetProfile(id string) (*MiningProfile, bool) { pm.mutex.RLock() defer pm.mutex.RUnlock() profile, exists := pm.profiles[id] return profile, exists } // profiles := pm.GetAllProfiles() // for _, p := range profiles { render(p) } func (pm *ProfileManager) GetAllProfiles() []*MiningProfile { pm.mutex.RLock() defer pm.mutex.RUnlock() profileList := make([]*MiningProfile, 0, len(pm.profiles)) for _, profile := range pm.profiles { profileList = append(profileList, profile) } return profileList } // profile.Name = "XMRig GPU" // if err := pm.UpdateProfile(profile); err != nil { return err } func (pm *ProfileManager) UpdateProfile(profile *MiningProfile) error { pm.mutex.Lock() defer pm.mutex.Unlock() oldProfile, exists := pm.profiles[profile.ID] if !exists { return fmt.Errorf("profile with ID %s not found", profile.ID) } // Update in-memory state pm.profiles[profile.ID] = profile // Save to disk - rollback if save fails if err := pm.saveProfiles(); err != nil { // Restore old profile on save failure pm.profiles[profile.ID] = oldProfile return fmt.Errorf("failed to save profile: %w", err) } return nil } // if err := pm.DeleteProfile("abc-123"); err != nil { return err } func (pm *ProfileManager) DeleteProfile(id string) error { pm.mutex.Lock() defer pm.mutex.Unlock() profile, exists := pm.profiles[id] if !exists { return fmt.Errorf("profile with ID %s not found", id) } delete(pm.profiles, id) // Save to disk - rollback if save fails if err := pm.saveProfiles(); err != nil { // Restore profile on save failure pm.profiles[id] = profile return fmt.Errorf("failed to delete profile: %w", err) } return nil }