diff --git a/api/alarms.go b/api/alarms.go index 36a6599..1ccdf62 100644 --- a/api/alarms.go +++ b/api/alarms.go @@ -13,7 +13,13 @@ import ( func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, router *gin.RouterGroup) { router.GET("/alarms/next", func(c *gin.Context) { + alarm, err := reveil.GetNextAlarm(db) + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) + return + } + c.JSON(http.StatusOK, alarm) }) router.GET("/alarms/single", func(c *gin.Context) { @@ -24,7 +30,6 @@ func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, router * } c.JSON(http.StatusOK, alarms) - }) router.POST("/alarms/single", func(c *gin.Context) { var alarm reveil.AlarmSingle @@ -100,7 +105,7 @@ func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, router * c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "End date is already passed."}) return } - if !time.Time(*alarm.End).After(time.Time(*alarm.Start)) { + if time.Time(*alarm.Start).After(time.Time(*alarm.End)) { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Start is defined after End. Please verify your inputs."}) return } @@ -184,7 +189,11 @@ func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, router * }) repeatedAlarmsRoutes.GET("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("alarm")) + alarm := c.MustGet("alarm").(*reveil.AlarmRepeated) + alarm.FillExcepts(db) + alarm.NextTime = alarm.GetNextOccurence(db) + + c.JSON(http.StatusOK, alarm) }) repeatedAlarmsRoutes.PUT("", func(c *gin.Context) { oldalarm := c.MustGet("alarm").(*reveil.AlarmRepeated) diff --git a/model/alarm.go b/model/alarm.go index 4c35d7f..0d6f1c3 100644 --- a/model/alarm.go +++ b/model/alarm.go @@ -2,6 +2,7 @@ package reveil import ( "fmt" + "sort" "time" ) @@ -37,13 +38,117 @@ func (h *Hour) UnmarshalJSON(src []byte) error { return nil } +func GetNextAlarm(db *LevelDBStorage) (*time.Time, error) { + alarmsRepeated, err := GetAlarmsRepeated(db) + if err != nil { + return nil, err + } + + var closestAlarm *time.Time + for _, alarm := range alarmsRepeated { + next := alarm.GetNextOccurence(db) + if next != nil && (closestAlarm == nil || closestAlarm.After(*next)) { + closestAlarm = next + } + } + + alarmsSingle, err := GetAlarmsSingle(db) + if err != nil { + return nil, err + } + + for _, alarm := range alarmsSingle { + if closestAlarm == nil || closestAlarm.After(alarm.Time) { + closestAlarm = &alarm.Time + } + } + + return closestAlarm, nil +} + +type Exceptions []time.Time + +func (e Exceptions) Len() int { + return len(e) +} + +func (e Exceptions) Less(i, j int) bool { + return e[i].Before(e[j]) +} + +func (e Exceptions) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + type AlarmRepeated struct { Id Identifier `json:"id"` - Weekday uint8 `json:"weekday"` + Weekday time.Weekday `json:"weekday"` StartTime *Hour `json:"time"` FollowingRoutines []Identifier `json:"routines"` IgnoreExceptions bool `json:"ignore_exceptions"` - Comment string `json:"comment"` + Comment string `json:"comment,omitempty"` + Excepts Exceptions `json:"excepts,omitempty"` + NextTime *time.Time `json:"next_time,omitempty"` +} + +func (a *AlarmRepeated) FillExcepts(db *LevelDBStorage) error { + if a.IgnoreExceptions { + return nil + } + + exceptions, err := GetAlarmExceptions(db) + if err != nil { + return err + } + + now := time.Now() + + for _, exception := range exceptions { + if now.After(time.Time(*exception.Start)) { + continue + } + + end := time.Time(*exception.End).AddDate(0, 0, 1) + for t := time.Time(*exception.Start); end.After(t); t = t.AddDate(0, 0, 1) { + if t.Weekday() == a.Weekday { + a.Excepts = append(a.Excepts, time.Date(t.Year(), t.Month(), t.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, now.Location())) + t.AddDate(0, 0, 6) + } + } + } + + sort.Sort(a.Excepts) + + return nil +} + +func (a *AlarmRepeated) GetNextOccurence(db *LevelDBStorage) *time.Time { + if len(a.Excepts) == 0 { + a.FillExcepts(db) + } + + now := time.Now() + + today := time.Date(now.Year(), now.Month(), now.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, now.Location()) + if now.After(today) { + today = today.AddDate(0, 0, 1) + } + + end := today.AddDate(0, 0, 7) + var nextOccurence time.Time + for nextOccurence = today; end.After(nextOccurence); nextOccurence = nextOccurence.AddDate(0, 0, 1) { + if nextOccurence.Weekday() == a.Weekday { + break + } + } + + for _, except := range a.Excepts { + if except.Equal(nextOccurence) { + nextOccurence = nextOccurence.AddDate(0, 0, 7) + } + } + + return &nextOccurence } func GetAlarmRepeated(db *LevelDBStorage, id Identifier) (alarm *AlarmRepeated, err error) { @@ -82,6 +187,9 @@ func PutAlarmRepeated(db *LevelDBStorage, alarm *AlarmRepeated) (err error) { } alarm.Id = id + // Don't store this, this is autocalculated + alarm.Excepts = nil + alarm.NextTime = nil return db.put(key, alarm) } @@ -94,7 +202,7 @@ type AlarmSingle struct { Id Identifier `json:"id"` Time time.Time `json:"time"` FollowingRoutines []Identifier `json:"routines"` - Comment string `json:"comment"` + Comment string `json:"comment,omitempty"` } func GetAlarmSingle(db *LevelDBStorage, id Identifier) (alarm *AlarmSingle, err error) { @@ -145,7 +253,7 @@ type AlarmException struct { Id Identifier `json:"id"` Start *Date `json:"start"` End *Date `json:"end"` - Comment string `json:"comment"` + Comment string `json:"comment,omitempty"` } func GetAlarmException(db *LevelDBStorage, id Identifier) (alarm *AlarmException, err error) { diff --git a/ui/src/lib/alarmrepeated.js b/ui/src/lib/alarmrepeated.js index afbacda..baef914 100644 --- a/ui/src/lib/alarmrepeated.js +++ b/ui/src/lib/alarmrepeated.js @@ -5,13 +5,15 @@ export class AlarmRepeated { } } - update({ id, weekday, time, routines, ignore_exceptions, comment }) { + update({ id, weekday, time, routines, ignore_exceptions, comment, excepts, next_time }) { this.id = id; this.weekday = weekday; this.time = time; this.routines = routines; this.ignore_exceptions = ignore_exceptions; this.comment = comment; + this.excepts = excepts; + this.next_time = next_time; } async delete() { diff --git a/ui/src/lib/alarmsingle.js b/ui/src/lib/alarmsingle.js index 4aecc56..73c82c6 100644 --- a/ui/src/lib/alarmsingle.js +++ b/ui/src/lib/alarmsingle.js @@ -61,3 +61,28 @@ export async function getAlarmSingle(aid) { throw new Error((await res.json()).errmsg); } } + +export async function getNextAlarm() { + const res = await fetch(`api/alarms/next`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return new Date(await res.json()); + } else { + throw new Error((await res.json()).errmsg); + } +} + +export async function newNCyclesAlarm(nCycles) { + const res = await fetch('api/alarms/single', { + method: 'POST', + headers: {'Accept': 'application/json'}, + body: JSON.stringify({ + time: new Date(Date.now() + 600000 + 5400000 * nCycles) + }), + }); + if (res.status == 200) { + const data = await res.json(); + return new AlarmSingle(data); + } else { + throw new Error((await res.json()).errmsg); + } +} diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte index b5037bf..1f848e1 100644 --- a/ui/src/routes/+layout.svelte +++ b/ui/src/routes/+layout.svelte @@ -21,6 +21,7 @@ />
+ {alarm.comment} +
+ {/if}+ {exception.comment} +
+ {/if} Entre le