Changed the structure of the whole application, should be no changes to functionality.
This commit is contained in:
32
pkg/api/index.go
Normal file
32
pkg/api/index.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"music-server/pkg/helpers"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.SetCorsAndNoCacheHeaders(&w, r)
|
||||
|
||||
if r.URL.Path == "/version" {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
history := server.GetVersionHistory()
|
||||
_ = json.NewEncoder(w).Encode(history)
|
||||
|
||||
} else if r.URL.Path == "/docs" {
|
||||
http.ServeFile(w, r, "./docs/swagger.yaml")
|
||||
|
||||
} else if r.URL.Path == "/" {
|
||||
_, err := fmt.Fprint(w, "Hello, World!!")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
79
pkg/api/music.go
Normal file
79
pkg/api/music.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"music-server/pkg/helpers"
|
||||
"music-server/pkg/models"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func MusicHandler(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.SetCorsAndNoCacheHeaders(&w, r)
|
||||
if r.URL.Path == "/music" && r.Method == http.MethodGet {
|
||||
song := r.URL.Query().Get("song")
|
||||
if song == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, err := fmt.Fprint(w, "song can't be empty")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
s := server.GetSong(song)
|
||||
helpers.SendSong(w, s)
|
||||
}
|
||||
} else if r.URL.Path == "/music/first" && r.Method == http.MethodGet {
|
||||
song := server.GetSoundCheckSong()
|
||||
helpers.SendSong(w, song)
|
||||
|
||||
} else if r.URL.Path == "/music/reset" && r.Method == http.MethodGet {
|
||||
server.Reset()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
} else if r.URL.Path == "/music/rand" && r.Method == http.MethodGet {
|
||||
song := server.GetRandomSong()
|
||||
helpers.SendSong(w, song)
|
||||
|
||||
} else if r.URL.Path == "/music/rand/low" && r.Method == http.MethodGet {
|
||||
chance := server.GetRandomSongLowChance()
|
||||
helpers.SendSong(w, chance)
|
||||
|
||||
} else if r.URL.Path == "/music/info" && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(server.GetSongInfo())
|
||||
|
||||
} else if r.URL.Path == "/music/list" && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(server.GetPlayedSongs())
|
||||
|
||||
} else if r.URL.Path == "/music/next" {
|
||||
song := server.GetNextSong()
|
||||
helpers.SendSong(w, song)
|
||||
|
||||
} else if r.URL.Path == "/music/previous" {
|
||||
song := server.GetPreviousSong()
|
||||
helpers.SendSong(w, song)
|
||||
|
||||
} else if r.URL.Path == "/music/all" && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(server.GetAllGames())
|
||||
|
||||
} else if r.URL.Path == "/music/all/random" && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(server.GetAllGamesRandom())
|
||||
|
||||
} else if r.URL.Path == "/music/played" && r.Method == http.MethodPut {
|
||||
var p models.Played
|
||||
err := json.NewDecoder(r.Body).Decode(&p)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
server.SetPlayed(p.Song)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else if r.URL.Path == "/music/addQue" && r.Method == http.MethodGet {
|
||||
server.AddLatestToQue()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
28
pkg/api/sync.go
Normal file
28
pkg/api/sync.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"music-server/pkg/helpers"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func SyncHandler(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.SetCorsAndNoCacheHeaders(&w, r)
|
||||
if r.URL.Path == "/sync" {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
server.SyncGames()
|
||||
_, err := fmt.Fprint(w, "Games are synced")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
} else if r.URL.Path == "/sync/reset" {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
server.ResetDB()
|
||||
_, err := fmt.Fprint(w, "Games and songs are deleted from the database")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
70
pkg/conf/conf.go
Normal file
70
pkg/conf/conf.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"log"
|
||||
"music-server/pkg/api"
|
||||
"music-server/pkg/db"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func SetupDb() {
|
||||
// Get the value of an Environment Variable
|
||||
host := os.Getenv("DB_HOST")
|
||||
dbPort, dbPortErr := strconv.Atoi(os.Getenv("DB_PORT"))
|
||||
if dbPortErr != nil {
|
||||
dbPort = 0
|
||||
}
|
||||
username := os.Getenv("DB_USERNAME")
|
||||
password := os.Getenv("DB_PASSWORD")
|
||||
dbName := os.Getenv("DB_NAME")
|
||||
|
||||
fmt.Printf("host: %s, dbPort: %v, username: %s, password: %s, dbName: %s\n",
|
||||
host, dbPort, username, password, dbName)
|
||||
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
if dbPort == 0 {
|
||||
dbPort = 5432
|
||||
}
|
||||
if username == "" {
|
||||
username = "sebastian"
|
||||
}
|
||||
if password == "" {
|
||||
password = "950100"
|
||||
}
|
||||
if dbName == "" {
|
||||
dbName = "music_dev_local"
|
||||
}
|
||||
|
||||
db.InitDB(host, dbPort, username, password, dbName)
|
||||
}
|
||||
|
||||
func CloseDb() {
|
||||
defer db.CloseDb()
|
||||
}
|
||||
|
||||
func SetupRestServer() {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/sync", api.SyncHandler)
|
||||
r.HandleFunc("/sync/{func}", api.SyncHandler)
|
||||
r.HandleFunc("/music", api.MusicHandler)
|
||||
r.HandleFunc("/music/{func}", api.MusicHandler)
|
||||
r.HandleFunc("/music/{func}/{func2}", api.MusicHandler)
|
||||
r.HandleFunc("/{func}", api.IndexHandler)
|
||||
http.Handle("/", r)
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
log.Printf("Defaulting to port %s", port)
|
||||
}
|
||||
|
||||
log.Printf("Listening on port %s", port)
|
||||
log.Printf("Open http://localhost:%s in the browser", port)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
|
||||
}
|
||||
61
pkg/db/dbHelper.go
Normal file
61
pkg/db/dbHelper.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"os"
|
||||
)
|
||||
|
||||
var pool *pgxpool.Pool
|
||||
|
||||
func InitDB(host string, port int, user string, password string, dbname string) {
|
||||
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
|
||||
"password=%s dbname=%s sslmode=disable",
|
||||
host, port, user, password, dbname)
|
||||
|
||||
fmt.Println(psqlInfo)
|
||||
|
||||
var err error
|
||||
pool, err = pgxpool.Connect(context.Background(), psqlInfo)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var success string
|
||||
err = pool.QueryRow(context.Background(), "select 'Successfully connected!'").Scan(&success)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(success)
|
||||
}
|
||||
|
||||
func CloseDb() {
|
||||
pool.Close()
|
||||
}
|
||||
|
||||
func Testf() {
|
||||
rows, dbErr := pool.Query(context.Background(), "select game_name from game")
|
||||
if dbErr != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", dbErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
for rows.Next() {
|
||||
var gameName string
|
||||
dbErr = rows.Scan(&gameName)
|
||||
if dbErr != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", dbErr)
|
||||
}
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%v\n", gameName)
|
||||
}
|
||||
}
|
||||
|
||||
func resetGameIdSeq() {
|
||||
_, err := pool.Query(context.Background(), "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
133
pkg/db/game.go
Normal file
133
pkg/db/game.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/jackc/pgtype"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetGameName(gameId int) string {
|
||||
var gameName = ""
|
||||
err := pool.QueryRow(context.Background(),
|
||||
"SELECT game_name FROM game WHERE id = $1", gameId).Scan(&gameName)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
return gameName
|
||||
}
|
||||
|
||||
func SetGameDeletionDate() {
|
||||
_, err := pool.Exec(context.Background(),
|
||||
"UPDATE game SET deleted=$1", time.Now())
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func ClearGames() {
|
||||
_, err := pool.Exec(context.Background(), "DELETE FROM game")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateGameName(id int, name string, path string) {
|
||||
_, err := pool.Exec(context.Background(),
|
||||
"UPDATE game SET game_name=$1, path=$2, last_changed=$3 WHERE id=$4",
|
||||
name, path, time.Now(), id)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveDeletionDate(id int) {
|
||||
_, err := pool.Exec(context.Background(),
|
||||
"UPDATE game SET deleted=null WHERE id=$1", id)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetIdByGameName(name string) int {
|
||||
var gameId = -1
|
||||
err := pool.QueryRow(context.Background(),
|
||||
"SELECT id FROM game WHERE game_name = $1", name).Scan(&gameId)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
return -1
|
||||
}
|
||||
return gameId
|
||||
}
|
||||
|
||||
func InsertGame(name string, path string) int {
|
||||
gameId := -1
|
||||
err := pool.QueryRow(context.Background(),
|
||||
"INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id",
|
||||
name, path, time.Now()).Scan(&gameId)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
resetGameIdSeq()
|
||||
err2 := pool.QueryRow(context.Background(),
|
||||
"INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id",
|
||||
name, path, time.Now()).Scan(&gameId)
|
||||
if err2 != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return gameId
|
||||
}
|
||||
|
||||
func InsertGameWithExistingId(id int, name string, path string) {
|
||||
_, err := pool.Exec(context.Background(),
|
||||
"INSERT INTO game(id, game_name, path, added) VALUES ($1, $2, $3, $4)",
|
||||
id, name, path, time.Now())
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func FindAllGames() []models.GameData {
|
||||
rows, err := pool.Query(context.Background(),
|
||||
"SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs "+
|
||||
"FROM game "+
|
||||
"ORDER BY game_name")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
var gameList []models.GameData
|
||||
for rows.Next() {
|
||||
var id, timesPlayed int
|
||||
var numberOfSongs pgtype.Int4
|
||||
var gameName, path string
|
||||
var added, deleted, lastChanged, lastPlayed pgtype.Timestamp
|
||||
err := rows.Scan(&id, &gameName, &added, &deleted, &lastChanged, &path, ×Played, &lastPlayed, &numberOfSongs)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
gameList = append(gameList, models.GameData{
|
||||
Id: id,
|
||||
GameName: gameName,
|
||||
Added: added.Time,
|
||||
Deleted: deleted.Time,
|
||||
LastChanged: lastChanged.Time,
|
||||
Path: path,
|
||||
TimesPlayed: timesPlayed,
|
||||
LastPlayed: lastPlayed.Time,
|
||||
NumberOfSongs: numberOfSongs.Int,
|
||||
})
|
||||
}
|
||||
return gameList
|
||||
}
|
||||
|
||||
func AddGamePlayed(id int) {
|
||||
_, err := pool.Exec(context.Background(),
|
||||
"UPDATE game SET times_played=times_played+1, last_played=now() WHERE id=$1", id)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
68
pkg/db/song.go
Normal file
68
pkg/db/song.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ClearSongs(gameId int) {
|
||||
if gameId == -1 {
|
||||
_, err := pool.Exec(context.Background(), "DELETE FROM song")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
_, err := pool.Exec(context.Background(), "DELETE FROM song where game_id=$1", gameId)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AddSong(song models.SongData) {
|
||||
_, err := pool.Exec(context.Background(),
|
||||
"INSERT INTO song(game_id, song_name, path) VALUES ($1, $2, $3)",
|
||||
song.GameId, song.SongName, song.Path)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func FindSongsFromGame(id int) []models.SongData {
|
||||
rows, err := pool.Query(context.Background(),
|
||||
"SELECT song_name, path, times_played FROM song WHERE game_id = $1", id)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var songDataList []models.SongData
|
||||
for rows.Next() {
|
||||
var songName string
|
||||
var path string
|
||||
var timesPlayed int
|
||||
|
||||
err := rows.Scan(&songName, &path, ×Played)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
|
||||
songDataList = append(songDataList, models.SongData{
|
||||
GameId: id,
|
||||
SongName: songName,
|
||||
Path: path,
|
||||
TimesPlayed: timesPlayed,
|
||||
})
|
||||
}
|
||||
return songDataList
|
||||
}
|
||||
|
||||
func AddSongPlayed(id int, name string) {
|
||||
_, err := pool.Exec(context.Background(),
|
||||
"UPDATE song SET times_played=times_played+1 WHERE game_id=$1 AND song_name=$2", id, name)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
74
pkg/helpers/helpers.go
Normal file
74
pkg/helpers/helpers.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetCorsAndNoCacheHeaders(w *http.ResponseWriter, r *http.Request) {
|
||||
var etagHeaders = []string{
|
||||
"ETag",
|
||||
"If-Modified-Since",
|
||||
"If-Match",
|
||||
"If-None-Match",
|
||||
"If-Range",
|
||||
"If-Unmodified-Since",
|
||||
}
|
||||
|
||||
(*w).Header().Set("Expires", "Tue, 03 Jul 2001 06:00:00 GMT")
|
||||
(*w).Header().Set("Last-Modified", time.Now().String()+" GMT")
|
||||
(*w).Header().Set("Cache-Control", "no-cache, no-store, private, max-age=0")
|
||||
(*w).Header().Set("Pragma", "no-cache")
|
||||
(*w).Header().Set("X-Accel-Expires", "0")
|
||||
(*w).Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
for _, v := range etagHeaders {
|
||||
if r.Header.Get(v) != "" {
|
||||
r.Header.Del(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendSong(writer http.ResponseWriter, Filename string) {
|
||||
fmt.Println("Client requests: " + Filename)
|
||||
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(Filename)
|
||||
if err != nil {
|
||||
//File not found, send 404
|
||||
http.Error(writer, "Song not found.", 404)
|
||||
return
|
||||
}
|
||||
defer func(openFile *os.File) {
|
||||
_ = openFile.Close()
|
||||
}(openFile) //Close after function return
|
||||
|
||||
//File is found, create and send the correct headers
|
||||
|
||||
//Get the Content-Type of the file
|
||||
//Create a buffer to store the header of the file in
|
||||
FileHeader := make([]byte, 512)
|
||||
//Copy the headers into the FileHeader buffer
|
||||
_, _ = openFile.Read(FileHeader)
|
||||
//Get content type of file
|
||||
//FileContentType := http.DetectContentType(FileHeader)
|
||||
|
||||
//Get the file size
|
||||
FileStat, _ := openFile.Stat() //Get info from file
|
||||
FileSize := strconv.FormatInt(FileStat.Size(), 10) //Get file size as a string
|
||||
|
||||
//Send the headers
|
||||
//writer.Header().Set("Content-Disposition", "attachment; filename="+Filename)
|
||||
writer.Header().Set("Content-Type", "audio/mpeg")
|
||||
writer.Header().Set("Content-Length", FileSize)
|
||||
|
||||
//Send the file
|
||||
//We read 512 bytes from the file already, so we reset the offset back to 0
|
||||
_, _ = openFile.Seek(0, 0)
|
||||
_, _ = io.Copy(writer, openFile) //'Copy' the file to the client
|
||||
return
|
||||
}
|
||||
41
pkg/models/models.go
Normal file
41
pkg/models/models.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Played struct {
|
||||
Song int
|
||||
}
|
||||
|
||||
type VersionData struct {
|
||||
Version string `json:"version"`
|
||||
Changelog string `json:"changelog"`
|
||||
History []VersionData `json:"history"`
|
||||
}
|
||||
|
||||
type SongInfo struct {
|
||||
Game string `json:"Game"`
|
||||
GamePlayed int `json:"GamePlayed"`
|
||||
Song string `json:"Song"`
|
||||
SongPlayed int `json:"SongPlayed"`
|
||||
CurrentlyPlaying bool `json:"CurrentlyPlaying"`
|
||||
SongNo int `json:"SongNo"`
|
||||
}
|
||||
|
||||
type GameData struct {
|
||||
Id int
|
||||
GameName string
|
||||
Added time.Time
|
||||
Deleted time.Time
|
||||
LastChanged time.Time
|
||||
Path string
|
||||
TimesPlayed int
|
||||
LastPlayed time.Time
|
||||
NumberOfSongs int32
|
||||
}
|
||||
|
||||
type SongData struct {
|
||||
GameId int
|
||||
SongName string
|
||||
Path string
|
||||
TimesPlayed int
|
||||
}
|
||||
76
pkg/server/index.go
Normal file
76
pkg/server/index.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"music-server/pkg/db"
|
||||
"music-server/pkg/models"
|
||||
)
|
||||
|
||||
func GetVersionHistory() models.VersionData {
|
||||
db.Testf()
|
||||
|
||||
data := models.VersionData{Version: "2.2.0",
|
||||
Changelog: "Changed the structure of the whole application, should be no changes to functionality.",
|
||||
History: []models.VersionData{
|
||||
{
|
||||
Version: "2.1.4",
|
||||
Changelog: "Game list should now be sorted, a new endpoint with the game list in random order have been added.",
|
||||
},
|
||||
{
|
||||
Version: "2.1.3",
|
||||
Changelog: "Added a check to see if song exists before returning it, if not a new song will be picked up.",
|
||||
},
|
||||
{
|
||||
Version: "2.1.2",
|
||||
Changelog: "Added test server to swagger file.",
|
||||
},
|
||||
{
|
||||
Version: "2.1.1",
|
||||
Changelog: "Fixed bug where wrong song was showed as currently played.",
|
||||
},
|
||||
{
|
||||
Version: "2.1.0",
|
||||
Changelog: "Added /addQue to add the last received song to the songQue. " +
|
||||
"Changed /rand and /rand/low to not add song to the que. " +
|
||||
"Changed /next to not call /rand when the end of the que is reached, instead the last song in the que will be resent.",
|
||||
},
|
||||
{
|
||||
Version: "2.0.3",
|
||||
Changelog: "Another small change that should fix the caching problem.",
|
||||
},
|
||||
{
|
||||
Version: "2.0.2",
|
||||
Changelog: "Hopefully fixed the caching problem with random.",
|
||||
},
|
||||
{
|
||||
Version: "2.0.1",
|
||||
Changelog: "Fixed CORS",
|
||||
},
|
||||
{
|
||||
Version: "2.0.0",
|
||||
Changelog: "Rebuilt the application in Go.",
|
||||
},
|
||||
{
|
||||
Version: "1.2.0",
|
||||
Changelog: "Made the /sync endpoint async. " +
|
||||
"Fixed bug where the game list wasn't reloaded when using /reset. " +
|
||||
"Fixed bug where the songNo showed in the list didn't match what should be sent.",
|
||||
},
|
||||
{
|
||||
Version: "1.1.0",
|
||||
Changelog: "Added sync endpoint, don't really trust it to 100%, would say beta. " +
|
||||
"Fixed bug with /next after /previous. Added /reset endpoint. " +
|
||||
"Added some info more to /info and /list.",
|
||||
},
|
||||
{
|
||||
Version: "1.0.0",
|
||||
Changelog: "Added swagger documentation. Created version 1.0.",
|
||||
},
|
||||
{
|
||||
Version: "0.5.5",
|
||||
Changelog: "Added increase played endpoint.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
233
pkg/server/music.go
Normal file
233
pkg/server/music.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"music-server/pkg/db"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var currentSong = -1
|
||||
var games []models.GameData
|
||||
var songQue []models.SongData
|
||||
var lastFetched models.SongData
|
||||
|
||||
func GetSoundCheckSong() string {
|
||||
Reset()
|
||||
|
||||
files, err := ioutil.ReadDir("songs")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fileInfo := files[rand.Intn(len(files))]
|
||||
return "songs/" + fileInfo.Name()
|
||||
}
|
||||
|
||||
func Reset() {
|
||||
songQue = nil
|
||||
currentSong = -1
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
func AddLatestToQue() {
|
||||
if lastFetched.Path != "" {
|
||||
currentSong = len(songQue)
|
||||
songQue = append(songQue, lastFetched)
|
||||
lastFetched = models.SongData{}
|
||||
}
|
||||
}
|
||||
|
||||
func GetRandomSong() string {
|
||||
if games == nil || len(games) == 0 {
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
song := getSongFromList(games)
|
||||
|
||||
lastFetched = song
|
||||
return song.Path
|
||||
}
|
||||
|
||||
func GetRandomSongLowChance() string {
|
||||
gameList := db.FindAllGames()
|
||||
|
||||
var listOfGames []models.GameData
|
||||
|
||||
var averagePlayed = getAveragePlayed(gameList)
|
||||
|
||||
for _, data := range gameList {
|
||||
var timesToAdd = averagePlayed - data.TimesPlayed
|
||||
if timesToAdd <= 0 {
|
||||
listOfGames = append(listOfGames, data)
|
||||
} else {
|
||||
for i := 0; i < timesToAdd; i++ {
|
||||
listOfGames = append(listOfGames, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
song := getSongFromList(listOfGames)
|
||||
|
||||
lastFetched = song
|
||||
return song.Path
|
||||
|
||||
}
|
||||
|
||||
func GetSongInfo() models.SongInfo {
|
||||
if songQue == nil {
|
||||
return models.SongInfo{}
|
||||
}
|
||||
var currentSongData = songQue[currentSong]
|
||||
|
||||
currentGameData := getCurrentGame(currentSongData)
|
||||
|
||||
return models.SongInfo{
|
||||
Game: currentGameData.GameName,
|
||||
GamePlayed: currentGameData.TimesPlayed,
|
||||
Song: currentSongData.SongName,
|
||||
SongPlayed: currentSongData.TimesPlayed,
|
||||
CurrentlyPlaying: true,
|
||||
SongNo: currentSong,
|
||||
}
|
||||
}
|
||||
|
||||
func GetPlayedSongs() []models.SongInfo {
|
||||
var songList []models.SongInfo
|
||||
|
||||
for i, song := range songQue {
|
||||
gameData := getCurrentGame(song)
|
||||
songList = append(songList, models.SongInfo{
|
||||
Game: gameData.GameName,
|
||||
GamePlayed: gameData.TimesPlayed,
|
||||
Song: song.SongName,
|
||||
SongPlayed: song.TimesPlayed,
|
||||
CurrentlyPlaying: i == currentSong,
|
||||
SongNo: i,
|
||||
})
|
||||
}
|
||||
return songList
|
||||
}
|
||||
|
||||
func GetSong(song string) string {
|
||||
currentSong, _ = strconv.Atoi(song)
|
||||
if currentSong >= len(songQue) {
|
||||
currentSong = len(songQue) - 1
|
||||
} else if currentSong < 0 {
|
||||
currentSong = 0
|
||||
}
|
||||
var songData = songQue[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
|
||||
func GetAllGames() []string {
|
||||
if games == nil || len(games) == 0 {
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
var jsonArray []string
|
||||
for _, game := range games {
|
||||
jsonArray = append(jsonArray, game.GameName)
|
||||
}
|
||||
return jsonArray
|
||||
}
|
||||
|
||||
func GetAllGamesRandom() []string {
|
||||
if games == nil || len(games) == 0 {
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
var jsonArray []string
|
||||
for _, game := range games {
|
||||
jsonArray = append(jsonArray, game.GameName)
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
rand.Shuffle(len(jsonArray), func(i, j int) { jsonArray[i], jsonArray[j] = jsonArray[j], jsonArray[i] })
|
||||
return jsonArray
|
||||
}
|
||||
|
||||
func SetPlayed(songNumber int) {
|
||||
if songQue == nil || len(songQue) == 0 || songNumber >= len(songQue) {
|
||||
return
|
||||
}
|
||||
var songData = songQue[songNumber]
|
||||
db.AddGamePlayed(songData.GameId)
|
||||
db.AddSongPlayed(songData.GameId, songData.SongName)
|
||||
}
|
||||
|
||||
func GetNextSong() string {
|
||||
if songQue == nil {
|
||||
return ""
|
||||
}
|
||||
if currentSong == len(songQue)-1 || currentSong == -1 {
|
||||
var songData = songQue[currentSong]
|
||||
return songData.Path
|
||||
} else {
|
||||
currentSong = currentSong + 1
|
||||
var songData = songQue[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
}
|
||||
|
||||
func GetPreviousSong() string {
|
||||
if songQue == nil {
|
||||
return ""
|
||||
}
|
||||
if currentSong == -1 || currentSong == 0 {
|
||||
var songData = songQue[0]
|
||||
return songData.Path
|
||||
} else {
|
||||
currentSong = currentSong - 1
|
||||
var songData = songQue[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
}
|
||||
|
||||
func getSongFromList(games []models.GameData) models.SongData {
|
||||
songFound := false
|
||||
var song models.SongData
|
||||
for !songFound {
|
||||
game := getRandomGame(games)
|
||||
songs := db.FindSongsFromGame(game.Id)
|
||||
song = songs[rand.Intn(len(songs))]
|
||||
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(song.Path)
|
||||
if err != nil {
|
||||
//File not found
|
||||
log.Fatal("Song not found, maybe delete song and/or game" + song.SongName + " songPath: " + song.Path)
|
||||
} else {
|
||||
songFound = true
|
||||
}
|
||||
|
||||
err = openFile.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return song
|
||||
}
|
||||
|
||||
func getCurrentGame(currentSongData models.SongData) models.GameData {
|
||||
for _, game := range games {
|
||||
if game.Id == currentSongData.GameId {
|
||||
return game
|
||||
}
|
||||
}
|
||||
return models.GameData{}
|
||||
}
|
||||
|
||||
func getAveragePlayed(gameList []models.GameData) int {
|
||||
var sum int
|
||||
for _, data := range gameList {
|
||||
sum += data.TimesPlayed
|
||||
}
|
||||
return sum / len(gameList)
|
||||
}
|
||||
|
||||
func getRandomGame(listOfGames []models.GameData) models.GameData {
|
||||
return listOfGames[rand.Intn(len(listOfGames))]
|
||||
}
|
||||
135
pkg/server/sync.go
Normal file
135
pkg/server/sync.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"music-server/pkg/db"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SyncGames() {
|
||||
host := os.Getenv("DB_HOST")
|
||||
var dir string
|
||||
if host != "" {
|
||||
dir = "/sorted/"
|
||||
} else {
|
||||
dir = "/Users/sebastian/Resilio Sync/Sorterat_test/"
|
||||
}
|
||||
fmt.Printf("dir: %s\n", dir)
|
||||
foldersToSkip := []string{".sync"}
|
||||
fmt.Println(foldersToSkip)
|
||||
db.SetGameDeletionDate()
|
||||
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() && !contains(foldersToSkip, file.Name()) {
|
||||
fmt.Println(file.Name())
|
||||
path := dir + file.Name() + "/"
|
||||
fmt.Println(path)
|
||||
|
||||
innerFiles, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
id := -1
|
||||
for _, song := range innerFiles {
|
||||
id = getIdFromFile(song)
|
||||
if id != -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if id == -1 {
|
||||
addNewGame(file.Name(), path)
|
||||
} else {
|
||||
checkIfChanged(id, file.Name(), path)
|
||||
checkSongs(path, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ResetDB() {
|
||||
db.ClearSongs(-1)
|
||||
db.ClearGames()
|
||||
}
|
||||
|
||||
func getIdFromFile(file os.FileInfo) int {
|
||||
name := file.Name()
|
||||
if !file.IsDir() && strings.HasSuffix(name, ".id") {
|
||||
name = strings.Replace(name, ".id", "", 1)
|
||||
name = strings.Replace(name, ".", "", 1)
|
||||
i, _ := strconv.Atoi(name)
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func checkIfChanged(id int, name string, path string) {
|
||||
fmt.Printf("Id from file: %v\n", id)
|
||||
nameFromDb := db.GetGameName(id)
|
||||
fmt.Printf("Name from file: %v\n", name)
|
||||
fmt.Printf("Name from DB: %v\n", nameFromDb)
|
||||
if nameFromDb == "" {
|
||||
fmt.Println("Not in db")
|
||||
db.InsertGameWithExistingId(id, name, path)
|
||||
fmt.Println("Added to db")
|
||||
} else if name != nameFromDb {
|
||||
fmt.Println("Diff name")
|
||||
db.UpdateGameName(id, name, path)
|
||||
}
|
||||
db.RemoveDeletionDate(id)
|
||||
}
|
||||
|
||||
func addNewGame(name string, path string) {
|
||||
newId := db.GetIdByGameName(name)
|
||||
if newId == -1 {
|
||||
newId = db.InsertGame(name, path)
|
||||
}
|
||||
|
||||
fmt.Printf("newId = %v", newId)
|
||||
fileName := path + "/." + strconv.Itoa(newId) + ".id"
|
||||
fmt.Printf("fileName = %v", fileName)
|
||||
|
||||
err := ioutil.WriteFile(fileName, nil, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
checkSongs(path, newId)
|
||||
}
|
||||
|
||||
func checkSongs(gameDir string, gameId int) {
|
||||
songs := make([]models.SongData, 0)
|
||||
findSongsFromGame := db.FindSongsFromGame(gameId)
|
||||
|
||||
files, err := ioutil.ReadDir(gameDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, entry := range files {
|
||||
path := gameDir + entry.Name()
|
||||
songName := entry.Name()
|
||||
if !entry.IsDir() && !strings.HasSuffix(songName, ".id") {
|
||||
songs = append(songs, models.SongData{GameId: gameId, SongName: songName, Path: path})
|
||||
}
|
||||
}
|
||||
if len(songs) != len(findSongsFromGame) {
|
||||
db.ClearSongs(gameId)
|
||||
for _, song := range songs {
|
||||
db.AddSong(song)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s []string, searchTerm string) bool {
|
||||
i := sort.SearchStrings(s, searchTerm)
|
||||
return i < len(s) && s[i] == searchTerm
|
||||
}
|
||||
Reference in New Issue
Block a user