Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,27 @@ The release action is triggered when a tag is pushed to the master branch.
3. Squash commits and merge to master
4. When ready to release, add a tag
5. Wait for Github Action to process the release

## Manual testing checklist

Before merging changes, verify the following scenarios work correctly. Build the binary first with `go build -o qrcp .`.

### Send (local to mobile)

- Basic send: `./qrcp README.md` — scan the QR code on a mobile browser and confirm the file downloads correctly
- Custom port: `./qrcp -p 9090 README.md` — verify the QR code URL uses port 9090 and the download works
- Large file: send a file >100MB and confirm it transfers completely without errors or corruption

### Receive (mobile to local)

- Basic receive: `./qrcp r` — scan the QR code on a mobile browser, upload a file, and confirm it appears in the current directory
- Large file: upload a file >100MB from mobile and confirm it is received completely

### Cross-browser compatibility

Test the QR code URL directly in each of the following mobile browsers and confirm send/receive both work:

- Safari (iOS)
- Chrome (Android)
- Firefox (Android)
- Samsung Internet
29 changes: 29 additions & 0 deletions cmd/qrcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,38 @@ package cmd

import (
"github.com/claudiodangelis/qrcp/application"
"github.com/claudiodangelis/qrcp/logger"
"github.com/claudiodangelis/qrcp/server"
"github.com/eiannone/keyboard"
"github.com/spf13/cobra"
)

// listenForQuit starts a goroutine that shuts down the server when "q" or
// Ctrl+C is pressed. The returned function must be deferred by the caller to
// release the keyboard.
func listenForQuit(srv *server.Server, log logger.Logger) func() {
if err := keyboard.Open(); err != nil {
log.Print("Warning: keyboard not detected:", err)
return func() {}
}
go func() {
for {
char, key, err := keyboard.GetKey()
if err != nil {
break
}
if string(char) == "q" || key == keyboard.KeyCtrlC {
srv.Shutdown()
}
}
}()
return func() {
if err := keyboard.Close(); err != nil {
log.Print("keyboard.Close:", err)
}
}
}

var app application.App

func init() {
Expand Down
24 changes: 5 additions & 19 deletions cmd/receive.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package cmd

import (
"fmt"

"github.com/claudiodangelis/qrcp/config"
"github.com/claudiodangelis/qrcp/logger"
"github.com/claudiodangelis/qrcp/qr"
"github.com/claudiodangelis/qrcp/server"
"github.com/eiannone/keyboard"
"github.com/spf13/cobra"
)

func receiveCmdFunc(command *cobra.Command, args []string) error {
log := logger.New(app.Flags.Quiet)
// Load configuration
cfg := config.New(app)
cfg, err := config.New(app)
if err != nil {
return err
}
// Create the server
srv, err := server.New(&cfg)
if err != nil {
Expand All @@ -32,21 +32,7 @@ func receiveCmdFunc(command *cobra.Command, args []string) error {
if app.Flags.Browser {
srv.DisplayQR(srv.ReceiveURL)
}
if err := keyboard.Open(); err == nil {
defer func() {
keyboard.Close()
}()
go func() {
for {
char, key, _ := keyboard.GetKey()
if string(char) == "q" || key == keyboard.KeyCtrlC {
srv.Shutdown()
}
}
}()
} else {
log.Print(fmt.Sprintf("Warning: keyboard not detected: %v", err))
}
defer listenForQuit(srv, log)()
if err := srv.Wait(); err != nil {
return err
}
Expand Down
25 changes: 5 additions & 20 deletions cmd/send.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package cmd

import (
"fmt"

"github.com/claudiodangelis/qrcp/body"
"github.com/claudiodangelis/qrcp/config"
"github.com/claudiodangelis/qrcp/logger"
"github.com/claudiodangelis/qrcp/qr"
"github.com/eiannone/keyboard"

"github.com/claudiodangelis/qrcp/server"
"github.com/spf13/cobra"
)
Expand All @@ -19,7 +15,10 @@ func sendCmdFunc(command *cobra.Command, args []string) error {
if err != nil {
return err
}
cfg := config.New(app)
cfg, err := config.New(app)
if err != nil {
return err
}
srv, err := server.New(&cfg)
if err != nil {
return err
Expand All @@ -32,21 +31,7 @@ func sendCmdFunc(command *cobra.Command, args []string) error {
if app.Flags.Browser {
srv.DisplayQR(srv.SendURL)
}
if err := keyboard.Open(); err == nil {
defer func() {
keyboard.Close()
}()
go func() {
for {
char, key, _ := keyboard.GetKey()
if string(char) == "q" || key == keyboard.KeyCtrlC {
srv.Shutdown()
}
}
}()
} else {
log.Print(fmt.Sprintf("Warning: keyboard not detected: %v", err))
}
defer listenForQuit(srv, log)()
if err := srv.Wait(); err != nil {
return err
}
Expand Down
42 changes: 23 additions & 19 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ type Config struct {
Reversed bool `yaml:",omitempty"`
}

var interactive bool = false
func New(app application.App) (Config, error) {
cfg, _, err := newConfig(app, false)
return cfg, err
}

func New(app application.App) Config {
func newConfig(app application.App, interactive bool) (Config, *viper.Viper, error) {
log := logger.New(app.Flags.Quiet)
v := getViperInstance(app)
var err error
Expand All @@ -42,16 +45,16 @@ func New(app application.App) Config {
_, err = os.Stat(v.ConfigFileUsed())
if os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(v.ConfigFileUsed()), os.ModeDir|os.ModePerm); err != nil {
panic(err)
return Config{}, nil, err
}
file, err := os.Create(v.ConfigFileUsed())
if err != nil {
panic(err)
return Config{}, nil, err
}
defer file.Close()
defer func() { _ = file.Close() }()
}
if err := v.ReadInConfig(); err != nil {
panic(fmt.Errorf("fatal error config file: %s", err))
return Config{}, nil, fmt.Errorf("fatal error config file: %s", err)
}
// Load file
cfg.Interface = v.GetString("interface")
Expand Down Expand Up @@ -104,18 +107,18 @@ func New(app application.App) Config {
// Discover interface if it's not been set yet
if !interactive {
if cfg.Interface == "" {
cfg.Interface, err = chooseInterface(app.Flags)
cfg.Interface, err = chooseInterface(app.Flags, interactive)
if err != nil {
panic(err)
return Config{}, nil, err
}
v.Set("interface", cfg.Interface)
if err := v.WriteConfig(); err != nil {
log.Print(fmt.Sprintf("Warning: the configuration file could not be saved: %v\n", err))
log.Print("Warning: the configuration file could not be saved:", err)
}
}
}

return cfg
return cfg, v, nil
}

func getViperInstance(app application.App) *viper.Viper {
Expand Down Expand Up @@ -144,14 +147,14 @@ func getViperInstance(app application.App) *viper.Viper {
}

func Wizard(app application.App) error {
interactive = true
cfg := New(app)
v := getViperInstance(app)
cfg, v, err := newConfig(app, true)
if err != nil {
return err
}
// Choose interface
var err error
cfg.Interface, err = chooseInterface(app.Flags)
cfg.Interface, err = chooseInterface(app.Flags, true)
if err != nil {
panic(err)
return err
}
v.Set("interface", cfg.Interface)
if err := v.WriteConfig(); err != nil {
Expand Down Expand Up @@ -188,7 +191,7 @@ func Wizard(app application.App) error {
promptPort := promptui.Prompt{
Validate: validatePort,
Label: "Choose port, 0 means random port",
Default: fmt.Sprintf("%d", cfg.Port),
Default: strconv.Itoa(cfg.Port),
}
if promptPortResultString, err := promptPort.Run(); err == nil {
if port, err := strconv.ParseUint(promptPortResultString, 10, 16); err == nil {
Expand Down Expand Up @@ -309,8 +312,9 @@ func Wizard(app application.App) error {
}
if promptOutputResultString, err := promptOutput.Run(); err == nil {
if promptOutputResultString != "" {
output, _ := filepath.Abs(promptOutputResultString)
v.Set("output", output)
if output, err := filepath.Abs(promptOutputResultString); err == nil {
v.Set("output", output)
}
}
}
promptReversed := promptui.Select{
Expand Down
11 changes: 7 additions & 4 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
func TestNew(t *testing.T) {
os.Clearenv()
_, f, _, _ := runtime.Caller(0)
foundIface, err := chooseInterface(application.Flags{})
foundIface, err := chooseInterface(application.Flags{}, false)
if err != nil {
panic(err)
}
Expand All @@ -22,12 +22,12 @@ func TestNew(t *testing.T) {
if err != nil {
t.Skip()
}
defer os.Remove(tempfile.Name())
defer func() { _ = os.Remove(tempfile.Name()) }()
partialconfig, err := os.CreateTemp("", "qrcp*partial.yml")
if err != nil {
panic(err)
}
defer os.Remove(partialconfig.Name())
defer func() { _ = os.Remove(partialconfig.Name()) }()
if err := os.WriteFile(partialconfig.Name(), []byte(`port: 9090`), os.ModePerm); err != nil {
panic(err)
}
Expand Down Expand Up @@ -124,7 +124,10 @@ func TestNew(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := New(tt.args.app)
got, err := New(tt.args.app)
if err != nil {
t.Fatal(err)
}
got.Interface = foundIface
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
Expand Down
2 changes: 1 addition & 1 deletion config/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/manifoldco/promptui"
)

func chooseInterface(flags application.Flags) (string, error) {
func chooseInterface(flags application.Flags, interactive bool) (string, error) {
interfaces, err := util.Interfaces(flags.ListAllInterfaces)
if err != nil {
return "", err
Expand Down
Loading
Loading