diff --git a/.gitignore b/.gitignore index 11ac438..009f82e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ reveil +tracks vendor ui/build ui/node_modules \ No newline at end of file diff --git a/api/tracks.go b/api/tracks.go index be54d2e..9a2bc6a 100644 --- a/api/tracks.go +++ b/api/tracks.go @@ -1,37 +1,94 @@ 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 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) { - + c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "TODO"}) }) tracksRoutes := router.Group("/tracks/:tid") - tracksRoutes.Use(trackHandler) + 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 base64.StdEncoding.EncodeToString(t.Id) == 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.PUT("", func(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("track")) + 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) { - c.JSON(http.StatusOK, c.MustGet("track")) + 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) }) } - -func trackHandler(c *gin.Context) { - c.Set("track", nil) - - c.Next() -} diff --git a/model/track.go b/model/track.go new file mode 100644 index 0000000..8c6e019 --- /dev/null +++ b/model/track.go @@ -0,0 +1,88 @@ +package reveil + +import ( + "crypto/sha512" + "io/fs" + "os" + "path/filepath" + "strings" + "time" + + "git.nemunai.re/nemunaire/reveil/config" +) + +type Track struct { + Id []byte `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + Enabled bool `json:"enabled"` +} + +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[:63], + Name: strings.TrimSuffix(d.Name(), filepath.Ext(d.Name())), + Path: path, + Enabled: len(strings.Split(path, "/")) == 2, + }) + } + + return nil + }) + + return +} + +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 + 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 + 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/ui/src/components/TrackList.svelte b/ui/src/components/TrackList.svelte index 1c557d2..f6b8ee5 100644 --- a/ui/src/components/TrackList.svelte +++ b/ui/src/components/TrackList.svelte @@ -5,33 +5,24 @@ import { Button, Icon, + Spinner, } from 'sveltestrap'; - let musiks = [ - { - id: 1, - title: "Hall Of Fame", - artist: "The Script", - enabled: true, - }, - { - id: 2, - title: "Poker face", - artist: "Lady Gaga", - }, - { - id: 3, - title: "Puisque tu m'aimes encore", - artist: "Céline Dion", - enabled: true, - }, - ]; + import { tracks } from '../stores/tracks'; export let flush = false; export let edit = false; export { className as class }; let className = ''; + + let refreshInProgress = false; + function refresh_tracks() { + refreshInProgress = true; + tracks.refresh().then(() => { + refreshInProgress = false; + }); + }