Refactor federation + can sync track between instances
This commit is contained in:
parent
e99efdb43f
commit
c8c6282216
@ -62,7 +62,7 @@ func declareAlarmRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, srv := range settings.Federation {
|
for k, srv := range settings.Federation {
|
||||||
err = player.FederatedStop(srv)
|
err = srv.WakeStop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.nemunai.re/nemunaire/reveil/config"
|
"git.nemunai.re/nemunaire/reveil/config"
|
||||||
|
"git.nemunai.re/nemunaire/reveil/model"
|
||||||
"git.nemunai.re/nemunaire/reveil/player"
|
"git.nemunai.re/nemunaire/reveil/player"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,4 +50,127 @@ func declareFederationRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
c.JSON(http.StatusOK, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.GET("/federation", 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.Federation)
|
||||||
|
})
|
||||||
|
|
||||||
|
federationsRoutes := router.Group("/federation/:fid")
|
||||||
|
federationsRoutes.Use(func(c *gin.Context) {
|
||||||
|
settings, err := reveil.ReadSettings(cfg.SettingsFile)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, ok := settings.Federation[string(c.Param("fid"))]
|
||||||
|
if !ok {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Action not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("federation", &f)
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
federationsRoutes.GET("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, c.MustGet("federation"))
|
||||||
|
})
|
||||||
|
federationsRoutes.POST("sync", func(c *gin.Context) {
|
||||||
|
srv := c.MustGet("federation").(*reveil.FederationServer)
|
||||||
|
|
||||||
|
// Retrieve music list on remote
|
||||||
|
remoteMusics, err := srv.GetMusics()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to retrieve remote tracks lists: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve local music list
|
||||||
|
localMusics, err := reveil.LoadTracks(cfg)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to retrieve local tracks: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute diff
|
||||||
|
var newMusics []reveil.Track
|
||||||
|
var oldMusics []reveil.Track
|
||||||
|
var musicsToEnable []reveil.Track
|
||||||
|
|
||||||
|
for _, rTrack := range remoteMusics {
|
||||||
|
found := false
|
||||||
|
for _, lTrack := range localMusics {
|
||||||
|
if bytes.Compare(lTrack.Id, rTrack.Id) == 0 || lTrack.Name == rTrack.Name {
|
||||||
|
if lTrack.Enabled != rTrack.Enabled {
|
||||||
|
if lTrack.Enabled {
|
||||||
|
musicsToEnable = append(musicsToEnable, rTrack)
|
||||||
|
} else {
|
||||||
|
oldMusics = append(oldMusics, *lTrack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found && rTrack.Enabled {
|
||||||
|
oldMusics = append(oldMusics, rTrack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lTrack := range localMusics {
|
||||||
|
found := false
|
||||||
|
for _, rTrack := range remoteMusics {
|
||||||
|
if bytes.Compare(lTrack.Id, rTrack.Id) == 0 || lTrack.Name == rTrack.Name {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found && lTrack.Enabled {
|
||||||
|
newMusics = append(newMusics, *lTrack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable unexistant musics on local
|
||||||
|
for _, t := range oldMusics {
|
||||||
|
t.Enabled = false
|
||||||
|
err = srv.UpdateTrack(&t)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs when disabling remote tracks (unexistant on local): %s: %s", t.Id.ToString(), err.Error())})
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable existant musics on remote
|
||||||
|
for _, t := range musicsToEnable {
|
||||||
|
t.Enabled = true
|
||||||
|
err = srv.UpdateTrack(&t)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send new musics
|
||||||
|
for _, t := range newMusics {
|
||||||
|
err = srv.SendTrack(&t)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
137
model/federation.go
Normal file
137
model/federation.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package reveil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FederationServer struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Delay uint `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) WakeUp(seed int64) error {
|
||||||
|
req := map[string]interface{}{"seed": seed}
|
||||||
|
req_enc, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.Post(srv.URL+"/api/federation/wakeup", "application/json", bytes.NewBuffer(req_enc))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) WakeStop() error {
|
||||||
|
res, err := http.Post(srv.URL+"/api/federation/wakeok", "application/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) GetMusics() ([]Track, error) {
|
||||||
|
res, err := http.Get(srv.URL + "/api/tracks")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var tracks []Track
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&tracks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) UpdateTrack(t *Track) error {
|
||||||
|
req_enc, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", srv.URL+"/api/tracks/"+t.Id.ToString(), bytes.NewBuffer(req_enc))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode == http.StatusOK {
|
||||||
|
var track Track
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&track)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var errmsg map[string]string
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&errmsg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%s", errmsg["errmsg"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) SendTrack(track *Track) error {
|
||||||
|
// Retrieve file
|
||||||
|
fd, err := track.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := multipart.NewWriter(&b)
|
||||||
|
|
||||||
|
var fw io.Writer
|
||||||
|
// Add an image file
|
||||||
|
if fw, err = w.CreateFormFile("trackfile", fd.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(fw, fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
//
|
||||||
|
req, err := http.NewRequest("POST", srv.URL+"/api/tracks", &b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("bad status: %s", res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -6,20 +6,15 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FederationSettings struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Delay uint `json:"delay"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settings represents the settings panel.
|
// Settings represents the settings panel.
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
GongInterval time.Duration `json:"gong_interval"`
|
GongInterval time.Duration `json:"gong_interval"`
|
||||||
WeatherDelay time.Duration `json:"weather_delay"`
|
WeatherDelay time.Duration `json:"weather_delay"`
|
||||||
WeatherAction string `json:"weather_action"`
|
WeatherAction string `json:"weather_action"`
|
||||||
MaxRunTime time.Duration `json:"max_run_time"`
|
MaxRunTime time.Duration `json:"max_run_time"`
|
||||||
MaxVolume uint16 `json:"max_volume"`
|
MaxVolume uint16 `json:"max_volume"`
|
||||||
Federation map[string]FederationSettings `json:"federation"`
|
Federation map[string]FederationServer `json:"federation"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExistsSettings checks if the settings file can by found at the given path.
|
// ExistsSettings checks if the settings file can by found at the given path.
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
package player
|
package player
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.nemunai.re/nemunaire/reveil/model"
|
"git.nemunai.re/nemunaire/reveil/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FederatedWakeUp(k string, srv reveil.FederationSettings, seed int64) {
|
func FederatedWakeUp(k string, srv reveil.FederationServer, seed int64) {
|
||||||
if srv.Delay == 0 {
|
if srv.Delay == 0 {
|
||||||
err := federatedWakeUp(srv, seed)
|
err := srv.WakeUp(seed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||||
} else {
|
} else {
|
||||||
@ -21,7 +18,7 @@ func FederatedWakeUp(k string, srv reveil.FederationSettings, seed int64) {
|
|||||||
} else {
|
} else {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Duration(srv.Delay) * time.Millisecond)
|
time.Sleep(time.Duration(srv.Delay) * time.Millisecond)
|
||||||
err := federatedWakeUp(srv, seed)
|
err := srv.WakeUp(seed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||||
} else {
|
} else {
|
||||||
@ -30,29 +27,3 @@ func FederatedWakeUp(k string, srv reveil.FederationSettings, seed int64) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func federatedWakeUp(srv reveil.FederationSettings, seed int64) error {
|
|
||||||
req := map[string]interface{}{"seed": seed}
|
|
||||||
req_enc, err := json.Marshal(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := http.Post(srv.URL+"/api/federation/wakeup", "application/json", bytes.NewBuffer(req_enc))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FederatedStop(srv reveil.FederationSettings) error {
|
|
||||||
res, err := http.Post(srv.URL+"/api/federation/wakeok", "application/json", nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
InputGroup,
|
InputGroup,
|
||||||
InputGroupText,
|
InputGroupText,
|
||||||
Row,
|
Row,
|
||||||
|
Spinner,
|
||||||
} from '@sveltestrap/sveltestrap';
|
} from '@sveltestrap/sveltestrap';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
@ -27,6 +28,21 @@
|
|||||||
delete value[bak];
|
delete value[bak];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const syncInProgress = { };
|
||||||
|
async function syncMusic(srv) {
|
||||||
|
syncInProgress[srv] = true;
|
||||||
|
|
||||||
|
const res = await fetch(`api/federation/${srv}/sync`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Accept': 'application/json'}
|
||||||
|
});
|
||||||
|
if (res.status != 200) {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncInProgress[srv] = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
@ -57,15 +73,34 @@
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<InputGroup>
|
<Row>
|
||||||
<Input
|
<Col>
|
||||||
type="number"
|
<InputGroup>
|
||||||
placeholder="60"
|
<Input
|
||||||
bind:value={value[key].delay}
|
type="number"
|
||||||
on:change={() => dispatch("input")}
|
placeholder="60"
|
||||||
/>
|
bind:value={value[key].delay}
|
||||||
<InputGroupText>ms</InputGroupText>
|
on:change={() => dispatch("input")}
|
||||||
</InputGroup>
|
/>
|
||||||
|
<InputGroupText>ms</InputGroupText>
|
||||||
|
</InputGroup>
|
||||||
|
</Col>
|
||||||
|
<Col xs="auto">
|
||||||
|
<Button
|
||||||
|
color="info"
|
||||||
|
disabled={syncInProgress[key]}
|
||||||
|
title="Synchroniser les musiques"
|
||||||
|
type="button"
|
||||||
|
on:click={() => syncMusic(key)}
|
||||||
|
>
|
||||||
|
{#if syncInProgress[key]}
|
||||||
|
<Spinner size="sm" />
|
||||||
|
{:else}
|
||||||
|
<Icon name="music-note-list" />
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/each}
|
{/each}
|
||||||
|
Loading…
Reference in New Issue
Block a user