diff --git a/api/fs.go b/api/fs.go index ae41b9e..7b05b51 100644 --- a/api/fs.go +++ b/api/fs.go @@ -2,6 +2,16 @@ package api import "context" +func (c *Client) RemoveAll(ctx context.Context, paths ...string) error { + return c.client.Call( + ctx, + "FS", + "RemoveAll", + paths, + nil, + ) +} + func (c *Client) Remove(ctx context.Context, paths ...string) error { return c.client.Call( ctx, @@ -22,6 +32,16 @@ func (c *Client) Rename(ctx context.Context, old, new string) error { ) } +func (c *Client) MkdirAll(ctx context.Context, paths ...string) error { + return c.client.Call( + ctx, + "FS", + "MkdirAll", + paths, + nil, + ) +} + func (c *Client) Mkdir(ctx context.Context, paths ...string) error { return c.client.Call( ctx, diff --git a/api/resources.go b/api/resources.go new file mode 100644 index 0000000..8690ddc --- /dev/null +++ b/api/resources.go @@ -0,0 +1,26 @@ +package api + +import ( + "context" + + "go.arsenm.dev/infinitime" +) + +// LoadResources loads resources onto the watch from the given +// file path to the resources zip +func (c *Client) LoadResources(ctx context.Context, path string) (<-chan infinitime.ResourceLoadProgress, error) { + progCh := make(chan infinitime.ResourceLoadProgress) + + err := c.client.Call( + ctx, + "FS", + "LoadResources", + path, + progCh, + ) + if err != nil { + return nil, err + } + + return progCh, nil +} diff --git a/cmd/itctl/firmware.go b/cmd/itctl/firmware.go index cc11df8..bcf41fb 100644 --- a/cmd/itctl/firmware.go +++ b/cmd/itctl/firmware.go @@ -11,6 +11,19 @@ import ( ) func fwUpgrade(c *cli.Context) error { + resources := c.String("resources") + if resources != "" { + absRes, err := filepath.Abs(resources) + if err != nil { + return err + } + + err = resLoad(c.Context, []string{absRes}) + if err != nil { + return err + } + } + start := time.Now() var upgType api.UpgradeType diff --git a/cmd/itctl/fs.go b/cmd/itctl/fs.go index 38ac25d..fc2ec0a 100644 --- a/cmd/itctl/fs.go +++ b/cmd/itctl/fs.go @@ -34,7 +34,12 @@ func fsMkdir(c *cli.Context) error { return cli.Exit("Command mkdir requires one or more arguments", 1) } - err := client.Mkdir(c.Context, c.Args().Slice()...) + var err error + if c.Bool("parents") { + err = client.MkdirAll(c.Context, c.Args().Slice()...) + } else { + err = client.Mkdir(c.Context, c.Args().Slice()...) + } if err != nil { return err } @@ -109,7 +114,12 @@ func fsRemove(c *cli.Context) error { return cli.Exit("Command remove requires one or more arguments", 1) } - err := client.Remove(c.Context, c.Args().Slice()...) + var err error + if c.Bool("recursive") { + err = client.RemoveAll(c.Context, c.Args().Slice()...) + } else { + err = client.Remove(c.Context, c.Args().Slice()...) + } if err != nil { return err } diff --git a/cmd/itctl/main.go b/cmd/itctl/main.go index b548126..c4f865d 100644 --- a/cmd/itctl/main.go +++ b/cmd/itctl/main.go @@ -1,11 +1,11 @@ package main import ( - "time" "context" "os" "os/signal" "syscall" + "time" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -24,17 +24,17 @@ func main() { syscall.SIGINT, syscall.SIGTERM, ) - + // This goroutine ensures that itctl will exit // at most 200ms after the user sends SIGINT/SIGTERM. go func() { <-ctx.Done() - time.Sleep(200*time.Millisecond) + time.Sleep(200 * time.Millisecond) os.Exit(0) }() app := cli.App{ - Name: "itctl", + Name: "itctl", HideHelpCommand: true, Flags: []cli.Flag{ &cli.StringFlag{ @@ -46,10 +46,23 @@ func main() { }, Commands: []*cli.Command{ { - Name: "help", + Name: "help", ArgsUsage: "", - Usage: "Display help screen for a command", - Action: helpCmd, + Usage: "Display help screen for a command", + Action: helpCmd, + }, + { + Name: "resources", + Aliases: []string{"res"}, + Usage: "Handle InfiniTime resource loading", + Subcommands: []*cli.Command{ + { + Name: "load", + ArgsUsage: "", + Usage: "Load an InifiniTime resources package", + Action: resourcesLoad, + }, + }, }, { Name: "filesystem", @@ -64,6 +77,13 @@ func main() { Action: fsList, }, { + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "parents", + Aliases: []string{"p"}, + Usage: "Make parent directories if needed, no error if already existing", + }, + }, Name: "mkdir", ArgsUsage: "", Usage: "Create new directories", @@ -84,6 +104,13 @@ func main() { Action: fsRead, }, { + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "recursive", + Aliases: []string{"r", "R"}, + Usage: "Remove directories and their contents recursively", + }, + }, Name: "remove", ArgsUsage: "", Aliases: []string{"rm"}, @@ -116,6 +143,11 @@ func main() { Aliases: []string{"f"}, Usage: "Path to firmware image (.bin file)", }, + &cli.PathFlag{ + Name: "resources", + Aliases: []string{"r"}, + Usage: "Path to resources file (.zip file)", + }, &cli.PathFlag{ Name: "archive", Aliases: []string{"a"}, @@ -284,4 +316,4 @@ func isHelpCmd() bool { } } return false -} \ No newline at end of file +} diff --git a/cmd/itctl/resources.go b/cmd/itctl/resources.go new file mode 100644 index 0000000..2e5338e --- /dev/null +++ b/cmd/itctl/resources.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "path/filepath" + + "github.com/cheggaaa/pb/v3" + "github.com/urfave/cli/v2" + "go.arsenm.dev/infinitime" +) + +func resourcesLoad(c *cli.Context) error { + return resLoad(c.Context, c.Args().Slice()) +} + +func resLoad(ctx context.Context, args []string) error { + if len(args) == 0 { + return cli.Exit("Command load requires one argument.", 1) + } + + // Create progress bar templates + rmTmpl := `Removing {{string . "filename"}}` + upTmpl := `Uploading {{string . "filename"}} {{counters . }} B {{bar . "|" "-" (cycle .) " " "|"}} {{percent . }} {{rtime . "%s"}}` + // Start full bar at 0 total + bar := pb.ProgressBarTemplate(rmTmpl).Start(0) + + path, err := filepath.Abs(args[0]) + if err != nil { + return err + } + + progCh, err := client.LoadResources(ctx, path) + if err != nil { + return err + } + + for evt := range progCh { + if evt.Operation == infinitime.ResourceOperationRemoveObsolete { + bar.SetTemplateString(rmTmpl) + bar.Set("filename", evt.Name) + } else { + bar.SetTemplateString(upTmpl) + bar.Set("filename", evt.Name) + + bar.SetTotal(evt.Total) + bar.SetCurrent(evt.Sent) + } + } + + bar.Finish() + + return nil +} diff --git a/go.mod b/go.mod index b99795e..7ec585f 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/mozillazg/go-pinyin v0.19.0 github.com/rs/zerolog v1.26.1 github.com/urfave/cli/v2 v2.3.0 - go.arsenm.dev/infinitime v0.0.0-20220819210252-d199fba93c2f + go.arsenm.dev/infinitime v0.0.0-20221016193942-01970b2bb770 go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0 golang.org/x/text v0.3.7 modernc.org/sqlite v1.17.2 @@ -38,7 +38,7 @@ require ( github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mattn/go-runewidth v0.0.12 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect diff --git a/go.sum b/go.sum index 76bdf4a..e7130cf 100644 --- a/go.sum +++ b/go.sum @@ -270,8 +270,9 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb h1:+fP6ENsbd+BUOmD/kSjNtrOmi2vgJ/JfWDSWjTKmTVY= @@ -396,8 +397,8 @@ github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs= github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= -go.arsenm.dev/infinitime v0.0.0-20220819210252-d199fba93c2f h1:Np0ZNlgVC5D9NOilN14HJ1mSXM8vl2LYGfK0fZOYUbY= -go.arsenm.dev/infinitime v0.0.0-20220819210252-d199fba93c2f/go.mod h1:K3NJ6fyPv5qqHUedB3MccKOE0whJMJZ80l/yTzzTrgc= +go.arsenm.dev/infinitime v0.0.0-20221016193942-01970b2bb770 h1:OZ8kYFHCXt+7nxz9G0BTnSyJKnIpQnQ5m7kyIssG81Y= +go.arsenm.dev/infinitime v0.0.0-20221016193942-01970b2bb770/go.mod h1:K3NJ6fyPv5qqHUedB3MccKOE0whJMJZ80l/yTzzTrgc= go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0 h1:1K96g1eww+77GeGchwMhd0NTrs7Mk/Hc3M3ItW5NbG4= go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0/go.mod h1:goK9z735lfXmqlDxu9qN7FS8t0HJHN3PjyDtCToUY4w= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= diff --git a/socket.go b/socket.go index 5dba567..6fcba5c 100644 --- a/socket.go +++ b/socket.go @@ -293,6 +293,17 @@ type FS struct { fs *blefs.FS } +func (fs *FS) RemoveAll(_ *server.Context, paths []string) error { + fs.updateFS() + for _, path := range paths { + err := fs.fs.RemoveAll(path) + if err != nil { + return err + } + } + return nil +} + func (fs *FS) Remove(_ *server.Context, paths []string) error { fs.updateFS() for _, path := range paths { @@ -309,6 +320,17 @@ func (fs *FS) Rename(_ *server.Context, paths [2]string) error { return fs.fs.Rename(paths[0], paths[1]) } +func (fs *FS) MkdirAll(_ *server.Context, paths []string) error { + fs.updateFS() + for _, path := range paths { + err := fs.fs.MkdirAll(path) + if err != nil { + return err + } + } + return nil +} + func (fs *FS) Mkdir(_ *server.Context, paths []string) error { fs.updateFS() for _, path := range paths { @@ -424,6 +446,32 @@ func (fs *FS) Download(ctx *server.Context, paths [2]string) error { return nil } +func (fs *FS) LoadResources(ctx *server.Context, path string) error { + resFl, err := os.Open(path) + if err != nil { + return err + } + + progCh, err := infinitime.LoadResources(resFl, fs.fs) + if err != nil { + return err + } + + ch, err := ctx.MakeChannel() + if err != nil { + return err + } + + go func() { + for evt := range progCh { + ch <- evt + } + close(ch) + }() + + return nil +} + func (fs *FS) updateFS() { if fs.fs == nil || updateFS { // Get new FS