diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index eca3b89..0000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -ui/node_modules -ui/build \ No newline at end of file diff --git a/.drone-manifest.yml b/.drone-manifest.yml deleted file mode 100644 index 3b6929c..0000000 --- a/.drone-manifest.yml +++ /dev/null @@ -1,22 +0,0 @@ -image: registry.nemunai.re/reveil:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} -{{#if build.tags}} -tags: -{{#each build.tags}} - - {{this}} -{{/each}} -{{/if}} -manifests: - - image: registry.nemunai.re/reveil:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 - platform: - architecture: amd64 - os: linux - - image: registry.nemunai.re/reveil:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 - platform: - architecture: arm64 - os: linux - variant: v8 - - image: registry.nemunai.re/reveil:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm - platform: - architecture: arm - os: linux - variant: v7 diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 9551cff..0000000 --- a/.drone.yml +++ /dev/null @@ -1,92 +0,0 @@ ---- -kind: pipeline -type: docker -name: build-arm - -platform: - os: linux - arch: arm - -workspace: - base: /go - path: src/git.nemunai.re/nemunaire/reveil - -steps: -- name: build front - image: node:18-alpine - commands: - - mkdir deploy - - cd ui - - npm install --network-timeout=100000 - - sed -i 's!@popperjs/core/dist/esm/popper!@popperjs/core!' node_modules/sveltestrap/src/*.js node_modules/sveltestrap/src/*.svelte - - npm run build - - tar chjf ../deploy/static.tar.bz2 build - -- name: build - image: golang:1-alpine - commands: - - apk --no-cache add alsa-lib-dev build-base git pkgconf - - go get -v -d - - go vet -v - - go build -v -ldflags '-w -X main.Version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o deploy/reveil-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - - ln deploy/reveil-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} reveil - when: - event: - exclude: - - tag - -- name: build tag - image: golang:1-alpine - commands: - - apk --no-cache add alsa-lib-dev build-base git pkgconf - - go get -v -d - - go vet -v - - go build -v -ldflags '-w -X main.Version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o deploy/reveil-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - - ln deploy/reveil-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} reveil - when: - event: - - tag - -- name: docker - image: plugins/docker - settings: - registry: registry.nemunai.re - repo: registry.nemunai.re/reveil - auto_tag: true - auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - dockerfile: Dockerfile-norebuild - username: - from_secret: docker_username - password: - from_secret: docker_password - -trigger: - event: - - cron - - push - - tag - ---- -kind: pipeline -name: docker-manifest - -steps: -- name: publish on Docker Hub - image: plugins/manifest - settings: - auto_tag: true - ignore_missing: true - spec: .drone-manifest.yml - username: - from_secret: docker_username - password: - from_secret: docker_password - -trigger: - event: - - cron - - push - - tag - -depends_on: -- build-arm diff --git a/.gitignore b/.gitignore index 878065d..bc5efb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1 @@ -/actions -/alarms.db -/gongs -/reveil -/routines -/settings.json -/tracks/ -/vendor -/ui/build -/ui/node_modules \ No newline at end of file +reveil \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 156c54f..0000000 --- a/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM node:18-alpine as nodebuild - -WORKDIR /ui - -COPY ui/ . - -RUN npm install --network-timeout=100000 && \ - sed -i 's!@popperjs/core/dist/esm/popper!@popperjs/core!' node_modules/sveltestrap/src/*.js node_modules/sveltestrap/src/*.svelte && \ - npm run build - - -FROM golang:1-alpine AS build - -RUN apk --no-cache add git go-bindata - -COPY . /go/src/git.nemunai.re/nemunaire/reveil -COPY --from=nodebuild /ui/build /go/src/git.nemunai.re/nemunaire/reveil/ui/build -WORKDIR /go/src/git.nemunai.re/nemunaire/reveil -RUN go get -v && go generate -v && go build -v -ldflags="-s -w" - - -FROM alpine:3.16 - -VOLUME /data -WORKDIR /data - -EXPOSE 8080 -CMD ["/srv/reveil"] - -COPY --from=build /go/src/git.nemunai.re/nemunaire/reveil/reveil /srv/reveil diff --git a/Dockerfile-norebuild b/Dockerfile-norebuild deleted file mode 100644 index 9476b53..0000000 --- a/Dockerfile-norebuild +++ /dev/null @@ -1,9 +0,0 @@ -FROM alpine:3.16 - -VOLUME /data -WORKDIR /data - -EXPOSE 8080 -CMD ["/srv/reveil"] - -COPY reveil /srv/reveil diff --git a/api/actions.go b/api/actions.go deleted file mode 100644 index 05fe2db..0000000 --- a/api/actions.go +++ /dev/null @@ -1,93 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" -) - -func declareActionsRoutes(cfg *config.Config, router *gin.RouterGroup) { - router.GET("/actions", func(c *gin.Context) { - actions, err := reveil.LoadActions(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, actions) - }) - router.POST("/actions", func(c *gin.Context) { - c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "TODO"}) - }) - - actionsRoutes := router.Group("/actions/:tid") - actionsRoutes.Use(func(c *gin.Context) { - actions, err := reveil.LoadActions(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - for _, t := range actions { - if t.Id.ToString() == c.Param("tid") { - c.Set("action", t) - c.Next() - return - } - } - - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Action not found"}) - }) - - actionsRoutes.GET("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("action")) - }) - actionsRoutes.PUT("", func(c *gin.Context) { - oldaction := c.MustGet("action").(*reveil.Action) - - var action reveil.Action - if err := c.ShouldBindJSON(&action); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if action.Name != oldaction.Name { - err := oldaction.Rename(action.Name) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to rename the action: %s", err.Error())}) - return - } - } - - if action.Enabled != oldaction.Enabled { - var err error - if action.Enabled { - err = oldaction.Enable() - } else { - err = oldaction.Disable() - } - - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to enable/disable the action: %s", err.Error())}) - return - } - } - - c.JSON(http.StatusOK, oldaction) - }) - actionsRoutes.DELETE("", func(c *gin.Context) { - action := c.MustGet("action").(*reveil.Action) - - err := action.Remove() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to remove the action: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, nil) - }) -} diff --git a/api/alarm.go b/api/alarm.go deleted file mode 100644 index 7b1e9d0..0000000 --- a/api/alarm.go +++ /dev/null @@ -1,47 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/player" -) - -func declareAlarmRoutes(cfg *config.Config, router *gin.RouterGroup) { - router.GET("/alarm", func(c *gin.Context) { - if player.CommonPlayer == nil { - c.JSON(http.StatusOK, false) - } else { - c.JSON(http.StatusOK, true) - } - }) - - router.POST("/alarm/run", func(c *gin.Context) { - if player.CommonPlayer == nil { - err := player.WakeUp(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - } else { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Player already running"}) - return - } - - c.JSON(http.StatusOK, true) - }) - - router.DELETE("/alarm", func(c *gin.Context) { - if player.CommonPlayer != nil { - err := player.CommonPlayer.Stop() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - } - - c.JSON(http.StatusOK, true) - }) -} diff --git a/api/alarms.go b/api/alarms.go deleted file mode 100644 index 59dfe98..0000000 --- a/api/alarms.go +++ /dev/null @@ -1,293 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "time" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" -) - -func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, resetTimer func(), 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) { - alarms, err := reveil.GetAlarmsSingle(db) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, alarms) - }) - router.POST("/alarms/single", func(c *gin.Context) { - var alarm reveil.AlarmSingle - if err := c.ShouldBindJSON(&alarm); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if time.Now().After(alarm.Time) { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "This date is already passed."}) - return - } - - alarm.Id = nil - if err := reveil.PutAlarmSingle(db, &alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, alarm) - }) - - router.GET("/alarms/repeated", func(c *gin.Context) { - alarms, err := reveil.GetAlarmsRepeated(db) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, alarms) - }) - router.POST("/alarms/repeated", func(c *gin.Context) { - var alarm reveil.AlarmRepeated - if err := c.ShouldBindJSON(&alarm); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - alarm.Id = nil - if err := reveil.PutAlarmRepeated(db, &alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, alarm) - }) - - router.GET("/alarms/exceptions", func(c *gin.Context) { - exceptions, err := reveil.GetAlarmExceptions(db) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, exceptions) - }) - router.POST("/alarms/exceptions", func(c *gin.Context) { - var alarm reveil.AlarmException - if err := c.ShouldBindJSON(&alarm); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if alarm.Start == nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Start not defined"}) - return - } - if alarm.End == nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "End not defined"}) - return - } - if time.Now().After(time.Time(*alarm.End)) { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "End date is already passed."}) - return - } - 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 - } - - alarm.Id = nil - if err := reveil.PutAlarmException(db, &alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, alarm) - }) - - singleAlarmsRoutes := router.Group("/alarms/single/:aid") - singleAlarmsRoutes.Use(func(c *gin.Context) { - id, err := reveil.NewIdentifierFromString(c.Param("aid")) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Invalid alarm idenfifier: %s", err.Error())}) - return - } - - alarm, err := reveil.GetAlarmSingle(db, id) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": err.Error()}) - return - } - - c.Set("alarm", alarm) - - c.Next() - }) - - singleAlarmsRoutes.GET("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("alarm")) - }) - singleAlarmsRoutes.PUT("", func(c *gin.Context) { - oldalarm := c.MustGet("alarm").(*reveil.AlarmSingle) - - var alarm reveil.AlarmSingle - if err := c.ShouldBindJSON(&alarm); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - alarm.Id = oldalarm.Id - if err := reveil.PutAlarmSingle(db, &alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, alarm) - }) - singleAlarmsRoutes.DELETE("", func(c *gin.Context) { - alarm := c.MustGet("alarm").(*reveil.AlarmSingle) - - if err := reveil.DeleteAlarmSingle(db, alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, nil) - }) - - repeatedAlarmsRoutes := router.Group("/alarms/repeated/:aid") - repeatedAlarmsRoutes.Use(func(c *gin.Context) { - id, err := reveil.NewIdentifierFromString(c.Param("aid")) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Invalid alarm idenfifier: %s", err.Error())}) - return - } - - alarm, err := reveil.GetAlarmRepeated(db, id) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": err.Error()}) - return - } - - c.Set("alarm", alarm) - - c.Next() - }) - - repeatedAlarmsRoutes.GET("", func(c *gin.Context) { - 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) - - var alarm reveil.AlarmRepeated - if err := c.ShouldBindJSON(&alarm); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - alarm.Id = oldalarm.Id - if err := reveil.PutAlarmRepeated(db, &alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, alarm) - }) - repeatedAlarmsRoutes.DELETE("", func(c *gin.Context) { - alarm := c.MustGet("alarm").(*reveil.AlarmRepeated) - - if err := reveil.DeleteAlarmRepeated(db, alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, nil) - }) - - exceptionAlarmsRoutes := router.Group("/alarms/exceptions/:aid") - exceptionAlarmsRoutes.Use(func(c *gin.Context) { - id, err := reveil.NewIdentifierFromString(c.Param("aid")) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Invalid alarm idenfifier: %s", err.Error())}) - return - } - - alarm, err := reveil.GetAlarmException(db, id) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": err.Error()}) - return - } - - c.Set("alarm", alarm) - - c.Next() - }) - - exceptionAlarmsRoutes.GET("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("alarm")) - }) - exceptionAlarmsRoutes.PUT("", func(c *gin.Context) { - oldalarm := c.MustGet("alarm").(*reveil.AlarmException) - - var alarm reveil.AlarmException - if err := c.ShouldBindJSON(&alarm); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - alarm.Id = oldalarm.Id - if err := reveil.PutAlarmException(db, &alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, alarm) - }) - exceptionAlarmsRoutes.DELETE("", func(c *gin.Context) { - alarm := c.MustGet("alarm").(*reveil.AlarmException) - - if err := reveil.DeleteAlarmException(db, alarm); err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - resetTimer() - - c.JSON(http.StatusOK, nil) - }) -} diff --git a/api/gongs.go b/api/gongs.go deleted file mode 100644 index 93aaecd..0000000 --- a/api/gongs.go +++ /dev/null @@ -1,148 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "os" - "path" - "strings" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" -) - -func declareGongsRoutes(cfg *config.Config, router *gin.RouterGroup) { - router.GET("/gongs", func(c *gin.Context) { - gongs, err := reveil.LoadGongs(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, gongs) - }) - router.POST("/gongs", func(c *gin.Context) { - fgong, err := c.FormFile("gongfile") - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "No gong found"}) - return - } - - // Check file extension - if path.Ext(fgong.Filename) != ".mp3" && path.Ext(fgong.Filename) != ".flac" && path.Ext(fgong.Filename) != ".wav" { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad file type. You should only upload .mp3, .wav or .flac files."}) - return - } - - if strings.Contains(fgong.Filename, "/") { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad file name."}) - return - } - - dst := path.Join(cfg.GongsDir, fgong.Filename) - err = c.SaveUploadedFile(fgong, dst) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Something goes wrong when saving the gong: %s", err.Error())}) - return - } - - d, err := os.Stat(dst) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Something goes wrong when saving the gong: %s", err.Error())}) - return - } - - gong, err := reveil.LoadGong(cfg, dst, d) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to load gong: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, gong) - }) - - gongsRoutes := router.Group("/gongs/:tid") - gongsRoutes.Use(func(c *gin.Context) { - gongs, err := reveil.LoadGongs(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - for _, g := range gongs { - if g.Id.ToString() == c.Param("tid") { - c.Set("gong", g) - c.Next() - return - } - } - - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Gong not found"}) - }) - - gongsRoutes.GET("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("gong")) - }) - gongsRoutes.GET("/stream", func(c *gin.Context) { - gong := c.MustGet("gong").(*reveil.Gong) - - size, err := gong.Size() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to open the gong: %s", err.Error())}) - return - } - - fd, err := gong.Open() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to open the gong: %s", err.Error())}) - return - } - defer fd.Close() - - c.DataFromReader(http.StatusOK, size, gong.ContentType(), fd, map[string]string{}) - }) - gongsRoutes.PUT("", func(c *gin.Context) { - oldgong := c.MustGet("gong").(*reveil.Gong) - - var gong reveil.Gong - if err := c.ShouldBindJSON(&gong); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if gong.Name != oldgong.Name { - err := oldgong.Rename(gong.Name) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to rename the gong: %s", err.Error())}) - return - } - } - - if gong.Enabled != oldgong.Enabled { - var err error - if gong.Enabled { - err = oldgong.SetDefault(cfg) - } - - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to set the new default gong: %s", err.Error())}) - return - } - } - - c.JSON(http.StatusOK, oldgong) - }) - gongsRoutes.DELETE("", func(c *gin.Context) { - gong := c.MustGet("gong").(*reveil.Gong) - - err := gong.Remove() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to remove the gong: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, nil) - }) -} diff --git a/api/history.go b/api/history.go deleted file mode 100644 index 0df39bc..0000000 --- a/api/history.go +++ /dev/null @@ -1,13 +0,0 @@ -package api - -import ( - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" -) - -func declareHistoryRoutes(cfg *config.Config, router *gin.RouterGroup) { - router.GET("/stats", func(c *gin.Context) { - - }) -} diff --git a/api/quotes.go b/api/quotes.go deleted file mode 100644 index 508bc39..0000000 --- a/api/quotes.go +++ /dev/null @@ -1,133 +0,0 @@ -package api - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" -) - -var quotes = []Quote{ - Quote{1, "La qualité d’un homme se calcule à sa démesure ; tentez, essayer, échouez même, ce sera votre réussite.", "Jacques brel"}, - Quote{2, "Certains veulent que ça arrive, d’autres aimeraient que ça arrive, et les autres font que ça arrive.", "Michael Jordan"}, - Quote{3, "Les winners sont des loosers qui se lèvent et essaient une fois de plus.", "Dennis DeYoung"}, - Quote{4, "L’homme le plus heureux est celui qui fait le bonheur d’un plus grand nombre d’autres.", "Denis Diderot"}, - Quote{5, "La joie est en tout ; il faut savoir l’extraire.", "Confucius"}, - Quote{6, "L’optimiste ne refuse jamais de voir le côté négatif des choses ; il refuse simplement de s’attarder dessus.", "Alexandre Lockhart"}, - Quote{7, "L’obstination est le chemin de la réussite.", "Charlie chaplin"}, - Quote{8, "Si on veut obtenir quelque chose que l’on n’a jamais eu, il faut tenter quelque chose que l’on n’a jamais fait.", "Péricles"}, - Quote{9, "La plus grande erreur que puisse faire un homme est d’avoir peur d’en faire une.", "Elbert Hubbard"}, - Quote{10, "La définition de la folie, c’est de refaire toujours la même chose, et d’attendre un résultat différent.", "Albert Einstein"}, - Quote{11, "Peu importe qui tu es ou qui tu as été, tu peux être qui tu veux.", "Clement Stone"}, - Quote{12, "Celui qui attend que tout danger soir écarté pour mettre les voiles ne prendra jamais la mer.", "Thomas Fuller"}, - Quote{13, "Beaucoup de ceux qui ont échoué n’ont pas réalisé qu’ils étaient aussi près du succès quand ils ont abandonné.", "Thomas Edison"}, - Quote{14, "La vie, c’est comme une bicyclette, il faut avancer pour ne pas perdre l’équilibre.", "Albert Einstein"}, - Quote{15, "Vous ne pouvez choisir ni comment mourir, ni quand. Mais vous pouvez décider de comment vous allez vivre. Maintenant.", "Joan Baez"}, - Quote{16, "Je passe mon temps à faire ce que je ne sais pas faire, pour apprendre à le faire.", "Pablo Picasso"}, - Quote{17, "Le dictionnaire, c’est le seul endroit où Succès arrive avant Travail.", "Vince Lombardi"}, - Quote{18, "Si on me donnait six heures pour abattre un arbre, je passerais la première à affûter la hache.", "Abraham Lincoln"}, - Quote{19, "A la fin, ce qui compte, ce ne sont pas les années qu’il y a eu dans la vie. C’est la vie qu’il y a eu dans les années.", "Abraham Lincoln"}, - Quote{20, "La logique peut vous mener d’un point A à un point B. L’imagination peut vous mener partout.", "Albert Einstein"}, - Quote{21, "N’allez pas où va le chemin. Allez là où il n’y en a pas encore, et ouvrez la route.", "Ralph Waldo Emerson"}, - Quote{22, "Un homme doit être assez grand pour admettre ses erreurs, assez intelligent pour apprendre de celles-ci et assez fort pour les corriger. John C.", "Maxwell"}, - Quote{23, "Si ce que vous faites ne vous rapproche pas de vos buts, alors c’est que ça vous éloigne de ceux-ci.", "Brian Tracy"}, - Quote{24, "Qui veut faire quelque chose trouve un moyen, qui ne veut rien faire trouve une excuse.", "Proverbe arabe"}, - Quote{25, "Si vous voulez que la vie vous sourie, apportez-lui votre bonne humeur.", "Baruch Spinoza"}, - Quote{26, "Nulle pierre ne peut être polie sans friction, nul homme ne peut parfaire son expérience sans épreuve.", "Confucius"}, - Quote{27, "Se donner du mal pour les petites choses, c’est parvenir aux grandes, avec le temps.", "Samuel Beckett"}, - Quote{28, "Le succès c’est d’avoir ce que vous désirez. Le bonheur c’est aimer ce que vous avez. H.", "Jackson Brown"}, - Quote{29, "La chose la plus difficile est de n’attribuer aucune importance aux choses qui n’ont aucune importance.", "Charles de Gaulle"}, - Quote{30, "Nous commençons à vieillir quand nous remplaçons nos rêves par des regrets.", "Sénèque"}, - Quote{31, "Je préfère vivre en optimiste et me tromper, que vivre en pessimiste pour la seule satisfaction d’avoir eu raison.", "Milan Kundera"}, - Quote{32, "Les optimistes proclament que nous vivons dans un monde rempli de possibilités… Les pessimistes ont peur que ce soit vrai !", "James Branch Cabell"}, - Quote{33, "L’une des meilleures façons d’aider quelqu’un est de lui donner une responsabilité et de lui faire savoir que vous lui faites confiance.", "Booker Washington"}, - Quote{34, "La seule limite à notre épanouissement de demain sera nos doutes d’aujourd’hui.", "Franklin Roosevelt"}, - Quote{35, "C’est dans les moments les plus sombres qu’on voit le mieux les étoiles.", "Charles Beard"}, - Quote{36, "Ne jugez pas chaque journée par votre récolte, mais par les graines que vous avez plantées.", "Robert Stevenson"}, - Quote{37, "Contentez-vous d’agir et laissez les autres parler.", "Baltasar Gracian"}, - Quote{38, "Mettez en tout un grain d’audace.", "Baltazar Gracian"}, - Quote{39, "Un sourire coûte moins cher que l’électricité, mais donne autant de lumière.", "Abbé Pierre"}, - Quote{40, "N’attendez pas d’être heureux pour sourire, souriez pour être heureux.", "Edward Kramer"}, - Quote{41, "Tout le monde est un génie. Mais si on juge un poisson sur sa capacité à grimper à un arbre, il passera sa vie à croire qu’il est stupide.", "Albert Einstein"}, - Quote{42, "Le succès est la capacité d’aller d’échec en échec sans perdre son enthousiasme.", "Winston Churchill"}, - Quote{43, "Rien ne sert de défendre le monde d’hier quand on peut construire le monde de demain.", "Peter Drucker"}, - Quote{44, "Le plus grand plaisir de la vie est de réaliser ce que les autres vous pensent incapable de réaliser.", "Walter Bagehot"}, - Quote{45, "Nous avons deux choix dans la vie : le premier est d’accepter les choses comme elles sont et la deuxième est de prendre la décision de les changer.", "Denis Waitley"}, - Quote{46, "N’acceptez jamais la défaite, vous êtes peut-être à un pas de la réussite.", "Jack E Addington"}, - Quote{47, "L’échec est seulement l’opportunité de recommencer d’une façon plus intelligente.", "Henry Ford"}, - Quote{48, "Il n’y a qu’une façon d’échouer, c’est d’abandonner avant d’avoir réussi.", "Georges Clemenceau"}, - Quote{49, "Vous n’avez rien à craindre car l’échec est impossible. Vous ne pouvez qu’apprendre, évoluer et devenir meilleur que vous ne l’avez jamais été.", "Hal Elrod"}, - Quote{50, "Appréciez d’échouer, et apprenez de l’échec, car on n’apprend rien de ses succès.", "James Dyson"}, - Quote{51, "Si vous vivez un moment difficile, ne blâmez pas la vie. Vous êtes juste en train de devenir plus fort.", "Gandhi"}, - Quote{52, "Au milieu de toute difficulté se trouve cachée une opportunité.", "Albert Einstein"}, - Quote{53, "L’échec est l’épice qui donne sa saveur au succès.", "Truman Capote"}, - Quote{54, "Je n’ai pas échoué. J’ai simplement trouvé 10 000 façons de ne pas y arriver.", "Thomas Edison"}, - Quote{55, "Les plus belles années d’une vie sont celles que l’on n’a pas encore vécues.", "Victor Hugo"}, - Quote{56, "Si vous voulez que la vie vous sourie, apportez-lui d’abord votre bonne humeur.", "Baruch Spinoza"}, - Quote{57, "Ce n’est pas parce que les choses sont difficiles que nous n’osons pas, c’est parce que nous n’osons pas qu’elles sont difficiles.", "Sénèque"}, - Quote{58, "Si on veut obtenir quelque chose que l’on n’a jamais eu, il faut tenter quelque chose que l’on n’a jamais fait.", "Périclès"}, - Quote{59, "Accepte ce qui est, laisse aller ce qui était, aie confiance en ce qui sera.", "Bouddha"}, - Quote{60, "Choisis un travail que tu aimes, tu n’auras pas à travailler un seul jour de ta vie.", "Confucius"}, - Quote{61, "Il est de loin plus lucratif et plus amusant de capitaliser sur vos points forts que d’essayer de corriger tous vos points faibles.", "Tim Ferriss"}, - Quote{62, "Un homme ayant du succès est celui pouvant se construire une ferme fondation avec les briques que les autres lui jettent.", "David Brinkley"}, - Quote{63, "Ils ne savaient pas que c’était impossible alors ils l’ont fait.", "Mark Twain"}, - Quote{64, "Les gagnants trouvent des moyens, les perdants des excuses.", "Franklin Roosevelt"}, - Quote{65, "Croyez en vos rêves et ils se réaliseront peut-être. Croyez en vous et ils se réaliseront sûrement.", "Martin Luther King"}, - Quote{66, "Un voyage de mille lieues commence toujours par un premier pas.", "Lao Tseu"}, - Quote{67, "Il faut toujours viser la lune car même en cas d’échec on atterrit dans les étoiles.", "Oscar Wilde"}, - Quote{68, "Ce qui est plus triste qu’une œuvre inachevée, c’est une œuvre jamais commencée.", "Christinna Rosseti"}, - Quote{69, "L’obscurité ne peut pas chasser l’obscurité, seule la lumière le peut. La haine ne peut pas chasser la haine, seul l’amour le peut.", "Martin Luther King"}, - Quote{70, "Je n’ai pas peur de demain, car j’ai vu hier et j’aime aujourd’hui.", "William Allen White"}, - Quote{71, "Un pessimiste voit la difficulté dans chaque opportunité, un optimiste voit l’opportunité dans chaque difficulté.", "Winston Churchill"}, - Quote{72, "Si vous regardez avec attention, la plupart des succès obtenus du jour au lendemain prennent beaucoup de temps.", "Steve Jobs"}, - Quote{73, "Les échecs sont les marches que nous montons pour atteindre le succès.", "Roy Bennett"}, - Quote{74, "Si vous pouvez le rêver, vous pouvez le faire.", "Walt Disney"}, - Quote{75, "Croyez en vos rêves et ils se réaliseront peut-être. Croyez en vous, et ils se réaliseront sûrement.", "Martin Luther King"}, - Quote{76, "La première étape est de dire que tu peux.", "Will Smith"}, - Quote{77, "Quand on veut une chose, tout l’Univers conspire à nous permettre de réaliser notre rêve.", "Paulo Coelho"}, - Quote{78, "Tout est possible à qui rêve, ose, travaille et n’abandonne jamais.", "Xavier Dolan"}, - Quote{79, "La sagesse, c’est d’avoir des rêves suffisamment grands pour ne pas les perdre de vue lorsqu’on les poursuit.", "Oscar Wilde"}, - Quote{80, "Quand vous osez rêver grand, c’est là où votre système nerveux va créer du plaisir et être orienté solutions.", "David Laroche"}, - Quote{81, "Choisir sa vie, c’est se demander si l’on doit réaliser ses rêves ou subir le quotidien.", "Sonia Lahsaini"}, - Quote{82, "Fais de ta vie un rêve, et d’un rêve, une réalité.", "Antoine de Saint-Exupéry"}, -} - -func declareQuotesRoutes(cfg *config.Config, router *gin.RouterGroup) { - router.GET("/quoteoftheday", func(c *gin.Context) { - c.JSON(http.StatusOK, quotes[time.Now().Add(-5*time.Hour).YearDay()%len(quotes)]) - }) - - router.GET("/quotes", func(c *gin.Context) { - c.JSON(http.StatusOK, quotes) - }) - router.POST("/quotes", func(c *gin.Context) { - - }) - - quotesRoutes := router.Group("/quotes/:qid") - quotesRoutes.Use(quoteHandler) - - quotesRoutes.GET("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("quote")) - }) - quotesRoutes.PUT("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("quote")) - }) - quotesRoutes.DELETE("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("quote")) - }) -} - -func quoteHandler(c *gin.Context) { - c.Set("quote", nil) - - c.Next() -} - -type Quote struct { - Id int `json:"id"` - Content string `json:"content"` - Author string `json:"author"` -} diff --git a/api/routes.go b/api/routes.go deleted file mode 100644 index 38b3f40..0000000 --- a/api/routes.go +++ /dev/null @@ -1,22 +0,0 @@ -package api - -import ( - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" -) - -func DeclareRoutes(router *gin.Engine, cfg *config.Config, db *reveil.LevelDBStorage, resetTimer func()) { - apiRoutes := router.Group("/api") - - declareActionsRoutes(cfg, apiRoutes) - declareAlarmRoutes(cfg, apiRoutes) - declareAlarmsRoutes(cfg, db, resetTimer, apiRoutes) - declareGongsRoutes(cfg, apiRoutes) - declareHistoryRoutes(cfg, apiRoutes) - declareQuotesRoutes(cfg, apiRoutes) - declareRoutinesRoutes(cfg, apiRoutes) - declareTracksRoutes(cfg, apiRoutes) - declareSettingsRoutes(cfg, apiRoutes) -} diff --git a/api/routines.go b/api/routines.go deleted file mode 100644 index 16d7c0a..0000000 --- a/api/routines.go +++ /dev/null @@ -1,81 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" -) - -func declareRoutinesRoutes(cfg *config.Config, router *gin.RouterGroup) { - router.GET("/routines", func(c *gin.Context) { - routines, err := reveil.LoadRoutines(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, routines) - }) - router.POST("/routines", func(c *gin.Context) { - c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "TODO"}) - }) - - routinesRoutes := router.Group("/routines/:tid") - routinesRoutes.Use(func(c *gin.Context) { - routines, err := reveil.LoadRoutines(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - for _, t := range routines { - if t.Id.ToString() == c.Param("tid") { - c.Set("routine", t) - c.Next() - return - } - } - - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Routine not found"}) - }) - - routinesRoutes.GET("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("routine")) - }) - routinesRoutes.PUT("", func(c *gin.Context) { - oldroutine := c.MustGet("routine").(*reveil.Routine) - - var routine reveil.Routine - if err := c.ShouldBindJSON(&routine); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if routine.Name != oldroutine.Name { - err := oldroutine.Rename(routine.Name) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to rename the routine: %s", err.Error())}) - return - } - } - - // TODO: change actions - - c.JSON(http.StatusOK, oldroutine) - }) - routinesRoutes.DELETE("", func(c *gin.Context) { - routine := c.MustGet("routine").(*reveil.Routine) - - err := routine.Remove() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to remove the routine: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, nil) - }) -} diff --git a/api/settings.go b/api/settings.go deleted file mode 100644 index 7bc0a58..0000000 --- a/api/settings.go +++ /dev/null @@ -1,38 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" -) - -func declareSettingsRoutes(cfg *config.Config, router *gin.RouterGroup) { - router.GET("/settings", func(c *gin.Context) { - settings, err := reveil.ReadSettings(cfg.SettingsFile) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, settings) - }) - router.PUT("/settings", func(c *gin.Context) { - var config reveil.Settings - err := c.ShouldBindJSON(&config) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - err = reveil.SaveSettings(cfg.SettingsFile, config) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, config) - }) -} diff --git a/api/tracks.go b/api/tracks.go deleted file mode 100644 index 39ba861..0000000 --- a/api/tracks.go +++ /dev/null @@ -1,150 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "os" - "path" - "strings" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" -) - -func declareTracksRoutes(cfg *config.Config, router *gin.RouterGroup) { - router.GET("/tracks", func(c *gin.Context) { - tracks, err := reveil.LoadTracks(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, tracks) - }) - router.POST("/tracks", func(c *gin.Context) { - ftrack, err := c.FormFile("trackfile") - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "No track found"}) - return - } - - // Check file extension - if path.Ext(ftrack.Filename) != ".mp3" && path.Ext(ftrack.Filename) != ".flac" { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad file type. You should only upload .mp3 or .flac files."}) - return - } - - if strings.Contains(ftrack.Filename, "/") { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad file name."}) - return - } - - dst := path.Join(cfg.TracksDir, ftrack.Filename) - err = c.SaveUploadedFile(ftrack, dst) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Something goes wrong when saving the track: %s", err.Error())}) - return - } - - d, err := os.Stat(dst) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Something goes wrong when saving the track: %s", err.Error())}) - return - } - - track, err := reveil.LoadTrack(dst, d) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to load track: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, track) - }) - - tracksRoutes := router.Group("/tracks/:tid") - tracksRoutes.Use(func(c *gin.Context) { - tracks, err := reveil.LoadTracks(cfg) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - for _, t := range tracks { - if t.Id.ToString() == c.Param("tid") { - c.Set("track", t) - c.Next() - return - } - } - - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Track not found"}) - }) - - tracksRoutes.GET("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("track")) - }) - tracksRoutes.GET("/stream", func(c *gin.Context) { - track := c.MustGet("track").(*reveil.Track) - - size, err := track.Size() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to open the track: %s", err.Error())}) - return - } - - fd, err := track.Open() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to open the track: %s", err.Error())}) - return - } - defer fd.Close() - - c.DataFromReader(http.StatusOK, size, track.ContentType(), fd, map[string]string{}) - }) - tracksRoutes.PUT("", func(c *gin.Context) { - oldtrack := c.MustGet("track").(*reveil.Track) - - var track reveil.Track - if err := c.ShouldBindJSON(&track); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if track.Name != oldtrack.Name { - err := oldtrack.Rename(track.Name) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to rename the track: %s", err.Error())}) - return - } - } - - if track.Enabled != oldtrack.Enabled { - var err error - if track.Enabled { - err = oldtrack.Enable(cfg) - } else { - err = oldtrack.Disable() - } - - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to enable/disable the track: %s", err.Error())}) - return - } - } - - c.JSON(http.StatusOK, oldtrack) - }) - tracksRoutes.DELETE("", func(c *gin.Context) { - track := c.MustGet("track").(*reveil.Track) - - err := track.Remove() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to remove the track: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, nil) - }) -} diff --git a/app.go b/app.go deleted file mode 100644 index 778abd6..0000000 --- a/app.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -import ( - "context" - "log" - "net/http" - "time" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/api" - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" - "git.nemunai.re/nemunaire/reveil/player" - "git.nemunai.re/nemunaire/reveil/ui" -) - -type App struct { - cfg *config.Config - db *reveil.LevelDBStorage - router *gin.Engine - srv *http.Server - nextAlarm *time.Timer -} - -func NewApp(cfg *config.Config) *App { - if cfg.DevProxy == "" { - gin.SetMode(gin.ReleaseMode) - } - gin.ForceConsoleColor() - router := gin.Default() - - router.Use(func(c *gin.Context) { - c.Next() - }) - - // Open Database - db, err := reveil.NewLevelDBStorage(cfg.LevelDBPath) - if err != nil { - log.Fatal("Unable to open the database:", err) - } - - // Prepare struct - app := &App{ - cfg: cfg, - db: db, - router: router, - } - - // Register routes - ui.DeclareRoutes(router, cfg) - api.DeclareRoutes(router, cfg, db, app.ResetTimer) - - router.GET("/api/version", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"version": Version}) - }) - - return app -} - -func (app *App) Start() { - app.srv = &http.Server{ - Addr: app.cfg.Bind, - Handler: app.router, - } - - app.ResetTimer() - - log.Printf("Ready, listening on %s\n", app.cfg.Bind) - if err := app.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) - } -} - -func (app *App) ResetTimer() { - if app.nextAlarm != nil { - app.nextAlarm.Stop() - app.nextAlarm = nil - } - - if na, err := reveil.GetNextAlarm(app.db); err == nil && na != nil { - app.nextAlarm = time.AfterFunc(time.Until(*na), func() { - app.nextAlarm = nil - reveil.RemoveOldAlarmsSingle(app.db) - err := player.WakeUp(app.cfg) - if err != nil { - log.Println(err.Error()) - return - } - }) - log.Println("Next timer programmed for", *na) - } -} - -func (app *App) Stop() { - if app.nextAlarm != nil { - app.nextAlarm.Stop() - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := app.srv.Shutdown(ctx); err != nil { - log.Fatal("Server Shutdown:", err) - } - - if err := app.db.Close(); err != nil { - log.Fatal("Database Close:", err) - } -} diff --git a/config/cli.go b/config/cli.go deleted file mode 100644 index 1894daa..0000000 --- a/config/cli.go +++ /dev/null @@ -1,59 +0,0 @@ -package config - -import ( - "flag" -) - -// declareFlags registers flags for the structure Options. -func (c *Config) declareFlags() { - flag.Var(&c.ExternalURL, "external-url", "Public URL of the service") - flag.StringVar(&c.BaseURL, "baseurl", c.BaseURL, "URL prepended to each URL") - flag.StringVar(&c.Bind, "bind", c.Bind, "Bind port/socket") - flag.StringVar(&c.DevProxy, "dev", c.DevProxy, "Use ui directory instead of embedded assets") - flag.StringVar(&c.LevelDBPath, "leveldb-path", c.LevelDBPath, "Path to the LevelDB database") - flag.StringVar(&c.SettingsFile, "settings-file", c.SettingsFile, "Path to the file containing the settings") - flag.StringVar(&c.TracksDir, "tracks-dir", c.TracksDir, "Path to the directory containing the tracks") - flag.StringVar(&c.GongsDir, "gongs-dir", c.GongsDir, "Path to the directory containing the gongs") - flag.StringVar(&c.ActionsDir, "actions-dir", c.ActionsDir, "Path to the directory containing the actions") - flag.StringVar(&c.RoutinesDir, "routines-dir", c.RoutinesDir, "Path to the directory containing the routines") - flag.IntVar(&c.SampleRate, "samplerate", c.SampleRate, "Samplerate for unifying output stream") - - // Others flags are declared in some other files when they need specials configurations -} - -func Consolidated() (cfg *Config, err error) { - // Define defaults options - cfg = &Config{ - Bind: "127.0.0.1:8080", - LevelDBPath: "alarms.db", - SettingsFile: "./settings.json", - TracksDir: "./tracks/", - GongsDir: "./gongs/", - ActionsDir: "./actions/", - RoutinesDir: "./routines/", - SampleRate: 44100, - } - - cfg.declareFlags() - - // Then, overwrite that by what is present in the environment - err = cfg.FromEnv() - if err != nil { - return - } - - // Finaly, command line takes precedence - err = cfg.parseCLI() - if err != nil { - return - } - - return -} - -// parseCLI parse the flags and treats extra args as configuration filename. -func (c *Config) parseCLI() error { - flag.Parse() - - return nil -} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 26fe5c1..0000000 --- a/config/config.go +++ /dev/null @@ -1,38 +0,0 @@ -package config - -import ( - "flag" - "strings" -) - -type Config struct { - DevProxy string - Bind string - ExternalURL URL - BaseURL string - - LevelDBPath string - SettingsFile string - TracksDir string - GongsDir string - ActionsDir string - RoutinesDir string - - SampleRate int -} - -// parseLine treats a config line and place the read value in the variable -// declared to the corresponding flag. -func (c *Config) parseLine(line string) (err error) { - fields := strings.SplitN(line, "=", 2) - orig_key := strings.TrimSpace(fields[0]) - value := strings.TrimSpace(fields[1]) - - key := strings.TrimPrefix(orig_key, "REVEIL_") - key = strings.Replace(key, "_", "-", -1) - key = strings.ToLower(key) - - err = flag.Set(key, value) - - return -} diff --git a/config/custom.go b/config/custom.go deleted file mode 100644 index 716a2f7..0000000 --- a/config/custom.go +++ /dev/null @@ -1,44 +0,0 @@ -package config - -import ( - "encoding/base64" - "net/url" -) - -type JWTSecretKey []byte - -func (i *JWTSecretKey) String() string { - return base64.StdEncoding.EncodeToString(*i) -} - -func (i *JWTSecretKey) Set(value string) error { - z, err := base64.StdEncoding.DecodeString(value) - if err != nil { - return err - } - - *i = z - return nil -} - -type URL struct { - URL *url.URL -} - -func (i *URL) String() string { - if i.URL != nil { - return i.URL.String() - } else { - return "" - } -} - -func (i *URL) Set(value string) error { - u, err := url.Parse(value) - if err != nil { - return err - } - - i.URL = u - return nil -} diff --git a/config/env.go b/config/env.go deleted file mode 100644 index ae852a3..0000000 --- a/config/env.go +++ /dev/null @@ -1,21 +0,0 @@ -package config - -import ( - "fmt" - "os" - "strings" -) - -// FromEnv analyzes all the environment variables to find each one -// starting by GUSTUS_ -func (c *Config) FromEnv() error { - for _, line := range os.Environ() { - if strings.HasPrefix(line, "GUSTUS_") { - err := c.parseLine(line) - if err != nil { - return fmt.Errorf("error in environment (%q): %w", line, err) - } - } - } - return nil -} diff --git a/go.mod b/go.mod index 23551b2..cf2621a 100644 --- a/go.mod +++ b/go.mod @@ -1,40 +1,5 @@ -module git.nemunai.re/nemunaire/reveil +module git.nemunai.re/reveil -go 1.18 +go 1.16 -require ( - github.com/faiface/beep v0.0.0-00010101000000-000000000000 - github.com/gin-gonic/gin v1.8.1 - github.com/syndtr/goleveldb v1.0.0 -) - -require ( - github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.10.0 // indirect - github.com/goccy/go-json v0.9.7 // indirect - github.com/golang/snappy v0.0.1 // indirect - github.com/hajimehoshi/go-mp3 v0.3.0 // indirect - github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4 // indirect - github.com/icza/bitio v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mewkiz/flac v1.0.7 // indirect - github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.1 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/ugorji/go/codec v1.2.7 // indirect - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect - golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect - golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect - golang.org/x/text v0.3.7 // indirect - google.golang.org/protobuf v1.28.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect -) - -replace github.com/faiface/beep => github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755 +require github.com/faiface/beep v1.0.3-0.20210301102329-98afada94bff diff --git a/go.sum b/go.sum index 9d932f0..190f64d 100644 --- a/go.sum +++ b/go.sum @@ -1,152 +1,42 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755 h1:rkuKNEd+Izze/hA44R1kzPs9BXa544xXeufys8x0fkc= -github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755/go.mod h1:PWWzyIlbyHQjQ/gJzGiMyDAvjo/t5L8TC8qkyX8UfWs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7 h1:tmSauY5l3s/Cp5n+cEiG1epUR2AejmdHeMJMycMFxb0= -github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/faiface/beep v1.0.3-0.20210301102329-98afada94bff h1:4zdNP0II42+Z9XaMdFZms8q0NdGGB6cQi1b9zPntdcY= +github.com/faiface/beep v1.0.3-0.20210301102329-98afada94bff/go.mod h1:fQeOQNj1CiI7p0IeJ159hm16XDejdTP3B92ngeJMxac= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g= github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4 h1:m29xzbn3Pv5MgvgjMPs7m28uhUgVt3B3AIGjQLgkqUI= -github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4/go.mod h1:OdGUICBjy7upAjvqqacbB63XIuYR3fqXZ7kYtlVYJgQ= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk= +github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8= github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mewkiz/flac v1.0.7 h1:uIXEjnuXqdRaZttmSFM5v5Ukp4U6orrZsnYGGR3yow8= -github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU= +github.com/mewkiz/flac v1.0.6 h1:OnMwCWZPAnjDndjEzLynOZ71Y2U+/QYHoVI4JEKgKkk= +github.com/mewkiz/flac v1.0.6/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU= github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2 h1:EyTNMdePWaoWsRSGQnXiSoQu0r6RS1eA557AwJhlzHU= github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 812adbf..c7c9a94 100644 --- a/main.go +++ b/main.go @@ -1,65 +1,339 @@ package main import ( + "bytes" + "flag" + "fmt" "log" + "math" + "math/rand" "os" + "os/exec" "os/signal" + "path" + "strconv" + "strings" "syscall" + "time" - "git.nemunai.re/nemunaire/reveil/config" + "github.com/faiface/beep" + "github.com/faiface/beep/effects" + "github.com/faiface/beep/flac" + "github.com/faiface/beep/mp3" + "github.com/faiface/beep/speaker" + "github.com/faiface/beep/wav" ) var ( - Version = "custom-build" + MaxRunTime = 1 * time.Hour + ntick int64 = 0 ) -func main() { - cfg, err := config.Consolidated() - if err != nil { - log.Fatal("Unable to read configuration:", err) - } - - // Clean paths - if _, err := os.Stat(cfg.SettingsFile); os.IsNotExist(err) { - fd, err := os.Create(cfg.SettingsFile) - if err != nil { - log.Fatal("Unable to create settings file:", err) - } - - fd.Write([]byte{'{', '}'}) - - fd.Close() - } - - if _, err := os.Stat(cfg.TracksDir); os.IsNotExist(err) { - if err := os.Mkdir(cfg.TracksDir, 0755); err != nil { - log.Fatal("Unable to create tracks directory:", err) +func xPrintIdle() (idle uint64) { + cmd := exec.Command("xprintidle") + cmd.Env = append(os.Environ(), "DISPLAY=:0") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + log.Println(err) + } else { + s := string(out.Bytes()) + if idle, err = strconv.ParseUint(strings.TrimSpace(s), 10, 64); err != nil { + log.Println(err) } } - if _, err := os.Stat(cfg.GongsDir); os.IsNotExist(err) { - if err := os.Mkdir(cfg.GongsDir, 0755); err != nil { - log.Fatal("Unable to create gongs directory:", err) - } - } - if _, err := os.Stat(cfg.ActionsDir); os.IsNotExist(err) { - if err := os.Mkdir(cfg.ActionsDir, 0755); err != nil { - log.Fatal("Unable to create actions directory:", err) - } - } - if _, err := os.Stat(cfg.RoutinesDir); os.IsNotExist(err) { - if err := os.Mkdir(cfg.RoutinesDir, 0755); err != nil { - log.Fatal("Unable to create routines directory:", err) - } - } - - // Start app - a := NewApp(cfg) - go a.Start() - - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt, syscall.SIGTERM) - <-quit - log.Println("Stopping the service...") - a.Stop() - log.Println("Stopped") + return +} + +func speakToday() { + cmdSetVolume := exec.Command("amixer", "-D", "pulse", "set", "Master", fmt.Sprintf("%d%%", 50+50/(int64(MaxRunTime.Seconds()/3)/ntick+1))) + if err := cmdSetVolume.Run(); err != nil { + log.Println(err) + } + + cmd0 := exec.Command("/home/nemunaire/scripts/wakeup/today.sh") + if err := cmd0.Run(); err != nil { + log.Println(err) + } + + cmdSetBackVolume := exec.Command("amixer", "-D", "pulse", "set", "Master", "100%") + if err := cmdSetBackVolume.Run(); err != nil { + log.Println(err) + } +} + +func speakWeather() { + var icon = "partly-cloudy-day" + + cmd := exec.Command("/home/nemunaire/scripts/wakeup/ind_weather.sh") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + log.Println(err) + } else { + icon = string(out.Bytes()) + } + + var preset = "pika-summer-forest.xml" + switch icon { + case "clear-day", "clear-night": + preset = "grassland.xml" + case "rain", "hail": + preset = "the-perfect-storm.xml" + case "snow", "sleet": + preset = "a-walk-in-the-cold.xml" + case "wind", "tornado": + preset = "desert-wind.xml" + case "fog": + preset = "silent-hill-fog-world.xml" + case "cloudy": + preset = "autumn-forest.xml" + case "thunderstorm": + preset = "heavy-thunderstorm-for-me.xml" + default: + preset = "autumn-forest.xml" + } + + cmdAmbiant := exec.Command("python3", "ambient.py", path.Join("presets", preset)) + cmdAmbiant.Dir = "/home/nemunaire/workspace/pyambientmixer" + if err := cmdAmbiant.Start(); err != nil { + log.Println(err) + } + + cmdSetVolume := exec.Command("amixer", "-D", "pulse", "set", "Master", fmt.Sprintf("%d%%", 50+50/(int64(MaxRunTime.Seconds()/3)/ntick+1))) + if err := cmdSetVolume.Run(); err != nil { + log.Println(err) + } + + cmd0 := exec.Command("/home/nemunaire/scripts/wakeup/today.sh") + if err := cmd0.Run(); err != nil { + log.Println(err) + } + + cmd1 := exec.Command("/home/nemunaire/scripts/wakeup/weather.sh") + if err := cmd1.Run(); err != nil { + log.Println(err) + } + + cmd2 := exec.Command("/home/nemunaire/scripts/wakeup/airparif.sh") + if err := cmd2.Run(); err != nil { + log.Println(err) + } + + cmd3 := exec.Command("/home/nemunaire/scripts/wakeup/ratp-traffic.sh", "rers", "B") + if err := cmd3.Run(); err != nil { + log.Println(err) + } + + if cmdAmbiant.Process != nil { + (*cmdAmbiant.Process).Kill() + } + cmdAmbiant.Process.Wait() + + cmdSetBackVolume := exec.Command("amixer", "-D", "pulse", "set", "Master", "100%") + if err := cmdSetBackVolume.Run(); err != nil { + log.Println(err) + } +} + +func loadFile(filepath string) (name string, s beep.StreamSeekCloser, format beep.Format, err error) { + var fd *os.File + + name = path.Base(filepath) + + fd, err = os.Open(filepath) + if err != nil { + return + } + + switch strings.ToLower(path.Ext(filepath)) { + case ".flac": + s, format, err = flac.Decode(fd) + case ".mp3": + s, format, err = mp3.Decode(fd) + default: + s, format, err = wav.Decode(fd) + } + + if err != nil { + fd.Close() + return + } + + return + +} + +func main() { + var weatherTime = flag.Duration("weather", -1, "Speak weather?") + var noshuffle = flag.Bool("noshuffle", false, "Don't shuffle music order") + var ignoreidle = flag.Bool("ignoreidle", false, "Don't stop the reveil on idle detection change") + var sr = flag.Int("samplerate", 44100, "Samplerate for unifying output stream") + var claironTime = flag.Duration("clairon", -1, "Time before running the wake up clairon song") + flag.DurationVar(&MaxRunTime, "maxruntime", MaxRunTime, "Maximum duration before auto exit") + flag.Parse() + + if len(flag.Args()) < 1 { + log.Println("missing required argument: input file name") + return + } + + seed := time.Now().Unix() + seed -= seed % 172800 + + reverseOrder := int(time.Now().Unix()/86400)%2 == 0 + + log.Println("Starting reveil with seed:", seed, "; order:", reverseOrder) + + rand.Seed(seed) + + sampleRate := beep.SampleRate(*sr) + + paths := []string{} + playlist := []beep.Streamer{} + formats := []beep.Format{} + + // Load playlist + log.Println("Loading playlist...") + for _, arg := range flag.Args() { + p, s, f, err := loadFile(arg) + if err != nil { + log.Printf("Unable to load %s: %s", arg, err) + continue + } + paths = append(paths, p) + playlist = append(playlist, s) + formats = append(formats, f) + } + + if noshuffle == nil || *noshuffle == false { + log.Println("Shuffling playlist...") + // Shuffle the playlist + rand.Shuffle(len(playlist), func(i, j int) { + paths[i], paths[j] = paths[j], paths[i] + playlist[i], playlist[j] = playlist[j], playlist[i] + formats[i], formats[j] = formats[j], formats[i] + }) + } + log.Println("Playlist in use:", strings.Join(paths, " ; ")) + + var launched time.Time + var volume *effects.Volume + + dontUpdateVolume := false + hasClaironed := claironTime == nil || *claironTime == -1 + hasSpokeWeather := weatherTime == nil || *weatherTime == -1 + playedItem := -1 + + // Create infinite stream + stream := beep.Iterate(func() beep.Streamer { + if !hasClaironed && time.Since(launched) >= *claironTime { + log.Println("clairon time!") + *claironTime += *claironTime / 2 + //_, sample, format, err := loadFile("/home/nemunaire/www/audio/miracle-morning/clairon-reveil.mp3") + //_, sample, format, err := loadFile("/home/nemunaire/www/audio/miracle-morning/coq.flac") + _, sample, format, err := loadFile("/home/nemunaire/www/audio/miracle-morning/NukeAnthem.flac") + if err == nil { + volume.Volume = 0.1 + dontUpdateVolume = true + if format.SampleRate != sampleRate { + return beep.Resample(3, format.SampleRate, sampleRate, sample) + } else { + return sample + } + } else { + log.Println("Error loading clairon:", err) + } + } + + if !hasSpokeWeather && time.Since(launched) >= *weatherTime { + log.Println("weather time!") + hasSpokeWeather = true + return beep.Callback(speakWeather) + } + + dontUpdateVolume = false + volume.Volume = -2 - math.Log(5/float64(ntick))/3 + + if reverseOrder { + playedItem -= 1 + } else { + playedItem += 1 + } + + if playedItem >= len(playlist) { + playedItem = 0 + } else if playedItem < 0 { + playedItem = len(playlist) - 1 + } + + if i, ok := playlist[playedItem].(beep.StreamSeekCloser); ok { + // In case of loop, ensure we are at the beginning of the stream + i.Seek(0) + } + + // Resample if needed + log.Println("playing list item:", playedItem, "/", len(playlist)) + if formats[playedItem].SampleRate != sampleRate { + return beep.Resample(3, formats[playedItem].SampleRate, sampleRate, playlist[playedItem]) + } else { + return playlist[playedItem] + } + }) + + // Prepare sound player + log.Println("Initializing sound player...") + speaker.Init(sampleRate, sampleRate.N(time.Second/10)) + + volume = &effects.Volume{stream, 10, -2, false} + speaker.Play(volume) + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + launched = time.Now() + idle := xPrintIdle() + + // Prepare graceful shutdown + maxRun := time.After(MaxRunTime) + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) + +loop: + for { + select { + case <-maxRun: + break loop + case <-ticker.C: + ntick += 1 + if !dontUpdateVolume { + volume.Volume = -2 - math.Log(5/float64(ntick))/3 + } + + if ignoreidle == nil || !*ignoreidle { + if idle < 60000 { + idle = xPrintIdle() + } else if xPrintIdle() < idle { + break loop + } + } + case <-interrupt: + break loop + } + } + + // Tell parent process that it can launch the wake up procedure + if time.Since(launched) < MaxRunTime && time.Since(launched) > 60*time.Second { + if proc, err := os.FindProcess(os.Getppid()); err != nil { + log.Println(err) + } else if err := proc.Signal(syscall.SIGHUP); err != nil { + log.Println(err) + } + } + + // Calm down music + for i := 0; i < 2000; i += 1 { + volume.Volume -= 0.001 + time.Sleep(4 * time.Millisecond) + } } diff --git a/model/action.go b/model/action.go deleted file mode 100644 index 9c1ce81..0000000 --- a/model/action.go +++ /dev/null @@ -1,132 +0,0 @@ -package reveil - -import ( - "bufio" - "crypto/sha512" - "errors" - "io/fs" - "log" - "os" - "path/filepath" - "strings" - - "git.nemunai.re/nemunaire/reveil/config" -) - -type Action struct { - Id Identifier `json:"id"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Path string `json:"path"` - Enabled bool `json:"enabled"` -} - -func LoadAction(path string) (string, string, error) { - fd, err := os.Open(path) - if err != nil { - return "", "", err - } - defer fd.Close() - - fileScanner := bufio.NewScanner(fd) - fileScanner.Split(bufio.ScanLines) - - var ( - shebang = false - name = "" - description = "" - ) - for fileScanner.Scan() { - line := strings.TrimSpace(fileScanner.Text()) - if !shebang { - if len(line) < 2 || line[0] != '#' || line[1] != '!' { - return name, description, errors.New("Not a valid action file (shebang not found).") - } - shebang = true - continue - } - - if len(line) < 2 || line[0] != '#' { - if len(description) > 0 { - return name, strings.TrimSpace(description), nil - } - - continue - } - - if len(name) == 0 { - name = strings.TrimSpace(line[1:]) - } else { - description += strings.TrimSpace(line[1:]) + "\n" - } - } - - return name, description, nil -} - -func LoadActions(cfg *config.Config) (actions []*Action, err error) { - actionsDir, err := filepath.Abs(cfg.ActionsDir) - if err != nil { - return nil, err - } - - err = filepath.Walk(cfg.ActionsDir, func(path string, d fs.FileInfo, err error) error { - if d.Mode().IsRegular() { - hash := sha512.Sum512([]byte(path)) - - // Parse content - name, description, err := LoadAction(path) - if err != nil { - log.Printf("Invalid action file (trying to parse %s): %s", path, err.Error()) - // Ignore invalid files - return nil - } - - if description == "" { - return nil - } - - if apath, err := filepath.Abs(path); err == nil { - path = apath - } - - actions = append(actions, &Action{ - Id: hash[:], - Name: name, - Description: description, - Path: strings.TrimPrefix(path, actionsDir+"/"), - Enabled: d.Mode().Perm()&0111 != 0, - }) - } - - return nil - }) - - return -} - -func (a *Action) Rename(name string) error { - return errors.New("Not implemeted") -} - -func (a *Action) Enable() error { - fi, err := os.Stat(a.Path) - if err != nil { - return err - } - - return os.Chmod(a.Path, fi.Mode().Perm()|0111) -} - -func (a *Action) Disable() error { - fi, err := os.Stat(a.Path) - if err != nil { - return err - } - - return os.Chmod(a.Path, fi.Mode().Perm()&0666) -} - -func (a *Action) Remove() error { - return os.Remove(a.Path) -} diff --git a/model/alarm.go b/model/alarm.go deleted file mode 100644 index e6a8bbd..0000000 --- a/model/alarm.go +++ /dev/null @@ -1,321 +0,0 @@ -package reveil - -import ( - "fmt" - "sort" - "time" -) - -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 -} - -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 - } - - now := time.Now() - for _, alarm := range alarmsSingle { - if closestAlarm == nil || (closestAlarm.After(alarm.Time) && alarm.Time.After(now)) { - 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 time.Weekday `json:"weekday"` - StartTime *Hour `json:"time"` - FollowingRoutines []Identifier `json:"routines"` - IgnoreExceptions bool `json:"ignore_exceptions"` - 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) { - 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 - var id Identifier - - if alarm.Id.IsEmpty() { - key, id, err = db.findBytesKey("alarm-repeated-", IDENTIFIER_LEN) - if err != nil { - return err - } - } - - alarm.Id = id - // Don't store this, this is autocalculated - alarm.Excepts = nil - alarm.NextTime = nil - - 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"` - Comment string `json:"comment,omitempty"` -} - -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 - var id Identifier - - if alarm.Id.IsEmpty() { - key, id, err = db.findBytesKey("alarm-single-", IDENTIFIER_LEN) - if err != nil { - return err - } - } - - alarm.Id = id - - return db.put(key, alarm) -} - -func DeleteAlarmSingle(db *LevelDBStorage, alarm *AlarmSingle) (err error) { - return db.delete(fmt.Sprintf("alarm-single-%s", alarm.Id.ToString())) -} - -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 -} - -type AlarmException struct { - Id Identifier `json:"id"` - Start *Date `json:"start"` - End *Date `json:"end"` - Comment string `json:"comment,omitempty"` -} - -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 - var id Identifier - - if alarm.Id.IsEmpty() { - key, id, err = db.findBytesKey("alarm-exception-", IDENTIFIER_LEN) - if err != nil { - return err - } - } - - alarm.Id = id - - return db.put(key, alarm) -} - -func DeleteAlarmException(db *LevelDBStorage, alarm *AlarmException) (err error) { - return db.delete(fmt.Sprintf("alarm-exception-%s", alarm.Id.ToString())) -} diff --git a/model/database.go b/model/database.go deleted file mode 100644 index c54daab..0000000 --- a/model/database.go +++ /dev/null @@ -1,90 +0,0 @@ -package reveil - -import ( - "encoding/json" - "fmt" - "log" - "math/rand" - - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/errors" - "github.com/syndtr/goleveldb/leveldb/iterator" - "github.com/syndtr/goleveldb/leveldb/util" -) - -type LevelDBStorage struct { - db *leveldb.DB -} - -// NewMySQLStorage establishes the connection to the database -func NewLevelDBStorage(path string) (s *LevelDBStorage, err error) { - var db *leveldb.DB - - db, err = leveldb.OpenFile(path, nil) - if err != nil { - if _, ok := err.(*errors.ErrCorrupted); ok { - log.Println("LevelDB was corrupted; attempting recovery (%s)", err.Error()) - _, err = leveldb.RecoverFile(path, nil) - if err != nil { - return - } - log.Println("LevelDB recovery succeeded!") - } else { - return - } - } - - s = &LevelDBStorage{db} - return -} - -func (s *LevelDBStorage) Close() error { - return s.db.Close() -} - -func decodeData(data []byte, v interface{}) error { - return json.Unmarshal(data, v) -} - -func (s *LevelDBStorage) get(key string, v interface{}) error { - data, err := s.db.Get([]byte(key), nil) - if err != nil { - return err - } - - return decodeData(data, v) -} - -func (s *LevelDBStorage) put(key string, v interface{}) error { - data, err := json.Marshal(v) - if err != nil { - return err - } - - return s.db.Put([]byte(key), data, nil) -} - -func (s *LevelDBStorage) findBytesKey(prefix string, len int) (key string, id Identifier, err error) { - id = make([]byte, len) - found := true - for found { - if _, err = rand.Read(id); err != nil { - return - } - key = fmt.Sprintf("%s%s", prefix, id.ToString()) - - found, err = s.db.Has([]byte(key), nil) - if err != nil { - return - } - } - return -} - -func (s *LevelDBStorage) delete(key string) error { - return s.db.Delete([]byte(key), nil) -} - -func (s *LevelDBStorage) search(prefix string) iterator.Iterator { - return s.db.NewIterator(util.BytesPrefix([]byte(prefix)), nil) -} diff --git a/model/gong.go b/model/gong.go deleted file mode 100644 index 9b7a2f2..0000000 --- a/model/gong.go +++ /dev/null @@ -1,130 +0,0 @@ -package reveil - -import ( - "crypto/sha512" - "io/fs" - "os" - "path/filepath" - "strings" - - "git.nemunai.re/nemunaire/reveil/config" -) - -const CURRENT_GONG = "_current_gong" - -type Gong struct { - Id Identifier `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - Enabled bool `json:"enabled"` -} - -func CurrentGongPath(cfg *config.Config) string { - return filepath.Join(cfg.GongsDir, CURRENT_GONG) -} - -func LoadGong(cfg *config.Config, path string, d fs.FileInfo) (gong *Gong, err error) { - // Retrieve the path of the current gong - current_gong, err := os.Readlink(CurrentGongPath(cfg)) - if err == nil { - current_gong, _ = filepath.Abs(filepath.Join(cfg.GongsDir, current_gong)) - } - - hash := sha512.Sum512([]byte(path)) - pabs, _ := filepath.Abs(path) - return &Gong{ - Id: hash[:], - Name: strings.TrimSuffix(d.Name(), filepath.Ext(d.Name())), - Path: path, - Enabled: current_gong == pabs, - }, nil -} - -func LoadGongs(cfg *config.Config) (gongs []*Gong, err error) { - // Retrieve the path of the current gong - current_gong, err := os.Readlink(CurrentGongPath(cfg)) - if err == nil { - current_gong, _ = filepath.Abs(filepath.Join(cfg.GongsDir, current_gong)) - } - - // Retrieve the list - err = filepath.Walk(cfg.GongsDir, func(path string, d fs.FileInfo, err error) error { - if d.Mode().IsRegular() { - hash := sha512.Sum512([]byte(path)) - pabs, _ := filepath.Abs(path) - gongs = append(gongs, &Gong{ - Id: hash[:], - Name: strings.TrimSuffix(d.Name(), filepath.Ext(d.Name())), - Path: path, - Enabled: current_gong == pabs, - }) - } - - return nil - }) - - return -} - -func (g *Gong) Open() (*os.File, error) { - return os.Open(g.Path) -} - -func (g *Gong) Size() (int64, error) { - if st, err := os.Stat(g.Path); err != nil { - return 0, err - } else { - return st.Size(), err - } -} - -func (g *Gong) ContentType() string { - switch filepath.Ext(g.Path) { - case ".flac": - return "audio/flac" - case ".mp3": - return "audio/mpeg" - case ".ogg": - return "audio/ogg" - case ".wav": - return "audio/vnd.wav" - } - - return "application/octet-stream" -} - -func (g *Gong) Rename(newName string) error { - newPath := filepath.Join(filepath.Dir(g.Path), newName+filepath.Ext(g.Path)) - - err := os.Rename( - g.Path, - newPath, - ) - if err != nil { - return err - } - - g.Path = newPath - return nil -} - -func (g *Gong) SetDefault(cfg *config.Config) error { - linkpath := CurrentGongPath(cfg) - os.Remove(linkpath) - - pabs, err := filepath.Abs(g.Path) - if err != nil { - pabs = g.Path - } - - gdirabs, err := filepath.Abs(cfg.GongsDir) - if err != nil { - gdirabs = cfg.GongsDir - } - - return os.Symlink(strings.TrimPrefix(strings.TrimPrefix(pabs, gdirabs), "/"), linkpath) -} - -func (g *Gong) Remove() error { - return os.Remove(g.Path) -} diff --git a/model/identifier.go b/model/identifier.go deleted file mode 100644 index d1f00bb..0000000 --- a/model/identifier.go +++ /dev/null @@ -1,41 +0,0 @@ -package reveil - -import ( - "encoding/base64" - "errors" -) - -const IDENTIFIER_LEN = 48 - -type Identifier []byte - -func NewIdentifierFromString(src string) (id Identifier, err error) { - return base64.RawURLEncoding.DecodeString(src) -} - -func (i *Identifier) IsEmpty() bool { - return len(*i) == 0 -} - -func (i *Identifier) ToString() string { - return base64.RawURLEncoding.EncodeToString(*i) -} - -func (i Identifier) MarshalJSON() (dst []byte, err error) { - dst = make([]byte, base64.RawURLEncoding.EncodedLen(len(i))) - base64.RawURLEncoding.Encode(dst, i) - dst = append([]byte{'"'}, dst...) - dst = append(dst, '"') - return -} - -func (i *Identifier) UnmarshalJSON(src []byte) error { - if len(src) < 2 || src[0] != '"' || src[len(src)-1] != '"' { - return errors.New("Unvalid character found to encapsulate the JSON value") - } - - *i = make([]byte, base64.RawURLEncoding.DecodedLen(len(src)-2)) - _, err := base64.RawURLEncoding.Decode(*i, src[1:len(src)-1]) - - return err -} diff --git a/model/routine.go b/model/routine.go deleted file mode 100644 index 86b06a2..0000000 --- a/model/routine.go +++ /dev/null @@ -1,119 +0,0 @@ -package reveil - -import ( - "crypto/sha512" - "io/fs" - "io/ioutil" - "log" - "os" - "path/filepath" - "strconv" - "strings" - - "git.nemunai.re/nemunaire/reveil/config" -) - -type RoutineStep struct { - Delay uint64 `json:"delay"` - Action string `json:"action"` - Args []string `json:"args,omitempty"` -} - -type Routine struct { - Id Identifier `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - Steps []RoutineStep `json:"steps"` -} - -func LoadRoutine(path string, cfg *config.Config) ([]RoutineStep, error) { - ds, err := ioutil.ReadDir(path) - if err != nil { - return nil, err - } - - actionsDir, err := filepath.Abs(cfg.ActionsDir) - if err != nil { - return nil, err - } - - var steps []RoutineStep - - for _, f := range ds { - fullpath := filepath.Join(path, f.Name()) - if f.Mode()&os.ModeSymlink == 0 { - continue - } - - dst, err := os.Readlink(fullpath) - if err != nil { - return nil, err - } - - if adst, err := filepath.Abs(filepath.Join(path, dst)); err == nil { - dst = adst - } - - args := strings.Split(f.Name(), "_") - - delay, err := strconv.ParseUint(args[0], 10, 64) - - step := RoutineStep{ - Delay: delay, - Action: strings.TrimPrefix(dst, actionsDir+"/"), - } - if len(args) > 1 { - step.Args = args[1:] - } - - steps = append(steps, step) - } - - return steps, nil -} - -func LoadRoutines(cfg *config.Config) (routines []*Routine, err error) { - err = filepath.Walk(cfg.RoutinesDir, func(path string, d fs.FileInfo, err error) error { - if d.IsDir() && path != cfg.RoutinesDir { - hash := sha512.Sum512([]byte(path)) - - // Explore directory - steps, err := LoadRoutine(path, cfg) - if err != nil { - log.Printf("Invalid routine directory (trying to walk through %s): %s", path, err.Error()) - // Ignore invalid routines - return nil - } - - routines = append(routines, &Routine{ - Id: hash[:], - Name: d.Name(), - Path: path, - Steps: steps, - }) - } - - return nil - }) - - return -} - -func (r *Routine) Rename(newName string) error { - newPath := filepath.Join(filepath.Dir(r.Path), newName) - - err := os.Rename( - r.Path, - newPath, - ) - if err != nil { - return err - } - - r.Path = newPath - return nil -} - -func (a *Routine) Remove() error { - return os.Remove(a.Path) -} diff --git a/model/settings.go b/model/settings.go deleted file mode 100644 index 7c1cb51..0000000 --- a/model/settings.go +++ /dev/null @@ -1,55 +0,0 @@ -package reveil - -import ( - "encoding/json" - "os" - "time" -) - -// Settings represents the settings panel. -type Settings struct { - Language string `json:"language"` - GongInterval time.Duration `json:"gong_interval"` - WeatherDelay time.Duration `json:"weather_delay"` - WeatherAction string `json:"weather_action"` - MaxRunTime time.Duration `json:"max_run_time"` -} - -// ExistsSettings checks if the settings file can by found at the given path. -func ExistsSettings(settingsPath string) bool { - _, err := os.Stat(settingsPath) - return !os.IsNotExist(err) -} - -// ReadSettings parses the file at the given location. -func ReadSettings(path string) (*Settings, error) { - var s Settings - if fd, err := os.Open(path); err != nil { - return nil, err - } else { - defer fd.Close() - jdec := json.NewDecoder(fd) - - if err := jdec.Decode(&s); err != nil { - return &s, err - } - - return &s, nil - } -} - -// SaveSettings saves settings at the given location. -func SaveSettings(path string, s interface{}) error { - if fd, err := os.Create(path); err != nil { - return err - } else { - defer fd.Close() - jenc := json.NewEncoder(fd) - - if err := jenc.Encode(s); err != nil { - return err - } - - return nil - } -} diff --git a/model/track.go b/model/track.go deleted file mode 100644 index 1ec27f9..0000000 --- a/model/track.go +++ /dev/null @@ -1,133 +0,0 @@ -package reveil - -import ( - "crypto/sha512" - "io/fs" - "os" - "path/filepath" - "strings" - "time" - - "git.nemunai.re/nemunaire/reveil/config" -) - -type Track struct { - Id Identifier `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - Enabled bool `json:"enabled"` -} - -func LoadTrack(path string, d fs.FileInfo) (track *Track, err error) { - hash := sha512.Sum512([]byte(path)) - return &Track{ - Id: hash[:], - Name: strings.TrimSuffix(d.Name(), filepath.Ext(d.Name())), - Path: path, - Enabled: len(strings.Split(path, "/")) == 2, - }, nil -} - -func LoadTracks(cfg *config.Config) (tracks []*Track, err error) { - err = filepath.Walk(cfg.TracksDir, func(path string, d fs.FileInfo, err error) error { - if d.Mode().IsRegular() { - hash := sha512.Sum512([]byte(path)) - tracks = append(tracks, &Track{ - Id: hash[:], - Name: strings.TrimSuffix(d.Name(), filepath.Ext(d.Name())), - Path: path, - Enabled: len(strings.Split(path, "/")) == 2, - }) - } - - return nil - }) - - return -} - -func (t *Track) Open() (*os.File, error) { - return os.Open(t.Path) -} - -func (t *Track) Size() (int64, error) { - if st, err := os.Stat(t.Path); err != nil { - return 0, err - } else { - return st.Size(), err - } -} - -func (t *Track) ContentType() string { - switch filepath.Ext(t.Path) { - case ".flac": - return "audio/flac" - case ".mp3": - return "audio/mpeg" - case ".ogg": - return "audio/ogg" - case ".wav": - return "audio/vnd.wav" - } - - return "application/octet-stream" -} - -func (t *Track) Rename(newName string) error { - newPath := filepath.Join(filepath.Dir(t.Path), newName+filepath.Ext(t.Path)) - - err := os.Rename( - t.Path, - newPath, - ) - if err != nil { - return err - } - - t.Path = newPath - - // Recalculate hash - hash := sha512.Sum512([]byte(t.Path)) - t.Id = hash[:] - return nil -} - -func (t *Track) MoveTo(path string) error { - os.Mkdir(filepath.Dir(path), 0755) - - err := os.Rename( - t.Path, - path, - ) - if err != nil { - return err - } - - t.Path = path - - // Recalculate hash - hash := sha512.Sum512([]byte(t.Path)) - t.Id = hash[:] - return nil -} - -func (t *Track) Disable() error { - if t.Enabled { - date := time.Now() - return t.MoveTo(filepath.Join(filepath.Dir(t.Path), date.Format("20060102"), filepath.Base(t.Path))) - } - - return nil -} - -func (t *Track) Enable(cfg *config.Config) error { - if !t.Enabled { - return t.MoveTo(filepath.Join(cfg.TracksDir, filepath.Base(t.Path))) - } - - return nil -} - -func (t *Track) Remove() error { - return os.Remove(t.Path) -} diff --git a/player/player.go b/player/player.go deleted file mode 100644 index 2162fd1..0000000 --- a/player/player.go +++ /dev/null @@ -1,256 +0,0 @@ -package player - -import ( - "fmt" - "log" - "math" - "math/rand" - "os" - "os/signal" - "path" - "strings" - "syscall" - "time" - - "github.com/faiface/beep" - "github.com/faiface/beep/effects" - "github.com/faiface/beep/flac" - "github.com/faiface/beep/mp3" - "github.com/faiface/beep/speaker" - "github.com/faiface/beep/wav" - - "git.nemunai.re/nemunaire/reveil/config" - "git.nemunai.re/nemunaire/reveil/model" -) - -var CommonPlayer *Player - -type Player struct { - Playlist []string - MaxRunTime time.Duration - Stopper chan bool - - sampleRate beep.SampleRate - - claironTime time.Duration - claironFile string - - ntick int64 - hasClaironed bool - launched time.Time - volume *effects.Volume - dontUpdateVolume bool - reverseOrder bool - playedItem int -} - -func WakeUp(cfg *config.Config) (err error) { - if CommonPlayer != nil { - return fmt.Errorf("Unable to start the player: a player is already running") - } - - CommonPlayer, err = NewPlayer(cfg) - if err != nil { - return err - } - - go CommonPlayer.WakeUp() - return nil -} - -func NewPlayer(cfg *config.Config) (*Player, error) { - // Load our settings - settings, err := reveil.ReadSettings(cfg.SettingsFile) - if err != nil { - return nil, fmt.Errorf("Unable to read settings: %w", err) - } - - p := Player{ - Stopper: make(chan bool, 1), - MaxRunTime: settings.MaxRunTime * time.Minute, - sampleRate: beep.SampleRate(cfg.SampleRate), - claironTime: settings.GongInterval * time.Minute, - claironFile: reveil.CurrentGongPath(cfg), - } - - // Load our track list - tracks, err := reveil.LoadTracks(cfg) - if err != nil { - return nil, fmt.Errorf("Unable to load tracks: %w", err) - } - - var playlist []string - - // Creating playlist - log.Println("Loading playlist...") - for _, track := range tracks { - if !track.Enabled { - continue - } - - p.Playlist = append(p.Playlist, track.Path) - } - - log.Println("Shuffling playlist...") - // Shuffle the playlist - rand.Shuffle(len(playlist), func(i, j int) { - playlist[i], playlist[j] = playlist[j], playlist[i] - }) - - return &p, nil -} - -func loadFile(filepath string) (name string, s beep.StreamSeekCloser, format beep.Format, err error) { - var fd *os.File - - name = path.Base(filepath) - - fd, err = os.Open(filepath) - if err != nil { - return - } - - switch strings.ToLower(path.Ext(filepath)) { - case ".flac": - s, format, err = flac.Decode(fd) - case ".mp3": - s, format, err = mp3.Decode(fd) - default: - s, format, err = wav.Decode(fd) - } - - if err != nil { - fd.Close() - return - } - - return -} - -func (p *Player) WakeUp() { - log.Println("RUN WAKEUP FUNC") - - log.Println("Playlist in use:", strings.Join(p.Playlist, " ; ")) - - // Create infinite stream - stream := beep.Iterate(func() beep.Streamer { - if !p.hasClaironed && time.Since(p.launched) >= p.claironTime { - log.Println("clairon time!") - p.claironTime += p.claironTime / 2 - _, sample, format, err := loadFile(p.claironFile) - if err == nil { - p.volume.Volume = 0.1 - p.dontUpdateVolume = true - if format.SampleRate != p.sampleRate { - return beep.Resample(3, format.SampleRate, p.sampleRate, sample) - } else { - return sample - } - } else { - log.Println("Error loading clairon:", err) - } - } - - p.dontUpdateVolume = false - p.volume.Volume = -2 - math.Log(5/float64(p.ntick))/3 - - if p.reverseOrder { - p.playedItem -= 1 - } else { - p.playedItem += 1 - } - - if p.playedItem >= len(p.Playlist) { - p.playedItem = 0 - } else if p.playedItem < 0 { - p.playedItem = len(p.Playlist) - 1 - } - - // Load our current item - _, sample, format, err := loadFile(p.Playlist[p.playedItem]) - if err != nil { - log.Println("Error loading audio file %s: %s", p.Playlist[p.playedItem], err.Error()) - return nil - } - - // Resample if needed - log.Println("playing list item:", p.playedItem, "/", len(p.Playlist), ":", p.Playlist[p.playedItem]) - if format.SampleRate != p.sampleRate { - return beep.Resample(3, format.SampleRate, p.sampleRate, sample) - } else { - return sample - } - }) - - // Prepare sound player - log.Println("Initializing sound player...") - speaker.Init(p.sampleRate, p.sampleRate.N(time.Second/10)) - defer speaker.Close() - - p.volume = &effects.Volume{stream, 10, -2, false} - speaker.Play(p.volume) - - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - - p.launched = time.Now() - - // Prepare graceful shutdown - maxRun := time.After(p.MaxRunTime) - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt, syscall.SIGHUP) - -loop: - for { - select { - case <-p.Stopper: - log.Println("Stopper activated") - break loop - case <-maxRun: - log.Println("Max run time exhausted") - break loop - case <-ticker.C: - p.ntick += 1 - if !p.dontUpdateVolume { - p.volume.Volume = -2 - math.Log(5/float64(p.ntick))/3 - } - case <-interrupt: - break loop - } - } - - log.Println("Stopping the player...") - - // Calm down music -loopcalm: - for i := 0; i < 2000; i += 1 { - p.volume.Volume -= 0.001 - - timer := time.NewTimer(4 * time.Millisecond) - select { - case <-p.Stopper: - log.Println("Hard stop received...") - timer.Stop() - p.volume.Volume = 0 - break loopcalm - case <-timer.C: - break - } - } - - if p == CommonPlayer { - log.Println("Destoying common player") - - CommonPlayer = nil - - // TODO: find a better way to deallocate the card - os.Exit(42) - } -} - -func (p *Player) Stop() error { - log.Println("Trying to stop the player") - p.Stopper <- true - - return nil -} diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 7155a72..0000000 --- a/renovate.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "packageRules": [ - { - "matchPackageNames": ["alpine", "github.com/gin-gonic/gin"], - "automerge": true, - "automergeType": "branch" - } - ] -} diff --git a/ui/.eslintrc.cjs b/ui/.eslintrc.cjs deleted file mode 100644 index 3ccf435..0000000 --- a/ui/.eslintrc.cjs +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], - plugins: ['svelte3', '@typescript-eslint'], - ignorePatterns: ['*.cjs'], - overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], - settings: { - 'svelte3/typescript': () => require('typescript') - }, - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020 - }, - env: { - browser: true, - es2017: true, - node: true - } -}; diff --git a/ui/.gitignore b/ui/.gitignore deleted file mode 100644 index f4401a3..0000000 --- a/ui/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example diff --git a/ui/.npmrc b/ui/.npmrc deleted file mode 100644 index b6f27f1..0000000 --- a/ui/.npmrc +++ /dev/null @@ -1 +0,0 @@ -engine-strict=true diff --git a/ui/.prettierrc b/ui/.prettierrc deleted file mode 100644 index ff2677e..0000000 --- a/ui/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100 -} diff --git a/ui/README.md b/ui/README.md deleted file mode 100644 index 82510ca..0000000 --- a/ui/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# create-svelte - -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte); - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```bash -# create a new project in the current directory -npm init svelte@next - -# create a new project in my-app -npm init svelte@next my-app -``` - -> Note: the `@next` is temporary - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -## Building - -Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then: - -```bash -npm run build -``` - -> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. diff --git a/ui/assets-dev.go b/ui/assets-dev.go deleted file mode 100644 index fac4f77..0000000 --- a/ui/assets-dev.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build dev -// +build dev - -package ui - -import ( - "flag" - "net/http" - "os" - "path/filepath" -) - -var ( - Assets http.FileSystem - StaticDir string = "ui/" -) - -func init() { - flag.StringVar(&StaticDir, "static", StaticDir, "Directory containing static files") -} - -func sanitizeStaticOptions() error { - StaticDir, _ = filepath.Abs(StaticDir) - if _, err := os.Stat(StaticDir); os.IsNotExist(err) { - StaticDir, _ = filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "ui")) - if _, err := os.Stat(StaticDir); os.IsNotExist(err) { - return err - } - } - Assets = http.Dir(StaticDir) - return nil -} diff --git a/ui/assets.go b/ui/assets.go deleted file mode 100644 index 6ec6008..0000000 --- a/ui/assets.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build !dev -// +build !dev - -package ui - -import ( - "embed" - "io/fs" - "log" - "net/http" -) - -//go:embed all:build -var _assets embed.FS - -var Assets http.FileSystem - -func init() { - sub, err := fs.Sub(_assets, "build") - if err != nil { - log.Fatal("Unable to cd to ui/build directory:", err) - } - Assets = http.FS(sub) -} - -func sanitizeStaticOptions() error { - return nil -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 2a9146b..0000000 --- a/ui/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "reveil", - "version": "0.0.1", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "package": "vite package", - "preview": "vite preview", - "check": "svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", - "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ." - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^1.0.0-next.18", - "@sveltejs/adapter-static": "^1.0.0-next.26", - "@sveltejs/kit": "^1.0.0-next.260", - "@typescript-eslint/eslint-plugin": "^5.0.0", - "@typescript-eslint/parser": "^5.0.0", - "bootstrap": "^5.1.3", - "bootstrap-icons": "^1.8.0", - "bootswatch": "^5.1.3", - "eslint": "^8.0.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-svelte3": "^4.0.0", - "prettier": "^2.4.1", - "prettier-plugin-svelte": "^2.6.0", - "svelte": "^3.46.4", - "svelte-check": "^2.4.2", - "svelte-preprocess": "^4.10.2", - "tslib": "^2.3.1", - "typescript": "^4.5.5" - }, - "type": "module", - "dependencies": { - "dayjs": "^1.11.5", - "sass": "^1.49.7", - "sass-loader": "^13.0.0", - "sveltestrap": "^5.8.3", - "vite": "^3.0.0" - } -} diff --git a/ui/routes.go b/ui/routes.go deleted file mode 100644 index cd554a5..0000000 --- a/ui/routes.go +++ /dev/null @@ -1,82 +0,0 @@ -package ui - -import ( - "io" - "net/http" - "net/url" - "path" - - "github.com/gin-gonic/gin" - - "git.nemunai.re/nemunaire/reveil/config" -) - -func serveOrReverse(forced_url string, cfg *config.Config) gin.HandlerFunc { - if cfg.DevProxy != "" { - // Forward to the Vue dev proxy - return func(c *gin.Context) { - if u, err := url.Parse(cfg.DevProxy); err != nil { - http.Error(c.Writer, err.Error(), http.StatusInternalServerError) - } else { - if forced_url != "" { - u.Path = path.Join(u.Path, forced_url) - } else { - u.Path = path.Join(u.Path, c.Request.URL.Path) - } - - if r, err := http.NewRequest(c.Request.Method, u.String(), c.Request.Body); err != nil { - http.Error(c.Writer, err.Error(), http.StatusInternalServerError) - } else if resp, err := http.DefaultClient.Do(r); err != nil { - http.Error(c.Writer, err.Error(), http.StatusBadGateway) - } else { - defer resp.Body.Close() - - for key := range resp.Header { - c.Writer.Header().Add(key, resp.Header.Get(key)) - } - c.Writer.WriteHeader(resp.StatusCode) - - io.Copy(c.Writer, resp.Body) - } - } - } - } else if forced_url != "" { - // Serve forced_url - return func(c *gin.Context) { - c.FileFromFS(forced_url, Assets) - } - } else { - // Serve requested file - return func(c *gin.Context) { - c.FileFromFS(c.Request.URL.Path, Assets) - } - } -} - -func DeclareRoutes(router *gin.Engine, cfg *config.Config) { - if cfg.DevProxy != "" { - router.GET("/.svelte-kit/*_", serveOrReverse("", cfg)) - router.GET("/node_modules/*_", serveOrReverse("", cfg)) - router.GET("/@vite/*_", serveOrReverse("", cfg)) - router.GET("/@fs/*_", serveOrReverse("", cfg)) - router.GET("/src/*_", serveOrReverse("", cfg)) - } - - router.GET("/", serveOrReverse("", cfg)) - router.GET("/alarms", serveOrReverse("/", cfg)) - router.GET("/alarms/*_", serveOrReverse("/", cfg)) - router.GET("/settings", serveOrReverse("/", cfg)) - router.GET("/settings/*_", serveOrReverse("/", cfg)) - router.GET("/routines", serveOrReverse("/", cfg)) - router.GET("/routines/*_", serveOrReverse("/", cfg)) - router.GET("/musiks", serveOrReverse("/", cfg)) - router.GET("/musiks/*_", serveOrReverse("/", cfg)) - router.GET("/history", serveOrReverse("/", cfg)) - router.GET("/history/*_", serveOrReverse("/", cfg)) - - router.GET("/_app/*_", serveOrReverse("", cfg)) - router.GET("/img/*_", serveOrReverse("", cfg)) - router.GET("/favicon.ico", serveOrReverse("", cfg)) - router.GET("/manifest.json", serveOrReverse("", cfg)) - router.GET("/service-worker.js", serveOrReverse("", cfg)) -} diff --git a/ui/src/app.html b/ui/src/app.html deleted file mode 100644 index cb871a8..0000000 --- a/ui/src/app.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/ui/src/global.d.ts b/ui/src/global.d.ts deleted file mode 100644 index 63908c6..0000000 --- a/ui/src/global.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/ui/src/lib/action.js b/ui/src/lib/action.js deleted file mode 100644 index 7fdf8ff..0000000 --- a/ui/src/lib/action.js +++ /dev/null @@ -1,66 +0,0 @@ -export class Action { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, name, description, path, enabled }) { - this.id = id; - this.name = name; - this.description = description; - this.path = path; - this.enabled = enabled; - } - - async delete() { - const res = await fetch(`api/actions/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status == 200) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - toggleEnable() { - this.enabled = !this.enabled; - this.save(); - return this; - } - - async save() { - const res = await fetch(this.id?`api/actions/${this.id}`:'api/actions', { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getActions() { - const res = await fetch(`api/actions`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return (await res.json()).map((t) => new Action(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getAction(aid) { - const res = await fetch(`api/actions/${aid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Action(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/ui/src/lib/alarm.js b/ui/src/lib/alarm.js deleted file mode 100644 index c3559d7..0000000 --- a/ui/src/lib/alarm.js +++ /dev/null @@ -1,46 +0,0 @@ -export async function isAlarmActive() { - const res = await fetch('api/alarm', { - headers: {'Accept': 'application/json'}, - }); - if (res.status == 200) { - return await res.json(); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function runAlarm() { - const res = await fetch('api/alarm/run', { - method: 'POST', - headers: {'Accept': 'application/json'}, - }); - if (res.status == 200) { - return await res.json(); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function alarmNextTrack() { - const res = await fetch('api/alarm/next', { - method: 'POST', - headers: {'Accept': 'application/json'}, - }); - if (res.status == 200) { - return await res.json(); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function alarmStop() { - const res = await fetch('api/alarm', { - method: 'DELETE', - headers: {'Accept': 'application/json'}, - }); - if (res.status == 200) { - return await res.json(); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/ui/src/lib/alarmexception.js b/ui/src/lib/alarmexception.js deleted file mode 100644 index f9f2249..0000000 --- a/ui/src/lib/alarmexception.js +++ /dev/null @@ -1,71 +0,0 @@ -export class AlarmException { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, start, end, comment }) { - this.id = id; - this.start = start; - this.end = end; - this.comment = comment; - } - - _start() { - return new Date(this.start); - } - - _end() { - return new Date(this.end); - } - - async delete() { - const res = await fetch(`api/alarms/exceptions/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status == 200) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async save() { - const res = await fetch(this.id?`api/alarms/exceptions/${this.id}`:'api/alarms/exceptions', { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getAlarmsException() { - const res = await fetch(`api/alarms/exceptions`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json(); - if (data === null) - return []; - else - return data.map((t) => new AlarmException(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getAlarmException(aid) { - const res = await fetch(`api/alarms/exceptions/${aid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new AlarmException(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/ui/src/lib/alarmrepeated.js b/ui/src/lib/alarmrepeated.js deleted file mode 100644 index baef914..0000000 --- a/ui/src/lib/alarmrepeated.js +++ /dev/null @@ -1,93 +0,0 @@ -export class AlarmRepeated { - constructor(res) { - if (res) { - this.update(res); - } - } - - 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() { - const res = await fetch(`api/alarms/repeated/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status == 200) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async save() { - const res = await fetch(this.id?`api/alarms/repeated/${this.id}`:'api/alarms/repeated', { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getAlarmsRepeated() { - const res = await fetch(`api/alarms/repeated`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json(); - if (data === null) - return []; - else - return data.map((t) => new AlarmRepeated(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getAlarmRepeated(aid) { - const res = await fetch(`api/alarms/repeated/${aid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new AlarmRepeated(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export function weekdayStr(weekday) { - switch (weekday) { - case 0: - case "0": - return "dimanche"; - case 1: - case "1": - return "lundi"; - case 2: - case "2": - return "mardi"; - case 3: - case "3": - return "mercredi"; - case 4: - case "4": - return "jeudi"; - case 5: - case "5": - return "vendredi"; - case 6: - case "6": - return "samedi"; - } -} diff --git a/ui/src/lib/alarmsingle.js b/ui/src/lib/alarmsingle.js deleted file mode 100644 index f8e5927..0000000 --- a/ui/src/lib/alarmsingle.js +++ /dev/null @@ -1,92 +0,0 @@ -export class AlarmSingle { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, time, routines, comment }) { - this.id = id; - this.time = new Date(time); - this.routines = routines; - this.comment = comment; - } - - async delete() { - const res = await fetch(`api/alarms/single/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status == 200) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async save() { - const res = await fetch(this.id?`api/alarms/single/${this.id}`:'api/alarms/single', { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getAlarmsSingle() { - const res = await fetch(`api/alarms/single`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json(); - if (data === null) - return []; - else - return data.map((t) => new AlarmSingle(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getAlarmSingle(aid) { - const res = await fetch(`api/alarms/single/${aid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new AlarmSingle(await res.json()); - } else { - 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) { - const data = await res.json(); - if (data) - return new Date(data); - else - return data; - } 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/lib/components/ActionList.svelte b/ui/src/lib/components/ActionList.svelte deleted file mode 100644 index 6f4e208..0000000 --- a/ui/src/lib/components/ActionList.svelte +++ /dev/null @@ -1,76 +0,0 @@ - - -
-

- Actions -

-
- {#if !flush} - - {/if} - -
-
-
- {#if $actions.list} - {#each $actions.list as action (action.id)} - - {action.name} - - {/each} - {:else} - {#await actions.refresh()} -
- Chargement en cours… -
- {:then} - test - {/await} - {/if} -
diff --git a/ui/src/lib/components/AlarmExceptionList.svelte b/ui/src/lib/components/AlarmExceptionList.svelte deleted file mode 100644 index aac62f0..0000000 --- a/ui/src/lib/components/AlarmExceptionList.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - -
-

- Exceptions -

- -
-
- {#if $alarmsExceptions.list !== null} - {#if $alarmsExceptions.list.length} -
- {#each $alarmsExceptions.list as alarm (alarm.id)} - - Du - - {/each} -
- {:else} -

Pas d'exception programmée

- {/if} - {:else} - {#await alarmsExceptions.refresh()} -
- Chargement en cours… -
- {/await} - {/if} -
diff --git a/ui/src/lib/components/AlarmRepeatedList.svelte b/ui/src/lib/components/AlarmRepeatedList.svelte deleted file mode 100644 index a1c93cf..0000000 --- a/ui/src/lib/components/AlarmRepeatedList.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - -
-

- Réveils habituels -

- -
-
- {#if $alarmsRepeated.list !== null} - {#if $alarmsRepeated.list.length} -
- {#each $alarmsRepeated.list as alarm (alarm.id)} - - Les {weekdayStr(alarm.weekday)}s à {alarm.time} - - {/each} -
- {:else} -

Pas de réveil habituel programmé

- {/if} - {:else} - {#await alarmsRepeated.refresh()} -
- Chargement en cours… -
- {/await} - {/if} -
diff --git a/ui/src/lib/components/AlarmSingleList.svelte b/ui/src/lib/components/AlarmSingleList.svelte deleted file mode 100644 index acd3b23..0000000 --- a/ui/src/lib/components/AlarmSingleList.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - -
-

- Réveils manuels -

- -
-
- {#if $alarmsSingle.list !== null} - {#if $alarmsSingle.list.length} -
- {#each $alarmsSingle.list as alarm (alarm.id)} - - Le - - {/each} -
- {:else} -

Pas de prochain réveil manuel programmé

- {/if} - {:else} - {#await alarmsSingle.refresh()} -
- Chargement en cours… -
- {/await} - {/if} -
diff --git a/ui/src/lib/components/CardRoutine.svelte b/ui/src/lib/components/CardRoutine.svelte deleted file mode 100644 index cc63566..0000000 --- a/ui/src/lib/components/CardRoutine.svelte +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - {routine.name} - - {#if routine.steps} - - {#each routine.steps as step} - - {#if $actions_idx && $actions_idx[step.action]} - {$actions_idx[step.action].name} - {:else} - {step.action} - {/if} - - {step.delay/60} min - - - {/each} - - {:else} - - Aucune action définie. - - {/if} - diff --git a/ui/src/lib/components/CardStatAlarms.svelte b/ui/src/lib/components/CardStatAlarms.svelte deleted file mode 100644 index 2d0de16..0000000 --- a/ui/src/lib/components/CardStatAlarms.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - - - - Liste des réveils - - - {#each awakingList as awaking (awaking.id)} - {awaking.date} - {/each} - - diff --git a/ui/src/lib/components/CardStatRoutines.svelte b/ui/src/lib/components/CardStatRoutines.svelte deleted file mode 100644 index 6107295..0000000 --- a/ui/src/lib/components/CardStatRoutines.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - - - Routines favorites - - - {#each routinesStats as routine (routine.id)} - - {routine.name} - - {routine.nb} - - - {/each} - - diff --git a/ui/src/lib/components/CardStatTimeAwaking.svelte b/ui/src/lib/components/CardStatTimeAwaking.svelte deleted file mode 100644 index 4e271e4..0000000 --- a/ui/src/lib/components/CardStatTimeAwaking.svelte +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Temps moyen de éteindre le réveil - - -

- 10 minutes -

-
-
diff --git a/ui/src/lib/components/CycleCounter.svelte b/ui/src/lib/components/CycleCounter.svelte deleted file mode 100644 index f3fac60..0000000 --- a/ui/src/lib/components/CycleCounter.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -{#if begins && ends} - - (dans {Math.trunc((ends.getTime()-begins.getTime())/5400000)} cycles + {Math.trunc(((ends.getTime()-begins.getTime())%5400000)/60000)} min) - -{/if} diff --git a/ui/src/lib/components/DateFormat.svelte b/ui/src/lib/components/DateFormat.svelte deleted file mode 100644 index f2ac452..0000000 --- a/ui/src/lib/components/DateFormat.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - -{formatDate(date, dateStyle, timeStyle)} diff --git a/ui/src/lib/components/DateRangeFormat.svelte b/ui/src/lib/components/DateRangeFormat.svelte deleted file mode 100644 index 0c5ab4e..0000000 --- a/ui/src/lib/components/DateRangeFormat.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - -{formatRange(startDate, endDate, dateStyle, timeStyle)} diff --git a/ui/src/lib/components/DateTimeInput.svelte b/ui/src/lib/components/DateTimeInput.svelte deleted file mode 100644 index 19118ed..0000000 --- a/ui/src/lib/components/DateTimeInput.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/ui/src/lib/components/GongsList.svelte b/ui/src/lib/components/GongsList.svelte deleted file mode 100644 index 06777e7..0000000 --- a/ui/src/lib/components/GongsList.svelte +++ /dev/null @@ -1,96 +0,0 @@ - - -
-

- Gongs -

-
- {#if !edit} - - {/if} - - -
-
-
- {#if $gongs.list} - {#each $gongs.list as gong (gong.id)} - - {/each} - {:else} - {#await gongs.refresh()} -
- Chargement en cours… -
- {/await} - {/if} -
diff --git a/ui/src/lib/components/Header.svelte b/ui/src/lib/components/Header.svelte deleted file mode 100644 index 11d0fb8..0000000 --- a/ui/src/lib/components/Header.svelte +++ /dev/null @@ -1,99 +0,0 @@ - - - - - Réveil - - - - diff --git a/ui/src/lib/components/MusiksLastPlayedList.svelte b/ui/src/lib/components/MusiksLastPlayedList.svelte deleted file mode 100644 index 1f0cae9..0000000 --- a/ui/src/lib/components/MusiksLastPlayedList.svelte +++ /dev/null @@ -1,32 +0,0 @@ - - -

- Dernières musiques jouées -

-
    - {#each tracks as track} -
  1. {musiks[track].artist} – {musiks[track].title}
  2. - {/each} -
diff --git a/ui/src/lib/components/Toaster.svelte b/ui/src/lib/components/Toaster.svelte deleted file mode 100644 index d0d9c46..0000000 --- a/ui/src/lib/components/Toaster.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - -
- {#each $ToastsStore.toasts as toast} - - - {#if toast.title}{toast.title}{:else}Gustus{/if} - - - {toast.msg} - - - {/each} -
diff --git a/ui/src/lib/components/TrackList.svelte b/ui/src/lib/components/TrackList.svelte deleted file mode 100644 index 551c87a..0000000 --- a/ui/src/lib/components/TrackList.svelte +++ /dev/null @@ -1,97 +0,0 @@ - - -
-

- Musiques {#if !flush}du réveil{/if} -

-
- {#if !edit} - - {/if} - - -
-
-
- {#if $tracks.list} - {#each $tracks.list as track (track.id)} - - {/each} - {:else} - {#await tracks.refresh()} -
- Chargement en cours… -
- {:then} - test - {/await} - {/if} -
diff --git a/ui/src/lib/gong.js b/ui/src/lib/gong.js deleted file mode 100644 index 43fd1fb..0000000 --- a/ui/src/lib/gong.js +++ /dev/null @@ -1,83 +0,0 @@ -export class Gong { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, name, path, enabled }) { - this.id = id; - this.name = name; - this.path = path; - this.enabled = enabled; - } - - async delete() { - const res = await fetch(`api/gongs/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status == 200) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async setDefault() { - this.enabled = !this.enabled; - return await this.save(); - } - - async save() { - const res = await fetch(this.id?`api/gongs/${this.id}`:'api/gongs', { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function uploadGong(files, meta) { - for (const file of files) { - const formData = new FormData(); - formData.append("gongfile", file); - formData.append("meta", JSON.stringify(meta)); - - const res = await fetch('/api/gongs', { - method: 'POST', - body: formData, - }); - if (res.ok) { - const data = await res.json(); - return new Gong(data) - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getGongs() { - const res = await fetch(`api/gongs`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return (await res.json()).map((g) => new Gong(g)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getGong(gid) { - const res = await fetch(`api/gongs/${gid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Gong(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/ui/src/lib/routine.js b/ui/src/lib/routine.js deleted file mode 100644 index 31c8f72..0000000 --- a/ui/src/lib/routine.js +++ /dev/null @@ -1,61 +0,0 @@ -export class Routine { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, name, path, steps }) { - this.id = id; - this.name = name; - this.path = path; - - steps.sort((a, b) => a.delay - b.delay); - this.steps = steps; - } - - async delete() { - const res = await fetch(`api/routines/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status == 200) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async save() { - const res = await fetch(this.id?`api/routines/${this.id}`:'api/routines', { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getRoutines() { - const res = await fetch(`api/routines`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return (await res.json()).map((r) => new Routine(r)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getRoutine(rid) { - const res = await fetch(`api/routines/${rid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Routine(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/ui/src/lib/settings.js b/ui/src/lib/settings.js deleted file mode 100644 index cb05812..0000000 --- a/ui/src/lib/settings.js +++ /dev/null @@ -1,39 +0,0 @@ -export class Settings { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ language, gong_interval, weather_delay, weather_action, max_run_time }) { - this.language = language; - this.gong_interval = gong_interval; - this.weather_delay = weather_delay; - this.weather_action = weather_action; - this.max_run_time = max_run_time; - } - - async save() { - const res = await fetch(`api/settings`, { - method: 'PUT', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getSettings() { - const res = await fetch(`api/settings`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Settings(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/ui/src/lib/stores/actions.js b/ui/src/lib/stores/actions.js deleted file mode 100644 index ca15c9c..0000000 --- a/ui/src/lib/stores/actions.js +++ /dev/null @@ -1,51 +0,0 @@ -import { derived, writable } from 'svelte/store'; - -import { getActions } from '$lib/action' - -function createActionsStore() { - const { subscribe, set, update } = writable({list: null, fileIdx: null}); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - refresh: async () => { - const list = await getActions(); - const fileIdx = {}; - list.forEach(function(action, k) { - fileIdx[action.path] = action; - }); - - update((m) => (Object.assign(m, {list, fileIdx}))); - return list; - }, - - update: (res_actions, cb=null) => { - if (res_actions.status === 200) { - res_actions.json().then((list) => { - const fileIdx = {}; - list.forEach(function(action, k) { - fileIdx[action.path] = action; - }) - - update((m) => (Object.assign(m, {list, fileIdx}))); - - if (cb) { - cb(list); - } - }); - } - }, - }; - -} - -export const actions = createActionsStore(); - -export const actions_idx = derived( - actions, - ($actions) => ($actions.fileIdx), -); diff --git a/ui/src/lib/stores/alarmexceptions.js b/ui/src/lib/stores/alarmexceptions.js deleted file mode 100644 index b6b872e..0000000 --- a/ui/src/lib/stores/alarmexceptions.js +++ /dev/null @@ -1,41 +0,0 @@ -import { derived, writable } from 'svelte/store'; - -import { getAlarmsException } from '$lib/alarmexception' - -function createAlarmsExceptionStore() { - const { subscribe, set, update } = writable({list: null}); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - clear: () => { - update((m) => m = {list: null}); - }, - - refresh: async () => { - const list = await getAlarmsException(); - - update((m) => (Object.assign(m, {list}))); - return list; - }, - - update: (res_AlarmsException, cb=null) => { - if (res_AlarmsException.status === 200) { - res_AlarmsException.json().then((list) => { - update((m) => (Object.assign(m, {list}))); - - if (cb) { - cb(list); - } - }); - } - }, - }; - -} - -export const alarmsExceptions = createAlarmsExceptionStore(); diff --git a/ui/src/lib/stores/alarmrepeated.js b/ui/src/lib/stores/alarmrepeated.js deleted file mode 100644 index 063c8eb..0000000 --- a/ui/src/lib/stores/alarmrepeated.js +++ /dev/null @@ -1,41 +0,0 @@ -import { derived, writable } from 'svelte/store'; - -import { getAlarmsRepeated } from '$lib/alarmrepeated' - -function createAlarmsRepeatedStore() { - const { subscribe, set, update } = writable({list: null}); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - clear: () => { - update((m) => m = {list: null}); - }, - - refresh: async () => { - const list = await getAlarmsRepeated(); - - update((m) => (Object.assign(m, {list}))); - return list; - }, - - update: (res_AlarmsRepeated, cb=null) => { - if (res_AlarmsRepeated.status === 200) { - res_AlarmsRepeated.json().then((list) => { - update((m) => (Object.assign(m, {list}))); - - if (cb) { - cb(list); - } - }); - } - }, - }; - -} - -export const alarmsRepeated = createAlarmsRepeatedStore(); diff --git a/ui/src/lib/stores/alarmsingle.js b/ui/src/lib/stores/alarmsingle.js deleted file mode 100644 index 38b34da..0000000 --- a/ui/src/lib/stores/alarmsingle.js +++ /dev/null @@ -1,41 +0,0 @@ -import { derived, writable } from 'svelte/store'; - -import { getAlarmsSingle } from '$lib/alarmsingle' - -function createAlarmsSingleStore() { - const { subscribe, set, update } = writable({list: null}); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - clear: () => { - update((m) => m = {list: null}); - }, - - refresh: async () => { - const list = await getAlarmsSingle(); - - update((m) => (Object.assign(m, {list}))); - return list; - }, - - update: (res_AlarmsSingle, cb=null) => { - if (res_AlarmsSingle.status === 200) { - res_AlarmsSingle.json().then((list) => { - update((m) => (Object.assign(m, {list}))); - - if (cb) { - cb(list); - } - }); - } - }, - }; - -} - -export const alarmsSingle = createAlarmsSingleStore(); diff --git a/ui/src/lib/stores/gongs.js b/ui/src/lib/stores/gongs.js deleted file mode 100644 index 33be920..0000000 --- a/ui/src/lib/stores/gongs.js +++ /dev/null @@ -1,36 +0,0 @@ -import { writable } from 'svelte/store'; - -import { getGongs } from '$lib/gong' - -function createGongsStore() { - const { subscribe, set, update } = writable({list: null}); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - refresh: async () => { - const list = await getGongs(); - update((m) => Object.assign(m, {list})); - return list; - }, - - update: (res_gongs, cb=null) => { - if (res_gongs.status === 200) { - res_gongs.json().then((list) => { - update((m) => (Object.assign(m, {list}))); - - if (cb) { - cb(list); - } - }); - } - }, - }; - -} - -export const gongs = createGongsStore(); diff --git a/ui/src/lib/stores/quotes.js b/ui/src/lib/stores/quotes.js deleted file mode 100644 index 0127add..0000000 --- a/ui/src/lib/stores/quotes.js +++ /dev/null @@ -1,43 +0,0 @@ -import { writable } from 'svelte/store'; - -function createQuotesStore() { - const { subscribe, set, update } = writable({quotes: [], quoteOfTheDay: null}); - - return { - subscribe, - - set: (quotes) => { - update((m) => Object.assign(m, {quotes})); - }, - - setQOTD: (quoteOfTheDay) => { - update((m) => Object.assign(m, {quoteOfTheDay})); - }, - - refreshQOTD: async () => { - const res = await fetch(`api/quoteoftheday`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const quoteOfTheDay = await res.json(); - update((m) => Object.assign(m, {quoteOfTheDay})); - return quoteOfTheDay; - } else { - throw new Error((await res.json()).errmsg); - } - }, - - update: (res_quotes, cb=null) => { - if (res_quotes.status === 200) { - res_quotes.json().then((quotes) => { - update((m) => (Object.assign(m, {quotes}))); - - if (cb) { - cb(quotes); - } - }); - } - }, - }; - -} - -export const quotes = createQuotesStore(); diff --git a/ui/src/lib/stores/routines.js b/ui/src/lib/stores/routines.js deleted file mode 100644 index b0eb2fd..0000000 --- a/ui/src/lib/stores/routines.js +++ /dev/null @@ -1,36 +0,0 @@ -import { writable } from 'svelte/store'; - -import { getRoutines } from '$lib/routine' - -function createRoutinesStore() { - const { subscribe, set, update } = writable({list: null}); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - refresh: async () => { - const list = await getRoutines(); - update((m) => Object.assign(m, {list})); - return list; - }, - - update: (res_routines, cb=null) => { - if (res_routines.status === 200) { - res_routines.json().then((list) => { - update((m) => (Object.assign(m, {list}))); - - if (cb) { - cb(list); - } - }); - } - }, - }; - -} - -export const routines = createRoutinesStore(); diff --git a/ui/src/lib/stores/toasts.js b/ui/src/lib/stores/toasts.js deleted file mode 100644 index f6e4d14..0000000 --- a/ui/src/lib/stores/toasts.js +++ /dev/null @@ -1,41 +0,0 @@ -import { writable } from 'svelte/store'; - -function createToastsStore() { - const { subscribe, update } = writable({toasts: []}); - - const addToast = (o) => { - o.timestamp = new Date(); - - o.close = () => { - update((i) => { - i.toasts = i.toasts.filter((j) => { - return !(j.title === o.title && j.msg === o.msg && j.timestamp === o.timestamp) - }); - return i; - }); - } - - update((i) => { - i.toasts.unshift(o); - return i; - }); - - o.cancel = setTimeout(o.close, o.dismiss?o.dismiss:5000); - }; - - const addErrorToast = (o) => { - if (!o.title) o.title = 'Une erreur est survenue !'; - if (!o.color) o.color = 'danger'; - - return addToast(o); - }; - - return { - subscribe, - addToast, - addErrorToast, - }; - -} - -export const ToastsStore = createToastsStore(); diff --git a/ui/src/lib/stores/tracks.js b/ui/src/lib/stores/tracks.js deleted file mode 100644 index 54b21e2..0000000 --- a/ui/src/lib/stores/tracks.js +++ /dev/null @@ -1,36 +0,0 @@ -import { writable } from 'svelte/store'; - -import { getTracks } from '$lib/track' - -function createTracksStore() { - const { subscribe, set, update } = writable({list: null}); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - refresh: async () => { - const list = await getTracks(); - update((m) => Object.assign(m, {list})); - return list; - }, - - update: (res_tracks, cb=null) => { - if (res_tracks.status === 200) { - res_tracks.json().then((list) => { - update((m) => (Object.assign(m, {list}))); - - if (cb) { - cb(list); - } - }); - } - }, - }; - -} - -export const tracks = createTracksStore(); diff --git a/ui/src/lib/track.js b/ui/src/lib/track.js deleted file mode 100644 index cc44068..0000000 --- a/ui/src/lib/track.js +++ /dev/null @@ -1,83 +0,0 @@ -export class Track { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, name, path, enabled }) { - this.id = id; - this.name = name; - this.path = path; - this.enabled = enabled; - } - - async delete() { - const res = await fetch(`api/tracks/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status == 200) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async toggleEnable() { - this.enabled = !this.enabled; - return await this.save(); - } - - async save() { - const res = await fetch(this.id?`api/tracks/${this.id}`:'api/tracks', { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return this; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function uploadTrack(files, meta) { - for (const file of files) { - const formData = new FormData(); - formData.append("trackfile", file); - formData.append("meta", JSON.stringify(meta)); - - const res = await fetch('/api/tracks', { - method: 'POST', - body: formData, - }); - if (res.ok) { - const data = await res.json(); - return new Track(data) - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getTracks() { - const res = await fetch(`api/tracks`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return (await res.json()).map((t) => new Track(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getTrack(tid) { - const res = await fetch(`api/tracks/${tid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Track(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/ui/src/reveil.scss b/ui/src/reveil.scss deleted file mode 100644 index 738dc8e..0000000 --- a/ui/src/reveil.scss +++ /dev/null @@ -1,46 +0,0 @@ -// Your variable overrides can go here, e.g.: -// $h1-font-size: 3rem; -//$primary: #ff485a; -//$secondary: #ff7b88; - -$blue: #2a9fd6; -$indigo: #6610f2; -$purple: #6f42c1; -$pink: #e83e8c; -$red: #c00; -$orange: #fd7e14; -$yellow: #f80; -$green: #77b300; -$teal: #20c997; -$cyan: #93c; - -$primary: $pink; -$success: $green; -$info: $cyan; -$warning: $yellow; -$danger: $red; - -$min-contrast-ratio: 2.25; - -$enable-shadows: true; -$enable-gradients: true; -$enable-responsive-font-sizes: true; - -$link-color: $primary; - -$navbar-padding-y: 0; -$nav-link-padding-y: 0.2rem; - -@import "bootstrap/scss/bootstrap"; - -a.btn, button.btn { - text-decoration: none !important; -} - -.fixed-bottom .nav-item a { - border-top: 1px solid $pink; -} -.fixed-bottom .nav-item a.active { - background: white; - color: $pink; -} diff --git a/ui/src/routes/+layout.js b/ui/src/routes/+layout.js deleted file mode 100644 index 89da957..0000000 --- a/ui/src/routes/+layout.js +++ /dev/null @@ -1,2 +0,0 @@ -export const ssr = false; -export const prerender = true; diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte deleted file mode 100644 index 3edb5b6..0000000 --- a/ui/src/routes/+layout.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - - - Réveil - - - - -
-
- -
-
- -
- - diff --git a/ui/src/routes/+page.svelte b/ui/src/routes/+page.svelte deleted file mode 100644 index 72707e0..0000000 --- a/ui/src/routes/+page.svelte +++ /dev/null @@ -1,155 +0,0 @@ - - - - {#if $quotes.quoteOfTheDay} -
-
-

{$quotes.quoteOfTheDay.content}

-
- -
- {:else} - {#await quotes.refreshQOTD()} -
- Loading... -
- {/await} - {/if} -
- {#await nextAlarmP} -
- Loading... -
- {:then nextalarm} - {#if nextalarm === null} - Pas de prochain réveil programmé - {:else} - Prochain réveil : - {#if nextalarm.getDay() == new Date().getDay() && nextalarm.getMonth() == new Date().getMonth() && nextalarm.getFullYear() == new Date().getFullYear()} - aujourd'hui à - -
- - {: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 à - -
- - {:else if nextalarm.getTime() < Date.now() + 604800000} - {nextalarm.toLocaleString('default', {weekday: 'long'})} - à - - {:else} - - {/if} - {/if} - {/await} -
- {#await isActiveP then isActive} - {#if !isActive} -
- - - Programmer un nouveau réveil - - - - -
- {:else} -
- - - -
- {/if} - {/await} -
diff --git a/ui/src/routes/alarms/+page.svelte b/ui/src/routes/alarms/+page.svelte deleted file mode 100644 index 2807c6e..0000000 --- a/ui/src/routes/alarms/+page.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/ui/src/routes/alarms/[kind]/+layout.js b/ui/src/routes/alarms/[kind]/+layout.js deleted file mode 100644 index d43d0cd..0000000 --- a/ui/src/routes/alarms/[kind]/+layout.js +++ /dev/null @@ -1 +0,0 @@ -export const prerender = false; diff --git a/ui/src/routes/alarms/[kind]/+layout.svelte b/ui/src/routes/alarms/[kind]/+layout.svelte deleted file mode 100644 index 9f6faaf..0000000 --- a/ui/src/routes/alarms/[kind]/+layout.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - -
-
- -
- - - - - - - - - - -
diff --git a/ui/src/routes/alarms/[kind]/+page.svelte b/ui/src/routes/alarms/[kind]/+page.svelte deleted file mode 100644 index b9d20b3..0000000 --- a/ui/src/routes/alarms/[kind]/+page.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - -

- Choisissez {slugToText($page.params.kind)} dans la liste. -

-
- - - diff --git a/ui/src/routes/alarms/[kind]/[aid]/+page.svelte b/ui/src/routes/alarms/[kind]/[aid]/+page.svelte deleted file mode 100644 index 7448f2a..0000000 --- a/ui/src/routes/alarms/[kind]/[aid]/+page.svelte +++ /dev/null @@ -1,182 +0,0 @@ - - - - {#if $page.params["kind"] == "single"} - {#await objP} -
- Chargement en cours… -
- {:then alarm} -

- {slugToTitle($page.params["kind"])} du -

- {#if alarm.comment} -

- {alarm.comment} -

- {/if} - - - Date du réveil - - - Heure du réveil - - - {/await} - {:else if $page.params["kind"] == "repeated"} - {#await objP} -
- Chargement en cours… -
- {:then alarm} -

- {slugToTitle($page.params["kind"])} des {weekdayStr(alarm.weekday)}s à {alarm.time} -

- {#if alarm.comment} -

- {alarm.comment} -

- {/if} - - - Jour de la semaine {weekdayStr(alarm.weekday)} - - - Heure du réveil {alarm.time} - - - Ignorer les exceptions ? {alarm.ignore_exceptions?"oui":"non"} - - {#if alarm.next_time} - - Prochaine occurrence - - {/if} - - {#if alarm.excepts} -

Prochaines exceptions

- - {#each alarm.excepts as except} - - - - {/each} - - {/if} - {/await} - {:else if $page.params["kind"] == "exceptions"} - {#await objP} -
- Chargement en cours… -
- {:then exception} -

- {slugToTitle($page.params["kind"])} du -

- {#if exception.comment} -

- {exception.comment} -

- {/if} - Entre le - {/await} - {/if} - - {#if !edit} - {#await objP then alarm} - - edit = !edit} - > - - Éditer ce {slugToTitle($page.params["kind"]).toLowerCase()} - - - - Supprimer ce {slugToTitle($page.params["kind"]).toLowerCase()} - - - {/await} - {/if} -
diff --git a/ui/src/routes/alarms/[kind]/new/+page.svelte b/ui/src/routes/alarms/[kind]/new/+page.svelte deleted file mode 100644 index 3dd0813..0000000 --- a/ui/src/routes/alarms/[kind]/new/+page.svelte +++ /dev/null @@ -1,160 +0,0 @@ - - - -
- -

- {slugToTitle($page.params["kind"])} -

- {#if $page.params["kind"] == "single"} - - - - - - - - {#if $routines.list} - - {#each $routines.list as routine (routine.id)} - - {/each} - - {:else} - {#await routines.refresh()} -
- Chargement en cours… -
- {/await} - {/if} -
- {:else if $page.params["kind"] == "repeated"} - - - - - - - - - - - - - - - - - - - - - {#if $routines.list} - - {#each $routines.list as routine (routine.id)} - - {/each} - - {:else} - {#await routines.refresh()} -
- Chargement en cours… -
- {/await} - {/if} -
- - - - - {:else if $page.params["kind"] == "exceptions"} - - - - - - - - - {/if} - - - - - - - -
-
diff --git a/ui/src/routes/history/+page.svelte b/ui/src/routes/history/+page.svelte deleted file mode 100644 index fec2b65..0000000 --- a/ui/src/routes/history/+page.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/ui/src/routes/musiks/+page.svelte b/ui/src/routes/musiks/+page.svelte deleted file mode 100644 index e83b1e4..0000000 --- a/ui/src/routes/musiks/+page.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - diff --git a/ui/src/routes/musiks/gongs/+layout.svelte b/ui/src/routes/musiks/gongs/+layout.svelte deleted file mode 100644 index ac66f16..0000000 --- a/ui/src/routes/musiks/gongs/+layout.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -
-
- -
- - - - - - - - - - -
diff --git a/ui/src/routes/musiks/gongs/+page.svelte b/ui/src/routes/musiks/gongs/+page.svelte deleted file mode 100644 index de806ba..0000000 --- a/ui/src/routes/musiks/gongs/+page.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - -

- Choisissez la piste dans la liste. -

-
- - - diff --git a/ui/src/routes/musiks/gongs/[gid]/+page.js b/ui/src/routes/musiks/gongs/[gid]/+page.js deleted file mode 100644 index d43d0cd..0000000 --- a/ui/src/routes/musiks/gongs/[gid]/+page.js +++ /dev/null @@ -1 +0,0 @@ -export const prerender = false; diff --git a/ui/src/routes/musiks/gongs/[gid]/+page.svelte b/ui/src/routes/musiks/gongs/[gid]/+page.svelte deleted file mode 100644 index 1a58f8c..0000000 --- a/ui/src/routes/musiks/gongs/[gid]/+page.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - -{#await getGong($page.params.gid)} -
- Chargement en cours… -
-{:then gong} - -

- {gong.name} -

- - - Chemin - {gong.path} - - - Par défaut ? - gong.setDefault()} checked={gong.enabled} disabled={gong.enabled} /> - - - ID - {gong.id} - - - -
- -
- - - {#if !confirmDeletion} - confirmDeletion = !confirmDeletion} - > - - Supprimer ce gong - - {:else} - -

- Êtes-vous sûr ? -

-
- - -
-
- {/if} -
-
-{/await} diff --git a/ui/src/routes/musiks/gongs/new/+page.svelte b/ui/src/routes/musiks/gongs/new/+page.svelte deleted file mode 100644 index f5a5ec1..0000000 --- a/ui/src/routes/musiks/gongs/new/+page.svelte +++ /dev/null @@ -1,45 +0,0 @@ - - - -

- Nouveau gong -

- -
- - - -
-
diff --git a/ui/src/routes/musiks/tracks/+layout.svelte b/ui/src/routes/musiks/tracks/+layout.svelte deleted file mode 100644 index 495389e..0000000 --- a/ui/src/routes/musiks/tracks/+layout.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -
-
- -
- - - - - - - - - - -
diff --git a/ui/src/routes/musiks/tracks/+page.svelte b/ui/src/routes/musiks/tracks/+page.svelte deleted file mode 100644 index de806ba..0000000 --- a/ui/src/routes/musiks/tracks/+page.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - -

- Choisissez la piste dans la liste. -

-
- - - diff --git a/ui/src/routes/musiks/tracks/[tid]/+page.js b/ui/src/routes/musiks/tracks/[tid]/+page.js deleted file mode 100644 index d43d0cd..0000000 --- a/ui/src/routes/musiks/tracks/[tid]/+page.js +++ /dev/null @@ -1 +0,0 @@ -export const prerender = false; diff --git a/ui/src/routes/musiks/tracks/[tid]/+page.svelte b/ui/src/routes/musiks/tracks/[tid]/+page.svelte deleted file mode 100644 index 8a2e635..0000000 --- a/ui/src/routes/musiks/tracks/[tid]/+page.svelte +++ /dev/null @@ -1,99 +0,0 @@ - - -{#await getTrack($page.params.tid)} -
- Chargement en cours… -
-{:then track} - -

- {track.name} -

- - - Chemin - {track.path} - - - Active ? - toggleEnable(track)} checked={track.enabled} /> - - - ID - {track.id} - - - -
- -
- - - {#if !confirmDeletion} - confirmDeletion = !confirmDeletion} - > - - Supprimer cette piste - - {:else} - -

- Êtes-vous sûr ? -

-
- - -
-
- {/if} -
-
-{/await} diff --git a/ui/src/routes/musiks/tracks/new/+page.svelte b/ui/src/routes/musiks/tracks/new/+page.svelte deleted file mode 100644 index 98b302c..0000000 --- a/ui/src/routes/musiks/tracks/new/+page.svelte +++ /dev/null @@ -1,45 +0,0 @@ - - - -

- Nouvelle musique -

- -
- - - -
-
diff --git a/ui/src/routes/routines/+page.svelte b/ui/src/routes/routines/+page.svelte deleted file mode 100644 index 9ae6561..0000000 --- a/ui/src/routes/routines/+page.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - {#if $routines.list} - {#each $routines.list as routine (routine.id)} - - {/each} - {:else} - {#await routines.refresh()} -
- Chargement en cours… -
- {:then} - test - {/await} - {/if} - - - - Ajouter une routine … - - -
- - - - -
-
diff --git a/ui/src/routes/routines/actions/+layout.svelte b/ui/src/routes/routines/actions/+layout.svelte deleted file mode 100644 index ccf2d75..0000000 --- a/ui/src/routes/routines/actions/+layout.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -
-
- -
- - - - - - - - - - -
diff --git a/ui/src/routes/routines/actions/+page.svelte b/ui/src/routes/routines/actions/+page.svelte deleted file mode 100644 index b360f8d..0000000 --- a/ui/src/routes/routines/actions/+page.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - -

- Choisissez la piste dans la liste. -

-
- - - diff --git a/ui/src/routes/routines/actions/[aid]/+page.js b/ui/src/routes/routines/actions/[aid]/+page.js deleted file mode 100644 index d43d0cd..0000000 --- a/ui/src/routes/routines/actions/[aid]/+page.js +++ /dev/null @@ -1 +0,0 @@ -export const prerender = false; diff --git a/ui/src/routes/routines/actions/[aid]/+page.svelte b/ui/src/routes/routines/actions/[aid]/+page.svelte deleted file mode 100644 index f983a88..0000000 --- a/ui/src/routes/routines/actions/[aid]/+page.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - -{#await getAction($page.params.aid)} -
- Chargement en cours… -
-{:then action} - -

- {action.name} -

-

- {action.description} -

- - - Chemin - {action.path} - - - Actif ? - action.toggleEnable()} checked={action.enabled} /> - - -
-{/await} diff --git a/ui/src/routes/routines/actions/new/+page.svelte b/ui/src/routes/routines/actions/new/+page.svelte deleted file mode 100644 index ff4047f..0000000 --- a/ui/src/routes/routines/actions/new/+page.svelte +++ /dev/null @@ -1,3 +0,0 @@ -

- Nouvelle musique -

diff --git a/ui/src/routes/settings/+page.svelte b/ui/src/routes/settings/+page.svelte deleted file mode 100644 index dc2c579..0000000 --- a/ui/src/routes/settings/+page.svelte +++ /dev/null @@ -1,114 +0,0 @@ - - - -

- Paramètres -

-
- {#await settingsP} -
- Chargement en cours… -
- {:then} - - - - settings.save()} - /> - min - - - - - - - settings.save()} - /> - min - - - - - - {#if $actions.list} - settings.save()} - > - {#each $actions.list as action (action.id)} - - {/each} - - {:else} - {#await actions.refresh()} -
- Chargement en cours… -
- {/await} - {/if} -
- - - - settings.save()} - > - - - - - - - - - - - settings.save()} - /> - min - - - {/await} -
-
diff --git a/ui/src/service-worker.ts b/ui/src/service-worker.ts deleted file mode 100644 index 96b80f5..0000000 --- a/ui/src/service-worker.ts +++ /dev/null @@ -1,80 +0,0 @@ -/// - -import { build, files, timestamp } from '$service-worker'; - -const worker = (self as unknown) as ServiceWorkerGlobalScope; -const FILES = `cache${timestamp}`; - -// `build` is an array of all the files generated by the bundler, -// `files` is an array of everything in the `static` directory -const to_cache = build.concat(files); -const staticAssets = new Set(to_cache); - -worker.addEventListener('install', (event) => { - event.waitUntil( - caches - .open(FILES) - .then((cache) => cache.addAll(to_cache)) - .then(() => { - worker.skipWaiting(); - }) - ); -}); - -worker.addEventListener('activate', (event) => { - event.waitUntil( - caches.keys().then(async (keys) => { - // delete old caches - for (const key of keys) { - if (key !== FILES) await caches.delete(key); - } - - worker.clients.claim(); - }) - ); -}); - -/** - * Fetch the asset from the network and store it in the cache. - * Fall back to the cache if the user is offline. - */ -async function fetchAndCache(request: Request) { - const cache = await caches.open(`offline${timestamp}`); - - try { - const response = await fetch(request); - cache.put(request, response.clone()); - return response; - } catch (err) { - const response = await cache.match(request); - if (response) return response; - - throw err; - } -} - -worker.addEventListener('fetch', (event) => { - if (event.request.method !== 'GET' || event.request.headers.has('range')) return; - - const url = new URL(event.request.url); - - // don't try to handle e.g. data: URIs - const isHttp = url.protocol.startsWith('http'); - const isDevServerRequest = - url.hostname === self.location.hostname && url.port !== self.location.port; - const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname); - const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset; - - if (isHttp && !isDevServerRequest && !skipBecauseUncached) { - event.respondWith( - (async () => { - // always serve static files and bundler-generated assets from cache. - // if your application has other URLs with data that will never change, - // set this variable to true for them and they will only be fetched once. - const cachedAsset = isStaticAsset && (await caches.match(event.request)); - - return cachedAsset || fetchAndCache(event.request); - })() - ); - } -}); diff --git a/ui/static/favicon.ico b/ui/static/favicon.ico deleted file mode 100644 index 655bc89..0000000 Binary files a/ui/static/favicon.ico and /dev/null differ diff --git a/ui/static/img/android-chrome-512x512.png b/ui/static/img/android-chrome-512x512.png deleted file mode 100644 index 7371a23..0000000 Binary files a/ui/static/img/android-chrome-512x512.png and /dev/null differ diff --git a/ui/static/img/apple-touch-icon.png b/ui/static/img/apple-touch-icon.png deleted file mode 100644 index e022ce6..0000000 Binary files a/ui/static/img/apple-touch-icon.png and /dev/null differ diff --git a/ui/static/img/favicon-32x32.png b/ui/static/img/favicon-32x32.png deleted file mode 100644 index c90aad1..0000000 Binary files a/ui/static/img/favicon-32x32.png and /dev/null differ diff --git a/ui/static/manifest.json b/ui/static/manifest.json deleted file mode 100644 index 6582523..0000000 --- a/ui/static/manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "manifest_version": 2, - "short_name": "Gustus", - "name": "Gustus", - "version": "0.1", - "author": "nemucorp", - "start_url": "/", - "icons": [ - { - "src": "img/android-chrome-512x512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "background_color": "#d62a49", - "display": "standalone", - "scope": "/", - "theme_color": "#ffffff", - "description": "Retrouvez facilement toutes vos recettes préférées" -} diff --git a/ui/svelte.config.js b/ui/svelte.config.js deleted file mode 100644 index c080695..0000000 --- a/ui/svelte.config.js +++ /dev/null @@ -1,20 +0,0 @@ -import adapter from '@sveltejs/adapter-static'; -import preprocess from 'svelte-preprocess'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - // Consult https://github.com/sveltejs/svelte-preprocess - // for more information about preprocessors - preprocess: preprocess(), - - kit: { - adapter: adapter({ - fallback: '404.html' - }), - paths: { - // base: '{{.urlbase}}', - } - } -}; - -export default config; diff --git a/ui/tsconfig.json b/ui/tsconfig.json deleted file mode 100644 index b6bfc59..0000000 --- a/ui/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "moduleResolution": "node", - "module": "es2020", - "lib": ["es2020", "DOM"], - "target": "es2020", - /** - svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript - to enforce using \`import type\` instead of \`import\` for Types. - */ - "importsNotUsedAsValues": "error", - "isolatedModules": true, - "resolveJsonModule": true, - /** - To have warnings/errors of the Svelte compiler at the correct position, - enable source maps by default. - */ - "sourceMap": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "baseUrl": ".", - "allowJs": true, - "checkJs": true, - "paths": { - "$lib": ["src/lib"], - "$lib/*": ["src/lib/*"] - } - }, - "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"] -} diff --git a/ui/vite.config.js b/ui/vite.config.js deleted file mode 100644 index 8747050..0000000 --- a/ui/vite.config.js +++ /dev/null @@ -1,8 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; - -/** @type {import('vite').UserConfig} */ -const config = { - plugins: [sveltekit()] -}; - -export default config;