Compare commits

..

No commits in common. 'main' and '1.3.5' have entirely different histories.
main ... 1.3.5

  1. 3
      .gitignore
  2. 2
      Makefile
  3. 66
      OBM.go
  4. 9
      README.md
  5. 16
      flags.go
  6. 3
      go.mod
  7. 0
      logger/logger.go
  8. 11
      manager/beatmap.go
  9. 10
      manager/parse.go
  10. 2
      manager/remove.go
  11. 4
      manager/replace.go
  12. 4
      manager/retrieve.go
  13. 0
      manager/search.go
  14. 15
      settings/settings.go
  15. 0
      settings/structure.go
  16. 3
      src/go.mod
  17. 0
      util/background.go
  18. 2
      util/checks.go
  19. 0
      util/copy.go
  20. 51
      worker.go

3
.gitignore vendored

@ -1,3 +0,0 @@
settings.json
OBM
logs

2
Makefile

@ -1,2 +0,0 @@
all:
cd src && go build && mv OBM ..

66
src/OBM.go → OBM.go

@ -7,68 +7,12 @@ import (
"sync" "sync"
"time" "time"
"unbewohnte/OBM/logger" "github.com/Unbewohnte/OBM/logger"
"unbewohnte/OBM/manager" "github.com/Unbewohnte/OBM/manager"
"unbewohnte/OBM/settings" "github.com/Unbewohnte/OBM/settings"
"unbewohnte/OBM/util" "github.com/Unbewohnte/OBM/util"
) )
const orderMessage string = "Order: Replace -> Retrieve -> Remove\n"
var (
cmdlnBeatmap = flag.String("beatmap", "", `Specifies a certain beatmap.
If set to non "" - the program will search for given name and perform the magic
provided in settings if successful`)
showOrder = flag.Bool("showOrder", false, "Prints an order in which functions are performed on a beatmap")
)
// a basic implementation of a concurrent worker
func worker(jobs <-chan job, results chan result, WG *sync.WaitGroup) {
defer WG.Done()
for job := range jobs {
var successful, failed uint = 0, 0
// the order is: Replace->Retrieve->Remove (if all 3 options are enabled)
if job.replacementImagePath != "" {
s, f := job.beatmap.ReplaceBackgrounds(job.replacementImagePath)
successful += s
failed += f
}
if job.retrievementPath != "" {
s, f := job.beatmap.RetrieveBackgrounds(job.retrievementPath)
successful += s
failed += f
}
if job.remove == true {
s, f := job.beatmap.RemoveBackgrounds()
successful += s
failed += f
}
results <- result{
beatmapName: job.beatmap.Name,
numberOfDiffs: uint(len(job.beatmap.Diffs)),
successful: successful,
failed: failed,
}
}
}
// the `starter` that `glues` workers and jobs together
func workerPool(jobs chan job, results chan result, numOfWorkers int, WG *sync.WaitGroup) {
// check if there are less jobs than workers
if numOfWorkers > len(jobs) {
numOfWorkers = len(jobs)
}
// replacing backgrounds for each beatmap concurrently
for i := 0; i < numOfWorkers; i++ {
WG.Add(1)
go worker(jobs, results, WG)
}
}
type result struct { type result struct {
beatmapName string beatmapName string
numberOfDiffs uint numberOfDiffs uint
@ -99,7 +43,7 @@ func init() {
// parse for `-beatmap` argument // parse for `-beatmap` argument
flag.Parse() flag.Parse()
// if `-showOrder` is checked - show the message and exit // if `-showOrder` is checked - show the message
if *showOrder == true { if *showOrder == true {
fmt.Print(orderMessage) fmt.Print(orderMessage)
os.Exit(0) os.Exit(0)

9
README.md

@ -1,7 +1,6 @@
# OBM (Osu!-Background-Manager) # OBM (Osu!-Background-Manager)
## This utility will help you with replacement, retrievement and removement of Osu!\`s beatmaps\` backgrounds ## This utility will help you with replacement and retrievement of Osu!\`s beatmaps\` backgrounds
## NOTE: does NOT work with Osu!Lazer (Lazer has a different handling for beatmaps)
**Use at your own risk !** **Use at your own risk !**
There is no way to return removed original backgrounds unless you delete all beatmaps and reimport newly downloaded versions of them again. There is no way to return removed original backgrounds unless you delete all beatmaps and reimport newly downloaded versions of them again.
@ -24,7 +23,7 @@ There is no way to return removed original backgrounds unless you delete all bea
--- ---
## Usage ## Usage
To run - `./OBM` in terminal (on Unix) || `OBM.exe` in command line (on Windows) (a simple double-click on exe will certainly work as well) To run - `./OBM` in terminal (on Unix) || `OBM` in command line (on Windows) (a simple double-click on exe will certainly work as well)
### First run ### First run
1. The program will generate a settings.json file if it is not already in the directory when you run it 1. The program will generate a settings.json file if it is not already in the directory when you run it
@ -36,8 +35,8 @@ To run - `./OBM` in terminal (on Unix) || `OBM.exe` in command line (on Windows)
1. Start the utility again. If it has found the settings file - it will perform the magic according to provided rules 1. Start the utility again. If it has found the settings file - it will perform the magic according to provided rules
### Flags ### Flags (starting from version 1.3.4)
Right now there are 2 arguments that you can specify before running the program - "beatmap" and "showOrder". Right now there is 2 arguments that you can specify before running the program - "beatmap" and "showOrder".
"-beatmap" flag takes a string; it will tell the program to do its work **ONLY** on beatmaps with specified name; others will be ignored. "-beatmap" flag takes a string; it will tell the program to do its work **ONLY** on beatmaps with specified name; others will be ignored.
The names of beatmaps in Osu! consist an id, artist and the name of the soundtrack, so you can The names of beatmaps in Osu! consist an id, artist and the name of the soundtrack, so you can
specify any name in the flag that will contain one of those parts. specify any name in the flag that will contain one of those parts.

16
flags.go

@ -0,0 +1,16 @@
package main
import (
"flag"
)
var (
orderMessage string = "Order: Replace -> Retrieve -> Remove\n"
)
var (
cmdlnBeatmap = flag.String("beatmap", "", `Specifies a certain beatmap.
If set to non "" - the program will search for given name and perform the magic
provided in settings if successful`)
showOrder = flag.Bool("showOrder", false, "Prints an order in which functions are performed on a beatmap")
)

3
go.mod

@ -0,0 +1,3 @@
module github.com/Unbewohnte/OBM
go 1.16

0
src/logger/logger.go → logger/logger.go

11
src/manager/beatmap.go → manager/beatmap.go

@ -1,11 +1,12 @@
package manager package manager
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"unbewohnte/OBM/util" "github.com/Unbewohnte/OBM/util"
) )
// the main beatmap struct, contains necessary data for functions // the main beatmap struct, contains necessary data for functions
@ -21,10 +22,10 @@ func getSongsDir(baseOsuDir string) (string, error) {
stat, err := os.Stat(songsDir) stat, err := os.Stat(songsDir)
if err != nil { if err != nil {
return "", fmt.Errorf("could not process the given path : %s", err) return "", errors.New(fmt.Sprintf("Could not process the given path : %s", err))
} }
if !stat.IsDir() { if !stat.IsDir() {
return "", fmt.Errorf("given Osu! directory is not a directory") return "", errors.New("Given Osu! directory is not a directory !")
} }
return songsDir, nil return songsDir, nil
@ -34,7 +35,7 @@ func getSongsDir(baseOsuDir string) (string, error) {
func getDiffs(path string) ([]string, error) { func getDiffs(path string) ([]string, error) {
files, err := os.ReadDir(path) files, err := os.ReadDir(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read a directory : %s", err) return nil, errors.New(fmt.Sprintf("Could not read a directory : %s", err))
} }
var diffs []string var diffs []string
@ -64,7 +65,7 @@ func GetBeatmaps(baseOsuDir string) ([]Beatmap, error) {
} }
contents, err := os.ReadDir(songsDir) contents, err := os.ReadDir(songsDir)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read a directory : %s", err) return nil, errors.New(fmt.Sprintf("Could not read a directory : %s", err))
} }
var beatmaps []Beatmap var beatmaps []Beatmap

10
src/manager/parse.go → manager/parse.go

@ -1,18 +1,18 @@
package manager package manager
import ( import (
"fmt" "errors"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"unbewohnte/OBM/util" "github.com/Unbewohnte/OBM/util"
) )
// parses given .osu file and returns the filename of its background // parses given .osu file and returns the filename of its background
// NOTE: Osu! beatmap (as whole) can have multiple backgrounds for each .osu file // NOTE: Osu! beatmap (as whole) can have multiple backgrounds for each .osu file
// the perfect example : https://osu.ppy.sh/beatmapsets/43701#osu/137122 // the perfect example : https://osu.ppy.sh/beatmapsets/43701#osu/137122
// this is why this function asks for a certain difficulty (.osu filename) to be sure // this is why this functions asks for a certain difficulty (.osu filename) to be sure
// to return the correct background name // to return the correct background name
func (BEATMAP *Beatmap) GetBackgroundName(diff string) (string, error) { func (BEATMAP *Beatmap) GetBackgroundName(diff string) (string, error) {
beatmapBytes, err := os.ReadFile(filepath.Join(BEATMAP.Path, diff)) beatmapBytes, err := os.ReadFile(filepath.Join(BEATMAP.Path, diff))
@ -24,13 +24,13 @@ func (BEATMAP *Beatmap) GetBackgroundName(diff string) (string, error) {
// get index of "[Events]" (this is where BG filename is stored) // get index of "[Events]" (this is where BG filename is stored)
eventsIndex := strings.Index(beatmapContents, "[Events]") eventsIndex := strings.Index(beatmapContents, "[Events]")
if eventsIndex == -1 { if eventsIndex == -1 {
return "", fmt.Errorf("could not retrieve index of \"[Events]\"") return "", errors.New("Could not retrieve index of \"[Events]\"")
} }
// get index of [TimingPoints] (this tag is right after the previous "[Events]" tag, // get index of [TimingPoints] (this tag is right after the previous "[Events]" tag,
// so we can grab the whole "[Events]" tag contents) // so we can grab the whole "[Events]" tag contents)
timingPointsIndex := strings.Index(beatmapContents, "[TimingPoints]") timingPointsIndex := strings.Index(beatmapContents, "[TimingPoints]")
if timingPointsIndex == -1 { if timingPointsIndex == -1 {
return "", fmt.Errorf("could not retrieve index of \"[TimingPoints]\"") return "", errors.New("Could not retrieve index of \"[TimingPoints]\"")
} }
contentBetween := strings.Split(beatmapContents[eventsIndex:timingPointsIndex], ",") contentBetween := strings.Split(beatmapContents[eventsIndex:timingPointsIndex], ",")

2
src/manager/remove.go → manager/remove.go

@ -5,7 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"unbewohnte/OBM/logger" "github.com/Unbewohnte/OBM/logger"
) )
// parses each difficulty for background info, removes found backgrounds // parses each difficulty for background info, removes found backgrounds

4
src/manager/replace.go → manager/replace.go

@ -5,8 +5,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"unbewohnte/OBM/logger" "github.com/Unbewohnte/OBM/logger"
"unbewohnte/OBM/util" "github.com/Unbewohnte/OBM/util"
) )
// parses each beatmap`s .osu file for background info; // parses each beatmap`s .osu file for background info;

4
src/manager/retrieve.go → manager/retrieve.go

@ -5,8 +5,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"unbewohnte/OBM/logger" "github.com/Unbewohnte/OBM/logger"
"unbewohnte/OBM/util" "github.com/Unbewohnte/OBM/util"
) )
// retrieves backgrounds from given beatmap folder (same as in `ReplaceBackgrounds`) and copies them to the retrievement path // retrieves backgrounds from given beatmap folder (same as in `ReplaceBackgrounds`) and copies them to the retrievement path

0
src/manager/search.go → manager/search.go

15
src/settings/settings.go → settings/settings.go

@ -2,11 +2,12 @@ package settings
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"unbewohnte/OBM/logger" "github.com/Unbewohnte/OBM/logger"
"unbewohnte/OBM/util" "github.com/Unbewohnte/OBM/util"
) )
const ( const (
@ -17,7 +18,7 @@ const (
func DoesExist() (bool, error) { func DoesExist() (bool, error) {
files, err := os.ReadDir(".") files, err := os.ReadDir(".")
if err != nil { if err != nil {
return false, fmt.Errorf("wasn`t able to read current directory %s", err) return false, errors.New(fmt.Sprintf("Unable to read current directory %s", err))
} }
for _, file := range files { for _, file := range files {
@ -41,9 +42,8 @@ func Create() error {
file, err := os.Create(settingsFilename) file, err := os.Create(settingsFilename)
if err != nil { if err != nil {
return fmt.Errorf("could not create settings file : %s", err) return errors.New(fmt.Sprintf("Unable to create settings file : %s", err))
} }
defer file.Close()
// marshaling default settings // marshaling default settings
settingsJson, err := json.MarshalIndent(Settings{ settingsJson, err := json.MarshalIndent(Settings{
@ -67,10 +67,11 @@ func Create() error {
Workers: 100, Workers: 100,
}, "", " ") }, "", " ")
if err != nil { if err != nil {
return fmt.Errorf("could not marshal settings into file : %s", err) return errors.New(fmt.Sprintf("Could not marshal settings into file : %s", err))
} }
file.Write(settingsJson) file.Write(settingsJson)
file.Close()
return nil return nil
} }
@ -89,7 +90,7 @@ func Get() Settings {
} }
// if all features are disabled // if all features are disabled
if !settings.BackgroundReplacement.Enabled && !settings.BackgroundRetrievement.Enabled && !settings.BackgroundRemovement.Enabled { if !settings.BackgroundReplacement.Enabled && !settings.BackgroundRetrievement.Enabled {
logger.LogInfo("No features enabled. Exiting...") logger.LogInfo("No features enabled. Exiting...")
os.Exit(0) os.Exit(0)
} }

0
src/settings/structure.go → settings/structure.go

3
src/go.mod

@ -1,3 +0,0 @@
module unbewohnte/OBM
go 1.16

0
src/util/background.go → util/background.go

2
src/util/checks.go → util/checks.go

@ -5,7 +5,6 @@ import (
"strings" "strings"
) )
// checks if given path is a directory
func IsDir(path string) bool { func IsDir(path string) bool {
info, err := os.Stat(path) info, err := os.Stat(path)
if err != nil { if err != nil {
@ -46,7 +45,6 @@ func IsImage(filename string) bool {
return false return false
} }
// checks if given directory/file does exist
func DoesExist(path string) bool { func DoesExist(path string) bool {
_, err := os.Stat(path) _, err := os.Stat(path)
if err != nil { if err != nil {

0
src/util/copy.go → util/copy.go

51
worker.go

@ -0,0 +1,51 @@
package main
import (
"sync"
)
// a basic implementation of a concurrent worker
func worker(jobs <-chan job, results chan result, WG *sync.WaitGroup) {
defer WG.Done()
for job := range jobs {
var successful, failed uint = 0, 0
// the order is: Replace->Retrieve->Remove (if all 3 options are enabled)
if job.replacementImagePath != "" {
s, f := job.beatmap.ReplaceBackgrounds(job.replacementImagePath)
successful += s
failed += f
}
if job.retrievementPath != "" {
s, f := job.beatmap.RetrieveBackgrounds(job.retrievementPath)
successful += s
failed += f
}
if job.remove == true {
s, f := job.beatmap.RemoveBackgrounds()
successful += s
failed += f
}
results <- result{
beatmapName: job.beatmap.Name,
numberOfDiffs: uint(len(job.beatmap.Diffs)),
successful: successful,
failed: failed,
}
}
}
func workerPool(jobs chan job, results chan result, numOfWorkers int, WG *sync.WaitGroup) {
// check if there are less jobs than workers
if numOfWorkers > len(jobs) {
numOfWorkers = len(jobs)
}
// replacing backgrounds for each beatmap concurrently
for i := 0; i < numOfWorkers; i++ {
WG.Add(1)
go worker(jobs, results, WG)
}
}
Loading…
Cancel
Save