refacto
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/cli
|
/cli
|
||||||
/server
|
/server
|
||||||
|
/env/
|
||||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -10,7 +10,7 @@
|
|||||||
"type": "go",
|
"type": "go",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "auto",
|
"mode": "auto",
|
||||||
"args": ["sync"],
|
"args": ["list", "-a", "http://localhost:8080"],
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"program": "${workspaceFolder}/cmd/cli"
|
"program": "${workspaceFolder}/cmd/cli"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (p *AddCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
|
|||||||
}
|
}
|
||||||
|
|
||||||
if p.name == "" {
|
if p.name == "" {
|
||||||
p.name = filepath.Base(filepath.Dir(path))
|
p.name = filepath.Base(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := game.Add(p.name, path)
|
m, err := game.Add(p.name, path)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"cloudsave/pkg/game"
|
"cloudsave/pkg/game"
|
||||||
|
"cloudsave/pkg/remote/client"
|
||||||
|
"cloudsave/pkg/tools/prompt/credentials"
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -12,6 +14,7 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
ListCmd struct {
|
ListCmd struct {
|
||||||
|
remote bool
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,20 +27,72 @@ func (*ListCmd) Usage() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ListCmd) SetFlags(f *flag.FlagSet) {
|
func (p *ListCmd) SetFlags(f *flag.FlagSet) {
|
||||||
|
f.BoolVar(&p.remote, "a", false, "list all including remote data")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ListCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
func (p *ListCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
datastore, err := game.All()
|
if p.remote {
|
||||||
if err != nil {
|
if f.NArg() != 1 {
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err)
|
fmt.Fprintln(os.Stderr, "error: missing remote url")
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
|
||||||
|
username, password, err := credentials.Read()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to read std output: %s", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := remote(f.Arg(0), username, password); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
|
if err := local(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
return subcommands.ExitFailure
|
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
|
return subcommands.ExitSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func local() error {
|
||||||
|
games, err := game.All()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load datastore: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range games {
|
||||||
|
fmt.Println("ID:", g.ID)
|
||||||
|
fmt.Println("Name:", g.Name)
|
||||||
|
fmt.Println("Last Version:", g.Date, "( Version Number", g.Version, ")")
|
||||||
|
fmt.Println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func remote(url, username, password string) error {
|
||||||
|
cli := client.New(url, username, password)
|
||||||
|
|
||||||
|
if err := cli.Ping(); err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to the remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
games, err := cli.All()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load games from remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Remote:", url)
|
||||||
|
fmt.Println("---")
|
||||||
|
for _, g := range games {
|
||||||
|
fmt.Println("ID:", g.ID)
|
||||||
|
fmt.Println("Name:", g.Name)
|
||||||
|
fmt.Println("Last Version:", g.Date, "( Version Number", g.Version, ")")
|
||||||
|
fmt.Println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package remote
|
package remote
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cloudsave/pkg/game"
|
||||||
"cloudsave/pkg/remote"
|
"cloudsave/pkg/remote"
|
||||||
|
"cloudsave/pkg/remote/client"
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -30,38 +32,60 @@ func (p *RemoteCmd) SetFlags(f *flag.FlagSet) {
|
|||||||
f.BoolVar(&p.set, "set", false, "set remote for a game")
|
f.BoolVar(&p.set, "set", false, "set remote for a game")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RemoteCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
func (p *RemoteCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
if p.list {
|
switch {
|
||||||
remotes, err := remote.All()
|
case p.list:
|
||||||
if err != nil {
|
{
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err)
|
if err := list(); err != nil {
|
||||||
return subcommands.ExitFailure
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
case p.set:
|
||||||
fmt.Println("ID | REMOTE URL")
|
{
|
||||||
fmt.Println("-- | ----------")
|
if f.NArg() != 2 {
|
||||||
for _, remote := range remotes {
|
subcommands.HelpCommand().Execute(ctx, f, nil)
|
||||||
fmt.Println(remote.GameID, "|", remote.URL)
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
if err := set(f.Arg(0), f.Arg(1)); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return subcommands.ExitSuccess
|
default:
|
||||||
}
|
{
|
||||||
|
subcommands.HelpCommand().Execute(ctx, f, nil)
|
||||||
if p.set {
|
|
||||||
if f.NArg() != 2 {
|
|
||||||
fmt.Fprintln(os.Stderr, "error: the command is expecting for 2 arguments")
|
|
||||||
f.Usage()
|
|
||||||
return subcommands.ExitUsageError
|
return subcommands.ExitUsageError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
|
|
||||||
err := remote.Set(f.Arg(0), f.Arg(1))
|
func list() error {
|
||||||
if err != nil {
|
games, err := game.All()
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to set remote:", err)
|
if err != nil {
|
||||||
return subcommands.ExitFailure
|
return fmt.Errorf("failed to load datastore: %w", err)
|
||||||
}
|
|
||||||
fmt.Println(f.Arg(0))
|
|
||||||
return subcommands.ExitSuccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Usage()
|
for _, g := range games {
|
||||||
return subcommands.ExitUsageError
|
r, err := remote.One(g.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load datastore: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cli := client.New(r.URL, "", "")
|
||||||
|
|
||||||
|
status := "OK"
|
||||||
|
if err := cli.Ping(); err != nil {
|
||||||
|
status = "ERROR: " + err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("'%s' -> %s (%s)\n", g.Name, r.URL, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(gameID, url string) error {
|
||||||
|
return remote.Set(gameID, url)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"cloudsave/pkg/remote/client"
|
"cloudsave/pkg/remote/client"
|
||||||
"cloudsave/pkg/tools/prompt/credentials"
|
"cloudsave/pkg/tools/prompt/credentials"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -34,45 +35,37 @@ func (p *SyncCmd) SetFlags(f *flag.FlagSet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
remotes, err := remote.All()
|
games, err := game.All()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err)
|
fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err)
|
||||||
return subcommands.ExitFailure
|
return subcommands.ExitFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(remotes) == 0 {
|
remoteCred := make(map[string]map[string]string)
|
||||||
fmt.Println("nothing to do: no remote found")
|
for _, g := range games {
|
||||||
return subcommands.ExitSuccess
|
r, err := remote.One(g.ID)
|
||||||
}
|
|
||||||
|
|
||||||
username, password, err := credentials.Read()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to read std output:", err)
|
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range remotes {
|
|
||||||
m, err := game.One(r.GameID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "error: cannot get metadata for this game: %w", err)
|
if errors.Is(err, remote.ErrNoRemote) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err)
|
||||||
return subcommands.ExitFailure
|
return subcommands.ExitFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
client := client.New(r.URL, username, password)
|
cli, err := connect(remoteCred, r)
|
||||||
|
if err != nil {
|
||||||
if !client.Ping() {
|
fmt.Fprintln(os.Stderr, "error: failed to connect to the remote:", err)
|
||||||
slog.Warn("remote is unavailable", "url", r.URL)
|
return subcommands.ExitFailure
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exists, err := client.Exists(r.GameID)
|
exists, err := cli.Exists(r.GameID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(err.Error())
|
slog.Error(err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
if err := push(r.GameID, m, client); err != nil {
|
if err := push(r.GameID, g, cli); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "failed to push:", err)
|
fmt.Fprintln(os.Stderr, "failed to push:", err)
|
||||||
return subcommands.ExitFailure
|
return subcommands.ExitFailure
|
||||||
}
|
}
|
||||||
@@ -85,7 +78,7 @@ func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
hremote, err := client.Hash(r.GameID)
|
hremote, err := cli.Hash(r.GameID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to get the file hash from the remote:", err)
|
fmt.Fprintln(os.Stderr, "error: failed to get the file hash from the remote:", err)
|
||||||
continue
|
continue
|
||||||
@@ -97,7 +90,7 @@ func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteMetadata, err := client.Metadata(r.GameID)
|
remoteMetadata, err := cli.Metadata(r.GameID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to get the game metadata from the remote:", err)
|
fmt.Fprintln(os.Stderr, "error: failed to get the game metadata from the remote:", err)
|
||||||
continue
|
continue
|
||||||
@@ -116,7 +109,7 @@ func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
if vlocal > remoteMetadata.Version {
|
if vlocal > remoteMetadata.Version {
|
||||||
if err := push(r.GameID, m, client); err != nil {
|
if err := push(r.GameID, g, cli); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "failed to push:", err)
|
fmt.Fprintln(os.Stderr, "failed to push:", err)
|
||||||
return subcommands.ExitFailure
|
return subcommands.ExitFailure
|
||||||
}
|
}
|
||||||
@@ -124,7 +117,7 @@ func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
if vlocal < remoteMetadata.Version {
|
if vlocal < remoteMetadata.Version {
|
||||||
if err := pull(r.GameID, client); err != nil {
|
if err := pull(r.GameID, cli); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "failed to push:", err)
|
fmt.Fprintln(os.Stderr, "failed to push:", err)
|
||||||
return subcommands.ExitFailure
|
return subcommands.ExitFailure
|
||||||
}
|
}
|
||||||
@@ -140,56 +133,58 @@ func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
if vlocal == remoteMetadata.Version {
|
if vlocal == remoteMetadata.Version {
|
||||||
g, err := game.One(r.GameID)
|
if err := conflict(r.GameID, g, remoteMetadata, cli); err != nil {
|
||||||
if err != nil {
|
fmt.Fprintln(os.Stderr, "error: failed to resolve conflict:", err)
|
||||||
slog.Warn("a conflict was found but the game is not found in the database")
|
|
||||||
slog.Debug("debug info", "gameID", r.GameID)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("--- /!\\ CONFLICT ---")
|
|
||||||
fmt.Println("----")
|
|
||||||
fmt.Println(g.Name, "(", g.Path, ")")
|
|
||||||
fmt.Println("----")
|
|
||||||
fmt.Println("Your version:", g.Date.Format(time.RFC1123))
|
|
||||||
fmt.Println("Their version:", remoteMetadata.Date.Format(time.RFC1123))
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
res := prompt.Conflict()
|
|
||||||
|
|
||||||
switch res {
|
|
||||||
case prompt.My:
|
|
||||||
{
|
|
||||||
if err := push(r.GameID, m, client); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "failed to push:", err)
|
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case prompt.Their:
|
|
||||||
{
|
|
||||||
if err := pull(r.GameID, client); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "failed to push:", err)
|
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
|
||||||
if err := game.SetVersion(r.GameID, remoteMetadata.Version); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to synchronize version number:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := game.SetDate(r.GameID, remoteMetadata.Date); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "error: failed to synchronize date:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return subcommands.ExitSuccess
|
return subcommands.ExitSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func conflict(gameID string, m, remoteMetadata game.Metadata, cli *client.Client) error {
|
||||||
|
g, err := game.One(gameID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("a conflict was found but the game is not found in the database")
|
||||||
|
slog.Debug("debug info", "gameID", gameID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("--- /!\\ CONFLICT ---")
|
||||||
|
fmt.Println(g.Name, "(", g.Path, ")")
|
||||||
|
fmt.Println("----")
|
||||||
|
fmt.Println("Your version:", g.Date.Format(time.RFC1123))
|
||||||
|
fmt.Println("Their version:", remoteMetadata.Date.Format(time.RFC1123))
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
res := prompt.Conflict()
|
||||||
|
|
||||||
|
switch res {
|
||||||
|
case prompt.My:
|
||||||
|
{
|
||||||
|
if err := push(gameID, m, cli); err != nil {
|
||||||
|
return fmt.Errorf("failed to push: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case prompt.Their:
|
||||||
|
{
|
||||||
|
if err := pull(gameID, cli); err != nil {
|
||||||
|
return fmt.Errorf("failed to push: %w", err)
|
||||||
|
}
|
||||||
|
if err := game.SetVersion(gameID, remoteMetadata.Version); err != nil {
|
||||||
|
return fmt.Errorf("failed to synchronize version number: %w", err)
|
||||||
|
}
|
||||||
|
if err := game.SetDate(gameID, remoteMetadata.Date); err != nil {
|
||||||
|
return fmt.Errorf("failed to synchronize date: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func push(gameID string, m game.Metadata, cli *client.Client) error {
|
func push(gameID string, m game.Metadata, cli *client.Client) error {
|
||||||
archivePath := filepath.Join(game.DatastorePath(), gameID, "data.tar.gz")
|
archivePath := filepath.Join(game.DatastorePath(), gameID, "data.tar.gz")
|
||||||
|
|
||||||
@@ -201,3 +196,30 @@ func pull(gameID string, cli *client.Client) error {
|
|||||||
|
|
||||||
return cli.Pull(gameID, archivePath)
|
return cli.Pull(gameID, archivePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func connect(remoteCred map[string]map[string]string, r remote.Remote) (*client.Client, error) {
|
||||||
|
var cli *client.Client
|
||||||
|
|
||||||
|
if v, ok := remoteCred[r.URL]; ok {
|
||||||
|
cli = client.New(r.URL, v["username"], v["password"])
|
||||||
|
return cli, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
username, password, err := credentials.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read std output: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cli = client.New(r.URL, username, password)
|
||||||
|
|
||||||
|
if err := cli.Ping(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to the remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(map[string]string)
|
||||||
|
c["username"] = username
|
||||||
|
c["password"] = password
|
||||||
|
remoteCred[r.URL] = c
|
||||||
|
|
||||||
|
return cli, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"cloudsave/cmd/cli/commands/version"
|
"cloudsave/cmd/cli/commands/version"
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/google/subcommands"
|
"github.com/google/subcommands"
|
||||||
@@ -33,8 +32,5 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
exitCode := subcommands.Execute(ctx)
|
os.Exit(int(subcommands.Execute(ctx)))
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
os.Exit(int(exitCode))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -44,9 +46,9 @@ func NewServer(documentRoot string, creds map[string]string, port int) *HTTPServ
|
|||||||
router.Use(recoverMiddleware)
|
router.Use(recoverMiddleware)
|
||||||
router.Use(middleware.GetHead)
|
router.Use(middleware.GetHead)
|
||||||
router.Use(middleware.Compress(5, "application/gzip"))
|
router.Use(middleware.Compress(5, "application/gzip"))
|
||||||
router.Use(BasicAuth("cloudsave", creds))
|
|
||||||
router.Use(middleware.Heartbeat("/heartbeat"))
|
router.Use(middleware.Heartbeat("/heartbeat"))
|
||||||
router.Route("/api", func(routerAPI chi.Router) {
|
router.Route("/api", func(routerAPI chi.Router) {
|
||||||
|
routerAPI.Use(BasicAuth("cloudsave", creds))
|
||||||
routerAPI.Route("/v1", func(r chi.Router) {
|
routerAPI.Route("/v1", func(r chi.Router) {
|
||||||
// Get information about the server
|
// Get information about the server
|
||||||
r.Get("/version", s.Information)
|
r.Get("/version", s.Information)
|
||||||
@@ -75,17 +77,30 @@ func NewServer(documentRoot string, creds map[string]string, port int) *HTTPServ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s HTTPServer) all(w http.ResponseWriter, r *http.Request) {
|
func (s HTTPServer) all(w http.ResponseWriter, r *http.Request) {
|
||||||
ds, err := os.ReadDir(s.documentRoot)
|
path := filepath.Join(s.documentRoot, "data")
|
||||||
|
datastore := make([]game.Metadata, 0)
|
||||||
|
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
ok(datastore, w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, "failed to open datastore (", s.documentRoot, "):", err)
|
||||||
|
internalServerError(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, err := os.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "failed to open datastore (", s.documentRoot, "):", err)
|
fmt.Fprintln(os.Stderr, "failed to open datastore (", s.documentRoot, "):", err)
|
||||||
internalServerError(w, r)
|
internalServerError(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
datastore := make([]game.Metadata, 0)
|
|
||||||
for _, d := range ds {
|
for _, d := range ds {
|
||||||
content, err := os.ReadFile(filepath.Join(s.documentRoot, d.Name(), "metadata.json"))
|
content, err := os.ReadFile(filepath.Join(path, d.Name(), "metadata.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
slog.Error("error: failed to load metadata.json", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,35 +211,62 @@ func (c *Client) Pull(gameID, archivePath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Ping() bool {
|
func (c *Client) Ping() error {
|
||||||
cli := http.Client{}
|
cli := http.Client{}
|
||||||
|
|
||||||
hburl, err := url.JoinPath(c.baseURL, "heartbeat")
|
hburl, err := url.JoinPath(c.baseURL, "heartbeat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "cannot connect to remote:", err)
|
return err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", hburl, nil)
|
req, err := http.NewRequest("GET", hburl, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "cannot connect to remote:", err)
|
return err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.SetBasicAuth(c.username, c.password)
|
req.SetBasicAuth(c.username, c.password)
|
||||||
|
|
||||||
res, err := cli.Do(req)
|
res, err := cli.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "cannot connect to remote:", err)
|
return err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
fmt.Fprintln(os.Stderr, "cannot connect to remote: server return code", res.StatusCode)
|
return fmt.Errorf("cannot connect to remote: server return code %s", res.Status)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) All() ([]game.Metadata, error) {
|
||||||
|
u, err := url.JoinPath(c.baseURL, "api", "v1", "games")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err := c.get(u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if games, ok := (o.Data).([]any); ok {
|
||||||
|
var res []game.Metadata
|
||||||
|
for _, g := range games {
|
||||||
|
if v, ok := g.(map[string]any); ok {
|
||||||
|
gm := game.Metadata{
|
||||||
|
ID: v["id"].(string),
|
||||||
|
Name: v["name"].(string),
|
||||||
|
Version: int(v["version"].(float64)),
|
||||||
|
Date: customtime.MustParse(time.RFC3339, v["date"].(string)),
|
||||||
|
}
|
||||||
|
res = append(res, gm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("invalid payload sent by the server")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) get(url string) (obj.HTTPObject, error) {
|
func (c *Client) get(url string) (obj.HTTPObject, error) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package remote
|
package remote
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cloudsave/pkg/game"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -20,6 +20,10 @@ var (
|
|||||||
datastorepath string
|
datastorepath string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoRemote error = errors.New("no remote found for this game")
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
roaming, err = os.UserConfigDir()
|
roaming, err = os.UserConfigDir()
|
||||||
@@ -34,45 +38,12 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 One(gameID string) (Remote, error) {
|
func One(gameID string) (Remote, error) {
|
||||||
content, err := os.ReadFile(filepath.Join(datastorepath, gameID, "remote.json"))
|
content, err := os.ReadFile(filepath.Join(datastorepath, gameID, "remote.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return Remote{}, ErrNoRemote
|
||||||
|
}
|
||||||
return Remote{}, err
|
return Remote{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,18 +53,7 @@ func One(gameID string) (Remote, error) {
|
|||||||
return Remote{}, fmt.Errorf("corrupted datastore: failed to parse %s/remote.json: %w", gameID, err)
|
return Remote{}, fmt.Errorf("corrupted datastore: failed to parse %s/remote.json: %w", gameID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err = os.ReadFile(filepath.Join(datastorepath, gameID, "metadata.json"))
|
r.GameID = gameID
|
||||||
if err != nil {
|
|
||||||
return Remote{}, fmt.Errorf("corrupted datastore: failed to read %s/metadata.json: %w", gameID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var m game.Metadata
|
|
||||||
err = json.Unmarshal(content, &m)
|
|
||||||
if err != nil {
|
|
||||||
return Remote{}, fmt.Errorf("corrupted datastore: failed to parse %s/metadata.json: %w", gameID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.GameID = m.ID
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +62,7 @@ func Set(gameID, url string) error {
|
|||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(filepath.Join(datastorepath, gameID, "remote.json"), os.O_WRONLY|os.O_CREATE, 0740)
|
f, err := os.OpenFile(filepath.Join(datastorepath, gameID, "remote.json"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0740)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user