Skip to content
Snippets Groups Projects
icecast.go 2.44 KiB
package node

import (
	"encoding/xml"
	"errors"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"time"

	"git.autistici.org/ale/radioai"
)

var (
	statusPageUrl = "http://localhost:8000/status-radioai.xsl"
)

type IcecastStatus struct {
	XMLName xml.Name                     `xml:"status",json:"-"`
	Mounts  []radioai.IcecastMountStatus `xml:"mount"`
	Up      bool
}

type IcecastController struct {
	PublicIp   string
	ConfigFile string
	InitScript string
	config     *icecastConfig
	status     *IcecastStatus
	stop       chan bool
}

func NewIcecastController(publicIp string, stop chan bool) *IcecastController {
	return &IcecastController{
		PublicIp:   publicIp,
		ConfigFile: "/etc/icecast2/icecast.xml",
		InitScript: "/etc/init.d/icecast2",
		config:     newIcecastConfig(publicIp),
		status:     &IcecastStatus{},
		stop:       make(chan bool, 1),
	}
}

func (ic *IcecastController) reload() error {
	err := exec.Command(ic.InitScript, "reload").Run()
	if err != nil {
		err = exec.Command(ic.InitScript, "start").Run()
	}
	return err
}

// Update reloads the Icecast daemon with a new configuration.
func (ic *IcecastController) Update(conf *ClusterConfig, isMaster bool, masterAddr string) error {
	if !isMaster && masterAddr == "" {
		return errors.New("unknown system state")
	}

	tmpf := ic.ConfigFile + ".tmp"
	defer os.Remove(tmpf)

	ic.config.Update(conf, isMaster, masterAddr)
	if err := ic.config.EncodeToFile(tmpf); err != nil {
		return err
	}

	if err := os.Rename(tmpf, ic.ConfigFile); err != nil {
		return err
	}

	return ic.reload()
}

func (ic *IcecastController) GetStatus() *IcecastStatus {
	return ic.status
}

func (ic *IcecastController) statusUpdater() {
	t := time.NewTicker(3 * time.Second)
	downStatus := &IcecastStatus{}
	for {
		select {
		case <-t.C:
			if status, err := ic.fetchStatus(); err == nil {
				ic.status = status
			} else {
				log.Printf("bad status from iceast: %v", err)
				ic.status = downStatus
			}
		case <-ic.stop:
			return
		}
	}
}

func (ic *IcecastController) fetchStatus() (*IcecastStatus, error) {
	resp, err := http.Get(statusPageUrl)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return ic.parseStatusPage(resp.Body)
}

func (ic *IcecastController) parseStatusPage(input io.Reader) (*IcecastStatus, error) {
	var status IcecastStatus
	if err := xml.NewDecoder(input).Decode(&status); err != nil {
		return nil, err
	}
	status.Up = true
	return &status, nil
}

func (ic *IcecastController) Run() {
	ic.statusUpdater()
}