diff --git a/.gitignore b/.gitignore index 009f82e..2a4d07c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +gongs reveil tracks vendor diff --git a/api/gongs.go b/api/gongs.go index 985b777..7410243 100644 --- a/api/gongs.go +++ b/api/gongs.go @@ -1,37 +1,92 @@ package api import ( + "encoding/base64" + "fmt" "net/http" "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) { - + c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "TODO"}) }) - gongsRoutes := router.Group("/gongs/:gid") - gongsRoutes.Use(gongHandler) + 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 _, t := range gongs { + if base64.StdEncoding.EncodeToString(t.Id) == c.Param("tid") { + c.Set("gong", t) + 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.PUT("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("gong")) + 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) { - c.JSON(http.StatusOK, c.MustGet("gong")) + 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) }) } - -func gongHandler(c *gin.Context) { - c.Set("gong", nil) - - c.Next() -} diff --git a/model/gong.go b/model/gong.go new file mode 100644 index 0000000..0e3c81a --- /dev/null +++ b/model/gong.go @@ -0,0 +1,86 @@ +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 []byte `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 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[:63], + Name: strings.TrimSuffix(d.Name(), filepath.Ext(d.Name())), + Path: path, + Enabled: current_gong == pabs, + }) + } + + return nil + }) + + return +} + +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/ui/src/components/GongsList.svelte b/ui/src/components/GongsList.svelte index 6af8935..ecc6115 100644 --- a/ui/src/components/GongsList.svelte +++ b/ui/src/components/GongsList.svelte @@ -5,29 +5,15 @@ import { Button, Icon, + Spinner, } from 'sveltestrap'; - let gongs = [ - { - id: 1, - title: "Coq", - }, - { - id: 2, - title: "Marseillaise", - enabled: true, - }, - { - id: 3, - title: "Trompette de l'armée française", - }, - ]; + import { gongs } from '../stores/gongs'; function chooseGong(gong) { - gongs = gongs.map((g) => { - g.enabled = g.id == gong.id - return g; - }) + gong.setDefault().then(() => { + gongs.refresh(); + }); } export let flush = false; @@ -35,6 +21,14 @@ export { className as class }; let className = ''; + + let refreshInProgress = false; + function refresh_gongs() { + refreshInProgress = true; + gongs.refresh().then(() => { + refreshInProgress = false; + }); + }