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
|
package morningalarm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/robfig/cron/v3"
|
"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 {
|
func (ma *MorningAlarm) nextAlarm() *time.Time {
|
||||||
if len(ma.cr.Entries()) == 0 {
|
if len(ma.cr.Entries()) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -25,102 +23,17 @@ func (ma *MorningAlarm) nextAlarm() *time.Time {
|
|||||||
return &min
|
return &min
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma *MorningAlarm) addAlarm(spec string, name string) (cron.EntryID, error) {
|
// armAlarm arms an alarm to fire at the specified time
|
||||||
// Check if alarm already exists
|
func (ma *MorningAlarm) armAlarm(alarm *alarm) (cron.EntryID, error) {
|
||||||
if ma.getAlarm(name) != nil {
|
return ma.cr.AddFunc(alarm.Time, ma.fireAlarm)
|
||||||
return 0, errors.New("Alarm already exists")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := ma.cr.AddFunc(spec, ma.fireAlarm)
|
// disarmAlarm disarms an alarm and prevents it from firing at the specified time
|
||||||
|
func (ma *MorningAlarm) disarmAlarm(alarm *alarm) {
|
||||||
if err != nil {
|
ma.cr.Remove(alarm.cronID)
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ma.alarms = append(ma.alarms, alarm{
|
|
||||||
id: id,
|
|
||||||
Time: spec,
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fireAlarm function that is called when an alarm is fired
|
||||||
func (ma *MorningAlarm) fireAlarm() {
|
func (ma *MorningAlarm) fireAlarm() {
|
||||||
ma.playWakeUpMusic()
|
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
|
|
||||||
}
|
|
||||||
|
@ -9,7 +9,8 @@ import (
|
|||||||
type alarm struct {
|
type alarm struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
Time string `json:"time" binding:"required"`
|
Time string `json:"time" binding:"required"`
|
||||||
id cron.EntryID
|
Disabled bool `json:"disabled"`
|
||||||
|
cronID cron.EntryID
|
||||||
}
|
}
|
||||||
|
|
||||||
type MorningAlarmConfig struct {
|
type MorningAlarmConfig struct {
|
||||||
@ -36,7 +37,10 @@ func New(config MorningAlarmConfig) *MorningAlarm {
|
|||||||
|
|
||||||
func (ma *MorningAlarm) Start() {
|
func (ma *MorningAlarm) Start() {
|
||||||
ma.cr = cron.New()
|
ma.cr = cron.New()
|
||||||
ma.loadAlarms()
|
|
||||||
|
ma.loadAlarmsFromFile()
|
||||||
|
ma.armAllAlarms()
|
||||||
|
|
||||||
ma.cr.Start()
|
ma.cr.Start()
|
||||||
|
|
||||||
ma.ro = gin.Default()
|
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"`
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AlarmPatch struct {
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
}
|
||||||
|
|
||||||
func (ma *MorningAlarm) setupWebserver() {
|
func (ma *MorningAlarm) setupWebserver() {
|
||||||
|
// Create a new alarm
|
||||||
ma.ro.POST("/api/alarm", func(c *gin.Context) {
|
ma.ro.POST("/api/alarm", func(c *gin.Context) {
|
||||||
var body alarm
|
var body alarm
|
||||||
|
|
||||||
@ -40,7 +45,7 @@ func (ma *MorningAlarm) setupWebserver() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
} else {
|
} else {
|
||||||
err = ma.saveAlarms()
|
err = ma.saveAlarmsToFile()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
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) {
|
ma.ro.GET("/api/alarm/:id", func(c *gin.Context) {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
|
|
||||||
@ -65,15 +71,17 @@ func (ma *MorningAlarm) setupWebserver() {
|
|||||||
c.JSON(http.StatusOK, alarm)
|
c.JSON(http.StatusOK, alarm)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get all alarms
|
||||||
ma.ro.GET("/api/alarm", func(c *gin.Context) {
|
ma.ro.GET("/api/alarm", func(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, ma.alarms)
|
c.JSON(http.StatusOK, ma.alarms)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Delete an alarm
|
||||||
ma.ro.DELETE("/api/alarm/:id", func(c *gin.Context) {
|
ma.ro.DELETE("/api/alarm/:id", func(c *gin.Context) {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
|
|
||||||
if ma.deleteAlarm(id) {
|
if ma.removeAlarm(id) {
|
||||||
err := ma.saveAlarms()
|
err := ma.saveAlarmsToFile()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
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) {
|
ma.ro.GET("/api/info", func(c *gin.Context) {
|
||||||
spotifyUser, err := ma.sp.CurrentUser(c)
|
spotifyUser, err := ma.sp.CurrentUser(c)
|
||||||
|
|
||||||
@ -117,6 +126,7 @@ func (ma *MorningAlarm) setupWebserver() {
|
|||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get available spotify devices
|
||||||
ma.ro.GET("/api/device", func(c *gin.Context) {
|
ma.ro.GET("/api/device", func(c *gin.Context) {
|
||||||
devices, err := ma.getAvailableDevices()
|
devices, err := ma.getAvailableDevices()
|
||||||
|
|
||||||
@ -128,6 +138,7 @@ func (ma *MorningAlarm) setupWebserver() {
|
|||||||
c.JSON(http.StatusOK, devices)
|
c.JSON(http.StatusOK, devices)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Trigger an alarm immediately
|
||||||
ma.ro.POST("/api/trigger", func(c *gin.Context) {
|
ma.ro.POST("/api/trigger", func(c *gin.Context) {
|
||||||
if _, err := ma.playWakeUpMusic(); err != nil {
|
if _, err := ma.playWakeUpMusic(); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
@ -136,4 +147,33 @@ func (ma *MorningAlarm) setupWebserver() {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{})
|
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…
Reference in New Issue
Block a user