diff --git a/OBM.go b/OBM.go index 8d0e2bb..9f1861f 100644 --- a/OBM.go +++ b/OBM.go @@ -12,21 +12,19 @@ import ( "github.com/Unbewohnte/OBM/util" ) -var ( - WG sync.WaitGroup -) +type result struct { + beatmapName string + numberOfDiffs uint + successful uint64 + failed uint64 +} type job struct { - beatmapFolderPath string + beatmap manager.Beatmap replacementImagePath string retrievementPath string } -type result struct { - successful uint64 - failed uint64 -} - func init() { exists, err := settings.DoesExist() if err != nil { @@ -44,6 +42,8 @@ func init() { } func main() { + var WG sync.WaitGroup + startingTime := time.Now() SETTINGS := settings.Get() @@ -58,23 +58,24 @@ func main() { } } - beatmaps, err := manager.GetBeatmapFolderPaths(SETTINGS.OsuDir) + beatmaps, err := manager.GetBeatmaps(SETTINGS.OsuDir) if err != nil { - logger.LogError(true, "Error getting beatmap folders: ", err) + logger.LogError(true, "Error getting beatmaps: ", err) } - logger.LogInfo(fmt.Sprintf("Found %d beatmap folders", len(beatmaps))) + logger.LogInfo(fmt.Sprintf("Found %d beatmaps", len(beatmaps))) // creating jobs for workers jobs := make(chan job, len(beatmaps)) for _, beatmap := range beatmaps { jobs <- job{ - beatmapFolderPath: beatmap, + beatmap: beatmap, replacementImagePath: SETTINGS.BackgroundReplacement.ReplacementImagePath, retrievementPath: SETTINGS.BackgroundRetrievement.RetrievementPath, } } close(jobs) + // perform the magic results := make(chan result, len(jobs)) workerPool(jobs, results, SETTINGS.Workers, &WG) WG.Wait() @@ -85,6 +86,9 @@ func main() { for result := range results { successful += result.successful failed += result.failed + + logger.LogInfo(fmt.Sprintf("Beatmap: %s; Number of diffs: %d;\n Successful: %d; Failed: %d", + result.beatmapName, result.numberOfDiffs, result.successful, result.failed)) } total := successful + failed diff --git a/manager/beatmap.go b/manager/beatmap.go new file mode 100644 index 0000000..f2d6b28 --- /dev/null +++ b/manager/beatmap.go @@ -0,0 +1,116 @@ +package manager + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Unbewohnte/OBM/logger" + "github.com/Unbewohnte/OBM/util" +) + +// the main beatmap struct, contains necessary data for functions +type Beatmap struct { + Name string + Path string + Diffs []string +} + +// parses given .osu file and returns the filename of its background +// NOTE: Osu! beatmap (as whole) can have multiple backgrounds for each .osu file +// the perfect example : https://osu.ppy.sh/beatmapsets/43701#osu/137122 +// this is why this functions asks for a certain difficulty (.osu filename) to be sure +// to return the correct background name +func (BEATMAP *Beatmap) GetBackgroundName(mapName string) (string, error) { + beatmapBytes, err := os.ReadFile(filepath.Join(BEATMAP.Path, mapName)) + if err != nil { + return "", err + } + beatmapContents := string(beatmapBytes) + + // get index of "[Events]" (this is where BG filename is stored) + eventsIndex := strings.Index(beatmapContents, "[Events]") + if eventsIndex == -1 { + return "", errors.New("Could not retrieve index of \"[Events]\"") + } + // get index of [TimingPoints] (this tag is right after the previous "[Events]" tag, + // so we can grab the whole "[Events]" tag contents) + timingPointsIndex := strings.Index(beatmapContents, "[TimingPoints]") + if timingPointsIndex == -1 { + return "", errors.New("Could not retrieve index of \"[TimingPoints]\"") + } + contentBetween := strings.Split(beatmapContents[eventsIndex:timingPointsIndex], ",") + + for _, chunk := range contentBetween { + if util.IsImage(chunk) { + return strings.Split(chunk, "\"")[1], nil + } + } + return "", nil +} + +// parses each beatmap`s .osu file for background info; +// removes original background and replaces it with copied version of given image +func (BEATMAP *Beatmap) ReplaceBackgrounds(replacementPicPath string) (successful, failed uint64) { + // looping through each .osu file of a beatmap + for _, diff := range BEATMAP.Diffs { + background, err := BEATMAP.GetBackgroundName(diff) + if err != nil || background == "" { + logger.LogError(false, fmt.Sprintf("BEATMAP: %s: Error gettings background filename: %s", diff, err)) + failed++ + continue + } + // remove old bg + err = os.Remove(filepath.Join(BEATMAP.Path, background)) + if err != nil { + logger.LogError(false, fmt.Sprintf("BEATMAP: %s: Could not remove the old bg: %s", diff, err)) + failed++ + continue + } + + // copy given picture, thus replacing background + err = util.CopyFile(replacementPicPath, filepath.Join(BEATMAP.Path, background)) + if err != nil { + logger.LogError(false, fmt.Sprintf("BEATMAP: %s: Could not copy: %s", diff, err)) + failed++ + continue + } + successful++ + } + return successful, failed +} + +// retrieves backgrounds from given beatmap folder (same as in `ReplaceBackgrounds`) and copies them to the retrievement path +func (BEATMAP *Beatmap) RetrieveBackgrounds(retrievementPath string) (successful, failed uint64) { + // looping through each .osu file of a beatmap + for _, diff := range BEATMAP.Diffs { + background, err := BEATMAP.GetBackgroundName(diff) + if err != nil || background == "" { + logger.LogError(false, fmt.Sprintf("BEATMAP: %s: Error gettings background filename: %s", diff, err)) + failed++ + continue + } + + // creating directory that represents current beatmap + dstPath := filepath.Join(retrievementPath, BEATMAP.Name) + + err = os.MkdirAll(dstPath, os.ModePerm) + if err != nil { + logger.LogError(false, fmt.Sprintf("BEATMAP: %s: Error creating a directory: %s", diff, err)) + failed++ + continue + } + + // copy background to the `retrievementPath` + err = util.CopyFile(filepath.Join(BEATMAP.Path, background), filepath.Join(dstPath, background)) + if err != nil { + logger.LogError(false, fmt.Sprintf("BEATMAP: %s: Could not copy: %s", diff, err)) + failed++ + continue + } + successful++ + } + return successful, failed +} diff --git a/manager/paths.go b/manager/paths.go new file mode 100644 index 0000000..4269855 --- /dev/null +++ b/manager/paths.go @@ -0,0 +1,82 @@ +package manager + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/Unbewohnte/OBM/util" +) + +// filepath.Joins the main osu directory with its songs folder +func getSongsDir(baseOsuDir string) (string, error) { + songsDir := filepath.Join(baseOsuDir, "Songs") + + stat, err := os.Stat(songsDir) + if err != nil { + return "", errors.New(fmt.Sprintf("Could not process the given path : %s", err)) + } + if !stat.IsDir() { + return "", errors.New("Given Osu! directory is not a directory !") + } + + return songsDir, nil +} + +// checks for .osu files in given path and returns all found instances +func getDiffs(path string) ([]string, error) { + files, err := os.ReadDir(path) + if err != nil { + return nil, errors.New(fmt.Sprintf("Could not read a directory : %s", err)) + } + + var diffs []string + for _, file := range files { + filename := file.Name() + if util.IsBeatmap(filename) { + diffs = append(diffs, filename) + } + } + return diffs, nil +} + +// constructs a `Beatmap` struct and returns it +func newBeatmap(name, path string, diffs []string) Beatmap { + return Beatmap{ + Name: name, + Path: path, + Diffs: diffs, + } +} + +// returns an array of beatmaps from given base Osu! directory +func GetBeatmaps(baseOsuDir string) ([]Beatmap, error) { + songsDir, err := getSongsDir(baseOsuDir) + if err != nil { + return nil, err + } + contents, err := os.ReadDir(songsDir) + if err != nil { + return nil, errors.New(fmt.Sprintf("Could not read a directory : %s", err)) + } + + var beatmaps []Beatmap + // looping through all folders in yourOsuDir/Songs/ directory + for _, file := range contents { + if file.IsDir() { + // retrieving all necessary data for creating a new instance of a beatmap + beatmapName := file.Name() + pathToBeatmap := filepath.Join(songsDir, beatmapName) + diffs, err := getDiffs(pathToBeatmap) + if err != nil { + continue + } + newBeatmap := newBeatmap(beatmapName, pathToBeatmap, diffs) + + beatmaps = append(beatmaps, newBeatmap) + } + } + + return beatmaps, nil +} diff --git a/settings/settings.go b/settings/settings.go index 4068e85..cbafc7e 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -69,7 +69,7 @@ func Create() error { return nil } -// unmarshalls settings.json into struct +// unmarshalls settings.json into struct and processes the edge-cases func Get() Settings { settingsFileContents, err := os.ReadFile(settingsFilename) if err != nil { @@ -82,6 +82,12 @@ func Get() Settings { logger.LogError(true, fmt.Sprintf("Could not unmarshal json file : %s", err)) } + // if all features are disabled + if !settings.BackgroundReplacement.Enabled && !settings.BackgroundRetrievement.Enabled { + logger.LogInfo("No features enabled. Exiting...") + os.Exit(0) + } + // checking for edge cases or mistakes made in the settings file, // enabled and disabled fields if settings.BackgroundReplacement.Enabled { diff --git a/util/copy.go b/util/copy.go index 0a7be28..79f4a15 100644 --- a/util/copy.go +++ b/util/copy.go @@ -8,14 +8,14 @@ import ( ) // opens given files, copies one into another -func CopyFile(src, dst string) error { - srcFile, err := os.Open(src) +func CopyFile(srcPath, dstPath string) error { + srcFile, err := os.Open(srcPath) if err != nil { return errors.New(fmt.Sprintf("Could not open src file : %s", err)) } defer srcFile.Close() - dstFile, err := os.OpenFile(dst, os.O_WRONLY, os.ModePerm) + dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { return errors.New(fmt.Sprintf("Could not open dst file : %s", err)) } diff --git a/worker.go b/worker.go index 81147cd..e797e7a 100644 --- a/worker.go +++ b/worker.go @@ -2,8 +2,6 @@ package main import ( "sync" - - "github.com/Unbewohnte/OBM/manager" ) // a basic implementation of a concurrent worker @@ -13,18 +11,20 @@ func worker(jobs <-chan job, results chan result, WG *sync.WaitGroup) { var successful, failed uint64 = 0, 0 if job.retrievementPath != "" { - s, f := manager.RetrieveBackgrounds(job.beatmapFolderPath, job.retrievementPath) + s, f := job.beatmap.RetrieveBackgrounds(job.retrievementPath) successful += s failed += f } if job.replacementImagePath != "" { - s, f := manager.ReplaceBackgrounds(job.beatmapFolderPath, job.replacementImagePath) + s, f := job.beatmap.ReplaceBackgrounds(job.replacementImagePath) successful += s failed += f } results <- result{ - successful: successful, - failed: failed, + beatmapName: job.beatmap.Name, + numberOfDiffs: uint(len(job.beatmap.Diffs)), + successful: successful, + failed: failed, } }