Working on next alarm retrieval

This commit is contained in:
nemunaire 2022-10-06 14:02:56 +02:00
parent 6e54ad1a87
commit 9a06d04ce0
8 changed files with 261 additions and 40 deletions

View File

@ -13,7 +13,13 @@ import (
func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, router *gin.RouterGroup) { func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, router *gin.RouterGroup) {
router.GET("/alarms/next", func(c *gin.Context) { 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) { 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) c.JSON(http.StatusOK, alarms)
}) })
router.POST("/alarms/single", func(c *gin.Context) { router.POST("/alarms/single", func(c *gin.Context) {
var alarm reveil.AlarmSingle 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."}) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "End date is already passed."})
return 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."}) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Start is defined after End. Please verify your inputs."})
return return
} }
@ -184,7 +189,11 @@ func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, router *
}) })
repeatedAlarmsRoutes.GET("", func(c *gin.Context) { 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) { repeatedAlarmsRoutes.PUT("", func(c *gin.Context) {
oldalarm := c.MustGet("alarm").(*reveil.AlarmRepeated) oldalarm := c.MustGet("alarm").(*reveil.AlarmRepeated)

View File

@ -2,6 +2,7 @@ package reveil
import ( import (
"fmt" "fmt"
"sort"
"time" "time"
) )
@ -37,13 +38,117 @@ func (h *Hour) UnmarshalJSON(src []byte) error {
return nil 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 { type AlarmRepeated struct {
Id Identifier `json:"id"` Id Identifier `json:"id"`
Weekday uint8 `json:"weekday"` Weekday time.Weekday `json:"weekday"`
StartTime *Hour `json:"time"` StartTime *Hour `json:"time"`
FollowingRoutines []Identifier `json:"routines"` FollowingRoutines []Identifier `json:"routines"`
IgnoreExceptions bool `json:"ignore_exceptions"` 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) { 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 alarm.Id = id
// Don't store this, this is autocalculated
alarm.Excepts = nil
alarm.NextTime = nil
return db.put(key, alarm) return db.put(key, alarm)
} }
@ -94,7 +202,7 @@ type AlarmSingle struct {
Id Identifier `json:"id"` Id Identifier `json:"id"`
Time time.Time `json:"time"` Time time.Time `json:"time"`
FollowingRoutines []Identifier `json:"routines"` FollowingRoutines []Identifier `json:"routines"`
Comment string `json:"comment"` Comment string `json:"comment,omitempty"`
} }
func GetAlarmSingle(db *LevelDBStorage, id Identifier) (alarm *AlarmSingle, err error) { func GetAlarmSingle(db *LevelDBStorage, id Identifier) (alarm *AlarmSingle, err error) {
@ -145,7 +253,7 @@ type AlarmException struct {
Id Identifier `json:"id"` Id Identifier `json:"id"`
Start *Date `json:"start"` Start *Date `json:"start"`
End *Date `json:"end"` End *Date `json:"end"`
Comment string `json:"comment"` Comment string `json:"comment,omitempty"`
} }
func GetAlarmException(db *LevelDBStorage, id Identifier) (alarm *AlarmException, err error) { func GetAlarmException(db *LevelDBStorage, id Identifier) (alarm *AlarmException, err error) {

View File

@ -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.id = id;
this.weekday = weekday; this.weekday = weekday;
this.time = time; this.time = time;
this.routines = routines; this.routines = routines;
this.ignore_exceptions = ignore_exceptions; this.ignore_exceptions = ignore_exceptions;
this.comment = comment; this.comment = comment;
this.excepts = excepts;
this.next_time = next_time;
} }
async delete() { async delete() {

View File

@ -61,3 +61,28 @@ export async function getAlarmSingle(aid) {
throw new Error((await res.json()).errmsg); 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);
}
}

View File

@ -21,6 +21,7 @@
/> />
<div class="flex-fill d-flex flex-column bg-light"> <div class="flex-fill d-flex flex-column bg-light">
<slot></slot> <slot></slot>
<div class="d-flex d-lg-none mt-1 mb-4"></div>
</div> </div>
<Toaster /> <Toaster />
<Header <Header

View File

@ -4,7 +4,19 @@
Icon, Icon,
} from 'sveltestrap'; } from 'sveltestrap';
import DateFormat from '../components/DateFormat.svelte';
import { getNextAlarm, newNCyclesAlarm } from '../lib/alarmsingle';
import { alarmsSingle } from '../stores/alarmsingle';
import { quotes } from '../stores/quotes'; import { quotes } from '../stores/quotes';
let nextAlarmP = getNextAlarm();
function newCyclesAlarm(ncycles) {
newNCyclesAlarm(ncycles).then(() => {
nextAlarmP = getNextAlarm();
alarmsSingle.clear();
})
}
</script> </script>
<Container class="flex-fill d-flex flex-column justify-content-center text-center"> <Container class="flex-fill d-flex flex-column justify-content-center text-center">
@ -24,19 +36,48 @@
</div> </div>
{/await} {/await}
{/if} {/if}
<div class="display-5 mb-4"> <div class="display-5 mb-5">
Prochain réveil&nbsp;: demain matin à 7h10 (dans 5 cycles) {#await nextAlarmP}
{:then nextalarm}
Prochain réveil&nbsp;:
{#if nextalarm.getDay() == new Date().getDay() && nextalarm.getMonth() == new Date().getMonth() && nextalarm.getFullYear() == new Date().getFullYear()}
aujourd'hui à
<DateFormat date={nextalarm} timeStyle="long" />
<br class="d-block d-md-none" />
<span class="text-muted">(dans {Math.trunc((nextalarm.getTime()-Date.now())/5400000)}&nbsp;cycles + {Math.trunc(((nextalarm.getTime()-Date.now())%5400000)/60000)}&nbsp;min)</span>
{:else if nextalarm.getDay() == new Date(Date.now() + 86400000).getDay() && nextalarm.getMonth() == new Date(Date.now() + 86400000).getMonth() && nextalarm.getFullYear() == new Date(Date.now() + 86400000).getFullYear()}
demain à
<DateFormat date={nextalarm} timeStyle="long" />
<br class="d-block d-md-none" />
<span class="text-muted">(dans {Math.trunc((nextalarm.getTime()-Date.now())/5400000)}&nbsp;cycles + {Math.trunc(((nextalarm.getTime()-Date.now())%5400000)/60000)}&nbsp;min)</span>
{:else if nextalarm.getTime() < Date.now() + 604800000}
<span title={nextalarm}>{nextalarm.toLocaleString('default', {weekday: 'long'})}</span>
à
<DateFormat date={nextalarm} timeStyle="long" />
{:else}
<DateFormat date={nextalarm} dateStyle="short" timeStyle="long" />
{/if}
{/await}
</div> </div>
<div class="d-flex gap-3 justify-content-center"> <div class="d-flex gap-3 justify-content-center">
<button class="btn btn-primary"> <a
href="alarms/single/new"
class="btn btn-primary"
>
<Icon name="node-plus" /> <Icon name="node-plus" />
Programmer un nouveau réveil Programmer un nouveau réveil
</button> </a>
<button class="btn btn-info"> <button
class="btn btn-info"
on:click={() => newCyclesAlarm(5)}
>
<Icon name="node-plus" /> <Icon name="node-plus" />
5 cycles 5 cycles
</button> </button>
<button class="btn btn-info"> <button
class="btn btn-info"
on:click={() => newCyclesAlarm(6)}
>
<Icon name="node-plus" /> <Icon name="node-plus" />
6 cycles 6 cycles
</button> </button>

View File

@ -51,9 +51,7 @@
objP.then((o) => obj = o); objP.then((o) => obj = o);
} }
function editThis() { let edit = false;
goto('alarms/' + $page.params["kind"]);
}
function deleteThis() { function deleteThis() {
obj.delete().then(() => { obj.delete().then(() => {
@ -84,6 +82,11 @@
<h2 class="mb-0"> <h2 class="mb-0">
{slugToTitle($page.params["kind"])} du <DateFormat date={alarm.time} dateStyle="long" /> {slugToTitle($page.params["kind"])} du <DateFormat date={alarm.time} dateStyle="long" />
</h2> </h2>
{#if alarm.comment}
<p>
{alarm.comment}
</p>
{/if}
<ListGroup class="my-2"> <ListGroup class="my-2">
<ListGroupItem> <ListGroupItem>
<strong>Date du réveil</strong> <DateFormat date={alarm.time} dateStyle="long" /> <strong>Date du réveil</strong> <DateFormat date={alarm.time} dateStyle="long" />
@ -100,8 +103,13 @@
</div> </div>
{:then alarm} {:then alarm}
<h2 class="mb-0"> <h2 class="mb-0">
{slugToTitle($page.params["kind"])} des {weekdayStr(alarm.weekday)} à {alarm.time} {slugToTitle($page.params["kind"])} des {weekdayStr(alarm.weekday)}s à {alarm.time}
</h2> </h2>
{#if alarm.comment}
<p>
{alarm.comment}
</p>
{/if}
<ListGroup class="my-2"> <ListGroup class="my-2">
<ListGroupItem> <ListGroupItem>
<strong>Jour de la semaine</strong> {weekdayStr(alarm.weekday)} <strong>Jour de la semaine</strong> {weekdayStr(alarm.weekday)}
@ -112,7 +120,22 @@
<ListGroupItem> <ListGroupItem>
<strong>Ignorer les exceptions&nbsp;?</strong> {alarm.ignore_exceptions?"oui":"non"} <strong>Ignorer les exceptions&nbsp;?</strong> {alarm.ignore_exceptions?"oui":"non"}
</ListGroupItem> </ListGroupItem>
{#if alarm.next_time}
<ListGroupItem>
<strong>Prochaine occurrence</strong> <DateFormat date={new Date(alarm.next_time)} dateStyle="long" />
</ListGroupItem>
{/if}
</ListGroup> </ListGroup>
{#if alarm.excepts}
<h3>Prochaines exceptions</h3>
<ListGroup class="my-2">
{#each alarm.excepts as except}
<ListGroupItem>
<DateFormat date={new Date(except)} dateStyle="long" />
</ListGroupItem>
{/each}
</ListGroup>
{/if}
{/await} {/await}
{:else if $page.params["kind"] == "exceptions"} {:else if $page.params["kind"] == "exceptions"}
{#await objP} {#await objP}
@ -123,30 +146,37 @@
<h2 class="mb-0"> <h2 class="mb-0">
{slugToTitle($page.params["kind"])} du <DateRangeFormat startDate={exception._start()} endDate={exception._end()} dateStyle="long" /> {slugToTitle($page.params["kind"])} du <DateRangeFormat startDate={exception._start()} endDate={exception._end()} dateStyle="long" />
</h2> </h2>
{#if exception.comment}
<p>
{exception.comment}
</p>
{/if}
Entre le <DateRangeFormat startDate={exception._start()} endDate={exception._end()} dateStyle="long" /> Entre le <DateRangeFormat startDate={exception._start()} endDate={exception._end()} dateStyle="long" />
{/await} {/await}
{/if} {/if}
{#await objP then alarm} {#if !edit}
<ListGroup class="my-2 text-center"> {#await objP then alarm}
<ListGroupItem <ListGroup class="my-2 text-center">
action <ListGroupItem
tag="button" action
class="text-info fw-bold" tag="button"
on:click={editThis} class="text-info fw-bold"
> on:click={() => edit = !edit}
<Icon name="pencil" /> >
Éditer ce {slugToTitle($page.params["kind"]).toLowerCase()} <Icon name="pencil" />
</ListGroupItem> Éditer ce {slugToTitle($page.params["kind"]).toLowerCase()}
<ListGroupItem </ListGroupItem>
action <ListGroupItem
tag="button" action
class="text-danger fw-bold" tag="button"
on:click={deleteThis} class="text-danger fw-bold"
> on:click={deleteThis}
<Icon name="trash" /> >
Supprimer ce {slugToTitle($page.params["kind"]).toLowerCase()} <Icon name="trash" />
</ListGroupItem> Supprimer ce {slugToTitle($page.params["kind"]).toLowerCase()}
</ListGroup> </ListGroupItem>
{/await} </ListGroup>
{/await}
{/if}
</Container> </Container>

View File

@ -148,6 +148,11 @@
</FormGroup> </FormGroup>
{/if} {/if}
<FormGroup>
<Label for="comment">Commentaire</Label>
<Input id="comment" type="text" bind:value={obj.comment} />
</FormGroup>
<Button type="submit" color="primary" class="d-none d-md-block"> <Button type="submit" color="primary" class="d-none d-md-block">
Ajouter Ajouter
</Button> </Button>