--- /dev/null
+package main
+
+import (
+ "code.octet-stream.net/broadcaster/internal/protocol"
+ "crypto/sha256"
+ "encoding/hex"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+)
+
+type FilesMachine struct {
+ specs []protocol.FileSpec
+ cachePath string
+ missing []string
+}
+
+func NewFilesMachine(cachePath string) FilesMachine {
+ if err := os.MkdirAll(cachePath, 0750); err != nil {
+ log.Fatal(err)
+ }
+ return FilesMachine{
+ cachePath: cachePath,
+ }
+}
+
+func (m *FilesMachine) UpdateSpecs(specs []protocol.FileSpec) {
+ m.specs = specs
+ m.RefreshMissing()
+}
+
+func (m *FilesMachine) RefreshMissing() {
+ // Delete any files in the cache dir who are not in the spec
+ entries, err := os.ReadDir(m.cachePath)
+ if err != nil {
+ log.Fatal(err)
+ }
+ okay := make([]string, 0)
+ for _, file := range entries {
+ hash := ""
+ for _, spec := range m.specs {
+ if file.Name() == spec.Name {
+ hash = spec.Hash
+ break
+ }
+ }
+ // if we have an extraneous file, delete it
+ if hash == "" {
+ log.Println("Deleting extraneous cached audio file:", file.Name())
+ os.Remove(filepath.Join(m.cachePath, file.Name()))
+ continue
+ }
+ // if the hash isn't right, delete it
+ f, err := os.Open(filepath.Join(m.cachePath, file.Name()))
+ if err != nil {
+ log.Fatal(err)
+ }
+ hasher := sha256.New()
+ io.Copy(hasher, f)
+ if hex.EncodeToString(hasher.Sum(nil)) != hash {
+ log.Println("Deleting cached audio file with incorrect hash:", file.Name())
+ os.Remove(filepath.Join(m.cachePath, file.Name()))
+ } else {
+ okay = append(okay, file.Name())
+ }
+ }
+ m.missing = nil
+ for _, spec := range m.specs {
+ missing := true
+ for _, file := range okay {
+ if spec.Name == file {
+ missing = false
+ }
+ }
+ if missing {
+ m.missing = append(m.missing, spec.Name)
+ }
+ }
+ if len(m.missing) > 1 {
+ log.Println(len(m.missing), "missing files")
+ } else if len(m.missing) == 1 {
+ log.Println("1 missing file")
+ } else {
+ log.Println("All files are in sync with server")
+ }
+ statusCollector.FilesInSync <- len(m.missing) == 0
+}
+
+func (m *FilesMachine) IsCacheComplete() bool {
+ return len(m.missing) == 0
+}
+
+func (m *FilesMachine) NextFile() string {
+ next, remainder := m.missing[0], m.missing[1:]
+ m.missing = remainder
+ return next
+}
+
+func (m *FilesMachine) DownloadSingle(filename string, downloadResult chan<- error) {
+ log.Println("Downloading", filename)
+ out, err := os.Create(filepath.Join(m.cachePath, filename))
+ if err != nil {
+ downloadResult <- err
+ return
+ }
+ defer out.Close()
+ resp, err := http.Get(config.ServerURL + "/file-downloads/" + filename)
+ if err != nil {
+ downloadResult <- err
+ return
+ }
+ defer resp.Body.Close()
+ _, err = io.Copy(out, resp.Body)
+ downloadResult <- err
+}