Changed the structure of the whole application, should be no changes to functionality.

This commit is contained in:
2021-12-05 11:18:48 +01:00
parent 0a73134381
commit e1de6f0f76
28 changed files with 935 additions and 1105 deletions

19
.idea/MusicServer.iml generated
View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true">
<buildTags>
<option name="customFlags">
<array>
<option value="appengine" />
</array>
</option>
</buildTags>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.idea/dataSources" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

11
.idea/dataSources.xml generated
View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="music_dev_local@localhost" uuid="e63caf71-2b1a-400d-9cc4-4b87b1f9b807">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/music_dev_local</jdbc-url>
</data-source>
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/MusicServer.iml" filepath="$PROJECT_DIR$/.idea/MusicServer.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

192
.idea/workspace.xml generated
View File

@@ -1,192 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BranchesTreeState">
<expand>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
</path>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
</path>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="GROUP_NODE:origin" type="e8cecc67:BranchNodeDescriptor" />
</path>
</expand>
<select />
</component>
<component name="ChangeListManager">
<list default="true" id="d639f8a9-4b6d-4957-9ba4-8361acac15ca" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/indexFacade.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/app.yaml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/musicFacade.go" beforeDir="false" afterPath="$PROJECT_DIR$/musicFacade.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/musicserver.go" beforeDir="false" afterPath="$PROJECT_DIR$/musicserver.go" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Go File" />
</list>
</option>
</component>
<component name="GOROOT" path="/usr/local/Cellar/go/1.15.5/libexec" />
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="1ipVJv6kTmBdByNysh6NxmS21gD" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="ASKED_SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="DatabaseDriversLRU" value="postgresql" />
<property name="DefaultGoTemplateProperty" value="Go File" />
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="go.import.settings.migrated" value="true" />
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/songs" />
<property name="node.js.detected.package.eslint" value="true" />
<property name="node.js.detected.package.tslint" value="true" />
<property name="node.js.path.for.package.eslint" value="project" />
<property name="node.js.path.for.package.tslint" value="project" />
<property name="node.js.selected.package.eslint" value="(autodetect)" />
<property name="node.js.selected.package.tslint" value="(autodetect)" />
<property name="restartRequiresConfirmation" value="false" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/songs" />
</key>
</component>
<component name="RunManager" selected="Go Build.App Engine">
<configuration name="App Engine" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="MusicServer" />
<working_directory value="$PROJECT_DIR$" />
<go_parameters value="-i" />
<kind value="PACKAGE" />
<filePath value="$PROJECT_DIR$" />
<package value="MusicServer" />
<directory value="$PROJECT_DIR$" />
<method v="2" />
</configuration>
<configuration name="Dockerfile" type="docker-deploy" factoryName="dockerfile" temporary="true" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="sourceFilePath" value="Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
<configuration name="Dockerfile" type="docker-deploy" factoryName="dockerfile" temporary="true" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="sansan91/music-server:2.0.0" />
<option name="buildCliOptions" value="" />
<option name="command" value="" />
<option name="containerName" value="music-server-go" />
<option name="entrypoint" value="" />
<option name="envVars">
<list>
<DockerEnvVarImpl>
<option name="name" value="DB_HOST" />
<option name="value" value="ssh.sanplex.xyz" />
</DockerEnvVarImpl>
<DockerEnvVarImpl>
<option name="name" value="DB_USERNAME" />
<option name="value" value="postgres" />
</DockerEnvVarImpl>
<DockerEnvVarImpl>
<option name="name" value="DB_PASSWORD" />
<option name="value" value="DkrSle9501" />
</DockerEnvVarImpl>
<DockerEnvVarImpl>
<option name="name" value="DB_NAME" />
<option name="value" value="music_test" />
</DockerEnvVarImpl>
</list>
</option>
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8080" />
<option name="hostPort" value="8080" />
</DockerPortBindingImpl>
</list>
</option>
<option name="commandLineOptions" value="" />
<option name="sourceFilePath" value="Dockerfile" />
<option name="volumeBindings">
<list>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/sorted" />
<option name="hostPath" value="$USER_HOME$/Resilio Sync/Sorterat_test" />
</DockerVolumeBindingImpl>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/doc" />
<option name="hostPath" value="$USER_HOME$/doc" />
</DockerVolumeBindingImpl>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/sorterat" />
<option name="hostPath" value="$USER_HOME$/Resilio Sync/Sorterat_test" />
</DockerVolumeBindingImpl>
</list>
</option>
</settings>
</deployment>
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Docker.Dockerfile" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
<option name="oldMeFiltersMigrated" value="true" />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="1.0 of Go version" />
<MESSAGE value="Small fix in Dockerfile" />
<MESSAGE value="Fixed CORS" />
<MESSAGE value="Hopefully fixed the caching problem with random" />
<MESSAGE value="Another small change that should fix the caching problem." />
<option name="LAST_COMMIT_MESSAGE" value="Another small change that should fix the caching problem." />
</component>
<component name="VgoProject">
<integration-enabled>true</integration-enabled>
</component>
<component name="XDebuggerManager">
<watches-manager>
<configuration name="GoApplicationRunConfiguration">
<watch expression="games" language="go" />
<watch expression="songQue" language="go" />
</configuration>
</watches-manager>
</component>
</project>

View File

@@ -19,9 +19,9 @@ ENV DB_PASSWORD ""
ENV DB_NAME "" ENV DB_NAME ""
COPY --from=build_go /music-server/MusicServer . COPY --from=build_go /music-server/MusicServer .
COPY ./doc/swagger.yaml . COPY docss/swagger.yaml .
COPY ./songs/ ./songs/ COPY ./songs/ ./songs/
COPY ./init.sh . COPY ./init.sh .
RUN chmod 777 ./init.sh RUN chmod 777 ./conf.sh
CMD ./init.sh CMD ./init.sh

View File

@@ -1,5 +1,5 @@
application: musicserver application: musicserver
version: 2.1.4 version: 2.2.0
runtime: go115 runtime: go115
api_version: go1 api_version: go1

13
cmd/backend/main.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"music-server/pkg/conf"
)
func main() {
conf.SetupDb()
conf.SetupRestServer()
conf.CloseDb()
}

View File

@@ -1,246 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/jackc/pgtype"
"github.com/jackc/pgx/v4/pgxpool"
"os"
"time"
)
var dbPool *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
dbPool, 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 = dbPool.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() {
dbPool.Close()
}
func getGameName(gameId int) string {
var gameName = ""
err := dbPool.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 := dbPool.Exec(context.Background(),
"UPDATE game SET deleted=$1", time.Now())
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
}
func clearSongs(gameId int) {
if gameId == -1 {
_, err := dbPool.Exec(context.Background(), "DELETE FROM song")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
} else {
_, err := dbPool.Exec(context.Background(), "DELETE FROM song where game_id=$1", gameId)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
}
}
func clearGames() {
_, err := dbPool.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 := dbPool.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 := dbPool.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 addSong(song SongData) {
_, err := dbPool.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) []SongData {
rows, err := dbPool.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 []SongData
for rows.Next() {
var songName string
var path string
var timesPlayed int
err := rows.Scan(&songName, &path, &timesPlayed)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
songDataList = append(songDataList, SongData{
gameId: id,
songName: songName,
path: path,
timesPlayed: timesPlayed,
})
}
return songDataList
}
func getIdByGameName(name string) int {
var gameId = -1
err := dbPool.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 := dbPool.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 := dbPool.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 := dbPool.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 resetGameIdSeq() {
_, err := dbPool.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)
}
}
func findAllGames() []GameData {
rows, err := dbPool.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 []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, &timesPlayed, &lastPlayed, &numberOfSongs)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
gameList = append(gameList, 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 := dbPool.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)
}
}
func addSongPlayed(id int, name string) {
_, err := dbPool.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)
}
}
func testf() {
rows, dbErr := dbPool.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)
}
}

View File

@@ -1,8 +1,8 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
version: "2.1.4" version: "2.2.0"
title: "Music Server" title: "Music Server"
description: "Added a check to see if song exists before returning it, if not a new song will be picked up." description: "Changed the structure of the whole application, should be no changes to functionality."
contact: contact:
email: "zarnor91@gmail.com" email: "zarnor91@gmail.com"
servers: servers:

2
go.mod
View File

@@ -1,4 +1,4 @@
module MusicServer module music-server
go 1.15 go 1.15

View File

@@ -1,107 +0,0 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
setCorsAndNoCacheHeaders(&w, r)
if r.URL.Path == "/version" {
w.Header().Add("Content-Type", "application/json")
testf()
data := 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.",
History: []VersionData{
{
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.",
},
},
}
_ = json.NewEncoder(w).Encode(data)
} else if r.URL.Path == "/doc" {
http.ServeFile(w, r, "./doc/swagger.yaml")
} else if r.URL.Path == "/" {
rows, dbErr := dbPool.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(w, "%v\n", gameName)
}
_, err := fmt.Fprint(w, "Hello, World!!")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
} else {
http.NotFound(w, r)
return
}
}

View File

@@ -1,348 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"strconv"
"time"
)
var currentSong = -1
var games []GameData
var songQue []SongData
var lastFetched 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 = findAllGames()
}
func addLatestToQue() {
if lastFetched.path != "" {
currentSong = len(songQue)
songQue = append(songQue, lastFetched)
lastFetched = SongData{}
}
}
func getRandomSong() string {
if games == nil || len(games) == 0 {
games = findAllGames()
}
song := getSongFromList(games)
lastFetched = song
return song.path
}
func getSongFromList(games []GameData) SongData {
songFound := false
var song SongData
for !songFound {
game := getRandomGame(games)
songs := 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 getRandomSongLowChance() string {
gameList := findAllGames()
var listOfGames []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() SongInfo {
if songQue == nil {
return SongInfo{}
}
var currentSongData = songQue[currentSong]
currentGameData := getCurrentGame(currentSongData)
return SongInfo{
Game: currentGameData.gameName,
GamePlayed: currentGameData.timesPlayed,
Song: currentSongData.songName,
SongPlayed: currentSongData.timesPlayed,
CurrentlyPlaying: true,
SongNo: currentSong,
}
}
func getPlayedSongs() []SongInfo {
var songList []SongInfo
for i, song := range songQue {
gameData := getCurrentGame(song)
songList = append(songList, 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 = 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 = 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]
addGamePlayed(songData.gameId)
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 musicHandler(w http.ResponseWriter, r *http.Request) {
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 := getSong(song)
sendSong(w, s)
}
} else if r.URL.Path == "/music/first" && r.Method == http.MethodGet {
song := getSoundCheckSong()
sendSong(w, song)
} else if r.URL.Path == "/music/reset" && r.Method == http.MethodGet {
reset()
w.WriteHeader(http.StatusOK)
} else if r.URL.Path == "/music/rand" && r.Method == http.MethodGet {
song := getRandomSong()
sendSong(w, song)
} else if r.URL.Path == "/music/rand/low" && r.Method == http.MethodGet {
chance := getRandomSongLowChance()
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(getSongInfo())
} else if r.URL.Path == "/music/list" && r.Method == http.MethodGet {
w.Header().Add("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(getPlayedSongs())
} else if r.URL.Path == "/music/next" {
song := getNextSong()
sendSong(w, song)
} else if r.URL.Path == "/music/previous" {
song := getPreviousSong()
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(getAllGames())
} else if r.URL.Path == "/music/all/random" && r.Method == http.MethodGet {
w.Header().Add("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(getAllGamesRandom())
} else if r.URL.Path == "/music/played" && r.Method == http.MethodPut {
var p Played
err := json.NewDecoder(r.Body).Decode(&p)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
setPlayed(p.song)
w.WriteHeader(http.StatusOK)
} else if r.URL.Path == "/music/addQue" && r.Method == http.MethodGet {
addLatestToQue()
w.WriteHeader(http.StatusOK)
}
}
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
}
type Played struct {
song int
}
func getCurrentGame(currentSongData SongData) GameData {
for _, game := range games {
if game.id == currentSongData.gameId {
return game
}
}
return GameData{}
}
func getAveragePlayed(gameList []GameData) int {
var sum int
for _, data := range gameList {
sum += data.timesPlayed
}
return sum / len(gameList)
}
func getRandomGame(listOfGames []GameData) GameData {
return listOfGames[rand.Intn(len(listOfGames))]
}

View File

@@ -1,122 +0,0 @@
package main
import (
"fmt"
"github.com/gorilla/mux"
"log"
"net/http"
"os"
"strconv"
"time"
)
func main() {
// 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"
}
initDB(host, dbPort, username, password, dbName)
defer closeDb()
r := mux.NewRouter()
r.HandleFunc("/sync", syncHandler)
r.HandleFunc("/sync/{func}", syncHandler)
r.HandleFunc("/music", musicHandler)
r.HandleFunc("/music/{func}", musicHandler)
r.HandleFunc("/music/{func}/{func2}", musicHandler)
r.HandleFunc("/{func}", 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))
}
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
}
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)
}
}
}

32
pkg/api/index.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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, &timesPlayed, &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
View 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, &timesPlayed)
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
View 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
View 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
View 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
View 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))]
}

View File

@@ -1,37 +1,18 @@
package main package server
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "music-server/pkg/db"
"music-server/pkg/models"
"os" "os"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
) )
func syncHandler(w http.ResponseWriter, r *http.Request) { func SyncGames() {
setCorsAndNoCacheHeaders(&w, r)
if r.URL.Path == "/sync" {
w.Header().Add("Content-Type", "application/json")
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")
resetDB()
_, err := fmt.Fprint(w, "Games and songs are deleted from the database")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
}
func syncGames() {
host := os.Getenv("DB_HOST") host := os.Getenv("DB_HOST")
var dir string var dir string
if host != "" { if host != "" {
@@ -42,7 +23,7 @@ func syncGames() {
fmt.Printf("dir: %s\n", dir) fmt.Printf("dir: %s\n", dir)
foldersToSkip := []string{".sync"} foldersToSkip := []string{".sync"}
fmt.Println(foldersToSkip) fmt.Println(foldersToSkip)
setGameDeletionDate() db.SetGameDeletionDate()
files, err := ioutil.ReadDir(dir) files, err := ioutil.ReadDir(dir)
if err != nil { if err != nil {
@@ -76,6 +57,11 @@ func syncGames() {
} }
} }
func ResetDB() {
db.ClearSongs(-1)
db.ClearGames()
}
func getIdFromFile(file os.FileInfo) int { func getIdFromFile(file os.FileInfo) int {
name := file.Name() name := file.Name()
if !file.IsDir() && strings.HasSuffix(name, ".id") { if !file.IsDir() && strings.HasSuffix(name, ".id") {
@@ -89,24 +75,24 @@ func getIdFromFile(file os.FileInfo) int {
func checkIfChanged(id int, name string, path string) { func checkIfChanged(id int, name string, path string) {
fmt.Printf("Id from file: %v\n", id) fmt.Printf("Id from file: %v\n", id)
nameFromDb := getGameName(id) nameFromDb := db.GetGameName(id)
fmt.Printf("Name from file: %v\n", name) fmt.Printf("Name from file: %v\n", name)
fmt.Printf("Name from DB: %v\n", nameFromDb) fmt.Printf("Name from DB: %v\n", nameFromDb)
if nameFromDb == "" { if nameFromDb == "" {
fmt.Println("Not in db") fmt.Println("Not in db")
insertGameWithExistingId(id, name, path) db.InsertGameWithExistingId(id, name, path)
fmt.Println("Added to db") fmt.Println("Added to db")
} else if name != nameFromDb { } else if name != nameFromDb {
fmt.Println("Diff name") fmt.Println("Diff name")
updateGameName(id, name, path) db.UpdateGameName(id, name, path)
} }
removeDeletionDate(id) db.RemoveDeletionDate(id)
} }
func addNewGame(name string, path string) { func addNewGame(name string, path string) {
newId := getIdByGameName(name) newId := db.GetIdByGameName(name)
if newId == -1 { if newId == -1 {
newId = insertGame(name, path) newId = db.InsertGame(name, path)
} }
fmt.Printf("newId = %v", newId) fmt.Printf("newId = %v", newId)
@@ -121,8 +107,8 @@ func addNewGame(name string, path string) {
} }
func checkSongs(gameDir string, gameId int) { func checkSongs(gameDir string, gameId int) {
songs := make([]SongData, 0) songs := make([]models.SongData, 0)
findSongsFromGame := findSongsFromGame(gameId) findSongsFromGame := db.FindSongsFromGame(gameId)
files, err := ioutil.ReadDir(gameDir) files, err := ioutil.ReadDir(gameDir)
if err != nil { if err != nil {
@@ -132,22 +118,17 @@ func checkSongs(gameDir string, gameId int) {
path := gameDir + entry.Name() path := gameDir + entry.Name()
songName := entry.Name() songName := entry.Name()
if !entry.IsDir() && !strings.HasSuffix(songName, ".id") { if !entry.IsDir() && !strings.HasSuffix(songName, ".id") {
songs = append(songs, SongData{gameId: gameId, songName: songName, path: path}) songs = append(songs, models.SongData{GameId: gameId, SongName: songName, Path: path})
} }
} }
if len(songs) != len(findSongsFromGame) { if len(songs) != len(findSongsFromGame) {
clearSongs(gameId) db.ClearSongs(gameId)
for _, song := range songs { for _, song := range songs {
addSong(song) db.AddSong(song)
} }
} }
} }
func resetDB() {
clearSongs(-1)
clearGames()
}
func contains(s []string, searchTerm string) bool { func contains(s []string, searchTerm string) bool {
i := sort.SearchStrings(s, searchTerm) i := sort.SearchStrings(s, searchTerm)
return i < len(s) && s[i] == searchTerm return i < len(s) && s[i] == searchTerm

Binary file not shown.

Binary file not shown.