reveil/model/alarm.go

395 lines
9.0 KiB
Go
Raw Permalink Normal View History

2022-10-05 20:33:31 +00:00
package reveil
import (
"fmt"
2022-10-06 12:02:56 +00:00
"sort"
2022-10-05 20:33:31 +00:00
"time"
"git.nemunai.re/nemunaire/reveil/config"
2022-10-05 20:33:31 +00:00
)
type Date time.Time
func (d *Date) MarshalJSON() (dst []byte, err error) {
return []byte(fmt.Sprintf("\"%04d-%02d-%02d\"", time.Time(*d).Year(), time.Time(*d).Month(), time.Time(*d).Day())), nil
}
func (d *Date) UnmarshalJSON(src []byte) error {
tmp, err := time.Parse("\"2006-01-02\"", string(src))
if err != nil {
return err
}
*d = Date(tmp)
return nil
}
type Hour time.Time
func (h *Hour) MarshalJSON() (dst []byte, err error) {
return []byte(fmt.Sprintf("\"%02d:%02d\"", time.Time(*h).Hour(), time.Time(*h).Minute())), nil
}
func (h *Hour) UnmarshalJSON(src []byte) error {
tmp, err := time.Parse("\"15:04\"", string(src))
if err != nil {
return err
}
*h = Hour(tmp)
return nil
}
2022-12-08 16:49:42 +00:00
func GetNextAlarm(cfg *config.Config, db *LevelDBStorage) (*time.Time, []Identifier, error) {
2022-10-06 12:02:56 +00:00
alarmsRepeated, err := GetAlarmsRepeated(db)
if err != nil {
2022-12-08 16:49:42 +00:00
return nil, nil, err
2022-10-06 12:02:56 +00:00
}
var closestAlarm *time.Time
2022-12-08 16:49:42 +00:00
var closestAlarmRoutines []Identifier
2022-10-06 12:02:56 +00:00
for _, alarm := range alarmsRepeated {
next := alarm.GetNextOccurence(cfg, db)
2022-10-06 12:02:56 +00:00
if next != nil && (closestAlarm == nil || closestAlarm.After(*next)) {
closestAlarm = next
2022-12-08 16:49:42 +00:00
closestAlarmRoutines = alarm.FollowingRoutines
2022-10-06 12:02:56 +00:00
}
}
alarmsSingle, err := GetAlarmsSingle(db)
if err != nil {
2022-12-08 16:49:42 +00:00
return nil, nil, err
2022-10-06 12:02:56 +00:00
}
2022-10-06 12:48:36 +00:00
now := time.Now()
2022-10-06 12:02:56 +00:00
for _, alarm := range alarmsSingle {
2022-10-06 12:48:36 +00:00
if closestAlarm == nil || (closestAlarm.After(alarm.Time) && alarm.Time.After(now)) {
2022-10-06 12:02:56 +00:00
closestAlarm = &alarm.Time
2022-12-08 16:49:42 +00:00
closestAlarmRoutines = alarm.FollowingRoutines
}
}
return closestAlarm, closestAlarmRoutines, nil
}
func GetNextException(cfg *config.Config, db *LevelDBStorage) (*time.Time, error) {
alarmsExceptions, err := GetAlarmExceptions(db)
if err != nil {
return nil, err
}
var closestException *time.Time
for _, except := range alarmsExceptions {
if except != nil && time.Time(*except.End).After(time.Now()) && (closestException == nil || closestException.After(time.Time(*except.Start))) {
tmp := time.Time(*except.Start)
closestException = &tmp
2022-10-06 12:02:56 +00:00
}
}
2022-12-08 16:49:42 +00:00
return closestException, nil
2022-10-06 12:02:56 +00:00
}
2022-12-08 15:43:56 +00:00
func DropNextAlarm(cfg *config.Config, db *LevelDBStorage) error {
2022-12-08 16:49:42 +00:00
timenext, _, err := GetNextAlarm(cfg, db)
2022-12-08 15:43:56 +00:00
if err != nil {
return err
}
alarmsRepeated, err := GetAlarmsRepeated(db)
if err != nil {
return err
}
for _, alarm := range alarmsRepeated {
next := alarm.GetNextOccurence(cfg, db)
if next != nil && *next == *timenext {
start := Date(*next)
stop := Date((*next).Add(time.Second))
return PutAlarmException(db, &AlarmException{
Start: &start,
End: &stop,
2023-01-09 13:19:22 +00:00
Comment: fmt.Sprintf("Automatic exception to cancel recurrent alarm %s", next.Format("Mon at 15:04")),
2022-12-08 15:43:56 +00:00
})
}
}
alarmsSingle, err := GetAlarmsSingle(db)
if err != nil {
return err
}
for _, alarm := range alarmsSingle {
if alarm.Time == *timenext {
return DeleteAlarmSingle(db, alarm)
}
}
return fmt.Errorf("Unable to find the next alarm")
}
2022-10-06 12:02:56 +00:00
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]
}
2022-10-05 20:33:31 +00:00
type AlarmRepeated struct {
Id Identifier `json:"id"`
2022-10-06 12:02:56 +00:00
Weekday time.Weekday `json:"weekday"`
2022-10-05 20:33:31 +00:00
StartTime *Hour `json:"time"`
FollowingRoutines []Identifier `json:"routines"`
IgnoreExceptions bool `json:"ignore_exceptions"`
2022-10-06 12:02:56 +00:00
Comment string `json:"comment,omitempty"`
2022-12-15 17:45:20 +00:00
Disabled bool `json:"disabled,omitempty"`
2022-10-06 12:02:56 +00:00
Excepts Exceptions `json:"excepts,omitempty"`
NextTime *time.Time `json:"next_time,omitempty"`
}
func (a *AlarmRepeated) FillExcepts(cfg *config.Config, db *LevelDBStorage) error {
2022-10-06 12:02:56 +00:00
if a.IgnoreExceptions {
return nil
}
exceptions, err := GetAlarmExceptions(db)
if err != nil {
return err
}
now := time.Now()
for _, exception := range exceptions {
2023-01-09 13:19:22 +00:00
end := time.Time(*exception.End).AddDate(0, 0, 1)
if now.After(end) {
2022-10-06 12:02:56 +00:00
continue
}
for t := time.Time(*exception.Start); end.After(t); t = t.AddDate(0, 0, 1) {
if t.Weekday() == a.Weekday {
2022-12-23 21:32:11 +00:00
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, time.Local))
2022-12-23 11:43:58 +00:00
t = t.AddDate(0, 0, 6)
2022-10-06 12:02:56 +00:00
}
}
}
sort.Sort(a.Excepts)
return nil
}
func (a *AlarmRepeated) GetNextOccurence(cfg *config.Config, db *LevelDBStorage) *time.Time {
2022-12-15 17:45:20 +00:00
if a.Disabled {
return nil
}
2022-10-06 12:02:56 +00:00
if len(a.Excepts) == 0 {
a.FillExcepts(cfg, db)
2022-10-06 12:02:56 +00:00
}
now := time.Now()
2022-12-23 21:32:11 +00:00
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, time.Local)
2022-10-06 12:02:56 +00:00
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
2022-10-05 20:33:31 +00:00
}
func GetAlarmRepeated(db *LevelDBStorage, id Identifier) (alarm *AlarmRepeated, err error) {
alarm = &AlarmRepeated{}
err = db.get(fmt.Sprintf("alarm-repeated-%s", id.ToString()), alarm)
return
}
func GetAlarmsRepeated(db *LevelDBStorage) (alarms []*AlarmRepeated, err error) {
iter := db.search("alarm-repeated-")
defer iter.Release()
for iter.Next() {
var a AlarmRepeated
err = decodeData(iter.Value(), &a)
if err != nil {
return
}
alarms = append(alarms, &a)
}
return
}
func PutAlarmRepeated(db *LevelDBStorage, alarm *AlarmRepeated) (err error) {
var key string
if alarm.Id.IsEmpty() {
2022-12-15 17:40:09 +00:00
var id Identifier
2022-10-05 20:33:31 +00:00
key, id, err = db.findBytesKey("alarm-repeated-", IDENTIFIER_LEN)
if err != nil {
return err
}
2022-12-15 17:40:09 +00:00
alarm.Id = id
} else {
key = fmt.Sprintf("alarm-repeated-%s", alarm.Id.ToString())
2022-10-05 20:33:31 +00:00
}
2022-10-06 12:02:56 +00:00
// Don't store this, this is autocalculated
alarm.Excepts = nil
alarm.NextTime = nil
2022-10-05 20:33:31 +00:00
return db.put(key, alarm)
}
func DeleteAlarmRepeated(db *LevelDBStorage, alarm *AlarmRepeated) (err error) {
return db.delete(fmt.Sprintf("alarm-repeated-%s", alarm.Id.ToString()))
}
type AlarmSingle struct {
Id Identifier `json:"id"`
Time time.Time `json:"time"`
FollowingRoutines []Identifier `json:"routines"`
2022-10-06 12:02:56 +00:00
Comment string `json:"comment,omitempty"`
2022-10-05 20:33:31 +00:00
}
func GetAlarmSingle(db *LevelDBStorage, id Identifier) (alarm *AlarmSingle, err error) {
alarm = &AlarmSingle{}
err = db.get(fmt.Sprintf("alarm-single-%s", id.ToString()), alarm)
return
}
func GetAlarmsSingle(db *LevelDBStorage) (alarms []*AlarmSingle, err error) {
iter := db.search("alarm-single-")
defer iter.Release()
for iter.Next() {
var a AlarmSingle
err = decodeData(iter.Value(), &a)
if err != nil {
return
}
alarms = append(alarms, &a)
}
return
}
func PutAlarmSingle(db *LevelDBStorage, alarm *AlarmSingle) (err error) {
var key string
if alarm.Id.IsEmpty() {
2022-12-15 17:40:09 +00:00
var id Identifier
2022-10-05 20:33:31 +00:00
key, id, err = db.findBytesKey("alarm-single-", IDENTIFIER_LEN)
if err != nil {
return err
}
2022-12-15 17:40:09 +00:00
alarm.Id = id
} else {
key = fmt.Sprintf("alarm-single-%s", alarm.Id.ToString())
2022-10-05 20:33:31 +00:00
}
return db.put(key, alarm)
}
func DeleteAlarmSingle(db *LevelDBStorage, alarm *AlarmSingle) (err error) {
return db.delete(fmt.Sprintf("alarm-single-%s", alarm.Id.ToString()))
}
2022-10-14 18:08:03 +00:00
func RemoveOldAlarmsSingle(db *LevelDBStorage) error {
alarms, err := GetAlarmsSingle(db)
if err != nil {
return err
}
now := time.Now()
for _, alarm := range alarms {
if now.After(time.Time(alarm.Time)) {
err = DeleteAlarmSingle(db, alarm)
if err != nil {
return err
}
}
}
return nil
}
2022-10-05 20:33:31 +00:00
type AlarmException struct {
Id Identifier `json:"id"`
Start *Date `json:"start"`
End *Date `json:"end"`
2022-10-06 12:02:56 +00:00
Comment string `json:"comment,omitempty"`
2022-10-05 20:33:31 +00:00
}
func GetAlarmException(db *LevelDBStorage, id Identifier) (alarm *AlarmException, err error) {
alarm = &AlarmException{}
err = db.get(fmt.Sprintf("alarm-exception-%s", id.ToString()), alarm)
return
}
func GetAlarmExceptions(db *LevelDBStorage) (alarms []*AlarmException, err error) {
iter := db.search("alarm-exception-")
defer iter.Release()
for iter.Next() {
var a AlarmException
err = decodeData(iter.Value(), &a)
if err != nil {
return
}
alarms = append(alarms, &a)
}
return
}
func PutAlarmException(db *LevelDBStorage, alarm *AlarmException) (err error) {
var key string
if alarm.Id.IsEmpty() {
2022-12-15 17:40:09 +00:00
var id Identifier
2022-10-05 20:33:31 +00:00
key, id, err = db.findBytesKey("alarm-exception-", IDENTIFIER_LEN)
if err != nil {
return err
}
2022-12-15 17:40:09 +00:00
alarm.Id = id
} else {
key = fmt.Sprintf("alarm-exception-%s", alarm.Id.ToString())
2022-10-05 20:33:31 +00:00
}
return db.put(key, alarm)
}
func DeleteAlarmException(db *LevelDBStorage, alarm *AlarmException) (err error) {
return db.delete(fmt.Sprintf("alarm-exception-%s", alarm.Id.ToString()))
}