From 2380a362711c3b2812bb5db2a0c2e0b5089125d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lie=20DELHAIE?= Date: Sat, 10 May 2025 21:52:52 +0200 Subject: [PATCH] first alpha version cli --- cmd/cli/commands/add/add.go | 10 ++-- cmd/cli/commands/list/list.go | 45 +++++++++++++++ cmd/cli/commands/remote/remote.go | 67 +++++++++++++++++++++++ cmd/cli/commands/remove/remove.go | 40 ++++++++++++++ cmd/cli/commands/run/run.go | 10 ++-- cmd/cli/main.go | 11 +++- pkg/game/game.go | 13 +++++ pkg/remote/remote.go | 91 +++++++++++++++++++++++++++++++ 8 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 cmd/cli/commands/list/list.go create mode 100644 cmd/cli/commands/remote/remote.go create mode 100644 cmd/cli/commands/remove/remove.go create mode 100644 pkg/remote/remote.go diff --git a/cmd/cli/commands/add/add.go b/cmd/cli/commands/add/add.go index 6f89288..b8cfe86 100644 --- a/cmd/cli/commands/add/add.go +++ b/cmd/cli/commands/add/add.go @@ -17,19 +17,19 @@ type ( } ) -func (AddCmd) Name() string { return "add" } -func (AddCmd) Synopsis() string { return "Add a folder to the sync list" } -func (AddCmd) Usage() string { +func (*AddCmd) Name() string { return "add" } +func (*AddCmd) Synopsis() string { return "Add a folder to the sync list" } +func (*AddCmd) Usage() string { return `add: Add a folder to the sync list ` } -func (p AddCmd) SetFlags(f *flag.FlagSet) { +func (p *AddCmd) SetFlags(f *flag.FlagSet) { f.StringVar(&p.name, "name", "", "Override the name of the game") } -func (p AddCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { +func (p *AddCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { if f.NArg() != 1 { fmt.Fprintln(os.Stderr, "error: the command is expecting for 1 argument") return subcommands.ExitUsageError diff --git a/cmd/cli/commands/list/list.go b/cmd/cli/commands/list/list.go new file mode 100644 index 0000000..ecbe152 --- /dev/null +++ b/cmd/cli/commands/list/list.go @@ -0,0 +1,45 @@ +package list + +import ( + "cloudsave/pkg/game" + "context" + "flag" + "fmt" + "os" + + "github.com/google/subcommands" +) + +type ( + ListCmd struct { + name string + } +) + +func (*ListCmd) Name() string { return "list" } +func (*ListCmd) Synopsis() string { return "list all game registered" } +func (*ListCmd) Usage() string { + return `add: + List all game registered +` +} + +func (p *ListCmd) SetFlags(f *flag.FlagSet) { + f.StringVar(&p.name, "name", "", "Override the name of the game") +} + +func (p *ListCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + datastore, err := game.All() + if err != nil { + fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err) + return subcommands.ExitFailure + } + + fmt.Println("ID | NAME | PATH") + fmt.Println("-- | ---- | ----") + for _, metadata := range datastore { + fmt.Println(metadata.ID, "|", metadata.Name, "|", metadata.Path) + } + + return subcommands.ExitSuccess +} diff --git a/cmd/cli/commands/remote/remote.go b/cmd/cli/commands/remote/remote.go new file mode 100644 index 0000000..86530c5 --- /dev/null +++ b/cmd/cli/commands/remote/remote.go @@ -0,0 +1,67 @@ +package remote + +import ( + "cloudsave/pkg/remote" + "context" + "flag" + "fmt" + "os" + + "github.com/google/subcommands" +) + +type ( + RemoteCmd struct { + set bool + list bool + } +) + +func (*RemoteCmd) Name() string { return "remote" } +func (*RemoteCmd) Synopsis() string { return "manage remote" } +func (*RemoteCmd) Usage() string { + return `remote: + manage remove +` +} + +func (p *RemoteCmd) SetFlags(f *flag.FlagSet) { + f.BoolVar(&p.list, "list", false, "list remotes") + f.BoolVar(&p.set, "set", false, "set remote for a game") +} + +func (p *RemoteCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if p.list { + remotes, err := remote.All() + if err != nil { + fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err) + return subcommands.ExitFailure + } + + fmt.Println("ID | REMOTE URL") + fmt.Println("-- | ----------") + for _, remote := range remotes { + fmt.Println(remote.GameID, "|", remote.URL) + } + return subcommands.ExitSuccess + } + + if p.set { + if f.NArg() != 2 { + fmt.Fprintln(os.Stderr, "error: the command is expecting for 2 arguments") + f.Usage() + return subcommands.ExitUsageError + } + + err := remote.Set(f.Arg(0), f.Arg(1)) + if err != nil { + fmt.Fprintln(os.Stderr, "error: failed to set remote:", err) + return subcommands.ExitFailure + } + fmt.Println(f.Arg(0)) + return subcommands.ExitSuccess + } + + f.Usage() + return subcommands.ExitUsageError +} diff --git a/cmd/cli/commands/remove/remove.go b/cmd/cli/commands/remove/remove.go new file mode 100644 index 0000000..ee051c7 --- /dev/null +++ b/cmd/cli/commands/remove/remove.go @@ -0,0 +1,40 @@ +package remove + +import ( + "cloudsave/pkg/game" + "context" + "flag" + "fmt" + "os" + + "github.com/google/subcommands" +) + +type ( + RemoveCmd struct{} +) + +func (*RemoveCmd) Name() string { return "remove" } +func (*RemoveCmd) Synopsis() string { return "unregister a game" } +func (*RemoveCmd) Usage() string { + return `remove: + Unregister a game +` +} + +func (p *RemoveCmd) SetFlags(f *flag.FlagSet) { +} + +func (p *RemoveCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if f.NArg() != 1 { + fmt.Fprintln(os.Stderr, "error: the command is expecting for 1 argument") + return subcommands.ExitUsageError + } + + err := game.Remove(f.Arg(0)) + if err != nil { + fmt.Fprintln(os.Stderr, "error: failed to unregister the game:", err) + return subcommands.ExitFailure + } + return subcommands.ExitSuccess +} diff --git a/cmd/cli/commands/run/run.go b/cmd/cli/commands/run/run.go index 422c5e4..8978951 100644 --- a/cmd/cli/commands/run/run.go +++ b/cmd/cli/commands/run/run.go @@ -20,17 +20,17 @@ type ( } ) -func (RunCmd) Name() string { return "run" } -func (RunCmd) Synopsis() string { return "Check and process all the folder" } -func (RunCmd) Usage() string { +func (*RunCmd) Name() string { return "run" } +func (*RunCmd) Synopsis() string { return "Check and process all the folder" } +func (*RunCmd) Usage() string { return `run: Check and process all the folder ` } -func (p RunCmd) SetFlags(f *flag.FlagSet) {} +func (p *RunCmd) SetFlags(f *flag.FlagSet) {} -func (p RunCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { +func (p *RunCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { datastore, err := game.All() if err != nil { fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err) diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 751011a..2fb7ba5 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -2,6 +2,9 @@ package main import ( "cloudsave/cmd/cli/commands/add" + "cloudsave/cmd/cli/commands/list" + "cloudsave/cmd/cli/commands/remote" + "cloudsave/cmd/cli/commands/remove" "cloudsave/cmd/cli/commands/run" "context" "flag" @@ -15,8 +18,12 @@ func main() { subcommands.Register(subcommands.FlagsCommand(), "help") subcommands.Register(subcommands.CommandsCommand(), "help") - subcommands.Register(add.AddCmd{}, "management") - subcommands.Register(run.RunCmd{}, "management") + subcommands.Register(&add.AddCmd{}, "management") + subcommands.Register(&run.RunCmd{}, "management") + subcommands.Register(&list.ListCmd{}, "management") + subcommands.Register(&remove.RemoveCmd{}, "management") + + subcommands.Register(&remote.RemoteCmd{}, "remote") flag.Parse() ctx := context.Background() diff --git a/pkg/game/game.go b/pkg/game/game.go index aad3467..919d837 100644 --- a/pkg/game/game.go +++ b/pkg/game/game.go @@ -42,6 +42,11 @@ func Add(name, path string) (Metadata, error) { Path: path, } + err := os.MkdirAll(filepath.Join(datastorepath, m.ID), 0740) + if err != nil { + panic("cannot make directory for the game:" + err.Error()) + } + f, err := os.OpenFile(filepath.Join(datastorepath, m.ID, "metadata.json"), os.O_CREATE|os.O_WRONLY, 0740) if err != nil { return Metadata{}, fmt.Errorf("cannot open the metadata file in the datastore: %w", err) @@ -84,3 +89,11 @@ func All() ([]Metadata, error) { func DatastorePath() string { return datastorepath } + +func Remove(gameID string) error { + err := os.RemoveAll(filepath.Join(datastorepath, gameID)) + if err != nil { + return err + } + return nil +} diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go new file mode 100644 index 0000000..84c40ff --- /dev/null +++ b/pkg/remote/remote.go @@ -0,0 +1,91 @@ +package remote + +import ( + "cloudsave/pkg/game" + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +type ( + Remote struct { + URL string `json:"url"` + GameID string `json:"-"` + } +) + +var ( + roaming string + datastorepath string +) + +func init() { + var err error + roaming, err = os.UserConfigDir() + if err != nil { + panic("failed to get user config path: " + err.Error()) + } + + datastorepath = filepath.Join(roaming, "cloudsave", "data") + err = os.MkdirAll(datastorepath, 0740) + if err != nil { + panic("cannot make the datastore:" + err.Error()) + } +} + +func All() ([]Remote, error) { + ds, err := os.ReadDir(datastorepath) + if err != nil { + return nil, fmt.Errorf("cannot open the datastore: %w", err) + } + + var remotes []Remote + for _, d := range ds { + content, err := os.ReadFile(filepath.Join(datastorepath, d.Name(), "remote.json")) + if err != nil { + continue + } + + var r Remote + err = json.Unmarshal(content, &r) + if err != nil { + return nil, fmt.Errorf("corrupted datastore: failed to parse %s/remote.json: %w", d.Name(), err) + } + + content, err = os.ReadFile(filepath.Join(datastorepath, d.Name(), "metadata.json")) + if err != nil { + return nil, fmt.Errorf("corrupted datastore: failed to read %s/metadata.json: %w", d.Name(), err) + } + + var m game.Metadata + err = json.Unmarshal(content, &m) + if err != nil { + return nil, fmt.Errorf("corrupted datastore: failed to parse %s/metadata.json: %w", d.Name(), err) + } + + r.GameID = m.ID + remotes = append(remotes, r) + } + return remotes, nil +} + +func Set(gameID, url string) error { + r := Remote{ + URL: url, + } + + f, err := os.OpenFile(filepath.Join(datastorepath, gameID, "remote.json"), os.O_WRONLY|os.O_CREATE, 0740) + if err != nil { + return err + } + defer f.Close() + + e := json.NewEncoder(f) + err = e.Encode(r) + if err != nil { + return err + } + + return nil +}