Added a new sync that uses hash

Added a new sync that uses hash and sqlc for the queries. Added db
migration. Started adding a config file.
This commit is contained in:
2024-12-19 12:11:20 +01:00
parent 8fa93d580d
commit 5ab19e16e5
26 changed files with 1527 additions and 45 deletions

View File

@@ -1,9 +1,10 @@
package api
import (
"github.com/gin-gonic/gin"
"music-server/pkg/server"
"net/http"
"github.com/gin-gonic/gin"
)
type Sync struct {
@@ -25,6 +26,12 @@ func (s *Sync) SyncGamesQuick(ctx *gin.Context) {
ctx.JSON(http.StatusOK, "Games are synced")
}
func (s *Sync) SyncGamesNew(ctx *gin.Context) {
response := server.SyncGamesNew()
server.Reset()
ctx.JSON(http.StatusOK, response)
}
func (s *Sync) ResetGames(ctx *gin.Context) {
server.ResetDB()
ctx.JSON(http.StatusOK, "Games and songs are deleted from the database")

View File

@@ -7,6 +7,7 @@ import (
"music-server/pkg/api"
"music-server/pkg/db"
"music-server/pkg/helpers"
"music-server/pkg/server"
"os"
"strconv"
@@ -15,6 +16,12 @@ import (
)
func SetupDb() {
/*err := server.ReadConf("conf.yaml")
if err != nil {
log.Fatal(err)
}*/
// Get the value of an Environment Variable
host := os.Getenv("DB_HOST")
dbPort, dbPortErr := strconv.Atoi(os.Getenv("DB_PORT"))
@@ -35,16 +42,35 @@ func SetupDb() {
dbPort = 5432
}
if username == "" {
username = "sebastian"
username = "postgres"
}
if password == "" {
password = "950100"
password = "postgres"
}
if dbName == "" {
dbName = "music_dev_local"
dbName = "music_test_local"
}
db.Migrate_db(host, dbPort, username, password, dbName)
db.InitDB(host, dbPort, username, password, dbName)
var dir string
if host != "localhost" {
dir = "/sorted/"
} else {
dir = "/Users/sebastian/ResilioSync/Sorterat_test/"
}
server.Conf = &server.Config{
Host: host,
Port: dbPort,
User: username,
Password: password,
Dbname: dbName,
Path: dir,
}
}
func CloseDb() {
@@ -60,6 +86,7 @@ func SetupRestServer(swagger embed.FS, search embed.FS) {
syncGroup := router.Group("/sync")
{
syncGroup.GET("", sync.SyncGames)
syncGroup.GET("/new", sync.SyncGamesNew)
syncGroup.GET("/quick", sync.SyncGamesQuick)
syncGroup.GET("/reset", sync.ResetGames)
}

View File

@@ -2,14 +2,21 @@ package db
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"github.com/golang-migrate/migrate"
"github.com/golang-migrate/migrate/database/postgres"
_ "github.com/golang-migrate/migrate/database/postgres"
_ "github.com/golang-migrate/migrate/source/file"
"github.com/jackc/pgx/v5/pgxpool"
_ "github.com/lib/pq"
)
var dbpool *pgxpool.Pool
var ctx = context.Background()
var Dbpool *pgxpool.Pool
var Ctx = context.Background()
func InitDB(host string, port int, user string, password string, dbname string) {
@@ -20,14 +27,14 @@ func InitDB(host string, port int, user string, password string, dbname string)
fmt.Println(psqlInfo)
var err error
dbpool, err = pgxpool.New(ctx, psqlInfo)
Dbpool, err = pgxpool.New(Ctx, 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(ctx, "select 'Successfully connected!'").Scan(&success)
err = Dbpool.QueryRow(Ctx, "select 'Successfully connected!'").Scan(&success)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
os.Exit(1)
@@ -37,11 +44,11 @@ func InitDB(host string, port int, user string, password string, dbname string)
}
func CloseDb() {
dbpool.Close()
Dbpool.Close()
}
func Testf() {
rows, dbErr := dbpool.Query(ctx, "select game_name from game")
rows, dbErr := Dbpool.Query(Ctx, "select game_name from game")
if dbErr != nil {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", dbErr)
os.Exit(1)
@@ -57,8 +64,60 @@ func Testf() {
}
func resetGameIdSeq() {
_, err := dbpool.Query(ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);")
_, err := Dbpool.Query(Ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
}
func Migrate_db(host string, port int, user string, password string, dbname string) {
migrationInfo := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable",
user, password, host, port, dbname)
fmt.Println("Migration Info: ", migrationInfo)
db, err := sql.Open("postgres", migrationInfo)
if err != nil {
log.Println(err)
}
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
log.Println(err)
}
m, err := migrate.NewWithDatabaseInstance(
"file://./db/migrations/",
"postgres", driver)
if err != nil {
log.Println(err)
}
version, _, err := m.Version()
if err != nil {
log.Println("Migration version err: ", err)
}
fmt.Println("Migration version before: ", version)
err = m.Force(1)
//err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run
if err != nil {
log.Println("Force err: ", err)
}
err = m.Migrate(2)
//err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run
if err != nil {
log.Println("Migration err: ", err)
}
versionAfter, _, err := m.Version()
if err != nil {
log.Println("Migration version err: ", err)
}
fmt.Println("Migration version after: ", versionAfter)
fmt.Println("Migration done")
db.Close()
}

View File

@@ -12,7 +12,7 @@ import (
func GetGameName(gameId int) string {
var gameName = ""
err := dbpool.QueryRow(ctx,
err := Dbpool.QueryRow(Ctx,
"SELECT game_name FROM game WHERE id = $1", gameId).Scan(&gameName)
if err != nil {
if compareError.Error() != err.Error() {
@@ -28,7 +28,7 @@ func GetGameById(gameId int) (models.GameData, error) {
var numberOfSongs pgtype.Int4
var gameName, path string
var added, deleted, lastChanged, lastPlayed pgtype.Timestamp
err := dbpool.QueryRow(ctx,
err := Dbpool.QueryRow(Ctx,
"SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs "+
"FROM game WHERE id = $1 AND deleted IS NULL ", gameId).Scan(&id, &gameName, &added, &deleted, &lastChanged, &path, &timesPlayed, &lastPlayed, &numberOfSongs)
if err != nil {
@@ -51,7 +51,7 @@ func GetGameById(gameId int) (models.GameData, error) {
}
func SetGameDeletionDate() {
_, err := dbpool.Exec(ctx,
_, err := Dbpool.Exec(Ctx,
"UPDATE game SET deleted=$1 WHERE deleted IS NULL", time.Now())
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
@@ -59,14 +59,14 @@ func SetGameDeletionDate() {
}
func ClearGames() {
_, err := dbpool.Exec(ctx, "DELETE FROM game")
_, err := Dbpool.Exec(Ctx, "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(ctx,
_, err := Dbpool.Exec(Ctx,
"UPDATE game SET game_name=$1, path=$2, last_changed=$3 WHERE id=$4",
name, path, time.Now(), id)
if err != nil {
@@ -75,7 +75,7 @@ func UpdateGameName(id int, name string, path string) {
}
func RemoveDeletionDate(id int) {
_, err := dbpool.Exec(ctx,
_, err := Dbpool.Exec(Ctx,
"UPDATE game SET deleted=null WHERE id=$1", id)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
@@ -84,7 +84,7 @@ func RemoveDeletionDate(id int) {
func GetIdByGameName(name string) int {
var gameId = -1
err := dbpool.QueryRow(ctx,
err := Dbpool.QueryRow(Ctx,
"SELECT id FROM game WHERE game_name = $1", name).Scan(&gameId)
if err != nil {
if compareError.Error() != err.Error() {
@@ -97,7 +97,7 @@ func GetIdByGameName(name string) int {
func InsertGame(name string, path string) int {
gameId := -1
err := dbpool.QueryRow(ctx,
err := Dbpool.QueryRow(Ctx,
"INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id",
name, path, time.Now()).Scan(&gameId)
if err != nil {
@@ -105,7 +105,7 @@ func InsertGame(name string, path string) int {
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
}
resetGameIdSeq()
err2 := dbpool.QueryRow(ctx,
err2 := Dbpool.QueryRow(Ctx,
"INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id",
name, path, time.Now()).Scan(&gameId)
if err2 != nil {
@@ -119,7 +119,7 @@ func InsertGame(name string, path string) int {
}
func InsertGameWithExistingId(id int, name string, path string) {
_, err := dbpool.Exec(ctx,
_, err := Dbpool.Exec(Ctx,
"INSERT INTO game(id, game_name, path, added) VALUES ($1, $2, $3, $4)",
id, name, path, time.Now())
if err != nil {
@@ -128,7 +128,7 @@ func InsertGameWithExistingId(id int, name string, path string) {
}
func FindAllGames() []models.GameData {
rows, err := dbpool.Query(ctx,
rows, err := Dbpool.Query(Ctx,
"SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs "+
"FROM game WHERE deleted IS NULL "+
"ORDER BY game_name")
@@ -165,7 +165,7 @@ func FindAllGames() []models.GameData {
}
func AddGamePlayed(id int) {
_, err := dbpool.Exec(ctx,
_, err := Dbpool.Exec(Ctx,
"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)

View File

@@ -12,12 +12,12 @@ var compareError = errors.New("no rows in result set")
func ClearSongs(gameId int) {
if gameId == -1 {
_, err := dbpool.Exec(ctx, "DELETE FROM song")
_, err := Dbpool.Exec(Ctx, "DELETE FROM song")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
} else {
_, err := dbpool.Exec(ctx, "DELETE FROM song where game_id=$1", gameId)
_, err := Dbpool.Exec(Ctx, "DELETE FROM song where game_id=$1", gameId)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
@@ -25,7 +25,7 @@ func ClearSongs(gameId int) {
}
func AddSong(song models.SongData) {
_, err := dbpool.Exec(ctx,
_, err := Dbpool.Exec(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 {
@@ -35,7 +35,7 @@ func AddSong(song models.SongData) {
func CheckSong(songPath string) bool {
var path string
err := dbpool.QueryRow(ctx,
err := Dbpool.QueryRow(Ctx,
"SELECT path FROM song WHERE path = $1", songPath).Scan(&path)
if err != nil {
if compareError.Error() != err.Error() {
@@ -46,7 +46,7 @@ func CheckSong(songPath string) bool {
}
func UpdateSong(songName string, fileName string, path string) {
_, err := dbpool.Exec(ctx,
_, err := Dbpool.Exec(Ctx,
"UPDATE song SET song_name=$1, file_name=$2 WHERE path = $3",
songName, fileName, path)
if err != nil {
@@ -55,7 +55,7 @@ func UpdateSong(songName string, fileName string, path string) {
}
func FindSongsFromGame(id int) []models.SongData {
rows, err := dbpool.Query(ctx,
rows, err := Dbpool.Query(Ctx,
"SELECT song_name, path, file_name, times_played FROM song WHERE game_id = $1", id)
if err != nil {
if compareError.Error() != err.Error() {
@@ -90,7 +90,7 @@ func FindSongsFromGame(id int) []models.SongData {
}
func AddSongPlayed(id int, name string) {
_, err := dbpool.Exec(ctx,
_, err := Dbpool.Exec(Ctx,
"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)
@@ -98,7 +98,7 @@ func AddSongPlayed(id int, name string) {
}
func FetchAllSongs() []models.SongData {
rows, err := dbpool.Query(ctx,
rows, err := Dbpool.Query(Ctx,
"SELECT song_name, path FROM song")
if err != nil {
if compareError.Error() != err.Error() {
@@ -128,7 +128,7 @@ func FetchAllSongs() []models.SongData {
}
func RemoveBrokenSong(song models.SongData) {
_, err := dbpool.Exec(ctx, "DELETE FROM song where path=$1", song.Path)
_, err := Dbpool.Exec(Ctx, "DELETE FROM song where path=$1", song.Path)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}
@@ -141,7 +141,7 @@ func RemoveBrokenSongs(songs []models.SongData) {
}
joined = strings.TrimSuffix(joined, ",")
_, err := dbpool.Exec(ctx, "DELETE FROM song where path in ($1)", joined)
_, err := Dbpool.Exec(Ctx, "DELETE FROM song where path in ($1)", joined)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
}

View File

@@ -8,7 +8,7 @@ import (
)
func InsertSongInList(song models.SongListData) {
_, err := dbpool.Exec(ctx,
_, err := Dbpool.Exec(Ctx,
`INSERT INTO song_list (match_date, match_id, song_no, game_name, song_name) VALUES ($1, $2, $3, $4, $5)`,
song.MatchDate, song.MatchId, song.SongNo, song.GameName, song.SongName)
if err != nil {
@@ -17,7 +17,7 @@ func InsertSongInList(song models.SongListData) {
}
func GetSongList(matchId int) []models.SongListData {
rows, err := dbpool.Query(ctx,
rows, err := Dbpool.Query(Ctx,
"SELECT match_date, match_id, song_no, game_name, song_name "+
"FROM song_list WHERE match_date = $1"+
"ORDER BY song_no DESC", matchId)

34
pkg/server/config.go Normal file
View File

@@ -0,0 +1,34 @@
package server
import (
"fmt"
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
Host string
Port int
User string
Password string
Dbname string
Path string
}
var Conf *Config
func ReadConf(filename string) error {
buf, err := os.ReadFile(filename)
if err != nil {
return err
}
c := &Config{}
err = yaml.Unmarshal(buf, c)
if err != nil {
return fmt.Errorf("in file %q: %w", filename, err)
}
Conf = c
return err
}

View File

@@ -48,6 +48,9 @@ func GetRandomSong() string {
if games == nil || len(games) == 0 {
games = db.FindAllGames()
}
if games == nil || len(games) == 0 {
return ""
}
song := getSongFromList(games)
lastFetched = song

View File

@@ -1,9 +1,14 @@
package server
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
"io/fs"
"log"
"music-server/db/repository"
"music-server/pkg/db"
"music-server/pkg/models"
"os"
@@ -11,17 +16,65 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/MShekow/directory-checksum/directory_checksum"
"github.com/spf13/afero"
)
var allGames []repository.Game
var gamesBeforeSync []repository.Game
var gamesAfterSync []repository.Game
var gamesAdded []string
var gamesReAdded []string
var gamesChangedTitle map[string]string
var gamesChangedContent []string
var gamesRemoved []string
var catchedErrors []string
var brokenSongs []string
type Response struct {
GamesAdded []string `json:"games_added"`
GamesReAdded []string `json:"games_re_added"`
GamesChangedTitle map[string]string `json:"games_changed_title"`
GamesChangedContent []string `json:"games_changed_content"`
GamesRemoved []string `json:"games_removed"`
CatchedErrors []string `json:"catched_errors"`
}
type GameStatus int
const (
NotChanged GameStatus = iota
TitleChanged
GameChanged
NewGame
)
var statusName = map[GameStatus]string{
NotChanged: "Not changed",
TitleChanged: "Title changed",
GameChanged: "Game changed",
NewGame: "New game",
}
func (gs GameStatus) String() string {
return statusName[gs]
}
var syncWg sync.WaitGroup
var repo *repository.Queries
var wg sync.WaitGroup
func SyncGames() {
start := time.Now()
host := os.Getenv("DB_HOST")
var dir string
if host != "" {
dir = "/sorted/"
} else {
dir = "/Users/sebastian/Resilio Sync/Sorterat_test/"
dir = "/Users/sebastian/ResilioSync/Sorterat_test/"
}
fmt.Printf("dir: %s\n", dir)
foldersToSkip := []string{".sync", "dist", "old"}
@@ -37,15 +90,21 @@ func SyncGames() {
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()
host := os.Getenv("DB_HOST")
var dir string
if host != "" {
dir = "/sorted/"
} else {
dir = "/Users/sebastian/Resilio Sync/Sorterat_test/"
dir = "/Users/sebastian/ResilioSync/Sorterat_test/"
}
fmt.Printf("dir: %s\n", dir)
foldersToSkip := []string{".sync", "dist", "old"}
@@ -66,6 +125,11 @@ func SyncGamesQuick() {
}()
}
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) {
@@ -133,7 +197,10 @@ func checkIfChanged(id int, name string, path string) {
func addNewGame(name string, path string) {
newId := db.GetIdByGameName(name)
if newId == -1 {
if newId != -1 {
checkBrokenSongs()
db.RemoveDeletionDate(newId)
} else {
newId = db.InsertGame(name, path)
}
@@ -194,6 +261,401 @@ func checkBrokenSongs() {
db.RemoveBrokenSongs(brokenSongs)
}
func SyncGamesNew() Response {
repo = repository.New(db.Dbpool)
start := time.Now()
fmt.Printf("dir: %s\n", Conf.Path)
foldersToSkip := []string{".sync", "dist", "old"}
fmt.Println(foldersToSkip)
var err error
gamesBeforeSync, err = repo.FindAllGames(db.Ctx)
handleError("FindAllGames Before", err, "")
fmt.Printf("Games Before: %d\n", len(gamesBeforeSync))
allGames, err = repo.GetAllGamesIncludingDeleted(db.Ctx)
handleError("GetAllGamesIncludingDeleted", err, "")
err = repo.SetGameDeletionDate(db.Ctx)
handleError("SetGameDeletionDate", err, "")
directories, err := os.ReadDir(Conf.Path)
if err != nil {
log.Fatal(err)
}
syncWg.Add(len(directories))
for _, dir := range directories {
go func() {
defer syncWg.Done()
syncGameNew(dir, foldersToSkip, Conf.Path)
}()
}
syncWg.Wait()
checkBrokenSongsNew()
gamesAfterSync, err = repo.FindAllGames(db.Ctx)
handleError("FindAllGames After", err, "")
fmt.Printf("\nGames Before: %d\n", len(gamesBeforeSync))
fmt.Printf("Games After: %d\n", len(gamesAfterSync))
fmt.Printf("\nGames added: \n")
for _, game := range gamesAdded {
fmt.Printf("%s\n", game)
}
fmt.Printf("\nGames readded: \n")
for _, game := range gamesReAdded {
fmt.Printf("%s\n", game)
}
fmt.Printf("\nGames with changed title: \n")
for key, value := range gamesChangedTitle {
fmt.Printf("The game: %s changed title to: %s\n", key, value)
}
fmt.Printf("\nGames with changed content: \n")
for _, game := range gamesChangedContent {
fmt.Printf("%s\n", game)
}
fmt.Printf("\n\n")
var gamesRemovedTemp []string
for _, beforeGame := range gamesBeforeSync {
var found bool = false
for _, afterGame := range gamesAfterSync {
if beforeGame.GameName == afterGame.GameName {
found = true
fmt.Printf("Game: %s, Found: %v break\n", beforeGame.GameName, found)
break
}
}
if !found {
fmt.Printf("Game: %s, Found: %v\n", beforeGame.GameName, found)
gamesRemovedTemp = append(gamesRemovedTemp, beforeGame.GameName)
}
}
for _, game := range gamesRemovedTemp {
var found bool = false
for key, _ := range gamesChangedTitle {
if game == key {
found = true
fmt.Printf("Game: %s, Found: %v break2\n", game, found)
break
}
}
if !found {
gamesRemoved = append(gamesRemoved, game)
}
}
fmt.Printf("\nGames removed: \n")
for _, game := range gamesRemoved {
fmt.Printf("%s\n", game)
}
fmt.Printf("\nErrors catched: \n")
for _, error := range catchedErrors {
fmt.Printf("%s\n", error)
}
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"))
return Response{
GamesAdded: gamesAdded,
GamesReAdded: gamesReAdded,
GamesChangedTitle: gamesChangedTitle,
GamesChangedContent: gamesChangedContent,
GamesRemoved: gamesRemoved,
CatchedErrors: catchedErrors,
}
}
func checkBrokenSongsNew() {
allSongs, err := repo.FetchAllSongs(db.Ctx)
handleError("FetchAllSongs", err, "")
var brokenWg sync.WaitGroup
brokenWg.Add(len(allSongs))
for _, song := range allSongs {
go func() {
defer brokenWg.Done()
checkBrokenSongNew(song)
}()
}
brokenWg.Wait()
err = repo.RemoveBrokenSongs(db.Ctx, brokenSongs)
handleError("RemoveBrokenSongs", err, "")
}
func checkBrokenSongNew(song repository.Song) {
//Check if file exists and open
openFile, err := os.Open(song.Path)
if err != nil {
//File not found
brokenSongs = append(brokenSongs, song.Path)
fmt.Printf("song broken: %v\n", song.Path)
} else {
err = openFile.Close()
if err != nil {
log.Println(err)
}
}
}
func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string) {
if file.IsDir() && !contains(foldersToSkip, file.Name()) {
gameDir := baseDir + file.Name() + "/"
dirHash := getHashForDir(gameDir)
var status GameStatus = NewGame
var oldGame repository.Game
var id int32 = -1
//fmt.Printf("Games before: %d\n", len(gamesBeforeSync))
for _, currentGame := range allGames {
oldGame = currentGame
//fmt.Printf("%s | %s\n", oldGame.GameName, oldGame.Hash)
if oldGame.GameName == file.Name() && oldGame.Hash == dirHash {
status = NotChanged
id = oldGame.ID
//fmt.Printf("Game not changed\n")
break
} else if oldGame.GameName == file.Name() && oldGame.Hash != dirHash {
status = GameChanged
id = oldGame.ID
//fmt.Printf("Game changed\n")
break
} else if oldGame.GameName != file.Name() && oldGame.Hash == dirHash {
status = TitleChanged
id = oldGame.ID
//fmt.Printf("GameName changed\n")
break
}
}
fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status)
entries, err := os.ReadDir(gameDir)
if err != nil {
log.Println(err)
}
switch status {
case NewGame:
if id != -1 {
for _, entry := range entries {
fileInfo, err := entry.Info()
if err != nil {
log.Println(err)
}
id = getIdFromFileNew(fileInfo)
if id != -1 {
break
}
}
err = repo.InsertGameWithExistingId(db.Ctx, repository.InsertGameWithExistingIdParams{ID: id, GameName: file.Name(), Path: gameDir, Hash: dirHash})
handleError("InsertGameWithExistingId", err, "")
if err != nil {
fmt.Printf("id = %v\n", id)
fileName := gameDir + "/." + strconv.Itoa(int(id)) + ".id"
fmt.Printf("fileName = %v\n", fileName)
err := os.Remove(fileName)
if err != nil {
fmt.Printf("%s\n", err)
}
newDirHash := getHashForDir(gameDir)
id = insertGameNew(file.Name(), gameDir, newDirHash)
}
} else {
id = insertGameNew(file.Name(), gameDir, dirHash)
}
gamesAdded = append(gamesAdded, file.Name())
newCheckSongs(entries, gameDir, id)
case GameChanged:
err = repo.UpdateGameHash(db.Ctx, repository.UpdateGameHashParams{Hash: dirHash, ID: id})
handleError("UpdateGameHash", err, "")
gamesChangedContent = append(gamesChangedContent, file.Name())
newCheckSongs(entries, gameDir, id)
case TitleChanged:
//println("TitleChanged")
err = repo.UpdateGameName(db.Ctx, repository.UpdateGameNameParams{Name: file.Name(), Path: gameDir, ID: id})
handleError("UpdateGameName", err, "")
newCheckSongs(entries, gameDir, id)
if gamesChangedTitle == nil {
gamesChangedTitle = make(map[string]string)
}
gamesChangedTitle[oldGame.GameName] = file.Name()
case NotChanged:
//println("NotChanged")
var found bool = false
for _, beforeGame := range gamesBeforeSync {
if dirHash == beforeGame.Hash {
found = true
//fmt.Printf("Game %s | %s | %s | %s | %v\n", beforeGame.GameName, beforeGame.Hash, dirHash, status, beforeGame.Deleted)
}
}
if !found {
newCheckSongs(entries, gameDir, id)
gamesReAdded = append(gamesReAdded, file.Name())
}
}
err = repo.RemoveDeletionDate(db.Ctx, id)
handleError("RemoveDeletionDate", err, "")
}
}
func insertGameNew(name string, path string, hash string) int32 {
var duplicateError = errors.New("ERROR: duplicate key value violates unique")
id, err := repo.InsertGame(db.Ctx, repository.InsertGameParams{GameName: name, Path: path, Hash: hash})
handleError("InsertGame", err, "")
if err != nil {
fmt.Printf("Handle id busy\n")
if strings.HasPrefix(err.Error(), duplicateError.Error()) {
fmt.Printf("Handeling this id\n")
_, err = repo.ResetGameIdSeq(db.Ctx)
handleError("ResetGameIdSeq", err, "")
id = insertGameNew(name, path, hash)
}
}
return id
}
func newCheckSongs(entries []os.DirEntry, gameDir string, id int32) int32 {
//hasher := md5.New()
var numberOfSongs int32
var songWg sync.WaitGroup
songWg.Add(len(entries))
for _, entry := range entries {
go func() {
defer songWg.Done()
newCheckSong(entry, gameDir, id)
}()
}
songWg.Wait()
return numberOfSongs
}
func newCheckSong(entry os.DirEntry, gameDir string, id int32) {
fileInfo, err := entry.Info()
if err != nil {
log.Println(err)
}
if isSong(fileInfo) {
path := gameDir + entry.Name()
songHash := getHashForFile(path)
//numberOfSongs++
fileName := entry.Name()
songName, _ := strings.CutSuffix(fileName, ".mp3")
song, err := repo.GetSongWithHash(db.Ctx, songHash)
handleError("GetSongWithHash", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
if err == nil {
if song.SongName == songName && song.Path == path {
return
}
}
fmt.Printf("Song Changed\n")
fmt.Printf("Path: %s | SongHash: %s\n", path, songHash)
count, err := repo.CheckSongWithHash(db.Ctx, songHash)
handleError("CheckSongWithHash", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
if err != nil {
count2, err := repo.CheckSong(db.Ctx, path)
handleError("CheckSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
if count2 > 0 {
err = repo.AddHashToSong(db.Ctx, repository.AddHashToSongParams{Hash: songHash, Path: path})
handleError("AddHashToSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
count, err = repo.CheckSongWithHash(db.Ctx, songHash)
handleError("CheckSongWithHash 2", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
}
}
//count, _ := repo.CheckSong(ctx, path)
if count > 0 {
err = repo.UpdateSong(db.Ctx, repository.UpdateSongParams{SongName: songName, FileName: &fileName, Path: path, Hash: songHash})
handleError("UpdateSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
} else {
count2, err := repo.CheckSong(db.Ctx, path)
handleError("CheckSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
if count2 > 0 {
err = repo.AddHashToSong(db.Ctx, repository.AddHashToSongParams{Hash: songHash, Path: path})
handleError("AddHashToSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
} else {
err = repo.AddSong(db.Ctx, repository.AddSongParams{GameID: id, SongName: songName, Path: path, FileName: &fileName, Hash: songHash})
handleError("AddSong", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
}
}
} else if isCoverImage(fileInfo) {
//TODO: Later add cover art image here in db
}
}
func handleError(funcName string, err error, msg string) {
var compareError = errors.New("no rows in result set")
if err != nil {
if compareError.Error() != err.Error() {
fmt.Printf("%s Error: %s\n", funcName, err)
if msg != "" {
fmt.Printf("%s\n", msg)
catchedErrors = append(catchedErrors, fmt.Sprintf("Func: %s\nError message: %s\nDebug message: %s\n", funcName, err, msg))
} else {
catchedErrors = append(catchedErrors, fmt.Sprintf("Func: %s\nError message: %s\n", funcName, err))
}
}
}
}
func getHashForDir(gameDir string) string {
directory, _ := directory_checksum.ScanDirectory(gameDir, afero.NewOsFs())
hash, _ := directory.ComputeDirectoryChecksums()
//fmt.Printf("Hash: |%s|\n", hash)
return hash
}
func getHashForFile(path string) string {
hasher := md5.New()
readFile, err := os.Open(path)
if err != nil {
panic(err)
}
defer readFile.Close()
hasher.Reset()
_, err = io.Copy(hasher, readFile)
if err != nil {
log.Fatal(err)
}
return hex.EncodeToString(hasher.Sum(nil))
}
func getIdFromFileNew(file os.FileInfo) int32 {
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 int32(i)
}
return -1
}
func isSong(entry fs.FileInfo) bool {
return !entry.IsDir() && strings.HasSuffix(entry.Name(), ".mp3")
}