refactor & ddisable alarms feature
This commit is contained in:
		
							parent
							
								
									7cb4f6c104
								
							
						
					
					
						commit
						cb6f86207a
					
				
							
								
								
									
										103
									
								
								internal/cron.go
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								internal/cron.go
									
									
									
									
									
								
							@ -1,14 +1,12 @@
 | 
			
		||||
package morningalarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/robfig/cron/v3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// nextAlarm returns the time of the next alarm to fire from the perspective of cron
 | 
			
		||||
func (ma *MorningAlarm) nextAlarm() *time.Time {
 | 
			
		||||
	if len(ma.cr.Entries()) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
@ -25,102 +23,17 @@ func (ma *MorningAlarm) nextAlarm() *time.Time {
 | 
			
		||||
	return &min
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ma *MorningAlarm) addAlarm(spec string, name string) (cron.EntryID, error) {
 | 
			
		||||
	// Check if alarm already exists
 | 
			
		||||
	if ma.getAlarm(name) != nil {
 | 
			
		||||
		return 0, errors.New("Alarm already exists")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	id, err := ma.cr.AddFunc(spec, ma.fireAlarm)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ma.alarms = append(ma.alarms, alarm{
 | 
			
		||||
		id:   id,
 | 
			
		||||
		Time: spec,
 | 
			
		||||
		Name: name,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return id, nil
 | 
			
		||||
// armAlarm arms an alarm to fire at the specified time
 | 
			
		||||
func (ma *MorningAlarm) armAlarm(alarm *alarm) (cron.EntryID, error) {
 | 
			
		||||
	return ma.cr.AddFunc(alarm.Time, ma.fireAlarm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ma *MorningAlarm) saveAlarms() error {
 | 
			
		||||
	content, err := json.Marshal(ma.alarms)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = os.WriteFile("alarms.json", content, 0644)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ma *MorningAlarm) loadAlarms() error {
 | 
			
		||||
	if _, err := os.Stat("alarms.json"); os.IsNotExist(err) {
 | 
			
		||||
		ma.alarms = []alarm{}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content, err := os.ReadFile("alarms.json")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var ent []alarm
 | 
			
		||||
	err = json.Unmarshal(content, &ent)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, entry := range ent {
 | 
			
		||||
		_, err := ma.addAlarm(entry.Time, entry.Name)
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
// disarmAlarm disarms an alarm and prevents it from firing at the specified time
 | 
			
		||||
func (ma *MorningAlarm) disarmAlarm(alarm *alarm) {
 | 
			
		||||
	ma.cr.Remove(alarm.cronID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fireAlarm function that is called when an alarm is fired
 | 
			
		||||
func (ma *MorningAlarm) fireAlarm() {
 | 
			
		||||
	ma.playWakeUpMusic()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ma *MorningAlarm) getAlarm(name string) *alarm {
 | 
			
		||||
	for _, alarm := range ma.alarms {
 | 
			
		||||
		if alarm.Name == name {
 | 
			
		||||
			return &alarm
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ma *MorningAlarm) deleteAlarm(name string) bool {
 | 
			
		||||
	for _, alarm := range ma.alarms {
 | 
			
		||||
		if alarm.Name == name {
 | 
			
		||||
			ma.cr.Remove(alarm.id)
 | 
			
		||||
 | 
			
		||||
			for i, a := range ma.alarms {
 | 
			
		||||
				if a.Name == name {
 | 
			
		||||
					ma.alarms = append(ma.alarms[:i], ma.alarms[i+1:]...)
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,9 +7,10 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type alarm struct {
 | 
			
		||||
	Name string `json:"name" binding:"required"`
 | 
			
		||||
	Time string `json:"time" binding:"required"`
 | 
			
		||||
	id   cron.EntryID
 | 
			
		||||
	Name     string `json:"name" binding:"required"`
 | 
			
		||||
	Time     string `json:"time" binding:"required"`
 | 
			
		||||
	Disabled bool   `json:"disabled"`
 | 
			
		||||
	cronID   cron.EntryID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MorningAlarmConfig struct {
 | 
			
		||||
@ -36,7 +37,10 @@ func New(config MorningAlarmConfig) *MorningAlarm {
 | 
			
		||||
 | 
			
		||||
func (ma *MorningAlarm) Start() {
 | 
			
		||||
	ma.cr = cron.New()
 | 
			
		||||
	ma.loadAlarms()
 | 
			
		||||
 | 
			
		||||
	ma.loadAlarmsFromFile()
 | 
			
		||||
	ma.armAllAlarms()
 | 
			
		||||
 | 
			
		||||
	ma.cr.Start()
 | 
			
		||||
 | 
			
		||||
	ma.ro = gin.Default()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										138
									
								
								internal/store.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								internal/store.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,138 @@
 | 
			
		||||
package morningalarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// addAlarm adds an alarm to the list of alarms and starts a cron job to fire it at the specified time
 | 
			
		||||
func (ma *MorningAlarm) addAlarm(time, name string) (*alarm, error) {
 | 
			
		||||
	// Check if alarm already exists
 | 
			
		||||
	if ma.getAlarm(name) != nil {
 | 
			
		||||
		return nil, errors.New("Alarm with name " + name + " already exists")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	alarm := alarm{
 | 
			
		||||
		Name:     name,
 | 
			
		||||
		Time:     time,
 | 
			
		||||
		Disabled: false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	alarm.cronID, _ = ma.armAlarm(&alarm)
 | 
			
		||||
 | 
			
		||||
	ma.alarms = append(ma.alarms, alarm)
 | 
			
		||||
 | 
			
		||||
	return &alarm, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ma *MorningAlarm) getAlarm(name string) *alarm {
 | 
			
		||||
	for _, alarm := range ma.alarms {
 | 
			
		||||
		if alarm.Name == name {
 | 
			
		||||
			return &alarm
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadAlarmsFromFile loads the list of alarms from alarms.json into memory
 | 
			
		||||
func (ma *MorningAlarm) loadAlarmsFromFile() error {
 | 
			
		||||
	if _, err := os.Stat("alarms.json"); os.IsNotExist(err) {
 | 
			
		||||
		ma.alarms = []alarm{}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content, err := os.ReadFile("alarms.json")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = json.Unmarshal(content, &ma.alarms)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// saveAlarmsToFile saves the list of alarms to alarms.json
 | 
			
		||||
func (ma *MorningAlarm) saveAlarmsToFile() error {
 | 
			
		||||
	content, err := json.Marshal(ma.alarms)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = os.WriteFile("alarms.json", content, 0644)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// removeAlarm removes an alarm from the list of alarms and stops cron from firing it. Returns true if the alarm was removed, false if it was not found
 | 
			
		||||
func (ma *MorningAlarm) removeAlarm(name string) bool {
 | 
			
		||||
	for _, alarm := range ma.alarms {
 | 
			
		||||
		if alarm.Name == name {
 | 
			
		||||
 | 
			
		||||
			// Stop cron from firing the alarm
 | 
			
		||||
			ma.disarmAlarm(&alarm)
 | 
			
		||||
 | 
			
		||||
			// Remove alarm from list of alarms
 | 
			
		||||
			for i, a := range ma.alarms {
 | 
			
		||||
				if a.Name == name {
 | 
			
		||||
					ma.alarms = append(ma.alarms[:i], ma.alarms[i+1:]...)
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// armAllAlarms arms all alarms in the list of alarms that are not disabled and returns an error if any of them fail to arm
 | 
			
		||||
func (ma *MorningAlarm) armAllAlarms() error {
 | 
			
		||||
	for _, alarm := range ma.alarms {
 | 
			
		||||
		if !alarm.Disabled {
 | 
			
		||||
			id, err := ma.armAlarm(&alarm)
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			alarm.cronID = id
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setDisabled sets the disabled status of an alarm
 | 
			
		||||
func (ma *MorningAlarm) setDisabled(name string, disabled bool) {
 | 
			
		||||
	for i, alarm := range ma.alarms {
 | 
			
		||||
		if alarm.Name == name {
 | 
			
		||||
			if alarm.Disabled == disabled {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ma.alarms[i].Disabled = disabled
 | 
			
		||||
 | 
			
		||||
			if disabled {
 | 
			
		||||
				ma.disarmAlarm(&alarm)
 | 
			
		||||
			} else {
 | 
			
		||||
				id, _ := ma.armAlarm(&alarm)
 | 
			
		||||
				ma.alarms[i].cronID = id
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -12,7 +12,12 @@ type Device struct {
 | 
			
		||||
	ID   string `json:"id"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AlarmPatch struct {
 | 
			
		||||
	Disabled bool `json:"disabled"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ma *MorningAlarm) setupWebserver() {
 | 
			
		||||
	// Create a new alarm
 | 
			
		||||
	ma.ro.POST("/api/alarm", func(c *gin.Context) {
 | 
			
		||||
		var body alarm
 | 
			
		||||
 | 
			
		||||
@ -40,7 +45,7 @@ func (ma *MorningAlarm) setupWebserver() {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 | 
			
		||||
		} else {
 | 
			
		||||
			err = ma.saveAlarms()
 | 
			
		||||
			err = ma.saveAlarmsToFile()
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 | 
			
		||||
@ -52,6 +57,7 @@ func (ma *MorningAlarm) setupWebserver() {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Get an alarm
 | 
			
		||||
	ma.ro.GET("/api/alarm/:id", func(c *gin.Context) {
 | 
			
		||||
		id := c.Param("id")
 | 
			
		||||
 | 
			
		||||
@ -65,15 +71,17 @@ func (ma *MorningAlarm) setupWebserver() {
 | 
			
		||||
		c.JSON(http.StatusOK, alarm)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Get all alarms
 | 
			
		||||
	ma.ro.GET("/api/alarm", func(c *gin.Context) {
 | 
			
		||||
		c.JSON(http.StatusOK, ma.alarms)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Delete an alarm
 | 
			
		||||
	ma.ro.DELETE("/api/alarm/:id", func(c *gin.Context) {
 | 
			
		||||
		id := c.Param("id")
 | 
			
		||||
 | 
			
		||||
		if ma.deleteAlarm(id) {
 | 
			
		||||
			err := ma.saveAlarms()
 | 
			
		||||
		if ma.removeAlarm(id) {
 | 
			
		||||
			err := ma.saveAlarmsToFile()
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 | 
			
		||||
@ -86,6 +94,7 @@ func (ma *MorningAlarm) setupWebserver() {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Get info about the server and the alarms
 | 
			
		||||
	ma.ro.GET("/api/info", func(c *gin.Context) {
 | 
			
		||||
		spotifyUser, err := ma.sp.CurrentUser(c)
 | 
			
		||||
 | 
			
		||||
@ -117,6 +126,7 @@ func (ma *MorningAlarm) setupWebserver() {
 | 
			
		||||
		c.JSON(http.StatusOK, response)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Get available spotify devices
 | 
			
		||||
	ma.ro.GET("/api/device", func(c *gin.Context) {
 | 
			
		||||
		devices, err := ma.getAvailableDevices()
 | 
			
		||||
 | 
			
		||||
@ -128,6 +138,7 @@ func (ma *MorningAlarm) setupWebserver() {
 | 
			
		||||
		c.JSON(http.StatusOK, devices)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Trigger an alarm immediately
 | 
			
		||||
	ma.ro.POST("/api/trigger", func(c *gin.Context) {
 | 
			
		||||
		if _, err := ma.playWakeUpMusic(); err != nil {
 | 
			
		||||
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 | 
			
		||||
@ -136,4 +147,33 @@ func (ma *MorningAlarm) setupWebserver() {
 | 
			
		||||
 | 
			
		||||
		c.JSON(http.StatusOK, gin.H{})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	ma.ro.PATCH("/api/alarm/:id", func(c *gin.Context) {
 | 
			
		||||
		id := c.Param("id")
 | 
			
		||||
 | 
			
		||||
		alarm := ma.getAlarm(id)
 | 
			
		||||
 | 
			
		||||
		if alarm == nil {
 | 
			
		||||
			c.JSON(http.StatusNotFound, gin.H{"error": "Alarm not found"})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var body AlarmPatch
 | 
			
		||||
 | 
			
		||||
		if err := c.BindJSON(&body); err != nil {
 | 
			
		||||
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ma.setDisabled(id, body.Disabled)
 | 
			
		||||
 | 
			
		||||
		err := ma.saveAlarmsToFile()
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.JSON(http.StatusOK, gin.H{})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user