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 {
|
||||
err = player.FederatedStop(srv)
|
||||
err = srv.WakeStop()
|
||||
if err != nil {
|
||||
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||
} else {
|
||||
|
@ -1,12 +1,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.nemunai.re/nemunaire/reveil/config"
|
||||
"git.nemunai.re/nemunaire/reveil/model"
|
||||
"git.nemunai.re/nemunaire/reveil/player"
|
||||
)
|
||||
|
||||
@ -47,4 +50,127 @@ func declareFederationRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
||||
|
||||
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,11 +6,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type FederationSettings struct {
|
||||
URL string `json:"url"`
|
||||
Delay uint `json:"delay"`
|
||||
}
|
||||
|
||||
// Settings represents the settings panel.
|
||||
type Settings struct {
|
||||
Language string `json:"language"`
|
||||
@ -19,7 +14,7 @@ type Settings struct {
|
||||
WeatherAction string `json:"weather_action"`
|
||||
MaxRunTime time.Duration `json:"max_run_time"`
|
||||
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.
|
||||
|
@ -1,18 +1,15 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
err := federatedWakeUp(srv, seed)
|
||||
err := srv.WakeUp(seed)
|
||||
if err != nil {
|
||||
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||
} else {
|
||||
@ -21,7 +18,7 @@ func FederatedWakeUp(k string, srv reveil.FederationSettings, seed int64) {
|
||||
} else {
|
||||
go func() {
|
||||
time.Sleep(time.Duration(srv.Delay) * time.Millisecond)
|
||||
err := federatedWakeUp(srv, seed)
|
||||
err := srv.WakeUp(seed)
|
||||
if err != nil {
|
||||
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||
} 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,
|
||||
InputGroupText,
|
||||
Row,
|
||||
Spinner,
|
||||
} from '@sveltestrap/sveltestrap';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@ -27,6 +28,21 @@
|
||||
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>
|
||||
|
||||
{#if value}
|
||||
@ -56,6 +72,8 @@
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col>
|
||||
<Row>
|
||||
<Col>
|
||||
<InputGroup>
|
||||
<Input
|
||||
@ -67,6 +85,23 @@
|
||||
<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>
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
|
Loading…
Reference in New Issue
Block a user