diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..723ef36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
\ No newline at end of file
diff --git a/.idea/MusicServer.iml b/.idea/MusicServer.iml
index ab1898c..47bf3f7 100644
--- a/.idea/MusicServer.iml
+++ b/.idea/MusicServer.iml
@@ -10,7 +10,9 @@
-
+
+
+
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..9a5fffc
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ postgresql
+ true
+ org.postgresql.Driver
+ jdbc:postgresql://localhost:5432/music_dev_local
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 83a0dd8..9b9ec91 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -1,25 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -31,27 +92,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$PROJECT_DIR$/musicFacade.go
+ 139
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..22b7044
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,27 @@
+FROM golang:1.15.5-alpine as build_go
+
+COPY *.go go.mod /music-server/
+WORKDIR /music-server
+RUN go build
+
+# Stage 2, distribution container
+FROM golang:1.15.5-alpine
+RUN apk add --no-cache bash
+EXPOSE 8080
+VOLUME /sorted
+VOLUME /sorterad
+VOLUME /doc
+
+ENV DB_HOST ""
+ENV DB_PORT ""
+ENV DB_USERNAME ""
+ENV DB_PASSWORD ""
+ENV DB_NAME ""
+
+COPY --from=build_go /music-server/MusicServer .
+COPY ./doc/swagger.yaml .
+COPY ./songs/ ./songs/
+COPY ./init.sh .
+RUN chmod 777 ./init.sh
+
+CMD ./init.sh
\ No newline at end of file
diff --git a/app.yaml b/app.yaml
index def3f4a..3b09099 100644
--- a/app.yaml
+++ b/app.yaml
@@ -1,5 +1,5 @@
application: musicserver
-version: 1
+version: 2.0.0
runtime: go115
api_version: go1
diff --git a/database.go b/database.go
new file mode 100644
index 0000000..fd12c92
--- /dev/null
+++ b/database.go
@@ -0,0 +1,244 @@
+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, ×Played)
+ 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")
+ 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, ×Played, &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)
+ }
+}
diff --git a/doc/swagger.yaml b/doc/swagger.yaml
new file mode 100644
index 0000000..68929d1
--- /dev/null
+++ b/doc/swagger.yaml
@@ -0,0 +1,290 @@
+openapi: 3.0.3
+info:
+ version: "2.0.0"
+ title: "Music Server"
+ description: "Rebuilt the application in Go."
+ contact:
+ email: "zarnor91@gmail.com"
+servers:
+ - url: https://music.sanplex.xyz/
+ description: Main (production) server
+tags:
+ - name: "Music"
+ description: "Endpoints for the music"
+ - name: "Version"
+ description: "Information about the application"
+ - name: "Sync"
+ description: "Sync the database with the folders"
+paths:
+ /music:
+ get:
+ tags:
+ - "Music"
+ summary: "Get song"
+ description: "Get a specified song from the que, if invalid number it will be the first or the last song in que"
+ operationId: "getSong"
+ parameters:
+ - name: "song"
+ in: "query"
+ schema:
+ type: integer
+ description: "The number the song has in the que"
+ required: true
+ responses:
+ "200":
+ description: "The speciefied file"
+ content:
+ audio/mpeg:
+ schema:
+ type: object
+ format: binary
+ "500":
+ description: "Something wnet wrong on the server"
+ /music/first:
+ get:
+ tags:
+ - "Music"
+ summary: "Start a match"
+ description: "Get a sound check song and starts a new song que"
+ operationId: "getFisrt"
+ responses:
+ "200":
+ description: "A file"
+ content:
+ audio/mpeg:
+ schema:
+ type: object
+ format: binary
+ "500":
+ description: "Something wnet wrong on the server"
+ /music/rand:
+ get:
+ tags:
+ - "Music"
+ summary: "Get random song"
+ description: "Takes a random song from a random game"
+ operationId: "getRandom"
+ responses:
+ "200":
+ description: "A file"
+ content:
+ audio/mpeg:
+ schema:
+ type: object
+ format: binary
+ "500":
+ description: "Something wnet wrong on the server"
+ /music/rand/low:
+ get:
+ tags:
+ - "Music"
+ summary: "Get random song"
+ description: "Takes a random song from a random game but increases the chans for games that haven't been played"
+ operationId: "getRandomLow"
+ responses:
+ "200":
+ description: "A file"
+ content:
+ audio/mpeg:
+ schema:
+ type: object
+ format: binary
+ "500":
+ description: "Something wnet wrong on the server"
+ /music/info:
+ get:
+ tags:
+ - "Music"
+ summary: "Get info of current song"
+ description: "Return the info of the song currently playing"
+ operationId: "getInfo"
+ responses:
+ "200":
+ description: "Info of current song"
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/info'
+ "500":
+ description: "Something wnet wrong on the server"
+ /music/list:
+ get:
+ tags:
+ - "Music"
+ summary: "Gets song que"
+ description: "Gets a list of all songs that have been fetched"
+ operationId: "getList"
+ responses:
+ "200":
+ description: "A list"
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/list'
+ "500":
+ description: "Something went wrong on the server"
+ /music/next:
+ get:
+ tags:
+ - "Music"
+ summary: "Get next song"
+ description: "Gets next song in the song que, if there is no more a new random song will be generated."
+ operationId: "getNext"
+ responses:
+ "200":
+ description: "A file"
+ content:
+ audio/mpeg:
+ schema:
+ type: object
+ format: binary
+ "500":
+ description: "Something wnet wrong on the server"
+ /music/previous:
+ get:
+ tags:
+ - "Music"
+ summary: "Get previous song"
+ description: "Gets previous song in the song que, if there is no more the first song will be returns."
+ operationId: "getPrevious"
+ responses:
+ "200":
+ description: "A file"
+ content:
+ audio/mpeg:
+ schema:
+ type: object
+ format: binary
+ "500":
+ description: "Something wnet wrong on the server"
+ /music/all:
+ get:
+ tags:
+ - "Music"
+ summary: "Gets all games"
+ description: "Gets a list of all games that is in the databse"
+ operationId: "getAll"
+ responses:
+ "200":
+ description: "A list"
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ example: ["God of War", "Final Fantasy VII"]
+ "500":
+ description: "Something went wrong on the server"
+ /music/played:
+ put:
+ tags:
+ - "Music"
+ summary: "Increase played"
+ description: "Increase the number of times the current game has been played"
+ operationId: "setPlayed"
+ responses:
+ "202":
+ description: "Accepted"
+ "401":
+ description: "Bad Request"
+ "500":
+ description: "Something went wrong on the server"
+ /music/reset:
+ get:
+ tags:
+ - "Music"
+ summary: "Resets the song que"
+ description: "Resets the song que for a new match"
+ operationId: "reset"
+ responses:
+ "200":
+ description: "successful operation"
+ "500":
+ description: "Something went wrong on the server"
+ /version:
+ get:
+ tags:
+ - "Version"
+ summary: "Returns pet inventories by status"
+ description: "Returns a map of status codes to quantities"
+ operationId: "getVersionInfo"
+ responses:
+ "200":
+ description: "successful operation"
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/versionInfo'
+ /sync:
+ get:
+ tags:
+ - "Sync"
+ summary: "Sync games"
+ description: "Sync the database with the folders"
+ operationId: "sync"
+ responses:
+ "200":
+ description: "successful operation"
+ "500":
+ description: "Something went wrong on the server"
+components:
+ schemas:
+ list:
+ type: object
+ properties:
+ Game:
+ type: string
+ example: God of War
+ Song:
+ type: string
+ example: Main Theme.mp3
+ CurrentlyPlaying:
+ type: boolean
+ example: true
+ description: Only included on the one that's playing
+ SongNo:
+ type: integer
+ example: 3
+ info:
+ type: object
+ properties:
+ Game:
+ type: string
+ example: God of War
+ GamePlayed:
+ type: integer
+ example: 10
+ Song:
+ type: string
+ example: Main Theme.mp3
+ SongPlayed:
+ type: integer
+ example: 10
+ SongNo:
+ type: integer
+ example: 3
+ versionInfo:
+ type: object
+ properties:
+ version:
+ type: string
+ example: 0.5.1
+ changelog:
+ type: string
+ example: "Added shadow to build fat jars. Added support to give property files through arguments."
+ history:
+ type: array
+ items:
+ $ref: '#/components/schemas/historyInfo'
+ historyInfo:
+ type: object
+ properties:
+ version:
+ type: string
+ example: 0.5.0
+ changelog:
+ type: string
+ example: "Updated kotlin version, gradle version and al libs. Added connection to database with exposed. Added new endpoints."
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..f62ba4a
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,9 @@
+module MusicServer
+
+go 1.15
+
+require (
+ github.com/gorilla/mux v1.8.0
+ github.com/jackc/pgtype v1.5.0
+ github.com/jackc/pgx/v4 v4.9.0
+)
diff --git a/init.sh b/init.sh
new file mode 100644
index 0000000..f96e23c
--- /dev/null
+++ b/init.sh
@@ -0,0 +1,4 @@
+#! /bin/sh
+mkdir /doc
+cp swagger.yaml /doc
+./MusicServer
\ No newline at end of file
diff --git a/musicFacade.go b/musicFacade.go
new file mode 100644
index 0000000..8089f91
--- /dev/null
+++ b/musicFacade.go
@@ -0,0 +1,243 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "net/http"
+ "strconv"
+)
+
+var currentSong = -1
+var games []GameData
+var songQue []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 getRandomSong() string {
+ if games == nil || len(games) == 0 {
+ games = findAllGames()
+ }
+
+ game := getRandomGame(games)
+
+ songs := findSongsFromGame(game.id)
+
+ song := songs[rand.Intn(len(songs))]
+
+ currentSong = len(songQue)
+ songQue = append(songQue, song)
+ return song.path
+}
+
+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)
+ }
+ }
+ }
+
+ game := getRandomGame(listOfGames)
+
+ songs := findSongsFromGame(game.id)
+
+ song := songs[rand.Intn(len(songs))]
+
+ currentSong = len(songQue)
+ songQue = append(songQue, song)
+ return song.path
+
+}
+
+func getSongInfo() 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 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 currentSong == len(songQue)-1 || currentSong == -1 {
+ return getRandomSong()
+ } else {
+ currentSong = currentSong + 1
+ var songData = songQue[currentSong]
+ return songData.path
+ }
+}
+
+func getPreviousSong() string {
+ 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) {
+ 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 {
+ http.ServeFile(w, r, getSong(song))
+ }
+ } else if r.URL.Path == "/music/first" && r.Method == http.MethodGet {
+ http.ServeFile(w, r, getSoundCheckSong())
+
+ } 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 {
+ http.ServeFile(w, r, getRandomSong())
+
+ } else if r.URL.Path == "/music/rand/low" && r.Method == http.MethodGet {
+ http.ServeFile(w, r, getRandomSongLowChance())
+
+ } 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" {
+ http.ServeFile(w, r, getNextSong())
+
+ } else if r.URL.Path == "/music/previous" {
+ http.ServeFile(w, r, getPreviousSong())
+
+ } 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/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)
+ }
+}
+
+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))]
+}
diff --git a/musicserver.go b/musicserver.go
index 2c6ea7b..faccbe4 100644
--- a/musicserver.go
+++ b/musicserver.go
@@ -1,14 +1,59 @@
package main
import (
+ "context"
+ "encoding/json"
"fmt"
+ "github.com/gorilla/mux"
"log"
"net/http"
"os"
+ "strconv"
+ "time"
)
func main() {
- http.HandleFunc("/", indexHandler)
+ // 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"
@@ -20,13 +65,91 @@ func main() {
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 indexHandler(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path == "/test" {
- _, err := fmt.Fprint(w, "Testing path")
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
+ if r.URL.Path == "/version" {
+ w.Header().Add("Content-Type", "application/json")
+
+ testf()
+
+ data := VersionData{Version: "2.0.0",
+ Changelog: "Rebuilt the application in Go.",
+ History: []VersionData{
+ {
+ 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)
@@ -36,4 +159,4 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
return
}
-}
\ No newline at end of file
+}
diff --git a/songs/01. Opening.mp3 b/songs/01. Opening.mp3
new file mode 100755
index 0000000..373c2d1
Binary files /dev/null and b/songs/01. Opening.mp3 differ
diff --git a/songs/01. Title.mp3 b/songs/01. Title.mp3
new file mode 100755
index 0000000..15bc24d
Binary files /dev/null and b/songs/01. Title.mp3 differ
diff --git a/syncFacade.go b/syncFacade.go
new file mode 100644
index 0000000..fada799
--- /dev/null
+++ b/syncFacade.go
@@ -0,0 +1,153 @@
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+func syncHandler(w http.ResponseWriter, r *http.Request) {
+ 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")
+ 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)
+ 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 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 := 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")
+ insertGameWithExistingId(id, name, path)
+ fmt.Println("Added to db")
+ } else if name != nameFromDb {
+ fmt.Println("Diff name")
+ updateGameName(id, name, path)
+ }
+ removeDeletionDate(id)
+}
+
+func addNewGame(name string, path string) {
+ newId := getIdByGameName(name)
+ if newId == -1 {
+ newId = 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([]SongData, 0)
+ findSongsFromGame := 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, SongData{gameId: gameId, songName: songName, path: path})
+ }
+ }
+ if len(songs) != len(findSongsFromGame) {
+ clearSongs(gameId)
+ for _, song := range songs {
+ addSong(song)
+ }
+ }
+}
+
+func resetDB() {
+ clearSongs(-1)
+ clearGames()
+}
+
+func contains(s []string, searchTerm string) bool {
+ i := sort.SearchStrings(s, searchTerm)
+ return i < len(s) && s[i] == searchTerm
+}