Moved around more code. Implemented more sqlc. Added support to generate swagger.

Added support for profiling. Removed the pkg module altogether.
Everything except old sync is now using code generated by sqlc.
This commit is contained in:
2025-01-15 16:04:14 +01:00
parent db8214cb02
commit d653463f58
43 changed files with 947 additions and 1133 deletions

103
internal/database/game.go Normal file
View File

@@ -0,0 +1,103 @@
package database
import (
"fmt"
"music-server/internal/db"
"os"
"time"
)
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
}
func GetGameName(gameId int) string {
var gameName = ""
err := db.Dbpool.QueryRow(db.Ctx,
"SELECT game_name FROM game WHERE id = $1", gameId).Scan(&gameName)
if err != nil {
if compareError.Error() != err.Error() {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
return ""
}
return gameName
}
func SetGameDeletionDate() {
_, err := db.Dbpool.Exec(db.Ctx,
"UPDATE game SET deleted=$1 WHERE deleted IS NULL", time.Now())
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
}
func UpdateGameName(id int, name string, path string) {
_, err := db.Dbpool.Exec(db.Ctx,
"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 := db.Dbpool.Exec(db.Ctx,
"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 := db.Dbpool.QueryRow(db.Ctx,
"SELECT id FROM game WHERE game_name = $1", name).Scan(&gameId)
if err != nil {
if compareError.Error() != err.Error() {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
return -1
}
return gameId
}
func InsertGame(name string, path string) int {
gameId := -1
err := db.Dbpool.QueryRow(db.Ctx,
"INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id",
name, path, time.Now()).Scan(&gameId)
if err != nil {
if compareError.Error() != err.Error() {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
db.ResetGameIdSeq()
err2 := db.Dbpool.QueryRow(db.Ctx,
"INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id",
name, path, time.Now()).Scan(&gameId)
if err2 != nil {
if compareError.Error() != err2.Error() {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
return -1
}
}
return gameId
}
func InsertGameWithExistingId(id int, name string, path string) {
_, err := db.Dbpool.Exec(db.Ctx,
"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)
}
}

View File

@@ -0,0 +1,206 @@
package database
import (
"fmt"
"io/fs"
"log"
"os"
"sort"
"strconv"
"strings"
"sync"
"time"
)
var wg sync.WaitGroup
func SyncGames() {
start := time.Now()
dir := os.Getenv("MUSIC_PATH")
fmt.Printf("dir: %s\n", dir)
foldersToSkip := []string{".sync", "dist", "old", "characters"}
fmt.Println(foldersToSkip)
SetGameDeletionDate()
checkBrokenSongs()
files, err := os.ReadDir(dir)
if err != nil {
log.Fatal(err)
}
for _, file := range files {
syncGame(file, foldersToSkip, dir)
}
finished := time.Now()
totalTime := finished.Sub(start)
out := time.Time{}.Add(totalTime)
fmt.Printf("\nTotal time: %v\n", totalTime)
fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000"))
}
func SyncGamesQuick() {
start := time.Now()
dir := os.Getenv("MUSIC_PATH")
fmt.Printf("dir: %s\n", dir)
foldersToSkip := []string{".sync", "dist", "old", "characters"}
fmt.Println(foldersToSkip)
SetGameDeletionDate()
checkBrokenSongs()
files, err := os.ReadDir(dir)
if err != nil {
log.Fatal(err)
}
for _, file := range files {
wg.Add(1)
go func() {
defer wg.Done()
syncGame(file, foldersToSkip, dir)
}()
}
wg.Wait()
finished := time.Now()
totalTime := finished.Sub(start)
out := time.Time{}.Add(totalTime)
fmt.Printf("\nTotal time: %v\n", totalTime)
fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000"))
}
func syncGame(file os.DirEntry, foldersToSkip []string, dir string) {
if file.IsDir() && !contains(foldersToSkip, file.Name()) {
fmt.Println(file.Name())
path := dir + file.Name() + "/"
fmt.Println(path)
entries, err := os.ReadDir(path)
if err != nil {
log.Println(err)
}
id := -1
for _, entry := range entries {
fileInfo, err := entry.Info()
if err != nil {
log.Println(err)
}
id = getIdFromFile(fileInfo)
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)
checkBrokenSongs()
}
RemoveDeletionDate(id)
}
func addNewGame(name string, path string) {
newId := GetIdByGameName(name)
if newId != -1 {
checkBrokenSongs()
RemoveDeletionDate(newId)
} else {
newId = InsertGame(name, path)
}
fmt.Printf("newId = %v", newId)
fileName := path + "/." + strconv.Itoa(newId) + ".id"
fmt.Printf("fileName = %v", fileName)
err := os.WriteFile(fileName, nil, 0644)
if err != nil {
panic(err)
}
checkSongs(path, newId)
}
func checkSongs(gameDir string, gameId int) {
files, err := os.ReadDir(gameDir)
if err != nil {
log.Println(err)
}
for _, file := range files {
entry, err := file.Info()
if err != nil {
log.Println(err)
}
if isSong(entry) {
path := gameDir + entry.Name()
fileName := entry.Name()
songName, _ := strings.CutSuffix(fileName, ".mp3")
if CheckSong(path) {
UpdateSong(songName, fileName, path)
} else {
AddSong(SongData{GameId: gameId, SongName: songName, Path: path, FileName: fileName})
}
} else if isCoverImage(entry) {
//TODO: Later add cover art image here in db
}
}
//TODO: Add number of songs here
}
func checkBrokenSongs() {
allSongs := FetchAllSongs()
var brokenSongs []SongData
for _, song := range allSongs {
//Check if file exists and open
openFile, err := os.Open(song.Path)
if err != nil {
//File not found
brokenSongs = append(brokenSongs, song)
fmt.Printf("song broken: %v", song.Path)
} else {
err = openFile.Close()
if err != nil {
log.Println(err)
}
}
}
RemoveBrokenSongs(brokenSongs)
}
func isSong(entry fs.FileInfo) bool {
return !entry.IsDir() && strings.HasSuffix(entry.Name(), ".mp3")
}
func isCoverImage(entry fs.FileInfo) bool {
return !entry.IsDir() && strings.Contains(entry.Name(), "cover") &&
(strings.HasSuffix(entry.Name(), ".jpg") || strings.HasSuffix(entry.Name(), ".png"))
}
func contains(s []string, searchTerm string) bool {
i := sort.SearchStrings(s, searchTerm)
return i < len(s) && s[i] == searchTerm
}

92
internal/database/song.go Normal file
View File

@@ -0,0 +1,92 @@
package database
import (
"errors"
"fmt"
"music-server/internal/db"
"os"
"strings"
)
type SongData struct {
GameId int
SongName string
Path string
TimesPlayed int
FileName string
}
var compareError = errors.New("no rows in result set")
func AddSong(song SongData) {
_, err := db.Dbpool.Exec(db.Ctx,
"INSERT INTO song(game_id, song_name, path, file_name) VALUES ($1, $2, $3, $4)",
song.GameId, song.SongName, song.Path, song.FileName)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
}
func CheckSong(songPath string) bool {
var path string
err := db.Dbpool.QueryRow(db.Ctx,
"SELECT path FROM song WHERE path = $1", songPath).Scan(&path)
if err != nil {
if compareError.Error() != err.Error() {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
}
return path != ""
}
func UpdateSong(songName string, fileName string, path string) {
_, err := db.Dbpool.Exec(db.Ctx,
"UPDATE song SET song_name=$1, file_name=$2 WHERE path = $3",
songName, fileName, path)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
}
func FetchAllSongs() []SongData {
rows, err := db.Dbpool.Query(db.Ctx,
"SELECT song_name, path FROM song")
if err != nil {
if compareError.Error() != err.Error() {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
return nil
}
var songDataList []SongData
for rows.Next() {
var songName string
var path string
err := rows.Scan(&songName, &path)
if err != nil {
if compareError.Error() != err.Error() {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
}
songDataList = append(songDataList, SongData{
SongName: songName,
Path: path,
})
}
return songDataList
}
func RemoveBrokenSongs(songs []SongData) {
joined := ""
for _, song := range songs {
joined += "'" + song.Path + "',"
}
joined = strings.TrimSuffix(joined, ",")
_, err := db.Dbpool.Exec(db.Ctx, "DELETE FROM song where path in ($1)", joined)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
}