From 90cce2cbee999127646c4b73b0a69dcebfc9752d Mon Sep 17 00:00:00 2001 From: Davide N Date: Mon, 20 Jan 2025 17:48:28 +0100 Subject: [PATCH 01/50] fix --- config/config.go | 38 ++++++++++++ config/config_test.go | 61 +++++++++++++++++++ config/testdata/fromenv/config.ini | 8 +++ .../.config/ArduinoCreateAgent/config.ini | 8 +++ .../.config/ArduinoCreateAgent/config.ini | 10 +++ main.go | 34 +---------- 6 files changed, 128 insertions(+), 31 deletions(-) create mode 100644 config/config_test.go create mode 100644 config/testdata/fromenv/config.ini create mode 100644 config/testdata/home/.config/ArduinoCreateAgent/config.ini create mode 100644 config/testdata/noconfig/.config/ArduinoCreateAgent/config.ini diff --git a/config/config.go b/config/config.go index 69d29eeee..50978eb82 100644 --- a/config/config.go +++ b/config/config.go @@ -142,3 +142,41 @@ func SetInstallCertsIni(filename string, value string) error { } return nil } + +func GetConfigPath() *paths.Path { + // Let's handle the config + configDir := GetDefaultConfigDir() + var configPath *paths.Path + + // see if the env var is defined, if it is take the config from there, this will override the default path + if envConfig := os.Getenv("ARDUINO_CREATE_AGENT_CONFIG"); envConfig != "" { + configPath = paths.New(envConfig) + if configPath.NotExist() { + log.Panicf("config from env var %s does not exists", envConfig) + } + log.Infof("using config from env variable: %s", configPath) + } else if defaultConfigPath := configDir.Join("config.ini"); defaultConfigPath.Exist() { + // by default take the config from the ~/.arduino-create/config.ini file + configPath = defaultConfigPath + log.Infof("using config from default: %s", configPath) + } else { + // Fall back to the old config.ini location + src, _ := os.Executable() + oldConfigPath := paths.New(src).Parent().Join("config.ini") + if oldConfigPath.Exist() { + err := oldConfigPath.CopyTo(defaultConfigPath) + if err != nil { + log.Errorf("cannot copy old %s, to %s, generating new config", oldConfigPath, configPath) + } else { + configPath = defaultConfigPath + log.Infof("copied old %s, to %s", oldConfigPath, configPath) + } + } + } + if configPath == nil { + configPath = GenerateConfig(configDir) + } + + return configPath + +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 000000000..76e6988c0 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,61 @@ +package config + +import ( + "fmt" + "os" + "testing" + + "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestGetConfigPath(t *testing.T) { + t.Run("read config.ini from ARDUINO_CREATE_AGENT_CONFIG", func(t *testing.T) { + os.Setenv("ARDUINO_CREATE_AGENT_CONFIG", "./testdata/fromenv/config.ini") + defer os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") + configPath := GetConfigPath() + assert.Equal(t, "./testdata/fromenv/config.ini", configPath.String()) + }) + + t.Run("panic if config.ini does not exist", func(t *testing.T) { + os.Setenv("ARDUINO_CREATE_AGENT_CONFIG", "./testdata/nonexistent_config.ini") + defer os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") + + defer func() { + if r := recover(); r != nil { + entry, ok := r.(*logrus.Entry) + if !ok { + t.Errorf("Expected panic of type *logrus.Entry but got %T", r) + } else { + assert.Equal(t, "config from env var ./testdata/nonexistent_config.ini does not exists", entry.Message) + } + } else { + t.Errorf("Expected panic but did not get one") + } + }() + + GetConfigPath() + }) + + t.Run("read config.ini from $HOME", func(t *testing.T) { + os.Setenv("HOME", "./testdata/home") + defer os.Unsetenv("HOME") + configPath := GetConfigPath() + assert.Equal(t, "testdata/home/.config/ArduinoCreateAgent/config.ini", configPath.String()) + }) + + t.Run("fallback old : read config.ini where the binary is launched", func(t *testing.T) { + src, _ := os.Executable() + paths.New(src).Parent().Join("config.ini").Create() // create a config.ini in the same directory as the binary + // The fallback path is the directory where the binary is launched + fmt.Println(src) + os.Setenv("HOME", "./testdata/noconfig") // force to not have a config in the home directory + defer os.Unsetenv("HOME") + + // expect it creates a config.ini in the same directory as the binary + configPath := GetConfigPath() + assert.Equal(t, "testdata/home/.config/ArduinoCreateAgent/config.ini", configPath.String()) + }) + +} diff --git a/config/testdata/fromenv/config.ini b/config/testdata/fromenv/config.ini new file mode 100644 index 000000000..5b31315b9 --- /dev/null +++ b/config/testdata/fromenv/config.ini @@ -0,0 +1,8 @@ +gc = std +hostname = unknown-hostname +regex = usb|acm|com +v = true +appName = CreateAgent/Stable +updateUrl = https://downloads.arduino.cc/ +origins = https://local.arduino.cc:8000, https://local.arduino.cc:8001, https://create-dev.arduino.cc, https://*.sparklyunicorn.cc, https://*.iot-cloud-arduino-cc.pages.dev, https://cloud.oniudra.cc, https://app.oniudra.cc,https://*.iot-cloud-arduino-cc.pages.dev +crashreport = false diff --git a/config/testdata/home/.config/ArduinoCreateAgent/config.ini b/config/testdata/home/.config/ArduinoCreateAgent/config.ini new file mode 100644 index 000000000..92f231faf --- /dev/null +++ b/config/testdata/home/.config/ArduinoCreateAgent/config.ini @@ -0,0 +1,8 @@ +gc = std +hostname = unknown-hostname +regex = usb|acm|com +v = true +appName = config-from-home-dir +updateUrl = https://downloads.arduino.cc/ +origins = https://local.arduino.cc:8000, https://local.arduino.cc:8001, https://*.iot-cloud-arduino-cc.pages.dev +crashreport = false diff --git a/config/testdata/noconfig/.config/ArduinoCreateAgent/config.ini b/config/testdata/noconfig/.config/ArduinoCreateAgent/config.ini new file mode 100644 index 000000000..f63377db5 --- /dev/null +++ b/config/testdata/noconfig/.config/ArduinoCreateAgent/config.ini @@ -0,0 +1,10 @@ +gc = std # Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage) +hostname = unknown-hostname # Override the hostname we get from the OS +regex = usb|acm|com # Regular expression to filter serial port list +v = true # show debug logging +appName = CreateAgent/Stable +updateUrl = https://downloads.arduino.cc/ +origins = https://local.arduino.cc:8000 +#httpProxy = http://your.proxy:port # Proxy server for HTTP requests +crashreport = false # enable crashreport logging +autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent) \ No newline at end of file diff --git a/main.go b/main.go index 1ca857b02..a83d7f4c5 100755 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ import ( _ "embed" "encoding/json" "flag" + "fmt" "html/template" "io" "os" @@ -188,38 +189,9 @@ func loop() { h.broadcastSys <- mapB } - // Let's handle the config - configDir := config.GetDefaultConfigDir() - var configPath *paths.Path + configPath := config.GetConfigPath() - // see if the env var is defined, if it is take the config from there, this will override the default path - if envConfig := os.Getenv("ARDUINO_CREATE_AGENT_CONFIG"); envConfig != "" { - configPath = paths.New(envConfig) - if configPath.NotExist() { - log.Panicf("config from env var %s does not exists", envConfig) - } - log.Infof("using config from env variable: %s", configPath) - } else if defaultConfigPath := configDir.Join("config.ini"); defaultConfigPath.Exist() { - // by default take the config from the ~/.arduino-create/config.ini file - configPath = defaultConfigPath - log.Infof("using config from default: %s", configPath) - } else { - // Fall back to the old config.ini location - src, _ := os.Executable() - oldConfigPath := paths.New(src).Parent().Join("config.ini") - if oldConfigPath.Exist() { - err := oldConfigPath.CopyTo(defaultConfigPath) - if err != nil { - log.Errorf("cannot copy old %s, to %s, generating new config", oldConfigPath, configPath) - } else { - configPath = defaultConfigPath - log.Infof("copied old %s, to %s", oldConfigPath, configPath) - } - } - } - if configPath == nil { - configPath = config.GenerateConfig(configDir) - } + fmt.Println("configPath: ", configPath) // if the default browser is Safari, prompt the user to install HTTPS certificates // and eventually install them From c96490b00cc260f6ee243d679d079a44864d8ad5 Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 21 Jan 2025 11:33:56 +0100 Subject: [PATCH 02/50] feat(config): add default configuration file and update config handling --- config/config-default.ini | 10 ++++++ config/config.go | 2 +- config/config_test.go | 48 +++++++++++++++++++++------ config/testdata/fromlegacy/.gitignore | 1 + 4 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 config/config-default.ini create mode 100644 config/testdata/fromlegacy/.gitignore diff --git a/config/config-default.ini b/config/config-default.ini new file mode 100644 index 000000000..f63377db5 --- /dev/null +++ b/config/config-default.ini @@ -0,0 +1,10 @@ +gc = std # Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage) +hostname = unknown-hostname # Override the hostname we get from the OS +regex = usb|acm|com # Regular expression to filter serial port list +v = true # show debug logging +appName = CreateAgent/Stable +updateUrl = https://downloads.arduino.cc/ +origins = https://local.arduino.cc:8000 +#httpProxy = http://your.proxy:port # Proxy server for HTTP requests +crashreport = false # enable crashreport logging +autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent) \ No newline at end of file diff --git a/config/config.go b/config/config.go index 50978eb82..9919bfbbc 100644 --- a/config/config.go +++ b/config/config.go @@ -108,7 +108,7 @@ func GetDefaultHomeDir() *paths.Path { return paths.New(homeDir) } -//go:embed config.ini +//go:embed config-default.ini var configContent []byte // GenerateConfig function will take a directory path as an input diff --git a/config/config_test.go b/config/config_test.go index 76e6988c0..4d1c64ec8 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,9 +1,9 @@ package config import ( - "fmt" "os" "testing" + "time" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" @@ -38,24 +38,52 @@ func TestGetConfigPath(t *testing.T) { GetConfigPath() }) - t.Run("read config.ini from $HOME", func(t *testing.T) { + t.Run("read config.ini from $HOME/.config/ArduinoCreateAgent folder", func(t *testing.T) { os.Setenv("HOME", "./testdata/home") defer os.Unsetenv("HOME") configPath := GetConfigPath() assert.Equal(t, "testdata/home/.config/ArduinoCreateAgent/config.ini", configPath.String()) }) - t.Run("fallback old : read config.ini where the binary is launched", func(t *testing.T) { - src, _ := os.Executable() - paths.New(src).Parent().Join("config.ini").Create() // create a config.ini in the same directory as the binary - // The fallback path is the directory where the binary is launched - fmt.Println(src) - os.Setenv("HOME", "./testdata/noconfig") // force to not have a config in the home directory + t.Run("legacy config are copied to new location", func(t *testing.T) { + + createLegacyConfig := func() string { + // Create a "legacy" config.ini in the same directory as the binary executable + src, err := os.Executable() + if err != nil { + t.Fatal(err) + } + legacyConfigPath, err := paths.New(src).Parent().Join("config.ini").Create() + if err != nil { + t.Fatal(err) + } + // adding a timestamp to the content to make it unique + c := "hostname = legacy-config-file-" + time.Now().String() + n, err := legacyConfigPath.WriteString(c) + if err != nil || n <= 0 { + t.Fatalf("Failed to write legacy config file: %v", err) + } + return c + } + + wantContent := createLegacyConfig() + + // Expectation: it copies the "legacy" config.ini into the location pointed by $HOME + os.Setenv("HOME", "./testdata/fromlegacy") defer os.Unsetenv("HOME") - // expect it creates a config.ini in the same directory as the binary + // remove any existing config.ini in the into the location pointed by $HOME + err := os.Remove("./testdata/fromlegacy/.config/ArduinoCreateAgent/config.ini") + if err != nil && !os.IsNotExist(err) { + t.Fatal(err) + } + configPath := GetConfigPath() - assert.Equal(t, "testdata/home/.config/ArduinoCreateAgent/config.ini", configPath.String()) + assert.Equal(t, "testdata/fromlegacy/.config/ArduinoCreateAgent/config.ini", configPath.String()) + + given, err := paths.New(configPath.String()).ReadFile() + assert.Nil(t, err) + assert.Equal(t, wantContent, string(given)) }) } diff --git a/config/testdata/fromlegacy/.gitignore b/config/testdata/fromlegacy/.gitignore new file mode 100644 index 000000000..2fa7ce7c4 --- /dev/null +++ b/config/testdata/fromlegacy/.gitignore @@ -0,0 +1 @@ +config.ini From 7b4859a7b35003f5323bc70a45eea87ed38a292c Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 21 Jan 2025 11:34:07 +0100 Subject: [PATCH 03/50] fix(.gitignore): add entry to ignore config.ini file --- config/testdata/fromlegacy/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/config/testdata/fromlegacy/.gitignore b/config/testdata/fromlegacy/.gitignore index 2fa7ce7c4..f67b7f089 100644 --- a/config/testdata/fromlegacy/.gitignore +++ b/config/testdata/fromlegacy/.gitignore @@ -1 +1,2 @@ +# ingore because this config file config.ini From 279de2ac65f7bfe6ad8597fcaaf00f6fc2df90e0 Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 21 Jan 2025 12:48:37 +0100 Subject: [PATCH 04/50] test(config): add test for writing default config.ini file and update test data --- config/config_test.go | 19 +++++++++++++++++++ config/testdata/fromdefault/.gitignore | 1 + config/testdata/fromenv/config.ini | 2 +- .../.config/ArduinoCreateAgent/config.ini | 2 +- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 config/testdata/fromdefault/.gitignore diff --git a/config/config_test.go b/config/config_test.go index 4d1c64ec8..666d85fbc 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -86,4 +86,23 @@ func TestGetConfigPath(t *testing.T) { assert.Equal(t, wantContent, string(given)) }) + t.Run("write the default config.ini file", func(t *testing.T) { + os.Setenv("HOME", "./testdata/fromdefault") + os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") + + // ensure the config.ini does not exist in the target directory + os.Remove("./testdata/fromdefault/.config/ArduinoCreateAgent/config.ini") + + configPath := GetConfigPath() + + assert.Equal(t, "testdata/fromdefault/.config/ArduinoCreateAgent/config.ini", configPath.String()) + + givenContent, err := paths.New(configPath.String()).ReadFile() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, configContent, givenContent) + }) + } diff --git a/config/testdata/fromdefault/.gitignore b/config/testdata/fromdefault/.gitignore new file mode 100644 index 000000000..2fa7ce7c4 --- /dev/null +++ b/config/testdata/fromdefault/.gitignore @@ -0,0 +1 @@ +config.ini diff --git a/config/testdata/fromenv/config.ini b/config/testdata/fromenv/config.ini index 5b31315b9..cfd58d812 100644 --- a/config/testdata/fromenv/config.ini +++ b/config/testdata/fromenv/config.ini @@ -1,5 +1,5 @@ gc = std -hostname = unknown-hostname +hostname = this-is-a-config-file-from-home-dir-from-ARDUINO_CREATE_AGENT_CONFIG-env regex = usb|acm|com v = true appName = CreateAgent/Stable diff --git a/config/testdata/home/.config/ArduinoCreateAgent/config.ini b/config/testdata/home/.config/ArduinoCreateAgent/config.ini index 92f231faf..a023d62d1 100644 --- a/config/testdata/home/.config/ArduinoCreateAgent/config.ini +++ b/config/testdata/home/.config/ArduinoCreateAgent/config.ini @@ -1,5 +1,5 @@ gc = std -hostname = unknown-hostname +hostname = this-is-a-config-file-from-home-dir regex = usb|acm|com v = true appName = config-from-home-dir From 4f3ac5a2ce1680ec895db5039357f0f7dddf30c8 Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 21 Jan 2025 17:27:03 +0100 Subject: [PATCH 05/50] test(config): add tests for retrieving config paths from XDG_CONFIG_HOME and HOME directories --- config/config_test.go | 176 ++++++++---------- .../.config/ArduinoCreateAgent/config.ini | 8 + .../fromxdghome/ArduinoCreateAgent/config.ini | 10 + 3 files changed, 99 insertions(+), 95 deletions(-) create mode 100644 config/testdata/fromhome/.config/ArduinoCreateAgent/config.ini create mode 100644 config/testdata/fromxdghome/ArduinoCreateAgent/config.ini diff --git a/config/config_test.go b/config/config_test.go index 666d85fbc..447f3793e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -6,103 +6,89 @@ import ( "time" "github.com/arduino/go-paths-helper" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) -func TestGetConfigPath(t *testing.T) { - t.Run("read config.ini from ARDUINO_CREATE_AGENT_CONFIG", func(t *testing.T) { - os.Setenv("ARDUINO_CREATE_AGENT_CONFIG", "./testdata/fromenv/config.ini") - defer os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") - configPath := GetConfigPath() - assert.Equal(t, "./testdata/fromenv/config.ini", configPath.String()) - }) - - t.Run("panic if config.ini does not exist", func(t *testing.T) { - os.Setenv("ARDUINO_CREATE_AGENT_CONFIG", "./testdata/nonexistent_config.ini") - defer os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") - - defer func() { - if r := recover(); r != nil { - entry, ok := r.(*logrus.Entry) - if !ok { - t.Errorf("Expected panic of type *logrus.Entry but got %T", r) - } else { - assert.Equal(t, "config from env var ./testdata/nonexistent_config.ini does not exists", entry.Message) - } - } else { - t.Errorf("Expected panic but did not get one") - } - }() - - GetConfigPath() - }) - - t.Run("read config.ini from $HOME/.config/ArduinoCreateAgent folder", func(t *testing.T) { - os.Setenv("HOME", "./testdata/home") - defer os.Unsetenv("HOME") - configPath := GetConfigPath() - assert.Equal(t, "testdata/home/.config/ArduinoCreateAgent/config.ini", configPath.String()) - }) - - t.Run("legacy config are copied to new location", func(t *testing.T) { - - createLegacyConfig := func() string { - // Create a "legacy" config.ini in the same directory as the binary executable - src, err := os.Executable() - if err != nil { - t.Fatal(err) - } - legacyConfigPath, err := paths.New(src).Parent().Join("config.ini").Create() - if err != nil { - t.Fatal(err) - } - // adding a timestamp to the content to make it unique - c := "hostname = legacy-config-file-" + time.Now().String() - n, err := legacyConfigPath.WriteString(c) - if err != nil || n <= 0 { - t.Fatalf("Failed to write legacy config file: %v", err) - } - return c - } - - wantContent := createLegacyConfig() - - // Expectation: it copies the "legacy" config.ini into the location pointed by $HOME - os.Setenv("HOME", "./testdata/fromlegacy") - defer os.Unsetenv("HOME") - - // remove any existing config.ini in the into the location pointed by $HOME - err := os.Remove("./testdata/fromlegacy/.config/ArduinoCreateAgent/config.ini") - if err != nil && !os.IsNotExist(err) { - t.Fatal(err) - } - - configPath := GetConfigPath() - assert.Equal(t, "testdata/fromlegacy/.config/ArduinoCreateAgent/config.ini", configPath.String()) - - given, err := paths.New(configPath.String()).ReadFile() - assert.Nil(t, err) - assert.Equal(t, wantContent, string(given)) - }) - - t.Run("write the default config.ini file", func(t *testing.T) { - os.Setenv("HOME", "./testdata/fromdefault") - os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") - - // ensure the config.ini does not exist in the target directory - os.Remove("./testdata/fromdefault/.config/ArduinoCreateAgent/config.ini") - - configPath := GetConfigPath() - - assert.Equal(t, "testdata/fromdefault/.config/ArduinoCreateAgent/config.ini", configPath.String()) - - givenContent, err := paths.New(configPath.String()).ReadFile() - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, configContent, givenContent) - }) +func TestGetConfigPathFromXDG_CONFIG_HOME(t *testing.T) { + // read config from $XDG_CONFIG_HOME/ArduinoCreateAgent/config.ini + os.Setenv("XDG_CONFIG_HOME", "./testdata/fromxdghome") + defer os.Unsetenv("XDG_CONFIG_HOME") + configPath := GetConfigPath() + assert.Equal(t, "testdata/fromxdghome/ArduinoCreateAgent/config.ini", configPath.String()) +} + +func TestGetConfigPathFromHOME(t *testing.T) { + // Test case 2: read config from $HOME/.config/ArduinoCreateAgent/config.ini " + os.Setenv("HOME", "./testdata/fromhome") + defer os.Unsetenv("HOME") + configPath := GetConfigPath() + assert.Equal(t, "testdata/fromhome/.config/ArduinoCreateAgent/config.ini", configPath.String()) + +} + +func TestGetConfigPathFromARDUINO_CREATE_AGENT_CONFIG(t *testing.T) { + // read config from ARDUINO_CREATE_AGENT_CONFIG/config.ini" + os.Setenv("HOME", "./fromhome") + os.Setenv("ARDUINO_CREATE_AGENT_CONFIG", "./testdata/fromenv/config.ini") + defer os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") + + configPath := GetConfigPath() + assert.Equal(t, "./testdata/fromenv/config.ini", configPath.String()) +} +func TestGetConfigPathFromLegacyConfig(t *testing.T) { + // If no config is found, copy the legacy config to the new location + src, err := os.Executable() + if err != nil { + t.Fatal(err) + } + legacyConfigPath, err := paths.New(src).Parent().Join("config.ini").Create() + if err != nil { + t.Fatal(err) + } + // adding a timestamp to the content to make it unique + legacyContent := "hostname = legacy-config-file-" + time.Now().String() + n, err := legacyConfigPath.WriteString(legacyContent) + if err != nil || n <= 0 { + t.Fatalf("Failed to write legacy config file: %v", err) + } + + // remove any existing config.ini in the into the location pointed by $HOME + err = os.Remove("./testdata/fromlegacy/.config/ArduinoCreateAgent/config.ini") + if err != nil && !os.IsNotExist(err) { + t.Fatal(err) + } + + // Expectation: it copies the "legacy" config.ini into the location pointed by $HOME + os.Setenv("HOME", "./testdata/fromlegacy") + defer os.Unsetenv("HOME") + + configPath := GetConfigPath() + assert.Equal(t, "testdata/fromlegacy/.config/ArduinoCreateAgent/config.ini", configPath.String()) + + given, err := paths.New(configPath.String()).ReadFile() + assert.Nil(t, err) + assert.Equal(t, legacyContent, string(given)) } + +// func TestGetConfigPathCreateDefaultConfig(t *testing.T) { +// os.Setenv("HOME", "./testdata/noconfig") +// os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") + +// // ensure the config.ini does not exist in HOME directory +// os.Remove("./testdata/noconfig/.config/ArduinoCreateAgent/config.ini") +// // ensure the config.ini does not exist in target directory +// os.Remove("./testdata/fromdefault/.config/ArduinoCreateAgent/config.ini") + +// configPath := GetConfigPath() + +// assert.Equal(t, "testdata/fromdefault/.config/ArduinoCreateAgent/config.ini", configPath.String()) + +// givenContent, err := paths.New(configPath.String()).ReadFile() +// if err != nil { +// t.Fatal(err) +// } + +// assert.Equal(t, string(configContent), string(givenContent)) + +// } diff --git a/config/testdata/fromhome/.config/ArduinoCreateAgent/config.ini b/config/testdata/fromhome/.config/ArduinoCreateAgent/config.ini new file mode 100644 index 000000000..a023d62d1 --- /dev/null +++ b/config/testdata/fromhome/.config/ArduinoCreateAgent/config.ini @@ -0,0 +1,8 @@ +gc = std +hostname = this-is-a-config-file-from-home-dir +regex = usb|acm|com +v = true +appName = config-from-home-dir +updateUrl = https://downloads.arduino.cc/ +origins = https://local.arduino.cc:8000, https://local.arduino.cc:8001, https://*.iot-cloud-arduino-cc.pages.dev +crashreport = false diff --git a/config/testdata/fromxdghome/ArduinoCreateAgent/config.ini b/config/testdata/fromxdghome/ArduinoCreateAgent/config.ini new file mode 100644 index 000000000..f63377db5 --- /dev/null +++ b/config/testdata/fromxdghome/ArduinoCreateAgent/config.ini @@ -0,0 +1,10 @@ +gc = std # Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage) +hostname = unknown-hostname # Override the hostname we get from the OS +regex = usb|acm|com # Regular expression to filter serial port list +v = true # show debug logging +appName = CreateAgent/Stable +updateUrl = https://downloads.arduino.cc/ +origins = https://local.arduino.cc:8000 +#httpProxy = http://your.proxy:port # Proxy server for HTTP requests +crashreport = false # enable crashreport logging +autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent) \ No newline at end of file From c41b815787b77a60660461f9d9896666e27bd412 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 22 Jan 2025 17:14:05 +0100 Subject: [PATCH 06/50] feat(config): pass config path to main loop and update handling --- main.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index a83d7f4c5..4bda82c0d 100755 --- a/main.go +++ b/main.go @@ -142,8 +142,11 @@ func main() { // Check if certificates made with Agent <=1.2.7 needs to be moved over the new location cert.MigrateCertificatesGeneratedWithOldAgentVersions(config.GetCertificatesDir()) + configPath := config.GetConfigPath() + fmt.Println("configPath: ", configPath) + // Launch main loop in a goroutine - go loop() + go loop(configPath) // SetupSystray is the main thread configDir := config.GetDefaultConfigDir() @@ -156,6 +159,7 @@ func main() { AdditionalConfig: *additionalConfig, ConfigDir: configDir, } + Systray.SetCurrentConfigFile(configPath) if src, err := os.Executable(); err != nil { panic(err) @@ -166,11 +170,15 @@ func main() { } } -func loop() { +func loop(configPath *paths.Path) { if *hibernate { return } + if configPath == nil { + log.Panic("configPath is nil") + } + log.SetLevel(log.InfoLevel) log.SetOutput(os.Stdout) @@ -189,10 +197,6 @@ func loop() { h.broadcastSys <- mapB } - configPath := config.GetConfigPath() - - fmt.Println("configPath: ", configPath) - // if the default browser is Safari, prompt the user to install HTTPS certificates // and eventually install them if runtime.GOOS == "darwin" { @@ -230,7 +234,6 @@ func loop() { if err != nil { log.Panicf("cannot parse arguments: %s", err) } - Systray.SetCurrentConfigFile(configPath) // Parse additional ini config if defined if len(*additionalConfig) > 0 { From 55ca9e5f67f0bde13288412840c4f449bd6cd14a Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 22 Jan 2025 17:34:56 +0100 Subject: [PATCH 07/50] remove global systray --- hub.go | 5 +++-- info.go | 23 +++++++++++++---------- main.go | 26 +++++++++++++------------- update.go | 25 ++++++++++++++----------- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/hub.go b/hub.go index a162dd01a..7ad58f686 100755 --- a/hub.go +++ b/hub.go @@ -223,9 +223,10 @@ func checkCmd(m []byte) { go logAction(sl) } else if strings.HasPrefix(sl, "restart") { log.Println("Received restart from the daemon. Why? Boh") - Systray.Restart() + // TODO enable them + // Systray.Restart() } else if strings.HasPrefix(sl, "exit") { - Systray.Quit() + // Systray.Quit() } else if strings.HasPrefix(sl, "memstats") { memoryStats() } else if strings.HasPrefix(sl, "gc") { diff --git a/info.go b/info.go index 88145c02b..7da0f6986 100644 --- a/info.go +++ b/info.go @@ -19,6 +19,7 @@ import ( "runtime" "strings" + "github.com/arduino/arduino-create-agent/systray" "github.com/gin-gonic/gin" "go.bug.st/serial" ) @@ -40,14 +41,16 @@ func infoHandler(c *gin.Context) { }) } -func pauseHandler(c *gin.Context) { - go func() { - ports, _ := serial.GetPortsList() - for _, element := range ports { - spClose(element) - } - *hibernate = true - Systray.Pause() - }() - c.JSON(200, nil) +func PauseHandler(s *systray.Systray) func(c *gin.Context) { + return func(c *gin.Context) { + go func() { + ports, _ := serial.GetPortsList() + for _, element := range ports { + spClose(element) + } + *hibernate = true + s.Pause() + }() + c.JSON(200, nil) + } } diff --git a/main.go b/main.go index 4bda82c0d..bb63cb211 100755 --- a/main.go +++ b/main.go @@ -103,9 +103,8 @@ var homeTemplateHTML string // global clients var ( - Tools *tools.Tools - Systray systray.Systray - Index *index.Resource + Tools *tools.Tools + Index *index.Resource ) type logWriter struct{} @@ -145,12 +144,10 @@ func main() { configPath := config.GetConfigPath() fmt.Println("configPath: ", configPath) - // Launch main loop in a goroutine - go loop(configPath) - // SetupSystray is the main thread configDir := config.GetDefaultConfigDir() - Systray = systray.Systray{ + + stray := &systray.Systray{ Hibernate: *hibernate, Version: version + "-" + commit, DebugURL: func() string { @@ -159,18 +156,21 @@ func main() { AdditionalConfig: *additionalConfig, ConfigDir: configDir, } - Systray.SetCurrentConfigFile(configPath) + stray.SetCurrentConfigFile(configPath) + + // Launch main loop in a goroutine + go loop(stray, configPath) if src, err := os.Executable(); err != nil { panic(err) } else if restartPath := updater.Start(src); restartPath != "" { - Systray.RestartWith(restartPath) + stray.RestartWith(restartPath) } else { - Systray.Start() + stray.Start() } } -func loop(configPath *paths.Path) { +func loop(stray *systray.Systray, configPath *paths.Path) { if *hibernate { return } @@ -435,8 +435,8 @@ func loop(configPath *paths.Path) { r.Handle("WS", "/socket.io/", socketHandler) r.Handle("WSS", "/socket.io/", socketHandler) r.GET("/info", infoHandler) - r.POST("/pause", pauseHandler) - r.POST("/update", updateHandler) + r.POST("/pause", PauseHandler(stray)) + r.POST("/update", UpdateHandler(stray)) // Mount goa handlers goa := v2.Server(config.GetDataDir().String(), Index) diff --git a/update.go b/update.go index 33c028bce..0065a09b5 100644 --- a/update.go +++ b/update.go @@ -30,20 +30,23 @@ package main import ( + "github.com/arduino/arduino-create-agent/systray" "github.com/arduino/arduino-create-agent/updater" "github.com/gin-gonic/gin" ) -func updateHandler(c *gin.Context) { - restartPath, err := updater.CheckForUpdates(version, *updateURL, *appName) - if err != nil { - c.JSON(500, gin.H{"error": err.Error()}) - return - } - c.JSON(200, gin.H{"success": "Please wait a moment while the agent reboots itself"}) - if restartPath == "quit" { - Systray.Quit() - } else { - Systray.RestartWith(restartPath) +func UpdateHandler(s *systray.Systray) func(c *gin.Context) { + return func(c *gin.Context) { + restartPath, err := updater.CheckForUpdates(version, *updateURL, *appName) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(200, gin.H{"success": "Please wait a moment while the agent reboots itself"}) + if restartPath == "quit" { + s.Quit() + } else { + s.RestartWith(restartPath) + } } } From f07103e566c6df074d2c8da3cf1b5e5d34a0ee2d Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 22 Jan 2025 18:02:08 +0100 Subject: [PATCH 08/50] WIP: remove hub and sh globals --- conn.go | 163 +++++++++++++++++++++++++------------------------- hub.go | 44 ++++++++------ main.go | 19 +++--- serial.go | 25 ++++---- serialport.go | 6 +- 5 files changed, 135 insertions(+), 122 deletions(-) diff --git a/conn.go b/conn.go index b6e03268c..e09cd7fcd 100644 --- a/conn.go +++ b/conn.go @@ -79,116 +79,119 @@ type Upload struct { var uploadStatusStr = "ProgrammerStatus" -func uploadHandler(c *gin.Context) { - data := new(Upload) - if err := c.BindJSON(data); err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("err with the payload. %v", err.Error())) - return - } - - log.Printf("%+v %+v %+v %+v %+v %+v", data.Port, data.Board, data.Rewrite, data.Commandline, data.Extra, data.Filename) - - if data.Port == "" { - c.String(http.StatusBadRequest, "port is required") - return - } - - if data.Board == "" { - c.String(http.StatusBadRequest, "board is required") - log.Error("board is required") - return - } - - if !data.Extra.Network { - if data.Signature == "" { - c.String(http.StatusBadRequest, "signature is required") +func UploadHandler(h *hub) func(c *gin.Context) { + return func(c *gin.Context) { + data := new(Upload) + if err := c.BindJSON(data); err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("err with the payload. %v", err.Error())) return } - if data.Commandline == "" { - c.String(http.StatusBadRequest, "commandline is required for local board") + log.Printf("%+v %+v %+v %+v %+v %+v", data.Port, data.Board, data.Rewrite, data.Commandline, data.Extra, data.Filename) + + if data.Port == "" { + c.String(http.StatusBadRequest, "port is required") return } - err := utilities.VerifyInput(data.Commandline, data.Signature) - - if err != nil { - c.String(http.StatusBadRequest, "signature is invalid") + if data.Board == "" { + c.String(http.StatusBadRequest, "board is required") + log.Error("board is required") return } - } - buffer := bytes.NewBuffer(data.Hex) + if !data.Extra.Network { + if data.Signature == "" { + c.String(http.StatusBadRequest, "signature is required") + return + } - filePath, err := utilities.SaveFileonTempDir(data.Filename, buffer) - if err != nil { - c.String(http.StatusBadRequest, err.Error()) - return - } + if data.Commandline == "" { + c.String(http.StatusBadRequest, "commandline is required for local board") + return + } - tmpdir, err := os.MkdirTemp("", "extrafiles") - if err != nil { - c.String(http.StatusBadRequest, err.Error()) - return - } + err := utilities.VerifyInput(data.Commandline, data.Signature) - for _, extraFile := range data.ExtraFiles { - path, err := utilities.SafeJoin(tmpdir, extraFile.Filename) - if err != nil { - c.String(http.StatusBadRequest, err.Error()) - return + if err != nil { + c.String(http.StatusBadRequest, "signature is invalid") + return + } } - log.Printf("Saving %s on %s", extraFile.Filename, path) - err = os.MkdirAll(filepath.Dir(path), 0744) - if err != nil { - c.String(http.StatusBadRequest, err.Error()) - return - } + buffer := bytes.NewBuffer(data.Hex) - err = os.WriteFile(path, extraFile.Hex, 0644) + filePath, err := utilities.SaveFileonTempDir(data.Filename, buffer) if err != nil { c.String(http.StatusBadRequest, err.Error()) return } - } - if data.Rewrite != "" { - data.Board = data.Rewrite - } - - go func() { - // Resolve commandline - commandline, err := upload.PartiallyResolve(data.Board, filePath, tmpdir, data.Commandline, data.Extra, Tools) + tmpdir, err := os.MkdirTemp("", "extrafiles") if err != nil { - send(map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) + c.String(http.StatusBadRequest, err.Error()) return } - l := PLogger{Verbose: true} - - // Upload - if data.Extra.Network { - err = errors.New("network upload is not supported anymore, pease use OTA instead") - } else { - send(map[string]string{uploadStatusStr: "Starting", "Cmd": "Serial"}) - err = upload.Serial(data.Port, commandline, data.Extra, l) + for _, extraFile := range data.ExtraFiles { + path, err := utilities.SafeJoin(tmpdir, extraFile.Filename) + if err != nil { + c.String(http.StatusBadRequest, err.Error()) + return + } + log.Printf("Saving %s on %s", extraFile.Filename, path) + + err = os.MkdirAll(filepath.Dir(path), 0744) + if err != nil { + c.String(http.StatusBadRequest, err.Error()) + return + } + + err = os.WriteFile(path, extraFile.Hex, 0644) + if err != nil { + c.String(http.StatusBadRequest, err.Error()) + return + } } - // Handle result - if err != nil { - send(map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) - return + if data.Rewrite != "" { + data.Board = data.Rewrite } - send(map[string]string{uploadStatusStr: "Done", "Flash": "Ok"}) - }() - c.String(http.StatusAccepted, "") + go func() { + // Resolve commandline + commandline, err := upload.PartiallyResolve(data.Board, filePath, tmpdir, data.Commandline, data.Extra, Tools) + if err != nil { + send(h, map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) + return + } + + l := PLogger{Verbose: true} + + // Upload + if data.Extra.Network { + err = errors.New("network upload is not supported anymore, pease use OTA instead") + } else { + send(h, map[string]string{uploadStatusStr: "Starting", "Cmd": "Serial"}) + err = upload.Serial(data.Port, commandline, data.Extra, l) + } + + // Handle result + if err != nil { + send(h, map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) + return + } + send(h, map[string]string{uploadStatusStr: "Done", "Flash": "Ok"}) + }() + + c.String(http.StatusAccepted, "") + } } // PLogger sends the info from the upload to the websocket type PLogger struct { Verbose bool + h *hub } // Debug only sends messages if verbose is true (always true for now) @@ -202,15 +205,15 @@ func (l PLogger) Debug(args ...interface{}) { func (l PLogger) Info(args ...interface{}) { output := fmt.Sprint(args...) log.Println(output) - send(map[string]string{uploadStatusStr: "Busy", "Msg": output}) + send(l.h, map[string]string{uploadStatusStr: "Busy", "Msg": output}) } -func send(args map[string]string) { +func send(h *hub, args map[string]string) { mapB, _ := json.Marshal(args) h.broadcastSys <- mapB } -func wsHandler() *WsServer { +func wsHandler(h *hub) *WsServer { server, err := socketio.NewServer(nil) if err != nil { log.Fatal(err) diff --git a/hub.go b/hub.go index 7ad58f686..5c4605b73 100755 --- a/hub.go +++ b/hub.go @@ -45,14 +45,20 @@ type hub struct { // Unregister requests from connections. unregister chan *connection + + // Serial hub to communicate with serial ports + serialHub *serialhub } -var h = hub{ - broadcast: make(chan []byte, 1000), - broadcastSys: make(chan []byte, 1000), - register: make(chan *connection), - unregister: make(chan *connection), - connections: make(map[*connection]bool), +func NewHub() *hub { + return &hub{ + broadcast: make(chan []byte, 1000), + broadcastSys: make(chan []byte, 1000), + register: make(chan *connection), + unregister: make(chan *connection), + connections: make(map[*connection]bool), + serialHub: NewSerialHub(), + } } const commands = `{ @@ -108,7 +114,7 @@ func (h *hub) run() { h.unregisterConnection(c) case m := <-h.broadcast: if len(m) > 0 { - checkCmd(m) + h.checkCmd(m) h.sendToRegisteredConnections(m) } case m := <-h.broadcastSys: @@ -117,7 +123,7 @@ func (h *hub) run() { } } -func checkCmd(m []byte) { +func (h *hub) checkCmd(m []byte) { //log.Print("Inside checkCmd") s := string(m[:]) @@ -154,7 +160,7 @@ func checkCmd(m []byte) { buftype := strings.Replace(args[3], "\n", "", -1) bufferAlgorithm = buftype } - go spHandlerOpen(args[1], baud, bufferAlgorithm) + go h.spHandlerOpen(args[1], baud, bufferAlgorithm) } else if strings.HasPrefix(sl, "close") { @@ -228,13 +234,13 @@ func checkCmd(m []byte) { } else if strings.HasPrefix(sl, "exit") { // Systray.Quit() } else if strings.HasPrefix(sl, "memstats") { - memoryStats() + h.memoryStats() } else if strings.HasPrefix(sl, "gc") { - garbageCollection() + h.garbageCollection() } else if strings.HasPrefix(sl, "hostname") { - getHostname() + h.getHostname() } else if strings.HasPrefix(sl, "version") { - getVersion() + h.getVersion() } else { go spErr("Could not understand command.") } @@ -254,7 +260,7 @@ func logAction(sl string) { } } -func memoryStats() { +func (h *hub) memoryStats() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) json, _ := json.Marshal(memStats) @@ -262,22 +268,22 @@ func memoryStats() { h.broadcastSys <- json } -func getHostname() { +func (h *hub) getHostname() { h.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") } -func getVersion() { +func (h *hub) getVersion() { h.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") } -func garbageCollection() { +func (h *hub) garbageCollection() { log.Printf("Starting garbageCollection()\n") h.broadcastSys <- []byte("{\"gc\":\"starting\"}") - memoryStats() + h.memoryStats() debug.SetGCPercent(100) debug.FreeOSMemory() debug.SetGCPercent(-1) log.Printf("Done with garbageCollection()\n") h.broadcastSys <- []byte("{\"gc\":\"done\"}") - memoryStats() + h.memoryStats() } diff --git a/main.go b/main.go index bb63cb211..4510d1bba 100755 --- a/main.go +++ b/main.go @@ -107,14 +107,15 @@ var ( Index *index.Resource ) -type logWriter struct{} +// TODO: enable it +// type logWriter struct{} -func (u *logWriter) Write(p []byte) (n int, err error) { - h.broadcastSys <- p - return len(p), nil -} +// func (u *logWriter) Write(p []byte) (n int, err error) { +// h.broadcastSys <- p +// return len(p), nil +// } -var loggerWs logWriter +// var loggerWs logWriter func homeHandler(c *gin.Context) { homeTemplate.Execute(c.Writer, c.Request.Host) @@ -191,6 +192,8 @@ func loop(stray *systray.Systray, configPath *paths.Path) { os.Exit(0) } + h := NewHub() + logger := func(msg string) { mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} mapB, _ := json.Marshal(mapD) @@ -390,7 +393,7 @@ func loop(stray *systray.Systray, configPath *paths.Path) { r := gin.New() - socketHandler := wsHandler().ServeHTTP + socketHandler := wsHandler(h).ServeHTTP extraOrigins := []string{ "https://create.arduino.cc", @@ -429,7 +432,7 @@ func loop(stray *systray.Systray, configPath *paths.Path) { r.LoadHTMLFiles("templates/nofirefox.html") r.GET("/", homeHandler) - r.POST("/upload", uploadHandler) + r.POST("/upload", UpdateHandler(stray)) r.GET("/socket.io/", socketHandler) r.POST("/socket.io/", socketHandler) r.Handle("WS", "/socket.io/", socketHandler) diff --git a/serial.go b/serial.go index 64e5b8f7f..b404f8f23 100755 --- a/serial.go +++ b/serial.go @@ -32,8 +32,13 @@ import ( type serialhub struct { // Opened serial ports. ports map[*serport]bool + mu sync.Mutex +} - mu sync.Mutex +func NewSerialHub() *serialhub { + return &serialhub{ + ports: make(map[*serport]bool), + } } // SerialPortList is the serial port list @@ -59,15 +64,11 @@ type SpPortItem struct { // serialPorts contains the ports attached to the machine var serialPorts SerialPortList -var sh = serialhub{ - ports: make(map[*serport]bool), -} - // Register serial ports from the connections. func (sh *serialhub) Register(port *serport) { sh.mu.Lock() //log.Print("Registering a port: ", p.portConf.Name) - h.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") + sh.hub.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") sh.ports[port] = true sh.mu.Unlock() } @@ -76,7 +77,7 @@ func (sh *serialhub) Register(port *serport) { func (sh *serialhub) Unregister(port *serport) { sh.mu.Lock() //log.Print("Unregistering a port: ", p.portConf.Name) - h.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") + sh.hub.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") delete(sh.ports, port) close(port.sendBuffered) close(port.sendNoBuf) @@ -105,10 +106,10 @@ func (sp *SerialPortList) List() { if err != nil { //log.Println(err) - h.broadcastSys <- []byte("Error creating json on port list " + + sh.hub.broadcastSys <- []byte("Error creating json on port list " + err.Error()) } else { - h.broadcastSys <- ls + sh.hub.broadcastSys <- ls } } @@ -257,13 +258,13 @@ func (sp *SerialPortList) getPortByName(portname string) *SpPortItem { func spErr(err string) { //log.Println("Sending err back: ", err) - //h.broadcastSys <- []byte(err) - h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") + //sh.hub.broadcastSys <- []byte(err) + sh.hub.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") } func spClose(portname string) { if myport, ok := sh.FindPortByName(portname); ok { - h.broadcastSys <- []byte("Closing serial port " + portname) + sh.hub.broadcastSys <- []byte("Closing serial port " + portname) myport.Close() } else { spErr("We could not find the serial port " + portname + " that you were trying to close.") diff --git a/serialport.go b/serialport.go index 0d386bbfc..1ee7bbf46 100755 --- a/serialport.go +++ b/serialport.go @@ -273,7 +273,7 @@ func (p *serport) writerRaw() { h.broadcastSys <- []byte(msgstr) } -func spHandlerOpen(portname string, baud int, buftype string) { +func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { log.Print("Inside spHandler") @@ -330,8 +330,8 @@ func spHandlerOpen(portname string, baud int, buftype string) { bw.Init() p.bufferwatcher = bw - sh.Register(p) - defer sh.Unregister(p) + h.serialHub.Register(p) + defer h.serialHub.Unregister(p) serialPorts.MarkPortAsOpened(portname) serialPorts.List() From ef94d97f587046305ac7cdc59a0934a7c5b12e00 Mon Sep 17 00:00:00 2001 From: Davide N Date: Fri, 24 Jan 2025 18:02:39 +0100 Subject: [PATCH 09/50] remove global clients: use callback to avoid nesting import --- hub.go | 60 +++++++++++++++++++++++++++++++++++---------------- info.go | 4 ++-- main.go | 12 +++++++---- serial.go | 48 +++++++++++++++++++++++------------------ serialport.go | 54 ++++++++++++++++++++++++++++++++-------------- 5 files changed, 116 insertions(+), 62 deletions(-) diff --git a/hub.go b/hub.go index 5c4605b73..2af0e8845 100755 --- a/hub.go +++ b/hub.go @@ -19,7 +19,6 @@ import ( "encoding/json" "fmt" "html" - "io" "os" "runtime" "runtime/debug" @@ -46,19 +45,41 @@ type hub struct { // Unregister requests from connections. unregister chan *connection + //TODO globals clients // Serial hub to communicate with serial ports serialHub *serialhub + + serialPortList *SerialPortList } -func NewHub() *hub { - return &hub{ - broadcast: make(chan []byte, 1000), - broadcastSys: make(chan []byte, 1000), - register: make(chan *connection), - unregister: make(chan *connection), - connections: make(map[*connection]bool), - serialHub: NewSerialHub(), +func NewHub(serialhub *serialhub, serialList *SerialPortList) *hub { + hub := &hub{ + broadcast: make(chan []byte, 1000), + broadcastSys: make(chan []byte, 1000), + register: make(chan *connection), + unregister: make(chan *connection), + connections: make(map[*connection]bool), + serialHub: serialhub, + serialPortList: serialList, + } + + hub.serialHub.OnRegister = func(port *serport) { + hub.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") + } + + hub.serialHub.OnUnregister = func(port *serport) { + hub.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") } + + hub.serialPortList.OnList = func(data []byte) { + hub.broadcastSys <- data + } + + hub.serialPortList.OnErr = func(err string) { + hub.broadcastSys <- []byte("{\"Error\":\"" + err + "\"}") + } + + return hub } const commands = `{ @@ -138,18 +159,18 @@ func (h *hub) checkCmd(m []byte) { args := strings.Split(s, " ") if len(args) < 3 { - go spErr("You did not specify a port and baud rate in your open cmd") + go h.spErr("You did not specify a port and baud rate in your open cmd") return } if len(args[1]) < 1 { - go spErr("You did not specify a serial port") + go h.spErr("You did not specify a serial port") return } baudStr := strings.Replace(args[2], "\n", "", -1) baud, err := strconv.Atoi(baudStr) if err != nil { - go spErr("Problem converting baud rate " + args[2]) + go h.spErr("Problem converting baud rate " + args[2]) return } // pass in buffer type now as string. if user does not @@ -166,9 +187,9 @@ func (h *hub) checkCmd(m []byte) { args := strings.Split(s, " ") if len(args) > 1 { - go spClose(args[1]) + go h.spClose(args[1]) } else { - go spErr("You did not specify a port to close") + go h.spErr("You did not specify a port to close") } } else if strings.HasPrefix(sl, "killupload") { @@ -181,9 +202,9 @@ func (h *hub) checkCmd(m []byte) { } else if strings.HasPrefix(sl, "send") { // will catch send and sendnobuf and sendraw - go spWrite(s) + go h.spWrite(s) } else if strings.HasPrefix(sl, "list") { - go serialPorts.List() + go h.serialPortList.List() } else if strings.HasPrefix(sl, "downloadtool") { go func() { args := strings.Split(s, " ") @@ -242,15 +263,16 @@ func (h *hub) checkCmd(m []byte) { } else if strings.HasPrefix(sl, "version") { h.getVersion() } else { - go spErr("Could not understand command.") + go h.spErr("Could not understand command.") } } func logAction(sl string) { if strings.HasPrefix(sl, "log on") { *logDump = "on" - multiWriter := io.MultiWriter(&loggerWs, os.Stderr) - log.SetOutput(multiWriter) + //TODO: pass the loggerSw in the constructor and enable again the log e + // multiWriter := io.MultiWriter(&loggerWs, os.Stderr) + // log.SetOutput(multiWriter) } else if strings.HasPrefix(sl, "log off") { *logDump = "off" log.SetOutput(os.Stderr) diff --git a/info.go b/info.go index 7da0f6986..b7a3585e4 100644 --- a/info.go +++ b/info.go @@ -41,12 +41,12 @@ func infoHandler(c *gin.Context) { }) } -func PauseHandler(s *systray.Systray) func(c *gin.Context) { +func PauseHandler(h *hub, s *systray.Systray) func(c *gin.Context) { return func(c *gin.Context) { go func() { ports, _ := serial.GetPortsList() for _, element := range ports { - spClose(element) + h.spClose(element) } *hibernate = true s.Pause() diff --git a/main.go b/main.go index 4510d1bba..a17bb5dca 100755 --- a/main.go +++ b/main.go @@ -115,8 +115,6 @@ var ( // return len(p), nil // } -// var loggerWs logWriter - func homeHandler(c *gin.Context) { homeTemplate.Execute(c.Writer, c.Request.Host) } @@ -192,7 +190,13 @@ func loop(stray *systray.Systray, configPath *paths.Path) { os.Exit(0) } - h := NewHub() + // serialPorts contains the ports attached to the machine + serialPorts := NewSerialPortList() + serialHub := NewSerialHub() + + // var loggerWs logWriter + + h := NewHub(serialHub, serialPorts) logger := func(msg string) { mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} @@ -438,7 +442,7 @@ func loop(stray *systray.Systray, configPath *paths.Path) { r.Handle("WS", "/socket.io/", socketHandler) r.Handle("WSS", "/socket.io/", socketHandler) r.GET("/info", infoHandler) - r.POST("/pause", PauseHandler(stray)) + r.POST("/pause", PauseHandler(h, stray)) r.POST("/update", UpdateHandler(stray)) // Mount goa handlers diff --git a/serial.go b/serial.go index b404f8f23..3333701a0 100755 --- a/serial.go +++ b/serial.go @@ -19,8 +19,8 @@ package main import ( "encoding/json" + "fmt" "slices" - "strconv" "strings" "sync" "time" @@ -33,6 +33,9 @@ type serialhub struct { // Opened serial ports. ports map[*serport]bool mu sync.Mutex + + OnRegister func(port *serport) + OnUnregister func(port *serport) } func NewSerialHub() *serialhub { @@ -45,6 +48,13 @@ func NewSerialHub() *serialhub { type SerialPortList struct { Ports []*SpPortItem portsLock sync.Mutex + + OnList func([]byte) `json:"-"` + OnErr func(string) `json:"-"` +} + +func NewSerialPortList() *SerialPortList { + return &SerialPortList{} } // SpPortItem is the serial port item @@ -61,14 +71,11 @@ type SpPortItem struct { ProductID string } -// serialPorts contains the ports attached to the machine -var serialPorts SerialPortList - // Register serial ports from the connections. func (sh *serialhub) Register(port *serport) { sh.mu.Lock() //log.Print("Registering a port: ", p.portConf.Name) - sh.hub.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") + sh.OnRegister(port) sh.ports[port] = true sh.mu.Unlock() } @@ -77,7 +84,7 @@ func (sh *serialhub) Register(port *serport) { func (sh *serialhub) Unregister(port *serport) { sh.mu.Lock() //log.Print("Unregistering a port: ", p.portConf.Name) - sh.hub.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") + sh.OnUnregister(port) delete(sh.ports, port) close(port.sendBuffered) close(port.sendNoBuf) @@ -105,11 +112,9 @@ func (sp *SerialPortList) List() { sp.portsLock.Unlock() if err != nil { - //log.Println(err) - sh.hub.broadcastSys <- []byte("Error creating json on port list " + - err.Error()) + sp.OnErr("Error creating json on port list " + err.Error()) } else { - sh.hub.broadcastSys <- ls + sp.OnList(ls) } } @@ -196,6 +201,7 @@ func (sp *SerialPortList) add(addedPort *discovery.Port) { // If the port is already in the list, just update the metadata... for _, oldPort := range sp.Ports { + fmt.Println("oldPort.Name: ", oldPort.Name) if oldPort.Name == addedPort.Address { oldPort.SerialNumber = props.Get("serialNumber") oldPort.VendorID = vid @@ -256,22 +262,22 @@ func (sp *SerialPortList) getPortByName(portname string) *SpPortItem { return nil } -func spErr(err string) { +func (h *hub) spErr(err string) { //log.Println("Sending err back: ", err) //sh.hub.broadcastSys <- []byte(err) - sh.hub.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") + h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") } -func spClose(portname string) { - if myport, ok := sh.FindPortByName(portname); ok { - sh.hub.broadcastSys <- []byte("Closing serial port " + portname) +func (h *hub) spClose(portname string) { + if myport, ok := h.serialHub.FindPortByName(portname); ok { + h.broadcastSys <- []byte("Closing serial port " + portname) myport.Close() } else { - spErr("We could not find the serial port " + portname + " that you were trying to close.") + h.spErr("We could not find the serial port " + portname + " that you were trying to close.") } } -func spWrite(arg string) { +func (h *hub) spWrite(arg string) { // we will get a string of comXX asdf asdf asdf //log.Println("Inside spWrite arg: " + arg) arg = strings.TrimPrefix(arg, " ") @@ -280,7 +286,7 @@ func spWrite(arg string) { if len(args) != 3 { errstr := "Could not parse send command: " + arg //log.Println(errstr) - spErr(errstr) + h.spErr(errstr) return } bufferingMode := args[0] @@ -291,10 +297,10 @@ func spWrite(arg string) { //log.Println("The data is:" + data + "---") // See if we have this port open - port, ok := sh.FindPortByName(portname) + port, ok := h.serialHub.FindPortByName(portname) if !ok { // we couldn't find the port, so send err - spErr("We could not find the serial port " + portname + " that you were trying to write to.") + h.spErr("We could not find the serial port " + portname + " that you were trying to write to.") return } @@ -303,7 +309,7 @@ func spWrite(arg string) { case "send", "sendnobuf", "sendraw": // valid buffering mode, go ahead default: - spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") + h.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") return } diff --git a/serialport.go b/serialport.go index 1ee7bbf46..06ea0aa64 100755 --- a/serialport.go +++ b/serialport.go @@ -61,6 +61,10 @@ type serport struct { BufferType string //bufferwatcher *BufferflowDummypause bufferwatcher Bufferflow + + // TODO: to remove global + OnMessage func([]byte) + OnClose func(*serport) } // SpPortMessage is the serial port message @@ -89,7 +93,8 @@ func (p *serport) reader(buftype string) { if p.isClosing.Load() { strmsg := "Shutting down reader on " + p.portConf.Name log.Println(strmsg) - h.broadcastSys <- []byte(strmsg) + // h.broadcastSys <- ([]byte(strmsg) + p.OnMessage([]byte(strmsg)) break } @@ -143,15 +148,19 @@ func (p *serport) reader(buftype string) { if err == io.EOF || err == io.ErrUnexpectedEOF { // hit end of file log.Println("Hit end of file on serial port") - h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got EOF (End of File) on port which usually means another app other than Serial Port JSON Server is locking your port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}") + // h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got EOF (End of File) on port which usually means another app other than Serial Port JSON Server is locking your port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}") + p.OnMessage([]byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got EOF (End of File) on port which usually means another app other than Serial Port JSON Server is locking your port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")) } if err != nil { log.Println(err) - h.broadcastSys <- []byte("Error reading on " + p.portConf.Name + " " + - err.Error() + " Closing port.") - h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got error reading on port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}") + // h.broadcastSys <- []byte("Error reading on " + p.portConf.Name + " " + + // err.Error() + " Closing port.") + p.OnMessage([]byte("Error reading on " + p.portConf.Name + " " + err.Error() + " Closing port.")) + + // h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got error reading on port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}") + p.OnMessage([]byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got error reading on port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")) p.isClosingDueToError = true break } @@ -209,7 +218,8 @@ func (p *serport) writerBuffered() { } msgstr := "writerBuffered just got closed. make sure you make a new one. port:" + p.portConf.Name log.Println(msgstr) - h.broadcastSys <- []byte(msgstr) + // h.broadcastSys <- []byte(msgstr) + p.OnMessage([]byte(msgstr)) } // this method runs as its own thread because it's instantiated @@ -230,15 +240,18 @@ func (p *serport) writerNoBuf() { if err != nil { errstr := "Error writing to " + p.portConf.Name + " " + err.Error() + " Closing port." log.Print(errstr) - h.broadcastSys <- []byte(errstr) + // h.broadcastSys <- []byte(errstr) + p.OnMessage([]byte(errstr)) break } } msgstr := "Shutting down writer on " + p.portConf.Name log.Println(msgstr) - h.broadcastSys <- []byte(msgstr) + // h.broadcastSys <- []byte(msgstr) + p.OnMessage([]byte(msgstr)) p.portIo.Close() - serialPorts.List() + // TODO: is this needed ? + // serialPorts.List() } // this method runs as its own thread because it's instantiated @@ -270,7 +283,8 @@ func (p *serport) writerRaw() { } msgstr := "writerRaw just got closed. make sure you make a new one. port:" + p.portConf.Name log.Println(msgstr) - h.broadcastSys <- []byte(msgstr) + // h.broadcastSys <- []byte(msgstr) + p.OnMessage([]byte(msgstr)) } func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { @@ -312,7 +326,16 @@ func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { portConf: conf, portIo: sp, portName: portname, - BufferType: buftype} + BufferType: buftype, + } + + p.OnMessage = func(msg []byte) { + h.broadcastSys <- msg + } + p.OnClose = func(port *serport) { + h.serialPortList.MarkPortAsClosed(p.portName) + h.serialPortList.List() + } var bw Bufferflow @@ -333,8 +356,8 @@ func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { h.serialHub.Register(p) defer h.serialHub.Unregister(p) - serialPorts.MarkPortAsOpened(portname) - serialPorts.List() + h.serialPortList.MarkPortAsOpened(portname) + h.serialPortList.List() // this is internally buffered thread to not send to serial port if blocked go p.writerBuffered() @@ -345,7 +368,7 @@ func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { p.reader(buftype) - serialPorts.List() + h.serialPortList.List() } func (p *serport) Close() { @@ -353,6 +376,5 @@ func (p *serport) Close() { p.bufferwatcher.Close() p.portIo.Close() - serialPorts.MarkPortAsClosed(p.portName) - serialPorts.List() + p.OnClose(p) } From 640e6449d79634bf8b3a983058b4e03083c94043 Mon Sep 17 00:00:00 2001 From: Davide N Date: Mon, 31 Mar 2025 17:06:28 +0200 Subject: [PATCH 10/50] refactor: rename variable 'h' to 'hub' for clarity in main.go and update related references --- main.go | 12 ++++++------ main_test.go | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index e04af0439..419ebd8ef 100755 --- a/main.go +++ b/main.go @@ -196,12 +196,12 @@ func loop(stray *systray.Systray, configPath *paths.Path) { // var loggerWs logWriter - h := NewHub(serialHub, serialPorts) + hub := NewHub(serialHub, serialPorts) logger := func(msg string) { mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB + hub.broadcastSys <- mapB } // if the default browser is Safari, prompt the user to install HTTPS certificates @@ -399,13 +399,13 @@ func loop(stray *systray.Systray, configPath *paths.Path) { // launch the discoveries for the running system go serialPorts.Run() // launch the hub routine which is the singleton for the websocket server - go h.run() + go hub.run() // launch our dummy data routine //go d.run() r := gin.New() - socketHandler := wsHandler(h).ServeHTTP + socketHandler := wsHandler(hub).ServeHTTP extraOrigins := []string{ "https://create.arduino.cc", @@ -444,13 +444,13 @@ func loop(stray *systray.Systray, configPath *paths.Path) { r.LoadHTMLFiles("templates/nofirefox.html") r.GET("/", homeHandler) - r.POST("/upload", uploadHandler(h, signaturePubKey)) + r.POST("/upload", uploadHandler(hub, signaturePubKey)) r.GET("/socket.io/", socketHandler) r.POST("/socket.io/", socketHandler) r.Handle("WS", "/socket.io/", socketHandler) r.Handle("WSS", "/socket.io/", socketHandler) r.GET("/info", infoHandler) - r.POST("/pause", PauseHandler(h, stray)) + r.POST("/pause", PauseHandler(hub, stray)) r.POST("/update", UpdateHandler(stray)) // Mount goa handlers diff --git a/main_test.go b/main_test.go index 1387fd221..67cb91a1d 100644 --- a/main_test.go +++ b/main_test.go @@ -56,7 +56,7 @@ func TestValidSignatureKey(t *testing.T) { func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { r := gin.New() - r.POST("/", uploadHandler(utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) + r.POST("/", uploadHandler(NewHub(NewSerialHub(), NewSerialPortList()), utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) ts := httptest.NewServer(r) uploadEvilFileName := Upload{ @@ -92,7 +92,8 @@ func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { func TestUploadHandlerAgainstBase64WithoutPaddingMustFail(t *testing.T) { r := gin.New() - r.POST("/", uploadHandler(utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) + + r.POST("/", uploadHandler(NewHub(NewSerialHub(), NewSerialPortList()), utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) ts := httptest.NewServer(r) defer ts.Close() From 99f8235a12c0492f014502c28848b203d61c165c Mon Sep 17 00:00:00 2001 From: Davide N Date: Mon, 31 Mar 2025 17:39:43 +0200 Subject: [PATCH 11/50] docs: add comments to clarify functions and handlers in config, hub, serial, and update files --- config/config.go | 5 +++++ hub.go | 2 ++ info.go | 2 +- main.go | 4 ++-- serial.go | 3 +++ update.go | 2 +- 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 9919bfbbc..82edecf41 100644 --- a/config/config.go +++ b/config/config.go @@ -143,6 +143,11 @@ func SetInstallCertsIni(filename string, value string) error { return nil } +// GetConfigPath returns the full path to the Arduino Create Agent configuration file. +// It will check if the config file exists in the default location +// and if not, it will generate a new one. +// It will also check if the ARDUINO_CREATE_AGENT_CONFIG environment variable is set, +// and if so, it will use that path instead of the default one. func GetConfigPath() *paths.Path { // Let's handle the config configDir := GetDefaultConfigDir() diff --git a/hub.go b/hub.go index 2af0e8845..0c95a2a22 100755 --- a/hub.go +++ b/hub.go @@ -52,6 +52,8 @@ type hub struct { serialPortList *SerialPortList } +// NewHub creates a hub that acts as a central hub for handling +// WebSocket connections, broadcasting messages, and processing commands. func NewHub(serialhub *serialhub, serialList *SerialPortList) *hub { hub := &hub{ broadcast: make(chan []byte, 1000), diff --git a/info.go b/info.go index b7a3585e4..bf04713e3 100644 --- a/info.go +++ b/info.go @@ -41,7 +41,7 @@ func infoHandler(c *gin.Context) { }) } -func PauseHandler(h *hub, s *systray.Systray) func(c *gin.Context) { +func pauseHandler(h *hub, s *systray.Systray) func(c *gin.Context) { return func(c *gin.Context) { go func() { ports, _ := serial.GetPortsList() diff --git a/main.go b/main.go index 419ebd8ef..c23050119 100755 --- a/main.go +++ b/main.go @@ -450,8 +450,8 @@ func loop(stray *systray.Systray, configPath *paths.Path) { r.Handle("WS", "/socket.io/", socketHandler) r.Handle("WSS", "/socket.io/", socketHandler) r.GET("/info", infoHandler) - r.POST("/pause", PauseHandler(hub, stray)) - r.POST("/update", UpdateHandler(stray)) + r.POST("/pause", pauseHandler(hub, stray)) + r.POST("/update", updateHandler(stray)) // Mount goa handlers goa := v2.Server(config.GetDataDir().String(), Index, signaturePubKey) diff --git a/serial.go b/serial.go index 3333701a0..da959ad75 100755 --- a/serial.go +++ b/serial.go @@ -38,6 +38,8 @@ type serialhub struct { OnUnregister func(port *serport) } +// NewSerialHub creates a new serial hub +// It is used to manage serial ports and their connections. func NewSerialHub() *serialhub { return &serialhub{ ports: make(map[*serport]bool), @@ -53,6 +55,7 @@ type SerialPortList struct { OnErr func(string) `json:"-"` } +// NewSerialPortList creates a new serial port list func NewSerialPortList() *SerialPortList { return &SerialPortList{} } diff --git a/update.go b/update.go index 0065a09b5..a12f1feb2 100644 --- a/update.go +++ b/update.go @@ -35,7 +35,7 @@ import ( "github.com/gin-gonic/gin" ) -func UpdateHandler(s *systray.Systray) func(c *gin.Context) { +func updateHandler(s *systray.Systray) func(c *gin.Context) { return func(c *gin.Context) { restartPath, err := updater.CheckForUpdates(version, *updateURL, *appName) if err != nil { From 5e2161e1eea2de99e6fb8344099d6d11f16f58f9 Mon Sep 17 00:00:00 2001 From: Davide N Date: Mon, 31 Mar 2025 17:41:40 +0200 Subject: [PATCH 12/50] refactor: rename 'hub' and 'serialhub' types to 'Hub' and 'Serialhub' for consistency --- conn.go | 8 ++++---- hub.go | 24 ++++++++++++------------ info.go | 2 +- serial.go | 18 +++++++++--------- serialport.go | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/conn.go b/conn.go index 584e1328f..f307f0c75 100644 --- a/conn.go +++ b/conn.go @@ -80,7 +80,7 @@ type Upload struct { var uploadStatusStr = "ProgrammerStatus" -func uploadHandler(h *hub, pubKey *rsa.PublicKey) func(*gin.Context) { +func uploadHandler(h *Hub, pubKey *rsa.PublicKey) func(*gin.Context) { return func(c *gin.Context) { data := new(Upload) if err := c.BindJSON(data); err != nil { @@ -193,7 +193,7 @@ func uploadHandler(h *hub, pubKey *rsa.PublicKey) func(*gin.Context) { // PLogger sends the info from the upload to the websocket type PLogger struct { Verbose bool - h *hub + h *Hub } // Debug only sends messages if verbose is true (always true for now) @@ -210,12 +210,12 @@ func (l PLogger) Info(args ...interface{}) { send(l.h, map[string]string{uploadStatusStr: "Busy", "Msg": output}) } -func send(h *hub, args map[string]string) { +func send(h *Hub, args map[string]string) { mapB, _ := json.Marshal(args) h.broadcastSys <- mapB } -func wsHandler(h *hub) *WsServer { +func wsHandler(h *Hub) *WsServer { server, err := socketio.NewServer(nil) if err != nil { log.Fatal(err) diff --git a/hub.go b/hub.go index 0c95a2a22..4013dfb09 100755 --- a/hub.go +++ b/hub.go @@ -29,7 +29,7 @@ import ( log "github.com/sirupsen/logrus" ) -type hub struct { +type Hub struct { // Registered connections. connections map[*connection]bool @@ -47,15 +47,15 @@ type hub struct { //TODO globals clients // Serial hub to communicate with serial ports - serialHub *serialhub + serialHub *Serialhub serialPortList *SerialPortList } // NewHub creates a hub that acts as a central hub for handling // WebSocket connections, broadcasting messages, and processing commands. -func NewHub(serialhub *serialhub, serialList *SerialPortList) *hub { - hub := &hub{ +func NewHub(serialhub *Serialhub, serialList *SerialPortList) *Hub { + hub := &Hub{ broadcast: make(chan []byte, 1000), broadcastSys: make(chan []byte, 1000), register: make(chan *connection), @@ -102,7 +102,7 @@ const commands = `{ ] }` -func (h *hub) unregisterConnection(c *connection) { +func (h *Hub) unregisterConnection(c *connection) { if _, contains := h.connections[c]; !contains { return } @@ -110,7 +110,7 @@ func (h *hub) unregisterConnection(c *connection) { close(c.send) } -func (h *hub) sendToRegisteredConnections(data []byte) { +func (h *Hub) sendToRegisteredConnections(data []byte) { for c := range h.connections { select { case c.send <- data: @@ -123,7 +123,7 @@ func (h *hub) sendToRegisteredConnections(data []byte) { } } -func (h *hub) run() { +func (h *Hub) run() { for { select { case c := <-h.register: @@ -146,7 +146,7 @@ func (h *hub) run() { } } -func (h *hub) checkCmd(m []byte) { +func (h *Hub) checkCmd(m []byte) { //log.Print("Inside checkCmd") s := string(m[:]) @@ -284,7 +284,7 @@ func logAction(sl string) { } } -func (h *hub) memoryStats() { +func (h *Hub) memoryStats() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) json, _ := json.Marshal(memStats) @@ -292,15 +292,15 @@ func (h *hub) memoryStats() { h.broadcastSys <- json } -func (h *hub) getHostname() { +func (h *Hub) getHostname() { h.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") } -func (h *hub) getVersion() { +func (h *Hub) getVersion() { h.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") } -func (h *hub) garbageCollection() { +func (h *Hub) garbageCollection() { log.Printf("Starting garbageCollection()\n") h.broadcastSys <- []byte("{\"gc\":\"starting\"}") h.memoryStats() diff --git a/info.go b/info.go index bf04713e3..ba77cf401 100644 --- a/info.go +++ b/info.go @@ -41,7 +41,7 @@ func infoHandler(c *gin.Context) { }) } -func pauseHandler(h *hub, s *systray.Systray) func(c *gin.Context) { +func pauseHandler(h *Hub, s *systray.Systray) func(c *gin.Context) { return func(c *gin.Context) { go func() { ports, _ := serial.GetPortsList() diff --git a/serial.go b/serial.go index da959ad75..8c9589991 100755 --- a/serial.go +++ b/serial.go @@ -29,7 +29,7 @@ import ( "github.com/sirupsen/logrus" ) -type serialhub struct { +type Serialhub struct { // Opened serial ports. ports map[*serport]bool mu sync.Mutex @@ -40,8 +40,8 @@ type serialhub struct { // NewSerialHub creates a new serial hub // It is used to manage serial ports and their connections. -func NewSerialHub() *serialhub { - return &serialhub{ +func NewSerialHub() *Serialhub { + return &Serialhub{ ports: make(map[*serport]bool), } } @@ -75,7 +75,7 @@ type SpPortItem struct { } // Register serial ports from the connections. -func (sh *serialhub) Register(port *serport) { +func (sh *Serialhub) Register(port *serport) { sh.mu.Lock() //log.Print("Registering a port: ", p.portConf.Name) sh.OnRegister(port) @@ -84,7 +84,7 @@ func (sh *serialhub) Register(port *serport) { } // Unregister requests from connections. -func (sh *serialhub) Unregister(port *serport) { +func (sh *Serialhub) Unregister(port *serport) { sh.mu.Lock() //log.Print("Unregistering a port: ", p.portConf.Name) sh.OnUnregister(port) @@ -94,7 +94,7 @@ func (sh *serialhub) Unregister(port *serport) { sh.mu.Unlock() } -func (sh *serialhub) FindPortByName(portname string) (*serport, bool) { +func (sh *Serialhub) FindPortByName(portname string) (*serport, bool) { sh.mu.Lock() defer sh.mu.Unlock() @@ -265,13 +265,13 @@ func (sp *SerialPortList) getPortByName(portname string) *SpPortItem { return nil } -func (h *hub) spErr(err string) { +func (h *Hub) spErr(err string) { //log.Println("Sending err back: ", err) //sh.hub.broadcastSys <- []byte(err) h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") } -func (h *hub) spClose(portname string) { +func (h *Hub) spClose(portname string) { if myport, ok := h.serialHub.FindPortByName(portname); ok { h.broadcastSys <- []byte("Closing serial port " + portname) myport.Close() @@ -280,7 +280,7 @@ func (h *hub) spClose(portname string) { } } -func (h *hub) spWrite(arg string) { +func (h *Hub) spWrite(arg string) { // we will get a string of comXX asdf asdf asdf //log.Println("Inside spWrite arg: " + arg) arg = strings.TrimPrefix(arg, " ") diff --git a/serialport.go b/serialport.go index 06ea0aa64..06b16ff41 100755 --- a/serialport.go +++ b/serialport.go @@ -287,7 +287,7 @@ func (p *serport) writerRaw() { p.OnMessage([]byte(msgstr)) } -func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { +func (h *Hub) spHandlerOpen(portname string, baud int, buftype string) { log.Print("Inside spHandler") From 910bac22cf515a1eed175c31a9d9770b52e17793 Mon Sep 17 00:00:00 2001 From: Davide N Date: Mon, 31 Mar 2025 17:48:44 +0200 Subject: [PATCH 13/50] refactor: rename 'Hub' and 'Serialhub' types to 'hub' and 'serialhub' for consistency --- conn.go | 8 ++++---- hub.go | 28 +++++++++++++--------------- info.go | 2 +- main.go | 6 +++--- main_test.go | 4 ++-- serial.go | 46 +++++++++++++++++++++------------------------- serialport.go | 2 +- 7 files changed, 45 insertions(+), 51 deletions(-) diff --git a/conn.go b/conn.go index f307f0c75..584e1328f 100644 --- a/conn.go +++ b/conn.go @@ -80,7 +80,7 @@ type Upload struct { var uploadStatusStr = "ProgrammerStatus" -func uploadHandler(h *Hub, pubKey *rsa.PublicKey) func(*gin.Context) { +func uploadHandler(h *hub, pubKey *rsa.PublicKey) func(*gin.Context) { return func(c *gin.Context) { data := new(Upload) if err := c.BindJSON(data); err != nil { @@ -193,7 +193,7 @@ func uploadHandler(h *Hub, pubKey *rsa.PublicKey) func(*gin.Context) { // PLogger sends the info from the upload to the websocket type PLogger struct { Verbose bool - h *Hub + h *hub } // Debug only sends messages if verbose is true (always true for now) @@ -210,12 +210,12 @@ func (l PLogger) Info(args ...interface{}) { send(l.h, map[string]string{uploadStatusStr: "Busy", "Msg": output}) } -func send(h *Hub, args map[string]string) { +func send(h *hub, args map[string]string) { mapB, _ := json.Marshal(args) h.broadcastSys <- mapB } -func wsHandler(h *Hub) *WsServer { +func wsHandler(h *hub) *WsServer { server, err := socketio.NewServer(nil) if err != nil { log.Fatal(err) diff --git a/hub.go b/hub.go index 4013dfb09..d812d6b80 100755 --- a/hub.go +++ b/hub.go @@ -29,7 +29,7 @@ import ( log "github.com/sirupsen/logrus" ) -type Hub struct { +type hub struct { // Registered connections. connections map[*connection]bool @@ -47,15 +47,13 @@ type Hub struct { //TODO globals clients // Serial hub to communicate with serial ports - serialHub *Serialhub + serialHub *serialhub - serialPortList *SerialPortList + serialPortList *serialPortList } -// NewHub creates a hub that acts as a central hub for handling -// WebSocket connections, broadcasting messages, and processing commands. -func NewHub(serialhub *Serialhub, serialList *SerialPortList) *Hub { - hub := &Hub{ +func newHub(serialhub *serialhub, serialList *serialPortList) *hub { + hub := &hub{ broadcast: make(chan []byte, 1000), broadcastSys: make(chan []byte, 1000), register: make(chan *connection), @@ -102,7 +100,7 @@ const commands = `{ ] }` -func (h *Hub) unregisterConnection(c *connection) { +func (h *hub) unregisterConnection(c *connection) { if _, contains := h.connections[c]; !contains { return } @@ -110,7 +108,7 @@ func (h *Hub) unregisterConnection(c *connection) { close(c.send) } -func (h *Hub) sendToRegisteredConnections(data []byte) { +func (h *hub) sendToRegisteredConnections(data []byte) { for c := range h.connections { select { case c.send <- data: @@ -123,7 +121,7 @@ func (h *Hub) sendToRegisteredConnections(data []byte) { } } -func (h *Hub) run() { +func (h *hub) run() { for { select { case c := <-h.register: @@ -146,7 +144,7 @@ func (h *Hub) run() { } } -func (h *Hub) checkCmd(m []byte) { +func (h *hub) checkCmd(m []byte) { //log.Print("Inside checkCmd") s := string(m[:]) @@ -284,7 +282,7 @@ func logAction(sl string) { } } -func (h *Hub) memoryStats() { +func (h *hub) memoryStats() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) json, _ := json.Marshal(memStats) @@ -292,15 +290,15 @@ func (h *Hub) memoryStats() { h.broadcastSys <- json } -func (h *Hub) getHostname() { +func (h *hub) getHostname() { h.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") } -func (h *Hub) getVersion() { +func (h *hub) getVersion() { h.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") } -func (h *Hub) garbageCollection() { +func (h *hub) garbageCollection() { log.Printf("Starting garbageCollection()\n") h.broadcastSys <- []byte("{\"gc\":\"starting\"}") h.memoryStats() diff --git a/info.go b/info.go index ba77cf401..bf04713e3 100644 --- a/info.go +++ b/info.go @@ -41,7 +41,7 @@ func infoHandler(c *gin.Context) { }) } -func pauseHandler(h *Hub, s *systray.Systray) func(c *gin.Context) { +func pauseHandler(h *hub, s *systray.Systray) func(c *gin.Context) { return func(c *gin.Context) { go func() { ports, _ := serial.GetPortsList() diff --git a/main.go b/main.go index c23050119..a325c2348 100755 --- a/main.go +++ b/main.go @@ -191,12 +191,12 @@ func loop(stray *systray.Systray, configPath *paths.Path) { } // serialPorts contains the ports attached to the machine - serialPorts := NewSerialPortList() - serialHub := NewSerialHub() + serialPorts := newSerialPortList() + serialHub := newSerialHub() // var loggerWs logWriter - hub := NewHub(serialHub, serialPorts) + hub := newHub(serialHub, serialPorts) logger := func(msg string) { mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} diff --git a/main_test.go b/main_test.go index 67cb91a1d..d30498669 100644 --- a/main_test.go +++ b/main_test.go @@ -56,7 +56,7 @@ func TestValidSignatureKey(t *testing.T) { func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { r := gin.New() - r.POST("/", uploadHandler(NewHub(NewSerialHub(), NewSerialPortList()), utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) + r.POST("/", uploadHandler(newHub(newSerialHub(), newSerialPortList()), utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) ts := httptest.NewServer(r) uploadEvilFileName := Upload{ @@ -93,7 +93,7 @@ func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { func TestUploadHandlerAgainstBase64WithoutPaddingMustFail(t *testing.T) { r := gin.New() - r.POST("/", uploadHandler(NewHub(NewSerialHub(), NewSerialPortList()), utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) + r.POST("/", uploadHandler(newHub(newSerialHub(), newSerialPortList()), utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) ts := httptest.NewServer(r) defer ts.Close() diff --git a/serial.go b/serial.go index 8c9589991..c3f1b38bf 100755 --- a/serial.go +++ b/serial.go @@ -29,7 +29,7 @@ import ( "github.com/sirupsen/logrus" ) -type Serialhub struct { +type serialhub struct { // Opened serial ports. ports map[*serport]bool mu sync.Mutex @@ -38,16 +38,13 @@ type Serialhub struct { OnUnregister func(port *serport) } -// NewSerialHub creates a new serial hub -// It is used to manage serial ports and their connections. -func NewSerialHub() *Serialhub { - return &Serialhub{ +func newSerialHub() *serialhub { + return &serialhub{ ports: make(map[*serport]bool), } } -// SerialPortList is the serial port list -type SerialPortList struct { +type serialPortList struct { Ports []*SpPortItem portsLock sync.Mutex @@ -55,9 +52,8 @@ type SerialPortList struct { OnErr func(string) `json:"-"` } -// NewSerialPortList creates a new serial port list -func NewSerialPortList() *SerialPortList { - return &SerialPortList{} +func newSerialPortList() *serialPortList { + return &serialPortList{} } // SpPortItem is the serial port item @@ -75,7 +71,7 @@ type SpPortItem struct { } // Register serial ports from the connections. -func (sh *Serialhub) Register(port *serport) { +func (sh *serialhub) Register(port *serport) { sh.mu.Lock() //log.Print("Registering a port: ", p.portConf.Name) sh.OnRegister(port) @@ -84,7 +80,7 @@ func (sh *Serialhub) Register(port *serport) { } // Unregister requests from connections. -func (sh *Serialhub) Unregister(port *serport) { +func (sh *serialhub) Unregister(port *serport) { sh.mu.Lock() //log.Print("Unregistering a port: ", p.portConf.Name) sh.OnUnregister(port) @@ -94,7 +90,7 @@ func (sh *Serialhub) Unregister(port *serport) { sh.mu.Unlock() } -func (sh *Serialhub) FindPortByName(portname string) (*serport, bool) { +func (sh *serialhub) FindPortByName(portname string) (*serport, bool) { sh.mu.Lock() defer sh.mu.Unlock() @@ -109,7 +105,7 @@ func (sh *Serialhub) FindPortByName(portname string) (*serport, bool) { } // List broadcasts a Json representation of the ports found -func (sp *SerialPortList) List() { +func (sp *serialPortList) List() { sp.portsLock.Lock() ls, err := json.MarshalIndent(sp, "", "\t") sp.portsLock.Unlock() @@ -122,7 +118,7 @@ func (sp *SerialPortList) List() { } // Run is the main loop for port discovery and management -func (sp *SerialPortList) Run() { +func (sp *serialPortList) Run() { for retries := 0; retries < 10; retries++ { sp.runSerialDiscovery() @@ -132,7 +128,7 @@ func (sp *SerialPortList) Run() { logrus.Errorf("Failed restarting serial discovery. Giving up...") } -func (sp *SerialPortList) runSerialDiscovery() { +func (sp *serialPortList) runSerialDiscovery() { // First ensure that all the discoveries are available if err := Tools.Download("builtin", "serial-discovery", "latest", "keep"); err != nil { logrus.Errorf("Error downloading serial-discovery: %s", err) @@ -176,13 +172,13 @@ func (sp *SerialPortList) runSerialDiscovery() { logrus.Errorf("Serial discovery stopped.") } -func (sp *SerialPortList) reset() { +func (sp *serialPortList) reset() { sp.portsLock.Lock() defer sp.portsLock.Unlock() sp.Ports = []*SpPortItem{} } -func (sp *SerialPortList) add(addedPort *discovery.Port) { +func (sp *serialPortList) add(addedPort *discovery.Port) { if addedPort.Protocol != "serial" { return } @@ -226,7 +222,7 @@ func (sp *SerialPortList) add(addedPort *discovery.Port) { }) } -func (sp *SerialPortList) remove(removedPort *discovery.Port) { +func (sp *serialPortList) remove(removedPort *discovery.Port) { sp.portsLock.Lock() defer sp.portsLock.Unlock() @@ -237,7 +233,7 @@ func (sp *SerialPortList) remove(removedPort *discovery.Port) { } // MarkPortAsOpened marks a port as opened by the user -func (sp *SerialPortList) MarkPortAsOpened(portname string) { +func (sp *serialPortList) MarkPortAsOpened(portname string) { sp.portsLock.Lock() defer sp.portsLock.Unlock() port := sp.getPortByName(portname) @@ -247,7 +243,7 @@ func (sp *SerialPortList) MarkPortAsOpened(portname string) { } // MarkPortAsClosed marks a port as no more opened by the user -func (sp *SerialPortList) MarkPortAsClosed(portname string) { +func (sp *serialPortList) MarkPortAsClosed(portname string) { sp.portsLock.Lock() defer sp.portsLock.Unlock() port := sp.getPortByName(portname) @@ -256,7 +252,7 @@ func (sp *SerialPortList) MarkPortAsClosed(portname string) { } } -func (sp *SerialPortList) getPortByName(portname string) *SpPortItem { +func (sp *serialPortList) getPortByName(portname string) *SpPortItem { for _, port := range sp.Ports { if port.Name == portname { return port @@ -265,13 +261,13 @@ func (sp *SerialPortList) getPortByName(portname string) *SpPortItem { return nil } -func (h *Hub) spErr(err string) { +func (h *hub) spErr(err string) { //log.Println("Sending err back: ", err) //sh.hub.broadcastSys <- []byte(err) h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") } -func (h *Hub) spClose(portname string) { +func (h *hub) spClose(portname string) { if myport, ok := h.serialHub.FindPortByName(portname); ok { h.broadcastSys <- []byte("Closing serial port " + portname) myport.Close() @@ -280,7 +276,7 @@ func (h *Hub) spClose(portname string) { } } -func (h *Hub) spWrite(arg string) { +func (h *hub) spWrite(arg string) { // we will get a string of comXX asdf asdf asdf //log.Println("Inside spWrite arg: " + arg) arg = strings.TrimPrefix(arg, " ") diff --git a/serialport.go b/serialport.go index 06b16ff41..06ea0aa64 100755 --- a/serialport.go +++ b/serialport.go @@ -287,7 +287,7 @@ func (p *serport) writerRaw() { p.OnMessage([]byte(msgstr)) } -func (h *Hub) spHandlerOpen(portname string, baud int, buftype string) { +func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { log.Print("Inside spHandler") From 17ed6b3e7f9ac1701b76c2c494cd97fcaada90ed Mon Sep 17 00:00:00 2001 From: Davide N Date: Mon, 31 Mar 2025 17:54:08 +0200 Subject: [PATCH 14/50] revert config changes --- config/config-default.ini | 10 -- config/config.go | 45 +-------- config/config_test.go | 94 ------------------- config/testdata/fromdefault/.gitignore | 1 - config/testdata/fromenv/config.ini | 8 -- .../.config/ArduinoCreateAgent/config.ini | 8 -- config/testdata/fromlegacy/.gitignore | 2 - .../fromxdghome/ArduinoCreateAgent/config.ini | 10 -- .../.config/ArduinoCreateAgent/config.ini | 8 -- .../.config/ArduinoCreateAgent/config.ini | 10 -- 10 files changed, 1 insertion(+), 195 deletions(-) delete mode 100644 config/config-default.ini delete mode 100644 config/config_test.go delete mode 100644 config/testdata/fromdefault/.gitignore delete mode 100644 config/testdata/fromenv/config.ini delete mode 100644 config/testdata/fromhome/.config/ArduinoCreateAgent/config.ini delete mode 100644 config/testdata/fromlegacy/.gitignore delete mode 100644 config/testdata/fromxdghome/ArduinoCreateAgent/config.ini delete mode 100644 config/testdata/home/.config/ArduinoCreateAgent/config.ini delete mode 100644 config/testdata/noconfig/.config/ArduinoCreateAgent/config.ini diff --git a/config/config-default.ini b/config/config-default.ini deleted file mode 100644 index f63377db5..000000000 --- a/config/config-default.ini +++ /dev/null @@ -1,10 +0,0 @@ -gc = std # Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage) -hostname = unknown-hostname # Override the hostname we get from the OS -regex = usb|acm|com # Regular expression to filter serial port list -v = true # show debug logging -appName = CreateAgent/Stable -updateUrl = https://downloads.arduino.cc/ -origins = https://local.arduino.cc:8000 -#httpProxy = http://your.proxy:port # Proxy server for HTTP requests -crashreport = false # enable crashreport logging -autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent) \ No newline at end of file diff --git a/config/config.go b/config/config.go index 82edecf41..69d29eeee 100644 --- a/config/config.go +++ b/config/config.go @@ -108,7 +108,7 @@ func GetDefaultHomeDir() *paths.Path { return paths.New(homeDir) } -//go:embed config-default.ini +//go:embed config.ini var configContent []byte // GenerateConfig function will take a directory path as an input @@ -142,46 +142,3 @@ func SetInstallCertsIni(filename string, value string) error { } return nil } - -// GetConfigPath returns the full path to the Arduino Create Agent configuration file. -// It will check if the config file exists in the default location -// and if not, it will generate a new one. -// It will also check if the ARDUINO_CREATE_AGENT_CONFIG environment variable is set, -// and if so, it will use that path instead of the default one. -func GetConfigPath() *paths.Path { - // Let's handle the config - configDir := GetDefaultConfigDir() - var configPath *paths.Path - - // see if the env var is defined, if it is take the config from there, this will override the default path - if envConfig := os.Getenv("ARDUINO_CREATE_AGENT_CONFIG"); envConfig != "" { - configPath = paths.New(envConfig) - if configPath.NotExist() { - log.Panicf("config from env var %s does not exists", envConfig) - } - log.Infof("using config from env variable: %s", configPath) - } else if defaultConfigPath := configDir.Join("config.ini"); defaultConfigPath.Exist() { - // by default take the config from the ~/.arduino-create/config.ini file - configPath = defaultConfigPath - log.Infof("using config from default: %s", configPath) - } else { - // Fall back to the old config.ini location - src, _ := os.Executable() - oldConfigPath := paths.New(src).Parent().Join("config.ini") - if oldConfigPath.Exist() { - err := oldConfigPath.CopyTo(defaultConfigPath) - if err != nil { - log.Errorf("cannot copy old %s, to %s, generating new config", oldConfigPath, configPath) - } else { - configPath = defaultConfigPath - log.Infof("copied old %s, to %s", oldConfigPath, configPath) - } - } - } - if configPath == nil { - configPath = GenerateConfig(configDir) - } - - return configPath - -} diff --git a/config/config_test.go b/config/config_test.go deleted file mode 100644 index 447f3793e..000000000 --- a/config/config_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package config - -import ( - "os" - "testing" - "time" - - "github.com/arduino/go-paths-helper" - "github.com/stretchr/testify/assert" -) - -func TestGetConfigPathFromXDG_CONFIG_HOME(t *testing.T) { - // read config from $XDG_CONFIG_HOME/ArduinoCreateAgent/config.ini - os.Setenv("XDG_CONFIG_HOME", "./testdata/fromxdghome") - defer os.Unsetenv("XDG_CONFIG_HOME") - configPath := GetConfigPath() - assert.Equal(t, "testdata/fromxdghome/ArduinoCreateAgent/config.ini", configPath.String()) -} - -func TestGetConfigPathFromHOME(t *testing.T) { - // Test case 2: read config from $HOME/.config/ArduinoCreateAgent/config.ini " - os.Setenv("HOME", "./testdata/fromhome") - defer os.Unsetenv("HOME") - configPath := GetConfigPath() - assert.Equal(t, "testdata/fromhome/.config/ArduinoCreateAgent/config.ini", configPath.String()) - -} - -func TestGetConfigPathFromARDUINO_CREATE_AGENT_CONFIG(t *testing.T) { - // read config from ARDUINO_CREATE_AGENT_CONFIG/config.ini" - os.Setenv("HOME", "./fromhome") - os.Setenv("ARDUINO_CREATE_AGENT_CONFIG", "./testdata/fromenv/config.ini") - defer os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") - - configPath := GetConfigPath() - assert.Equal(t, "./testdata/fromenv/config.ini", configPath.String()) -} - -func TestGetConfigPathFromLegacyConfig(t *testing.T) { - // If no config is found, copy the legacy config to the new location - src, err := os.Executable() - if err != nil { - t.Fatal(err) - } - legacyConfigPath, err := paths.New(src).Parent().Join("config.ini").Create() - if err != nil { - t.Fatal(err) - } - // adding a timestamp to the content to make it unique - legacyContent := "hostname = legacy-config-file-" + time.Now().String() - n, err := legacyConfigPath.WriteString(legacyContent) - if err != nil || n <= 0 { - t.Fatalf("Failed to write legacy config file: %v", err) - } - - // remove any existing config.ini in the into the location pointed by $HOME - err = os.Remove("./testdata/fromlegacy/.config/ArduinoCreateAgent/config.ini") - if err != nil && !os.IsNotExist(err) { - t.Fatal(err) - } - - // Expectation: it copies the "legacy" config.ini into the location pointed by $HOME - os.Setenv("HOME", "./testdata/fromlegacy") - defer os.Unsetenv("HOME") - - configPath := GetConfigPath() - assert.Equal(t, "testdata/fromlegacy/.config/ArduinoCreateAgent/config.ini", configPath.String()) - - given, err := paths.New(configPath.String()).ReadFile() - assert.Nil(t, err) - assert.Equal(t, legacyContent, string(given)) -} - -// func TestGetConfigPathCreateDefaultConfig(t *testing.T) { -// os.Setenv("HOME", "./testdata/noconfig") -// os.Unsetenv("ARDUINO_CREATE_AGENT_CONFIG") - -// // ensure the config.ini does not exist in HOME directory -// os.Remove("./testdata/noconfig/.config/ArduinoCreateAgent/config.ini") -// // ensure the config.ini does not exist in target directory -// os.Remove("./testdata/fromdefault/.config/ArduinoCreateAgent/config.ini") - -// configPath := GetConfigPath() - -// assert.Equal(t, "testdata/fromdefault/.config/ArduinoCreateAgent/config.ini", configPath.String()) - -// givenContent, err := paths.New(configPath.String()).ReadFile() -// if err != nil { -// t.Fatal(err) -// } - -// assert.Equal(t, string(configContent), string(givenContent)) - -// } diff --git a/config/testdata/fromdefault/.gitignore b/config/testdata/fromdefault/.gitignore deleted file mode 100644 index 2fa7ce7c4..000000000 --- a/config/testdata/fromdefault/.gitignore +++ /dev/null @@ -1 +0,0 @@ -config.ini diff --git a/config/testdata/fromenv/config.ini b/config/testdata/fromenv/config.ini deleted file mode 100644 index cfd58d812..000000000 --- a/config/testdata/fromenv/config.ini +++ /dev/null @@ -1,8 +0,0 @@ -gc = std -hostname = this-is-a-config-file-from-home-dir-from-ARDUINO_CREATE_AGENT_CONFIG-env -regex = usb|acm|com -v = true -appName = CreateAgent/Stable -updateUrl = https://downloads.arduino.cc/ -origins = https://local.arduino.cc:8000, https://local.arduino.cc:8001, https://create-dev.arduino.cc, https://*.sparklyunicorn.cc, https://*.iot-cloud-arduino-cc.pages.dev, https://cloud.oniudra.cc, https://app.oniudra.cc,https://*.iot-cloud-arduino-cc.pages.dev -crashreport = false diff --git a/config/testdata/fromhome/.config/ArduinoCreateAgent/config.ini b/config/testdata/fromhome/.config/ArduinoCreateAgent/config.ini deleted file mode 100644 index a023d62d1..000000000 --- a/config/testdata/fromhome/.config/ArduinoCreateAgent/config.ini +++ /dev/null @@ -1,8 +0,0 @@ -gc = std -hostname = this-is-a-config-file-from-home-dir -regex = usb|acm|com -v = true -appName = config-from-home-dir -updateUrl = https://downloads.arduino.cc/ -origins = https://local.arduino.cc:8000, https://local.arduino.cc:8001, https://*.iot-cloud-arduino-cc.pages.dev -crashreport = false diff --git a/config/testdata/fromlegacy/.gitignore b/config/testdata/fromlegacy/.gitignore deleted file mode 100644 index f67b7f089..000000000 --- a/config/testdata/fromlegacy/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# ingore because this config file -config.ini diff --git a/config/testdata/fromxdghome/ArduinoCreateAgent/config.ini b/config/testdata/fromxdghome/ArduinoCreateAgent/config.ini deleted file mode 100644 index f63377db5..000000000 --- a/config/testdata/fromxdghome/ArduinoCreateAgent/config.ini +++ /dev/null @@ -1,10 +0,0 @@ -gc = std # Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage) -hostname = unknown-hostname # Override the hostname we get from the OS -regex = usb|acm|com # Regular expression to filter serial port list -v = true # show debug logging -appName = CreateAgent/Stable -updateUrl = https://downloads.arduino.cc/ -origins = https://local.arduino.cc:8000 -#httpProxy = http://your.proxy:port # Proxy server for HTTP requests -crashreport = false # enable crashreport logging -autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent) \ No newline at end of file diff --git a/config/testdata/home/.config/ArduinoCreateAgent/config.ini b/config/testdata/home/.config/ArduinoCreateAgent/config.ini deleted file mode 100644 index a023d62d1..000000000 --- a/config/testdata/home/.config/ArduinoCreateAgent/config.ini +++ /dev/null @@ -1,8 +0,0 @@ -gc = std -hostname = this-is-a-config-file-from-home-dir -regex = usb|acm|com -v = true -appName = config-from-home-dir -updateUrl = https://downloads.arduino.cc/ -origins = https://local.arduino.cc:8000, https://local.arduino.cc:8001, https://*.iot-cloud-arduino-cc.pages.dev -crashreport = false diff --git a/config/testdata/noconfig/.config/ArduinoCreateAgent/config.ini b/config/testdata/noconfig/.config/ArduinoCreateAgent/config.ini deleted file mode 100644 index f63377db5..000000000 --- a/config/testdata/noconfig/.config/ArduinoCreateAgent/config.ini +++ /dev/null @@ -1,10 +0,0 @@ -gc = std # Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage) -hostname = unknown-hostname # Override the hostname we get from the OS -regex = usb|acm|com # Regular expression to filter serial port list -v = true # show debug logging -appName = CreateAgent/Stable -updateUrl = https://downloads.arduino.cc/ -origins = https://local.arduino.cc:8000 -#httpProxy = http://your.proxy:port # Proxy server for HTTP requests -crashreport = false # enable crashreport logging -autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent) \ No newline at end of file From 96f0f5134a69edbd30135ddbacbbe2c2cc394432 Mon Sep 17 00:00:00 2001 From: Davide N Date: Mon, 31 Mar 2025 18:09:50 +0200 Subject: [PATCH 15/50] refactor: update logger handling and improve config path management --- hub.go | 2 +- main.go | 67 ++++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/hub.go b/hub.go index d812d6b80..150c6d943 100755 --- a/hub.go +++ b/hub.go @@ -270,7 +270,7 @@ func (h *hub) checkCmd(m []byte) { func logAction(sl string) { if strings.HasPrefix(sl, "log on") { *logDump = "on" - //TODO: pass the loggerSw in the constructor and enable again the log e + // FIXME: pass the loggerSw in the constructor and enable again the log e // multiWriter := io.MultiWriter(&loggerWs, os.Stderr) // log.SetOutput(multiWriter) } else if strings.HasPrefix(sl, "log off") { diff --git a/main.go b/main.go index a325c2348..1c7d85de4 100755 --- a/main.go +++ b/main.go @@ -22,7 +22,6 @@ import ( _ "embed" "encoding/json" "flag" - "fmt" "html/template" "io" "os" @@ -103,11 +102,12 @@ var homeTemplateHTML string // global clients var ( - Tools *tools.Tools - Index *index.Resource + Tools *tools.Tools + Systray systray.Systray + Index *index.Resource ) -// TODO: enable it +// FIXME; the loggerWS is useind in the multiwrite in the hub // type logWriter struct{} // func (u *logWriter) Write(p []byte) (n int, err error) { @@ -115,6 +115,8 @@ var ( // return len(p), nil // } +// var loggerWs logWriter + func homeHandler(c *gin.Context) { homeTemplate.Execute(c.Writer, c.Request.Host) } @@ -140,13 +142,9 @@ func main() { // Check if certificates made with Agent <=1.2.7 needs to be moved over the new location cert.MigrateCertificatesGeneratedWithOldAgentVersions(config.GetCertificatesDir()) - configPath := config.GetConfigPath() - fmt.Println("configPath: ", configPath) - // SetupSystray is the main thread configDir := config.GetDefaultConfigDir() - - stray := &systray.Systray{ + stray := systray.Systray{ Hibernate: *hibernate, Version: version + "-" + commit, DebugURL: func() string { @@ -155,29 +153,24 @@ func main() { AdditionalConfig: *additionalConfig, ConfigDir: configDir, } - stray.SetCurrentConfigFile(configPath) // Launch main loop in a goroutine - go loop(stray, configPath) + go loop(&stray) if src, err := os.Executable(); err != nil { panic(err) } else if restartPath := updater.Start(src); restartPath != "" { - stray.RestartWith(restartPath) + Systray.RestartWith(restartPath) } else { - stray.Start() + Systray.Start() } } -func loop(stray *systray.Systray, configPath *paths.Path) { +func loop(stray *systray.Systray) { if *hibernate { return } - if configPath == nil { - log.Panic("configPath is nil") - } - log.SetLevel(log.InfoLevel) log.SetOutput(os.Stdout) @@ -190,12 +183,8 @@ func loop(stray *systray.Systray, configPath *paths.Path) { os.Exit(0) } - // serialPorts contains the ports attached to the machine serialPorts := newSerialPortList() serialHub := newSerialHub() - - // var loggerWs logWriter - hub := newHub(serialHub, serialPorts) logger := func(msg string) { @@ -204,6 +193,39 @@ func loop(stray *systray.Systray, configPath *paths.Path) { hub.broadcastSys <- mapB } + // Let's handle the config + configDir := config.GetDefaultConfigDir() + var configPath *paths.Path + + // see if the env var is defined, if it is take the config from there, this will override the default path + if envConfig := os.Getenv("ARDUINO_CREATE_AGENT_CONFIG"); envConfig != "" { + configPath = paths.New(envConfig) + if configPath.NotExist() { + log.Panicf("config from env var %s does not exists", envConfig) + } + log.Infof("using config from env variable: %s", configPath) + } else if defaultConfigPath := configDir.Join("config.ini"); defaultConfigPath.Exist() { + // by default take the config from the ~/.arduino-create/config.ini file + configPath = defaultConfigPath + log.Infof("using config from default: %s", configPath) + } else { + // Fall back to the old config.ini location + src, _ := os.Executable() + oldConfigPath := paths.New(src).Parent().Join("config.ini") + if oldConfigPath.Exist() { + err := oldConfigPath.CopyTo(defaultConfigPath) + if err != nil { + log.Errorf("cannot copy old %s, to %s, generating new config", oldConfigPath, configPath) + } else { + configPath = defaultConfigPath + log.Infof("copied old %s, to %s", oldConfigPath, configPath) + } + } + } + if configPath == nil { + configPath = config.GenerateConfig(configDir) + } + // if the default browser is Safari, prompt the user to install HTTPS certificates // and eventually install them if runtime.GOOS == "darwin" { @@ -241,6 +263,7 @@ func loop(stray *systray.Systray, configPath *paths.Path) { if err != nil { log.Panicf("cannot parse arguments: %s", err) } + Systray.SetCurrentConfigFile(configPath) // Parse additional ini config if defined if len(*additionalConfig) > 0 { From 36af1edac648aa0fe6c3a444ef186f7dffe76b59 Mon Sep 17 00:00:00 2001 From: Davide N Date: Mon, 31 Mar 2025 18:46:38 +0200 Subject: [PATCH 16/50] remove globas tools and index --- config/config.ini | 2 +- conn.go | 5 +++-- hub.go | 13 +++++++++-- main.go | 50 +++++++++++++++--------------------------- main_test.go | 34 ++++++++++++++++++++-------- serial.go | 12 ++++++---- tools/download.go | 14 ++++++------ tools/download_test.go | 8 +++---- tools/tools.go | 4 +--- 9 files changed, 78 insertions(+), 64 deletions(-) diff --git a/config/config.ini b/config/config.ini index f63377db5..65de6012f 100644 --- a/config/config.ini +++ b/config/config.ini @@ -7,4 +7,4 @@ updateUrl = https://downloads.arduino.cc/ origins = https://local.arduino.cc:8000 #httpProxy = http://your.proxy:port # Proxy server for HTTP requests crashreport = false # enable crashreport logging -autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent) \ No newline at end of file +autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent) diff --git a/conn.go b/conn.go index 584e1328f..d99f24edd 100644 --- a/conn.go +++ b/conn.go @@ -27,6 +27,7 @@ import ( "os" "path/filepath" + "github.com/arduino/arduino-create-agent/tools" "github.com/arduino/arduino-create-agent/upload" "github.com/arduino/arduino-create-agent/utilities" "github.com/gin-gonic/gin" @@ -80,7 +81,7 @@ type Upload struct { var uploadStatusStr = "ProgrammerStatus" -func uploadHandler(h *hub, pubKey *rsa.PublicKey) func(*gin.Context) { +func uploadHandler(h *hub, pubKey *rsa.PublicKey, tools *tools.Tools) func(*gin.Context) { return func(c *gin.Context) { data := new(Upload) if err := c.BindJSON(data); err != nil { @@ -162,7 +163,7 @@ func uploadHandler(h *hub, pubKey *rsa.PublicKey) func(*gin.Context) { go func() { // Resolve commandline - commandline, err := upload.PartiallyResolve(data.Board, filePath, tmpdir, data.Commandline, data.Extra, Tools) + commandline, err := upload.PartiallyResolve(data.Board, filePath, tmpdir, data.Commandline, data.Extra, tools) if err != nil { send(h, map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) return diff --git a/hub.go b/hub.go index 150c6d943..825cbb101 100755 --- a/hub.go +++ b/hub.go @@ -25,6 +25,7 @@ import ( "strconv" "strings" + "github.com/arduino/arduino-create-agent/tools" "github.com/arduino/arduino-create-agent/upload" log "github.com/sirupsen/logrus" ) @@ -50,9 +51,11 @@ type hub struct { serialHub *serialhub serialPortList *serialPortList + + tools *tools.Tools } -func newHub(serialhub *serialhub, serialList *serialPortList) *hub { +func newHub(serialhub *serialhub, serialList *serialPortList, tools *tools.Tools) *hub { hub := &hub{ broadcast: make(chan []byte, 1000), broadcastSys: make(chan []byte, 1000), @@ -61,6 +64,7 @@ func newHub(serialhub *serialhub, serialList *serialPortList) *hub { connections: make(map[*connection]bool), serialHub: serialhub, serialPortList: serialList, + tools: tools, } hub.serialHub.OnRegister = func(port *serport) { @@ -235,7 +239,12 @@ func (h *hub) checkCmd(m []byte) { behaviour = args[4] } - err := Tools.Download(pack, tool, toolVersion, behaviour) + reportPendingProgress := func(msg string) { + mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} + mapB, _ := json.Marshal(mapD) + h.broadcastSys <- mapB + } + err := h.tools.Download(pack, tool, toolVersion, behaviour, reportPendingProgress) if err != nil { mapD := map[string]string{"DownloadStatus": "Error", "Msg": err.Error()} mapB, _ := json.Marshal(mapD) diff --git a/main.go b/main.go index 1c7d85de4..fc1f2007b 100755 --- a/main.go +++ b/main.go @@ -20,7 +20,6 @@ package main import ( _ "embed" - "encoding/json" "flag" "html/template" "io" @@ -100,13 +99,6 @@ var homeTemplate = template.Must(template.New("home").Parse(homeTemplateHTML)) //go:embed home.html var homeTemplateHTML string -// global clients -var ( - Tools *tools.Tools - Systray systray.Systray - Index *index.Resource -) - // FIXME; the loggerWS is useind in the multiwrite in the hub // type logWriter struct{} @@ -160,9 +152,9 @@ func main() { if src, err := os.Executable(); err != nil { panic(err) } else if restartPath := updater.Start(src); restartPath != "" { - Systray.RestartWith(restartPath) + stray.RestartWith(restartPath) } else { - Systray.Start() + stray.Start() } } @@ -183,15 +175,21 @@ func loop(stray *systray.Systray) { os.Exit(0) } - serialPorts := newSerialPortList() + // Instantiate Index and Tools + index := index.Init(*indexURL, config.GetDataDir()) + if signatureKey == nil || len(*signatureKey) == 0 { + log.Panicf("signature public key should be set") + } + signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) + if err != nil { + log.Panicf("cannot parse signature key '%s'. %s", *signatureKey, err) + } + tools := tools.New(config.GetDataDir(), index, signaturePubKey) + + serialPorts := newSerialPortList(tools) serialHub := newSerialHub() - hub := newHub(serialHub, serialPorts) - logger := func(msg string) { - mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} - mapB, _ := json.Marshal(mapD) - hub.broadcastSys <- mapB - } + hub := newHub(serialHub, serialPorts, tools) // Let's handle the config configDir := config.GetDefaultConfigDir() @@ -263,7 +261,7 @@ func loop(stray *systray.Systray) { if err != nil { log.Panicf("cannot parse arguments: %s", err) } - Systray.SetCurrentConfigFile(configPath) + stray.SetCurrentConfigFile(configPath) // Parse additional ini config if defined if len(*additionalConfig) > 0 { @@ -283,18 +281,6 @@ func loop(stray *systray.Systray) { } } - if signatureKey == nil || len(*signatureKey) == 0 { - log.Panicf("signature public key should be set") - } - signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) - if err != nil { - log.Panicf("cannot parse signature key '%s'. %s", *signatureKey, err) - } - - // Instantiate Index and Tools - Index = index.Init(*indexURL, config.GetDataDir()) - Tools = tools.New(config.GetDataDir(), Index, logger, signaturePubKey) - // see if we are supposed to wait 5 seconds if *isLaunchSelf { launchSelfLater() @@ -467,7 +453,7 @@ func loop(stray *systray.Systray) { r.LoadHTMLFiles("templates/nofirefox.html") r.GET("/", homeHandler) - r.POST("/upload", uploadHandler(hub, signaturePubKey)) + r.POST("/upload", uploadHandler(hub, signaturePubKey, tools)) r.GET("/socket.io/", socketHandler) r.POST("/socket.io/", socketHandler) r.Handle("WS", "/socket.io/", socketHandler) @@ -477,7 +463,7 @@ func loop(stray *systray.Systray) { r.POST("/update", updateHandler(stray)) // Mount goa handlers - goa := v2.Server(config.GetDataDir().String(), Index, signaturePubKey) + goa := v2.Server(config.GetDataDir().String(), index, signaturePubKey) r.Any("/v2/*path", gin.WrapH(goa)) go func() { diff --git a/main_test.go b/main_test.go index d30498669..8adc1443b 100644 --- a/main_test.go +++ b/main_test.go @@ -29,9 +29,10 @@ import ( "testing" "github.com/arduino/arduino-create-agent/config" - "github.com/arduino/arduino-create-agent/gen/tools" + genTools "github.com/arduino/arduino-create-agent/gen/tools" "github.com/arduino/arduino-create-agent/globals" "github.com/arduino/arduino-create-agent/index" + "github.com/arduino/arduino-create-agent/tools" "github.com/arduino/arduino-create-agent/upload" "github.com/arduino/arduino-create-agent/utilities" v2 "github.com/arduino/arduino-create-agent/v2" @@ -56,7 +57,15 @@ func TestValidSignatureKey(t *testing.T) { func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { r := gin.New() - r.POST("/", uploadHandler(newHub(newSerialHub(), newSerialPortList()), utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) + + index := index.Init(*indexURL, config.GetDataDir()) + signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) + require.NoError(t, err) + tools := tools.New(config.GetDataDir(), index, signaturePubKey) + hub := newHub(newSerialHub(), newSerialPortList(tools), tools) + pubkey := utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)) + + r.POST("/", uploadHandler(hub, pubkey, tools)) ts := httptest.NewServer(r) uploadEvilFileName := Upload{ @@ -93,7 +102,14 @@ func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { func TestUploadHandlerAgainstBase64WithoutPaddingMustFail(t *testing.T) { r := gin.New() - r.POST("/", uploadHandler(newHub(newSerialHub(), newSerialPortList()), utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)))) + index := index.Init(*indexURL, config.GetDataDir()) + signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) + require.NoError(t, err) + tools := tools.New(config.GetDataDir(), index, signaturePubKey) + hub := newHub(newSerialHub(), newSerialPortList(tools), tools) + pubkey := utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)) + + r.POST("/", uploadHandler(hub, pubkey, tools)) ts := httptest.NewServer(r) defer ts.Close() @@ -127,7 +143,7 @@ func TestInstallToolV2(t *testing.T) { ts := httptest.NewServer(r) type test struct { - request tools.ToolPayload + request genTools.ToolPayload responseCode int responseBody string } @@ -135,7 +151,7 @@ func TestInstallToolV2(t *testing.T) { bossacURL := "http://downloads.arduino.cc/tools/bossac-1.7.0-arduino3-linux64.tar.gz" bossacChecksum := "SHA-256:1ae54999c1f97234a5c603eb99ad39313b11746a4ca517269a9285afa05f9100" bossacSignature := "382898a97b5a86edd74208f10107d2fecbf7059ffe9cc856e045266fb4db4e98802728a0859cfdcda1c0b9075ec01e42dbea1f430b813530d5a6ae1766dfbba64c3e689b59758062dc2ab2e32b2a3491dc2b9a80b9cda4ae514fbe0ec5af210111b6896976053ab76bac55bcecfcececa68adfa3299e3cde6b7f117b3552a7d80ca419374bb497e3c3f12b640cf5b20875416b45e662fc6150b99b178f8e41d6982b4c0a255925ea39773683f9aa9201dc5768b6fc857c87ff602b6a93452a541b8ec10ca07f166e61a9e9d91f0a6090bd2038ed4427af6251039fb9fe8eb62ec30d7b0f3df38bc9de7204dec478fb86f8eb3f71543710790ee169dce039d3e0" - bossacInstallURLOK := tools.ToolPayload{ + bossacInstallURLOK := genTools.ToolPayload{ Name: "bossac", Version: "1.7.0-arduino3", Packager: "arduino", @@ -147,7 +163,7 @@ func TestInstallToolV2(t *testing.T) { esptoolURL := "https://github.com/earlephilhower/esp-quick-toolchain/releases/download/2.5.0-3/x86_64-linux-gnu.esptool-f80ae31.tar.gz" esptoolChecksum := "SHA-256:bded1dca953377838b6086a9bcd40a1dc5286ba5f69f2372c22a1d1819baad24" esptoolSignature := "852b58871419ce5e5633ecfaa72c0f0fa890ceb51164b362b8133bc0e3e003a21cec48935b8cdc078f4031219cbf17fb7edd9d7c9ca8ed85492911c9ca6353c9aa4691eb91fda99563a6bd49aeca0d9981fb05ec76e45c6024f8a6822862ad1e34ddc652fbbf4fa909887a255d4f087398ec386577efcec523c21203be3d10fc9e9b0f990a7536875a77dc2bc5cbffea7734b62238e31719111b718bacccebffc9be689545540e81d23b81caa66214376f58a0d6a45cf7efc5d3af62ab932b371628162fffe403906f41d5534921e5be081c5ac2ecc9db5caec03a105cc44b00ce19a95ad079843501eb8182e0717ce327867380c0e39d2b48698547fc1d0d66" - esptoolInstallURLOK := tools.ToolPayload{ + esptoolInstallURLOK := genTools.ToolPayload{ Name: "esptool", Version: "2.5.0-3-20ed2b9", Packager: "esp8266", @@ -157,7 +173,7 @@ func TestInstallToolV2(t *testing.T) { } wrongSignature := "wr0ngs1gn4tur3" - bossacInstallWrongSig := tools.ToolPayload{ + bossacInstallWrongSig := genTools.ToolPayload{ Name: "bossac", Version: "1.7.0-arduino3", Packager: "arduino", @@ -167,7 +183,7 @@ func TestInstallToolV2(t *testing.T) { } wrongChecksum := "wr0ngch3cksum" - bossacInstallWrongCheck := tools.ToolPayload{ + bossacInstallWrongCheck := genTools.ToolPayload{ Name: "bossac", Version: "1.7.0-arduino3", Packager: "arduino", @@ -176,7 +192,7 @@ func TestInstallToolV2(t *testing.T) { Signature: &bossacSignature, } - bossacInstallNoURL := tools.ToolPayload{ + bossacInstallNoURL := genTools.ToolPayload{ Name: "bossac", Version: "1.7.0-arduino3", Packager: "arduino", diff --git a/serial.go b/serial.go index c3f1b38bf..7fe9ae1f9 100755 --- a/serial.go +++ b/serial.go @@ -25,6 +25,7 @@ import ( "sync" "time" + "github.com/arduino/arduino-create-agent/tools" discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" "github.com/sirupsen/logrus" ) @@ -45,6 +46,8 @@ func newSerialHub() *serialhub { } type serialPortList struct { + tools *tools.Tools + Ports []*SpPortItem portsLock sync.Mutex @@ -52,8 +55,8 @@ type serialPortList struct { OnErr func(string) `json:"-"` } -func newSerialPortList() *serialPortList { - return &serialPortList{} +func newSerialPortList(tools *tools.Tools) *serialPortList { + return &serialPortList{tools: tools} } // SpPortItem is the serial port item @@ -130,11 +133,12 @@ func (sp *serialPortList) Run() { func (sp *serialPortList) runSerialDiscovery() { // First ensure that all the discoveries are available - if err := Tools.Download("builtin", "serial-discovery", "latest", "keep"); err != nil { + noOpProgress := func(msg string) {} + if err := sp.tools.Download("builtin", "serial-discovery", "latest", "keep", noOpProgress); err != nil { logrus.Errorf("Error downloading serial-discovery: %s", err) panic(err) } - sd, err := Tools.GetLocation("serial-discovery") + sd, err := sp.tools.GetLocation("serial-discovery") if err != nil { logrus.Errorf("Error downloading serial-discovery: %s", err) panic(err) diff --git a/tools/download.go b/tools/download.go index 8c4a37a6c..da7df6806 100644 --- a/tools/download.go +++ b/tools/download.go @@ -42,7 +42,7 @@ import ( // If version is not "latest" and behaviour is "replace", it will download the // version again. If instead behaviour is "keep" it will not download the version // if it already exists. -func (t *Tools) Download(pack, name, version, behaviour string) error { +func (t *Tools) Download(pack, name, version, behaviour string, report func(msg string)) error { t.tools.SetBehaviour(behaviour) _, err := t.tools.Install(context.Background(), &tools.ToolPayload{Name: name, Version: version, Packager: pack}) @@ -58,16 +58,16 @@ func (t *Tools) Download(pack, name, version, behaviour string) error { // if the tool contains a post_install script, run it: it means it is a tool that needs to install drivers // AFAIK this is only the case for the windows-driver tool - err = t.installDrivers(safePath) + err = t.installDrivers(safePath, report) if err != nil { return err } // Ensure that the files are executable - t.logger("Ensure that the files are executable") + report("Ensure that the files are executable") // Update the tool map - t.logger("Updating map with location " + safePath) + report("Updating map with location " + safePath) t.setMapValue(name, safePath) t.setMapValue(name+"-"+version, safePath) @@ -75,7 +75,7 @@ func (t *Tools) Download(pack, name, version, behaviour string) error { return nil } -func (t *Tools) installDrivers(location string) error { +func (t *Tools) installDrivers(location string, report func(msg string)) error { OkPressed := 6 extension := ".bat" // add .\ to force locality @@ -86,11 +86,11 @@ func (t *Tools) installDrivers(location string) error { preamble = "./" } if _, err := os.Stat(filepath.Join(location, "post_install"+extension)); err == nil { - t.logger("Installing drivers") + report("Installing drivers") ok := MessageBox("Installing drivers", "We are about to install some drivers needed to use Arduino/Genuino boards\nDo you want to continue?") if ok == OkPressed { os.Chdir(location) - t.logger(preamble + "post_install" + extension) + report(preamble + "post_install" + extension) oscmd := exec.Command(preamble + "post_install" + extension) if runtime.GOOS != "linux" { // spawning a shell could be the only way to let the user type his password diff --git a/tools/download_test.go b/tools/download_test.go index 96a105fd7..b99f10773 100644 --- a/tools/download_test.go +++ b/tools/download_test.go @@ -130,12 +130,12 @@ func TestDownload(t *testing.T) { IndexFile: *paths.New("testdata", "test_tool_index.json"), LastRefresh: time.Now(), } - testTools := New(tempDirPath, &testIndex, func(msg string) { t.Log(msg) }, utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey))) + testTools := New(tempDirPath, &testIndex, utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey))) for _, tc := range testCases { t.Run(tc.name+"-"+tc.version, func(t *testing.T) { // Download the tool - err := testTools.Download("arduino-test", tc.name, tc.version, "replace") + err := testTools.Download("arduino-test", tc.name, tc.version, "replace", func(msg string) { t.Log(msg) }) require.NoError(t, err) // Check that the tool has been downloaded @@ -177,8 +177,8 @@ func TestCorruptedInstalled(t *testing.T) { defer fileJSON.Close() _, err = fileJSON.Write([]byte("Hello")) require.NoError(t, err) - testTools := New(tempDirPath, &testIndex, func(msg string) { t.Log(msg) }, utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey))) + testTools := New(tempDirPath, &testIndex, utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey))) // Download the tool - err = testTools.Download("arduino-test", "avrdude", "6.3.0-arduino17", "keep") + err = testTools.Download("arduino-test", "avrdude", "6.3.0-arduino17", "keep", func(msg string) { t.Log(msg) }) require.NoError(t, err) } diff --git a/tools/tools.go b/tools/tools.go index f371126b5..0ad95763a 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -46,7 +46,6 @@ import ( type Tools struct { directory *paths.Path index *index.Resource - logger func(msg string) installed map[string]string mutex sync.RWMutex tools *pkgs.Tools @@ -56,11 +55,10 @@ type Tools struct { // The New functions accept the directory to use to host the tools, // an index (used to download the tools), // and a logger to log the operations -func New(directory *paths.Path, index *index.Resource, logger func(msg string), signPubKey *rsa.PublicKey) *Tools { +func New(directory *paths.Path, index *index.Resource, signPubKey *rsa.PublicKey) *Tools { t := &Tools{ directory: directory, index: index, - logger: logger, installed: map[string]string{}, mutex: sync.RWMutex{}, tools: pkgs.New(index, directory.String(), "replace", signPubKey), From f939e32abc6ff665322b8cdc45ec0381543cb631 Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 1 Apr 2025 14:52:44 +0200 Subject: [PATCH 17/50] refactor: enhance logging mechanism by implementing logWriter and updating logAction method --- hub.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/hub.go b/hub.go index 825cbb101..2251043a3 100755 --- a/hub.go +++ b/hub.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "html" + "io" "os" "runtime" "runtime/debug" @@ -256,7 +257,7 @@ func (h *hub) checkCmd(m []byte) { } }() } else if strings.HasPrefix(sl, "log") { - go logAction(sl) + go h.logAction(sl) } else if strings.HasPrefix(sl, "restart") { log.Println("Received restart from the daemon. Why? Boh") // TODO enable them @@ -276,12 +277,26 @@ func (h *hub) checkCmd(m []byte) { } } -func logAction(sl string) { +type logWriter struct { + onWrite func([]byte) +} + +func (u *logWriter) Write(p []byte) (n int, err error) { + u.onWrite(p) + return len(p), nil +} + +func (h *hub) logAction(sl string) { if strings.HasPrefix(sl, "log on") { *logDump = "on" - // FIXME: pass the loggerSw in the constructor and enable again the log e - // multiWriter := io.MultiWriter(&loggerWs, os.Stderr) - // log.SetOutput(multiWriter) + + logWriter := logWriter{} + logWriter.onWrite = func(p []byte) { + h.broadcastSys <- p + } + + multiWriter := io.MultiWriter(&logWriter, os.Stderr) + log.SetOutput(multiWriter) } else if strings.HasPrefix(sl, "log off") { *logDump = "off" log.SetOutput(os.Stderr) From e22281f8cf08a075727cef60147b70809b0847a6 Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 1 Apr 2025 14:53:02 +0200 Subject: [PATCH 18/50] remove log websocket --- main.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/main.go b/main.go index fc1f2007b..819816efa 100755 --- a/main.go +++ b/main.go @@ -99,16 +99,6 @@ var homeTemplate = template.Must(template.New("home").Parse(homeTemplateHTML)) //go:embed home.html var homeTemplateHTML string -// FIXME; the loggerWS is useind in the multiwrite in the hub -// type logWriter struct{} - -// func (u *logWriter) Write(p []byte) (n int, err error) { -// h.broadcastSys <- p -// return len(p), nil -// } - -// var loggerWs logWriter - func homeHandler(c *gin.Context) { homeTemplate.Execute(c.Writer, c.Request.Host) } From 3bbf371bccf972fc46aa079d4b197aa0174dd437 Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 1 Apr 2025 15:03:19 +0200 Subject: [PATCH 19/50] remove unused global clients comment from hub struct --- hub.go | 1 - 1 file changed, 1 deletion(-) diff --git a/hub.go b/hub.go index 2251043a3..2a36b3249 100755 --- a/hub.go +++ b/hub.go @@ -47,7 +47,6 @@ type hub struct { // Unregister requests from connections. unregister chan *connection - //TODO globals clients // Serial hub to communicate with serial ports serialHub *serialhub From 0773f99774382a8942316fac17ce49818a17866b Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 1 Apr 2025 15:27:52 +0200 Subject: [PATCH 20/50] refactor: integrate systray into hub structure and update newHub function --- hub.go | 11 +++++++---- main.go | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/hub.go b/hub.go index 2a36b3249..8aa6d5c38 100755 --- a/hub.go +++ b/hub.go @@ -26,6 +26,7 @@ import ( "strconv" "strings" + "github.com/arduino/arduino-create-agent/systray" "github.com/arduino/arduino-create-agent/tools" "github.com/arduino/arduino-create-agent/upload" log "github.com/sirupsen/logrus" @@ -53,9 +54,11 @@ type hub struct { serialPortList *serialPortList tools *tools.Tools + + systray *systray.Systray } -func newHub(serialhub *serialhub, serialList *serialPortList, tools *tools.Tools) *hub { +func newHub(serialhub *serialhub, serialList *serialPortList, tools *tools.Tools, systray *systray.Systray) *hub { hub := &hub{ broadcast: make(chan []byte, 1000), broadcastSys: make(chan []byte, 1000), @@ -65,6 +68,7 @@ func newHub(serialhub *serialhub, serialList *serialPortList, tools *tools.Tools serialHub: serialhub, serialPortList: serialList, tools: tools, + systray: systray, } hub.serialHub.OnRegister = func(port *serport) { @@ -259,10 +263,9 @@ func (h *hub) checkCmd(m []byte) { go h.logAction(sl) } else if strings.HasPrefix(sl, "restart") { log.Println("Received restart from the daemon. Why? Boh") - // TODO enable them - // Systray.Restart() + h.systray.Restart() } else if strings.HasPrefix(sl, "exit") { - // Systray.Quit() + h.systray.Quit() } else if strings.HasPrefix(sl, "memstats") { h.memoryStats() } else if strings.HasPrefix(sl, "gc") { diff --git a/main.go b/main.go index 819816efa..9a1db15af 100755 --- a/main.go +++ b/main.go @@ -179,7 +179,7 @@ func loop(stray *systray.Systray) { serialPorts := newSerialPortList(tools) serialHub := newSerialHub() - hub := newHub(serialHub, serialPorts, tools) + hub := newHub(serialHub, serialPorts, tools, stray) // Let's handle the config configDir := config.GetDefaultConfigDir() From 9c2ca547816c73b486f728a1b50e9fa58fd98e77 Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 1 Apr 2025 15:30:56 +0200 Subject: [PATCH 21/50] pass tests --- main_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main_test.go b/main_test.go index 8adc1443b..a24c29c6d 100644 --- a/main_test.go +++ b/main_test.go @@ -32,6 +32,7 @@ import ( genTools "github.com/arduino/arduino-create-agent/gen/tools" "github.com/arduino/arduino-create-agent/globals" "github.com/arduino/arduino-create-agent/index" + "github.com/arduino/arduino-create-agent/systray" "github.com/arduino/arduino-create-agent/tools" "github.com/arduino/arduino-create-agent/upload" "github.com/arduino/arduino-create-agent/utilities" @@ -62,7 +63,7 @@ func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) require.NoError(t, err) tools := tools.New(config.GetDataDir(), index, signaturePubKey) - hub := newHub(newSerialHub(), newSerialPortList(tools), tools) + hub := newHub(newSerialHub(), newSerialPortList(tools), tools, &systray.Systray{}) pubkey := utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)) r.POST("/", uploadHandler(hub, pubkey, tools)) @@ -106,7 +107,7 @@ func TestUploadHandlerAgainstBase64WithoutPaddingMustFail(t *testing.T) { signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) require.NoError(t, err) tools := tools.New(config.GetDataDir(), index, signaturePubKey) - hub := newHub(newSerialHub(), newSerialPortList(tools), tools) + hub := newHub(newSerialHub(), newSerialPortList(tools), tools, &systray.Systray{}) pubkey := utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)) r.POST("/", uploadHandler(hub, pubkey, tools)) From fc511946ece9424d0025f2db0b06cec6ca4a0e14 Mon Sep 17 00:00:00 2001 From: Davide N Date: Tue, 1 Apr 2025 15:56:14 +0200 Subject: [PATCH 22/50] chore: add comment regarding potential removal of sysStray dependencies --- hub.go | 1 + 1 file changed, 1 insertion(+) diff --git a/hub.go b/hub.go index 8aa6d5c38..6e320e3cc 100755 --- a/hub.go +++ b/hub.go @@ -262,6 +262,7 @@ func (h *hub) checkCmd(m []byte) { } else if strings.HasPrefix(sl, "log") { go h.logAction(sl) } else if strings.HasPrefix(sl, "restart") { + // potentially, the sysStray dependencies can be removed https://github.com/arduino/arduino-create-agent/issues/1013 log.Println("Received restart from the daemon. Why? Boh") h.systray.Restart() } else if strings.HasPrefix(sl, "exit") { From 8106c84186a1d77783668afe98c0a0456500b2fd Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 14:30:55 +0200 Subject: [PATCH 23/50] feat: move serial port into new file --- serial.go | 187 ------------------------------------------- serialport.go | 16 +--- serialportlist.go | 197 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 200 deletions(-) create mode 100644 serialportlist.go diff --git a/serial.go b/serial.go index 7fe9ae1f9..d98a6cb73 100755 --- a/serial.go +++ b/serial.go @@ -19,7 +19,6 @@ package main import ( "encoding/json" - "fmt" "slices" "strings" "sync" @@ -45,34 +44,6 @@ func newSerialHub() *serialhub { } } -type serialPortList struct { - tools *tools.Tools - - Ports []*SpPortItem - portsLock sync.Mutex - - OnList func([]byte) `json:"-"` - OnErr func(string) `json:"-"` -} - -func newSerialPortList(tools *tools.Tools) *serialPortList { - return &serialPortList{tools: tools} -} - -// SpPortItem is the serial port item -type SpPortItem struct { - Name string - SerialNumber string - DeviceClass string - IsOpen bool - IsPrimary bool - Baud int - BufferAlgorithm string - Ver string - VendorID string - ProductID string -} - // Register serial ports from the connections. func (sh *serialhub) Register(port *serport) { sh.mu.Lock() @@ -107,164 +78,6 @@ func (sh *serialhub) FindPortByName(portname string) (*serport, bool) { return nil, false } -// List broadcasts a Json representation of the ports found -func (sp *serialPortList) List() { - sp.portsLock.Lock() - ls, err := json.MarshalIndent(sp, "", "\t") - sp.portsLock.Unlock() - - if err != nil { - sp.OnErr("Error creating json on port list " + err.Error()) - } else { - sp.OnList(ls) - } -} - -// Run is the main loop for port discovery and management -func (sp *serialPortList) Run() { - for retries := 0; retries < 10; retries++ { - sp.runSerialDiscovery() - - logrus.Errorf("Serial discovery stopped working, restarting it in 10 seconds...") - time.Sleep(10 * time.Second) - } - logrus.Errorf("Failed restarting serial discovery. Giving up...") -} - -func (sp *serialPortList) runSerialDiscovery() { - // First ensure that all the discoveries are available - noOpProgress := func(msg string) {} - if err := sp.tools.Download("builtin", "serial-discovery", "latest", "keep", noOpProgress); err != nil { - logrus.Errorf("Error downloading serial-discovery: %s", err) - panic(err) - } - sd, err := sp.tools.GetLocation("serial-discovery") - if err != nil { - logrus.Errorf("Error downloading serial-discovery: %s", err) - panic(err) - } - d := discovery.NewClient("serial", sd+"/serial-discovery") - dLogger := logrus.WithField("discovery", "serial") - if *verbose { - d.SetLogger(dLogger) - } - d.SetUserAgent("arduino-create-agent/" + version) - if err := d.Run(); err != nil { - logrus.Errorf("Error running serial-discovery: %s", err) - panic(err) - } - defer d.Quit() - - events, err := d.StartSync(10) - if err != nil { - logrus.Errorf("Error starting event watcher on serial-discovery: %s", err) - panic(err) - } - - logrus.Infof("Serial discovery started, watching for events") - for ev := range events { - logrus.WithField("event", ev).Debugf("Serial discovery event") - switch ev.Type { - case "add": - sp.add(ev.Port) - case "remove": - sp.remove(ev.Port) - } - } - - sp.reset() - logrus.Errorf("Serial discovery stopped.") -} - -func (sp *serialPortList) reset() { - sp.portsLock.Lock() - defer sp.portsLock.Unlock() - sp.Ports = []*SpPortItem{} -} - -func (sp *serialPortList) add(addedPort *discovery.Port) { - if addedPort.Protocol != "serial" { - return - } - props := addedPort.Properties - if !props.ContainsKey("vid") { - return - } - vid, pid := props.Get("vid"), props.Get("pid") - if vid == "0x0000" || pid == "0x0000" { - return - } - if portsFilter != nil && !portsFilter.MatchString(addedPort.Address) { - logrus.Debugf("ignoring port not matching filter. port: %v\n", addedPort.Address) - return - } - - sp.portsLock.Lock() - defer sp.portsLock.Unlock() - - // If the port is already in the list, just update the metadata... - for _, oldPort := range sp.Ports { - fmt.Println("oldPort.Name: ", oldPort.Name) - if oldPort.Name == addedPort.Address { - oldPort.SerialNumber = props.Get("serialNumber") - oldPort.VendorID = vid - oldPort.ProductID = pid - return - } - } - // ...otherwise, add it to the list - sp.Ports = append(sp.Ports, &SpPortItem{ - Name: addedPort.Address, - SerialNumber: props.Get("serialNumber"), - VendorID: vid, - ProductID: pid, - Ver: version, - IsOpen: false, - IsPrimary: false, - Baud: 0, - BufferAlgorithm: "", - }) -} - -func (sp *serialPortList) remove(removedPort *discovery.Port) { - sp.portsLock.Lock() - defer sp.portsLock.Unlock() - - // Remove the port from the list - sp.Ports = slices.DeleteFunc(sp.Ports, func(oldPort *SpPortItem) bool { - return oldPort.Name == removedPort.Address - }) -} - -// MarkPortAsOpened marks a port as opened by the user -func (sp *serialPortList) MarkPortAsOpened(portname string) { - sp.portsLock.Lock() - defer sp.portsLock.Unlock() - port := sp.getPortByName(portname) - if port != nil { - port.IsOpen = true - } -} - -// MarkPortAsClosed marks a port as no more opened by the user -func (sp *serialPortList) MarkPortAsClosed(portname string) { - sp.portsLock.Lock() - defer sp.portsLock.Unlock() - port := sp.getPortByName(portname) - if port != nil { - port.IsOpen = false - } -} - -func (sp *serialPortList) getPortByName(portname string) *SpPortItem { - for _, port := range sp.Ports { - if port.Name == portname { - return port - } - } - return nil -} - func (h *hub) spErr(err string) { //log.Println("Sending err back: ", err) //sh.hub.broadcastSys <- []byte(err) diff --git a/serialport.go b/serialport.go index 06ea0aa64..dea3ed9d0 100755 --- a/serialport.go +++ b/serialport.go @@ -62,7 +62,6 @@ type serport struct { //bufferwatcher *BufferflowDummypause bufferwatcher Bufferflow - // TODO: to remove global OnMessage func([]byte) OnClose func(*serport) } @@ -93,7 +92,6 @@ func (p *serport) reader(buftype string) { if p.isClosing.Load() { strmsg := "Shutting down reader on " + p.portConf.Name log.Println(strmsg) - // h.broadcastSys <- ([]byte(strmsg) p.OnMessage([]byte(strmsg)) break } @@ -148,18 +146,13 @@ func (p *serport) reader(buftype string) { if err == io.EOF || err == io.ErrUnexpectedEOF { // hit end of file log.Println("Hit end of file on serial port") - // h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got EOF (End of File) on port which usually means another app other than Serial Port JSON Server is locking your port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}") p.OnMessage([]byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got EOF (End of File) on port which usually means another app other than Serial Port JSON Server is locking your port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")) } if err != nil { log.Println(err) - // h.broadcastSys <- []byte("Error reading on " + p.portConf.Name + " " + - // err.Error() + " Closing port.") p.OnMessage([]byte("Error reading on " + p.portConf.Name + " " + err.Error() + " Closing port.")) - - // h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got error reading on port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}") p.OnMessage([]byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got error reading on port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")) p.isClosingDueToError = true break @@ -218,7 +211,6 @@ func (p *serport) writerBuffered() { } msgstr := "writerBuffered just got closed. make sure you make a new one. port:" + p.portConf.Name log.Println(msgstr) - // h.broadcastSys <- []byte(msgstr) p.OnMessage([]byte(msgstr)) } @@ -240,18 +232,17 @@ func (p *serport) writerNoBuf() { if err != nil { errstr := "Error writing to " + p.portConf.Name + " " + err.Error() + " Closing port." log.Print(errstr) - // h.broadcastSys <- []byte(errstr) p.OnMessage([]byte(errstr)) break } } msgstr := "Shutting down writer on " + p.portConf.Name log.Println(msgstr) - // h.broadcastSys <- []byte(msgstr) p.OnMessage([]byte(msgstr)) + p.portIo.Close() - // TODO: is this needed ? - // serialPorts.List() + // serialPorts.List( + } // this method runs as its own thread because it's instantiated @@ -283,7 +274,6 @@ func (p *serport) writerRaw() { } msgstr := "writerRaw just got closed. make sure you make a new one. port:" + p.portConf.Name log.Println(msgstr) - // h.broadcastSys <- []byte(msgstr) p.OnMessage([]byte(msgstr)) } diff --git a/serialportlist.go b/serialportlist.go new file mode 100644 index 000000000..64f10debb --- /dev/null +++ b/serialportlist.go @@ -0,0 +1,197 @@ +package main + +import ( + "encoding/json" + "slices" + "sync" + "time" + + "github.com/arduino/arduino-create-agent/tools" + discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" + "github.com/sirupsen/logrus" +) + +type serialPortList struct { + tools *tools.Tools + + Ports []*SpPortItem + portsLock sync.Mutex + + OnList func([]byte) `json:"-"` + OnErr func(string) `json:"-"` +} + +// SpPortItem is the serial port item +type SpPortItem struct { + Name string + SerialNumber string + DeviceClass string + IsOpen bool + IsPrimary bool + Baud int + BufferAlgorithm string + Ver string + VendorID string + ProductID string +} + +func newSerialPortList(tools *tools.Tools) *serialPortList { + return &serialPortList{tools: tools} +} + +// List broadcasts a Json representation of the ports found +func (sp *serialPortList) List() { + sp.portsLock.Lock() + ls, err := json.MarshalIndent(sp, "", "\t") + sp.portsLock.Unlock() + + if err != nil { + sp.OnErr("Error creating json on port list " + err.Error()) + } else { + sp.OnList(ls) + } +} + +// MarkPortAsOpened marks a port as opened by the user +func (sp *serialPortList) MarkPortAsOpened(portname string) { + sp.portsLock.Lock() + defer sp.portsLock.Unlock() + port := sp.getPortByName(portname) + if port != nil { + port.IsOpen = true + } +} + +// MarkPortAsClosed marks a port as no more opened by the user +func (sp *serialPortList) MarkPortAsClosed(portname string) { + sp.portsLock.Lock() + defer sp.portsLock.Unlock() + port := sp.getPortByName(portname) + if port != nil { + port.IsOpen = false + } +} + +// Run is the main loop for port discovery and management +func (sp *serialPortList) Run() { + for retries := 0; retries < 10; retries++ { + sp.runSerialDiscovery() + + logrus.Errorf("Serial discovery stopped working, restarting it in 10 seconds...") + time.Sleep(10 * time.Second) + } + logrus.Errorf("Failed restarting serial discovery. Giving up...") +} + +func (sp *serialPortList) runSerialDiscovery() { + // First ensure that all the discoveries are available + noOpProgress := func(msg string) {} + if err := sp.tools.Download("builtin", "serial-discovery", "latest", "keep", noOpProgress); err != nil { + logrus.Errorf("Error downloading serial-discovery: %s", err) + panic(err) + } + sd, err := sp.tools.GetLocation("serial-discovery") + if err != nil { + logrus.Errorf("Error downloading serial-discovery: %s", err) + panic(err) + } + d := discovery.NewClient("serial", sd+"/serial-discovery") + dLogger := logrus.WithField("discovery", "serial") + if *verbose { + d.SetLogger(dLogger) + } + d.SetUserAgent("arduino-create-agent/" + version) + if err := d.Run(); err != nil { + logrus.Errorf("Error running serial-discovery: %s", err) + panic(err) + } + defer d.Quit() + + events, err := d.StartSync(10) + if err != nil { + logrus.Errorf("Error starting event watcher on serial-discovery: %s", err) + panic(err) + } + + logrus.Infof("Serial discovery started, watching for events") + for ev := range events { + logrus.WithField("event", ev).Debugf("Serial discovery event") + switch ev.Type { + case "add": + sp.add(ev.Port) + case "remove": + sp.remove(ev.Port) + } + } + + sp.reset() + logrus.Errorf("Serial discovery stopped.") +} + +func (sp *serialPortList) reset() { + sp.portsLock.Lock() + defer sp.portsLock.Unlock() + sp.Ports = []*SpPortItem{} +} + +func (sp *serialPortList) add(addedPort *discovery.Port) { + if addedPort.Protocol != "serial" { + return + } + props := addedPort.Properties + if !props.ContainsKey("vid") { + return + } + vid, pid := props.Get("vid"), props.Get("pid") + if vid == "0x0000" || pid == "0x0000" { + return + } + if portsFilter != nil && !portsFilter.MatchString(addedPort.Address) { + logrus.Debugf("ignoring port not matching filter. port: %v\n", addedPort.Address) + return + } + + sp.portsLock.Lock() + defer sp.portsLock.Unlock() + + // If the port is already in the list, just update the metadata... + for _, oldPort := range sp.Ports { + if oldPort.Name == addedPort.Address { + oldPort.SerialNumber = props.Get("serialNumber") + oldPort.VendorID = vid + oldPort.ProductID = pid + return + } + } + // ...otherwise, add it to the list + sp.Ports = append(sp.Ports, &SpPortItem{ + Name: addedPort.Address, + SerialNumber: props.Get("serialNumber"), + VendorID: vid, + ProductID: pid, + Ver: version, + IsOpen: false, + IsPrimary: false, + Baud: 0, + BufferAlgorithm: "", + }) +} + +func (sp *serialPortList) remove(removedPort *discovery.Port) { + sp.portsLock.Lock() + defer sp.portsLock.Unlock() + + // Remove the port from the list + sp.Ports = slices.DeleteFunc(sp.Ports, func(oldPort *SpPortItem) bool { + return oldPort.Name == removedPort.Address + }) +} + +func (sp *serialPortList) getPortByName(portname string) *SpPortItem { + for _, port := range sp.Ports { + if port.Name == portname { + return port + } + } + return nil +} From 4dba1a8436f678af64267438c3c3247cd9d72902 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 14:32:46 +0200 Subject: [PATCH 24/50] feat: add serialhub implementation for managing serial ports --- serial.go => serialhub.go | 7 ------- 1 file changed, 7 deletions(-) rename serial.go => serialhub.go (94%) diff --git a/serial.go b/serialhub.go similarity index 94% rename from serial.go rename to serialhub.go index d98a6cb73..d248bc0a2 100755 --- a/serial.go +++ b/serialhub.go @@ -18,15 +18,8 @@ package main import ( - "encoding/json" - "slices" "strings" "sync" - "time" - - "github.com/arduino/arduino-create-agent/tools" - discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" - "github.com/sirupsen/logrus" ) type serialhub struct { From 7ea21c02794483d4aabb5c210acdbbf66eb405dc Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 14:45:23 +0200 Subject: [PATCH 25/50] refactor: remove serialhub parameter from newHub function and instantiate newSerialHub within it --- hub.go | 4 ++-- main.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/hub.go b/hub.go index 6e320e3cc..881d9ca7f 100755 --- a/hub.go +++ b/hub.go @@ -58,14 +58,14 @@ type hub struct { systray *systray.Systray } -func newHub(serialhub *serialhub, serialList *serialPortList, tools *tools.Tools, systray *systray.Systray) *hub { +func newHub(serialList *serialPortList, tools *tools.Tools, systray *systray.Systray) *hub { hub := &hub{ broadcast: make(chan []byte, 1000), broadcastSys: make(chan []byte, 1000), register: make(chan *connection), unregister: make(chan *connection), connections: make(map[*connection]bool), - serialHub: serialhub, + serialHub: newSerialHub(), serialPortList: serialList, tools: tools, systray: systray, diff --git a/main.go b/main.go index 9a1db15af..dfb217c26 100755 --- a/main.go +++ b/main.go @@ -177,9 +177,8 @@ func loop(stray *systray.Systray) { tools := tools.New(config.GetDataDir(), index, signaturePubKey) serialPorts := newSerialPortList(tools) - serialHub := newSerialHub() - hub := newHub(serialHub, serialPorts, tools, stray) + hub := newHub(serialPorts, tools, stray) // Let's handle the config configDir := config.GetDefaultConfigDir() From a152b028c7c6dc79dc36e0bb87917b9d4d76cf85 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 14:50:00 +0200 Subject: [PATCH 26/50] feat: move `spErr` `spClose` `spWrite` into hub (from serialhub) --- hub.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ serialhub.go | 55 ---------------------------------------------------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/hub.go b/hub.go index 881d9ca7f..047134ffc 100755 --- a/hub.go +++ b/hub.go @@ -336,3 +336,57 @@ func (h *hub) garbageCollection() { h.broadcastSys <- []byte("{\"gc\":\"done\"}") h.memoryStats() } + +func (h *hub) spErr(err string) { + //log.Println("Sending err back: ", err) + h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") +} + +func (h *hub) spClose(portname string) { + if myport, ok := h.serialHub.FindPortByName(portname); ok { + h.broadcastSys <- []byte("Closing serial port " + portname) + myport.Close() + } else { + h.spErr("We could not find the serial port " + portname + " that you were trying to close.") + } +} + +func (h *hub) spWrite(arg string) { + // we will get a string of comXX asdf asdf asdf + //log.Println("Inside spWrite arg: " + arg) + arg = strings.TrimPrefix(arg, " ") + //log.Println("arg after trim: " + arg) + args := strings.SplitN(arg, " ", 3) + if len(args) != 3 { + errstr := "Could not parse send command: " + arg + //log.Println(errstr) + h.spErr(errstr) + return + } + bufferingMode := args[0] + portname := strings.Trim(args[1], " ") + data := args[2] + + //log.Println("The port to write to is:" + portname + "---") + //log.Println("The data is:" + data + "---") + + // See if we have this port open + port, ok := h.serialHub.FindPortByName(portname) + if !ok { + // we couldn't find the port, so send err + h.spErr("We could not find the serial port " + portname + " that you were trying to write to.") + return + } + + // see if bufferingMode is valid + switch bufferingMode { + case "send", "sendnobuf", "sendraw": + // valid buffering mode, go ahead + default: + h.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") + return + } + + // send it to the write channel + port.Write(data, bufferingMode) +} diff --git a/serialhub.go b/serialhub.go index d248bc0a2..6c00718e6 100755 --- a/serialhub.go +++ b/serialhub.go @@ -70,58 +70,3 @@ func (sh *serialhub) FindPortByName(portname string) (*serport, bool) { } return nil, false } - -func (h *hub) spErr(err string) { - //log.Println("Sending err back: ", err) - //sh.hub.broadcastSys <- []byte(err) - h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") -} - -func (h *hub) spClose(portname string) { - if myport, ok := h.serialHub.FindPortByName(portname); ok { - h.broadcastSys <- []byte("Closing serial port " + portname) - myport.Close() - } else { - h.spErr("We could not find the serial port " + portname + " that you were trying to close.") - } -} - -func (h *hub) spWrite(arg string) { - // we will get a string of comXX asdf asdf asdf - //log.Println("Inside spWrite arg: " + arg) - arg = strings.TrimPrefix(arg, " ") - //log.Println("arg after trim: " + arg) - args := strings.SplitN(arg, " ", 3) - if len(args) != 3 { - errstr := "Could not parse send command: " + arg - //log.Println(errstr) - h.spErr(errstr) - return - } - bufferingMode := args[0] - portname := strings.Trim(args[1], " ") - data := args[2] - - //log.Println("The port to write to is:" + portname + "---") - //log.Println("The data is:" + data + "---") - - // See if we have this port open - port, ok := h.serialHub.FindPortByName(portname) - if !ok { - // we couldn't find the port, so send err - h.spErr("We could not find the serial port " + portname + " that you were trying to write to.") - return - } - - // see if bufferingMode is valid - switch bufferingMode { - case "send", "sendnobuf", "sendraw": - // valid buffering mode, go ahead - default: - h.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") - return - } - - // send it to the write channel - port.Write(data, bufferingMode) -} From fac0b109f761606a5fcedf082841011eb8118f14 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 14:51:28 +0200 Subject: [PATCH 27/50] refactor: remove newSerialHub instantiation from hub initialization in tests --- main_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main_test.go b/main_test.go index a24c29c6d..bbede14fd 100644 --- a/main_test.go +++ b/main_test.go @@ -63,7 +63,7 @@ func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) require.NoError(t, err) tools := tools.New(config.GetDataDir(), index, signaturePubKey) - hub := newHub(newSerialHub(), newSerialPortList(tools), tools, &systray.Systray{}) + hub := newHub(newSerialPortList(tools), tools, &systray.Systray{}) pubkey := utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)) r.POST("/", uploadHandler(hub, pubkey, tools)) @@ -107,7 +107,7 @@ func TestUploadHandlerAgainstBase64WithoutPaddingMustFail(t *testing.T) { signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) require.NoError(t, err) tools := tools.New(config.GetDataDir(), index, signaturePubKey) - hub := newHub(newSerialHub(), newSerialPortList(tools), tools, &systray.Systray{}) + hub := newHub(newSerialPortList(tools), tools, &systray.Systray{}) pubkey := utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)) r.POST("/", uploadHandler(hub, pubkey, tools)) From f04d5cc4c50abfc3c6029c1115442fbeedf14038 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 15:10:48 +0200 Subject: [PATCH 28/50] move `spHandlerOpen` to hub from serialport.go --- hub.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++ serialport.go | 84 ------------------------------------------------- 2 files changed, 86 insertions(+), 84 deletions(-) diff --git a/hub.go b/hub.go index 047134ffc..96ab3e1b6 100755 --- a/hub.go +++ b/hub.go @@ -16,6 +16,7 @@ package main import ( + "bytes" "encoding/json" "fmt" "html" @@ -30,6 +31,7 @@ import ( "github.com/arduino/arduino-create-agent/tools" "github.com/arduino/arduino-create-agent/upload" log "github.com/sirupsen/logrus" + "go.bug.st/serial" ) type hub struct { @@ -280,6 +282,90 @@ func (h *hub) checkCmd(m []byte) { } } +func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { + + log.Print("Inside spHandler") + + var out bytes.Buffer + + out.WriteString("Opening serial port ") + out.WriteString(portname) + out.WriteString(" at ") + out.WriteString(strconv.Itoa(baud)) + out.WriteString(" baud") + log.Print(out.String()) + + conf := &SerialConfig{Name: portname, Baud: baud, RtsOn: true} + + mode := &serial.Mode{ + BaudRate: baud, + } + + sp, err := serial.Open(portname, mode) + log.Print("Just tried to open port") + if err != nil { + //log.Fatal(err) + log.Print("Error opening port " + err.Error()) + //h.broadcastSys <- []byte("Error opening port. " + err.Error()) + h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}") + + return + } + log.Print("Opened port successfully") + //p := &serport{send: make(chan []byte, 256), portConf: conf, portIo: sp} + // we can go up to 256,000 lines of gcode in the buffer + p := &serport{ + sendBuffered: make(chan string, 256000), + sendNoBuf: make(chan []byte), + sendRaw: make(chan string), + portConf: conf, + portIo: sp, + portName: portname, + BufferType: buftype, + } + + p.OnMessage = func(msg []byte) { + h.broadcastSys <- msg + } + p.OnClose = func(port *serport) { + h.serialPortList.MarkPortAsClosed(p.portName) + h.serialPortList.List() + } + + var bw Bufferflow + + switch buftype { + case "timed": + bw = NewBufferflowTimed(portname, h.broadcastSys) + case "timedraw": + bw = NewBufferflowTimedRaw(portname, h.broadcastSys) + case "default": + bw = NewBufferflowDefault(portname, h.broadcastSys) + default: + log.Panicf("unknown buffer type: %s", buftype) + } + + bw.Init() + p.bufferwatcher = bw + + h.serialHub.Register(p) + defer h.serialHub.Unregister(p) + + h.serialPortList.MarkPortAsOpened(portname) + h.serialPortList.List() + + // this is internally buffered thread to not send to serial port if blocked + go p.writerBuffered() + // this is thread to send to serial port regardless of block + go p.writerNoBuf() + // this is thread to send to serial port but with base64 decoding + go p.writerRaw() + + p.reader(buftype) + + h.serialPortList.List() +} + type logWriter struct { onWrite func([]byte) } diff --git a/serialport.go b/serialport.go index dea3ed9d0..77f155d16 100755 --- a/serialport.go +++ b/serialport.go @@ -277,90 +277,6 @@ func (p *serport) writerRaw() { p.OnMessage([]byte(msgstr)) } -func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { - - log.Print("Inside spHandler") - - var out bytes.Buffer - - out.WriteString("Opening serial port ") - out.WriteString(portname) - out.WriteString(" at ") - out.WriteString(strconv.Itoa(baud)) - out.WriteString(" baud") - log.Print(out.String()) - - conf := &SerialConfig{Name: portname, Baud: baud, RtsOn: true} - - mode := &serial.Mode{ - BaudRate: baud, - } - - sp, err := serial.Open(portname, mode) - log.Print("Just tried to open port") - if err != nil { - //log.Fatal(err) - log.Print("Error opening port " + err.Error()) - //h.broadcastSys <- []byte("Error opening port. " + err.Error()) - h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}") - - return - } - log.Print("Opened port successfully") - //p := &serport{send: make(chan []byte, 256), portConf: conf, portIo: sp} - // we can go up to 256,000 lines of gcode in the buffer - p := &serport{ - sendBuffered: make(chan string, 256000), - sendNoBuf: make(chan []byte), - sendRaw: make(chan string), - portConf: conf, - portIo: sp, - portName: portname, - BufferType: buftype, - } - - p.OnMessage = func(msg []byte) { - h.broadcastSys <- msg - } - p.OnClose = func(port *serport) { - h.serialPortList.MarkPortAsClosed(p.portName) - h.serialPortList.List() - } - - var bw Bufferflow - - switch buftype { - case "timed": - bw = NewBufferflowTimed(portname, h.broadcastSys) - case "timedraw": - bw = NewBufferflowTimedRaw(portname, h.broadcastSys) - case "default": - bw = NewBufferflowDefault(portname, h.broadcastSys) - default: - log.Panicf("unknown buffer type: %s", buftype) - } - - bw.Init() - p.bufferwatcher = bw - - h.serialHub.Register(p) - defer h.serialHub.Unregister(p) - - h.serialPortList.MarkPortAsOpened(portname) - h.serialPortList.List() - - // this is internally buffered thread to not send to serial port if blocked - go p.writerBuffered() - // this is thread to send to serial port regardless of block - go p.writerNoBuf() - // this is thread to send to serial port but with base64 decoding - go p.writerRaw() - - p.reader(buftype) - - h.serialPortList.List() -} - func (p *serport) Close() { p.isClosing.Store(true) From 47213d120d25d2dbbe508eb5b3f1ef242db44066 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 15:14:55 +0200 Subject: [PATCH 29/50] refactor: remove unused serial package import from serialport.go --- serialport.go | 1 - 1 file changed, 1 deletion(-) diff --git a/serialport.go b/serialport.go index 77f155d16..5fadd87e0 100755 --- a/serialport.go +++ b/serialport.go @@ -25,7 +25,6 @@ import ( "unicode/utf8" log "github.com/sirupsen/logrus" - serial "go.bug.st/serial" ) // SerialConfig is the serial port configuration From 14ddd43c93200a31ba907dcb230022ce5b65a2e8 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 15:26:48 +0200 Subject: [PATCH 30/50] renamed h into hub --- conn.go | 26 +++--- hub.go | 246 ++++++++++++++++++++++++++++---------------------------- info.go | 4 +- 3 files changed, 138 insertions(+), 138 deletions(-) diff --git a/conn.go b/conn.go index d99f24edd..ad27c8fa6 100644 --- a/conn.go +++ b/conn.go @@ -81,7 +81,7 @@ type Upload struct { var uploadStatusStr = "ProgrammerStatus" -func uploadHandler(h *hub, pubKey *rsa.PublicKey, tools *tools.Tools) func(*gin.Context) { +func uploadHandler(hub *hub, pubKey *rsa.PublicKey, tools *tools.Tools) func(*gin.Context) { return func(c *gin.Context) { data := new(Upload) if err := c.BindJSON(data); err != nil { @@ -165,7 +165,7 @@ func uploadHandler(h *hub, pubKey *rsa.PublicKey, tools *tools.Tools) func(*gin. // Resolve commandline commandline, err := upload.PartiallyResolve(data.Board, filePath, tmpdir, data.Commandline, data.Extra, tools) if err != nil { - send(h, map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) + send(hub, map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) return } @@ -175,16 +175,16 @@ func uploadHandler(h *hub, pubKey *rsa.PublicKey, tools *tools.Tools) func(*gin. if data.Extra.Network { err = errors.New("network upload is not supported anymore, pease use OTA instead") } else { - send(h, map[string]string{uploadStatusStr: "Starting", "Cmd": "Serial"}) + send(hub, map[string]string{uploadStatusStr: "Starting", "Cmd": "Serial"}) err = upload.Serial(data.Port, commandline, data.Extra, l) } // Handle result if err != nil { - send(h, map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) + send(hub, map[string]string{uploadStatusStr: "Error", "Msg": err.Error()}) return } - send(h, map[string]string{uploadStatusStr: "Done", "Flash": "Ok"}) + send(hub, map[string]string{uploadStatusStr: "Done", "Flash": "Ok"}) }() c.String(http.StatusAccepted, "") @@ -194,7 +194,7 @@ func uploadHandler(h *hub, pubKey *rsa.PublicKey, tools *tools.Tools) func(*gin. // PLogger sends the info from the upload to the websocket type PLogger struct { Verbose bool - h *hub + hub *hub } // Debug only sends messages if verbose is true (always true for now) @@ -208,15 +208,15 @@ func (l PLogger) Debug(args ...interface{}) { func (l PLogger) Info(args ...interface{}) { output := fmt.Sprint(args...) log.Println(output) - send(l.h, map[string]string{uploadStatusStr: "Busy", "Msg": output}) + send(l.hub, map[string]string{uploadStatusStr: "Busy", "Msg": output}) } -func send(h *hub, args map[string]string) { +func send(hub *hub, args map[string]string) { mapB, _ := json.Marshal(args) - h.broadcastSys <- mapB + hub.broadcastSys <- mapB } -func wsHandler(h *hub) *WsServer { +func wsHandler(hub *hub) *WsServer { server, err := socketio.NewServer(nil) if err != nil { log.Fatal(err) @@ -224,13 +224,13 @@ func wsHandler(h *hub) *WsServer { server.On("connection", func(so socketio.Socket) { c := &connection{send: make(chan []byte, 256*10), ws: so} - h.register <- c + hub.register <- c so.On("command", func(message string) { - h.broadcast <- []byte(message) + hub.broadcast <- []byte(message) }) so.On("disconnection", func() { - h.unregister <- c + hub.unregister <- c }) go c.writer() }) diff --git a/hub.go b/hub.go index 96ab3e1b6..495f851c1 100755 --- a/hub.go +++ b/hub.go @@ -110,51 +110,51 @@ const commands = `{ ] }` -func (h *hub) unregisterConnection(c *connection) { - if _, contains := h.connections[c]; !contains { +func (hub *hub) unregisterConnection(c *connection) { + if _, contains := hub.connections[c]; !contains { return } - delete(h.connections, c) + delete(hub.connections, c) close(c.send) } -func (h *hub) sendToRegisteredConnections(data []byte) { - for c := range h.connections { +func (hub *hub) sendToRegisteredConnections(data []byte) { + for c := range hub.connections { select { case c.send <- data: //log.Print("did broadcast to ") //log.Print(c.ws.RemoteAddr()) //c.send <- []byte("hello world") default: - h.unregisterConnection(c) + hub.unregisterConnection(c) } } } -func (h *hub) run() { +func (hub *hub) run() { for { select { - case c := <-h.register: - h.connections[c] = true + case c := <-hub.register: + hub.connections[c] = true // send supported commands c.send <- []byte(fmt.Sprintf(`{"Version" : "%s"} `, version)) c.send <- []byte(html.EscapeString(commands)) c.send <- []byte(fmt.Sprintf(`{"Hostname" : "%s"} `, *hostname)) c.send <- []byte(fmt.Sprintf(`{"OS" : "%s"} `, runtime.GOOS)) - case c := <-h.unregister: - h.unregisterConnection(c) - case m := <-h.broadcast: + case c := <-hub.unregister: + hub.unregisterConnection(c) + case m := <-hub.broadcast: if len(m) > 0 { - h.checkCmd(m) - h.sendToRegisteredConnections(m) + hub.checkCmd(m) + hub.sendToRegisteredConnections(m) } - case m := <-h.broadcastSys: - h.sendToRegisteredConnections(m) + case m := <-hub.broadcastSys: + hub.sendToRegisteredConnections(m) } } } -func (h *hub) checkCmd(m []byte) { +func (hub *hub) checkCmd(m []byte) { //log.Print("Inside checkCmd") s := string(m[:]) @@ -169,18 +169,18 @@ func (h *hub) checkCmd(m []byte) { args := strings.Split(s, " ") if len(args) < 3 { - go h.spErr("You did not specify a port and baud rate in your open cmd") + go hub.spErr("You did not specify a port and baud rate in your open cmd") return } if len(args[1]) < 1 { - go h.spErr("You did not specify a serial port") + go hub.spErr("You did not specify a serial port") return } baudStr := strings.Replace(args[2], "\n", "", -1) baud, err := strconv.Atoi(baudStr) if err != nil { - go h.spErr("Problem converting baud rate " + args[2]) + go hub.spErr("Problem converting baud rate " + args[2]) return } // pass in buffer type now as string. if user does not @@ -191,30 +191,30 @@ func (h *hub) checkCmd(m []byte) { buftype := strings.Replace(args[3], "\n", "", -1) bufferAlgorithm = buftype } - go h.spHandlerOpen(args[1], baud, bufferAlgorithm) + go hub.spHandlerOpen(args[1], baud, bufferAlgorithm) } else if strings.HasPrefix(sl, "close") { args := strings.Split(s, " ") if len(args) > 1 { - go h.spClose(args[1]) + go hub.spClose(args[1]) } else { - go h.spErr("You did not specify a port to close") + go hub.spErr("You did not specify a port to close") } } else if strings.HasPrefix(sl, "killupload") { // kill the running process (assumes singleton for now) go func() { upload.Kill() - h.broadcastSys <- []byte("{\"uploadStatus\": \"Killed\"}") + hub.broadcastSys <- []byte("{\"uploadStatus\": \"Killed\"}") log.Println("{\"uploadStatus\": \"Killed\"}") }() } else if strings.HasPrefix(sl, "send") { // will catch send and sendnobuf and sendraw - go h.spWrite(s) + go hub.spWrite(s) } else if strings.HasPrefix(sl, "list") { - go h.serialPortList.List() + go hub.serialPortList.List() } else if strings.HasPrefix(sl, "downloadtool") { go func() { args := strings.Split(s, " ") @@ -225,7 +225,7 @@ func (h *hub) checkCmd(m []byte) { if len(args) <= 1 { mapD := map[string]string{"DownloadStatus": "Error", "Msg": "Not enough arguments"} mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB + hub.broadcastSys <- mapB return } if len(args) > 1 { @@ -248,41 +248,41 @@ func (h *hub) checkCmd(m []byte) { reportPendingProgress := func(msg string) { mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB + hub.broadcastSys <- mapB } - err := h.tools.Download(pack, tool, toolVersion, behaviour, reportPendingProgress) + err := hub.tools.Download(pack, tool, toolVersion, behaviour, reportPendingProgress) if err != nil { mapD := map[string]string{"DownloadStatus": "Error", "Msg": err.Error()} mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB + hub.broadcastSys <- mapB } else { mapD := map[string]string{"DownloadStatus": "Success", "Msg": "Map Updated"} mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB + hub.broadcastSys <- mapB } }() } else if strings.HasPrefix(sl, "log") { - go h.logAction(sl) + go hub.logAction(sl) } else if strings.HasPrefix(sl, "restart") { // potentially, the sysStray dependencies can be removed https://github.com/arduino/arduino-create-agent/issues/1013 log.Println("Received restart from the daemon. Why? Boh") - h.systray.Restart() + hub.systray.Restart() } else if strings.HasPrefix(sl, "exit") { - h.systray.Quit() + hub.systray.Quit() } else if strings.HasPrefix(sl, "memstats") { - h.memoryStats() + hub.memoryStats() } else if strings.HasPrefix(sl, "gc") { - h.garbageCollection() + hub.garbageCollection() } else if strings.HasPrefix(sl, "hostname") { - h.getHostname() + hub.getHostname() } else if strings.HasPrefix(sl, "version") { - h.getVersion() + hub.getVersion() } else { - go h.spErr("Could not understand command.") + go hub.spErr("Could not understand command.") } } -func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { +func (hub *hub) spHandlerOpen(portname string, baud int, buftype string) { log.Print("Inside spHandler") @@ -306,8 +306,8 @@ func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { if err != nil { //log.Fatal(err) log.Print("Error opening port " + err.Error()) - //h.broadcastSys <- []byte("Error opening port. " + err.Error()) - h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}") + //hub.broadcastSys <- []byte("Error opening port. " + err.Error()) + hub.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}") return } @@ -325,22 +325,22 @@ func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { } p.OnMessage = func(msg []byte) { - h.broadcastSys <- msg + hub.broadcastSys <- msg } p.OnClose = func(port *serport) { - h.serialPortList.MarkPortAsClosed(p.portName) - h.serialPortList.List() + hub.serialPortList.MarkPortAsClosed(p.portName) + hub.serialPortList.List() } var bw Bufferflow switch buftype { case "timed": - bw = NewBufferflowTimed(portname, h.broadcastSys) + bw = NewBufferflowTimed(portname, hub.broadcastSys) case "timedraw": - bw = NewBufferflowTimedRaw(portname, h.broadcastSys) + bw = NewBufferflowTimedRaw(portname, hub.broadcastSys) case "default": - bw = NewBufferflowDefault(portname, h.broadcastSys) + bw = NewBufferflowDefault(portname, hub.broadcastSys) default: log.Panicf("unknown buffer type: %s", buftype) } @@ -348,11 +348,11 @@ func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { bw.Init() p.bufferwatcher = bw - h.serialHub.Register(p) - defer h.serialHub.Unregister(p) + hub.serialHub.Register(p) + defer hub.serialHub.Unregister(p) - h.serialPortList.MarkPortAsOpened(portname) - h.serialPortList.List() + hub.serialPortList.MarkPortAsOpened(portname) + hub.serialPortList.List() // this is internally buffered thread to not send to serial port if blocked go p.writerBuffered() @@ -363,25 +363,69 @@ func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { p.reader(buftype) - h.serialPortList.List() + hub.serialPortList.List() } -type logWriter struct { - onWrite func([]byte) +func (hub *hub) spClose(portname string) { + if myport, ok := hub.serialHub.FindPortByName(portname); ok { + hub.broadcastSys <- []byte("Closing serial port " + portname) + myport.Close() + } else { + hub.spErr("We could not find the serial port " + portname + " that you were trying to close.") + } } -func (u *logWriter) Write(p []byte) (n int, err error) { - u.onWrite(p) - return len(p), nil +func (hub *hub) spWrite(arg string) { + // we will get a string of comXX asdf asdf asdf + //log.Println("Inside spWrite arg: " + arg) + arg = strings.TrimPrefix(arg, " ") + //log.Println("arg after trim: " + arg) + args := strings.SplitN(arg, " ", 3) + if len(args) != 3 { + errstr := "Could not parse send command: " + arg + //log.Println(errstr) + hub.spErr(errstr) + return + } + bufferingMode := args[0] + portname := strings.Trim(args[1], " ") + data := args[2] + + //log.Println("The port to write to is:" + portname + "---") + //log.Println("The data is:" + data + "---") + + // See if we have this port open + port, ok := hub.serialHub.FindPortByName(portname) + if !ok { + // we couldn't find the port, so send err + hub.spErr("We could not find the serial port " + portname + " that you were trying to write to.") + return + } + + // see if bufferingMode is valid + switch bufferingMode { + case "send", "sendnobuf", "sendraw": + // valid buffering mode, go ahead + default: + hub.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") + return + } + + // send it to the write channel + port.Write(data, bufferingMode) +} + +type logWriter struct { + onWrite func([]byte) } -func (h *hub) logAction(sl string) { +func (hub *hub) logAction(sl string) { if strings.HasPrefix(sl, "log on") { *logDump = "on" logWriter := logWriter{} logWriter.onWrite = func(p []byte) { - h.broadcastSys <- p + hub.broadcastSys <- p } multiWriter := io.MultiWriter(&logWriter, os.Stderr) @@ -391,88 +435,44 @@ func (h *hub) logAction(sl string) { log.SetOutput(os.Stderr) // } else if strings.HasPrefix(sl, "log show") { // TODO: send all the saved log to websocket - //h.broadcastSys <- []byte("{\"BufFlowDebug\" : \"" + *logDump + "\"}") + //hub.broadcastSys <- []byte("{\"BufFlowDebug\" : \"" + *logDump + "\"}") } } -func (h *hub) memoryStats() { +func (u *logWriter) Write(p []byte) (n int, err error) { + u.onWrite(p) + return len(p), nil +} + +func (hub *hub) memoryStats() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) json, _ := json.Marshal(memStats) log.Printf("memStats:%v\n", string(json)) - h.broadcastSys <- json + hub.broadcastSys <- json } -func (h *hub) getHostname() { - h.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") +func (hub *hub) getHostname() { + hub.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") } -func (h *hub) getVersion() { - h.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") +func (hub *hub) getVersion() { + hub.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") } -func (h *hub) garbageCollection() { +func (hub *hub) garbageCollection() { log.Printf("Starting garbageCollection()\n") - h.broadcastSys <- []byte("{\"gc\":\"starting\"}") - h.memoryStats() + hub.broadcastSys <- []byte("{\"gc\":\"starting\"}") + hub.memoryStats() debug.SetGCPercent(100) debug.FreeOSMemory() debug.SetGCPercent(-1) log.Printf("Done with garbageCollection()\n") - h.broadcastSys <- []byte("{\"gc\":\"done\"}") - h.memoryStats() + hub.broadcastSys <- []byte("{\"gc\":\"done\"}") + hub.memoryStats() } -func (h *hub) spErr(err string) { +func (hub *hub) spErr(err string) { //log.Println("Sending err back: ", err) - h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") -} - -func (h *hub) spClose(portname string) { - if myport, ok := h.serialHub.FindPortByName(portname); ok { - h.broadcastSys <- []byte("Closing serial port " + portname) - myport.Close() - } else { - h.spErr("We could not find the serial port " + portname + " that you were trying to close.") - } -} - -func (h *hub) spWrite(arg string) { - // we will get a string of comXX asdf asdf asdf - //log.Println("Inside spWrite arg: " + arg) - arg = strings.TrimPrefix(arg, " ") - //log.Println("arg after trim: " + arg) - args := strings.SplitN(arg, " ", 3) - if len(args) != 3 { - errstr := "Could not parse send command: " + arg - //log.Println(errstr) - h.spErr(errstr) - return - } - bufferingMode := args[0] - portname := strings.Trim(args[1], " ") - data := args[2] - - //log.Println("The port to write to is:" + portname + "---") - //log.Println("The data is:" + data + "---") - - // See if we have this port open - port, ok := h.serialHub.FindPortByName(portname) - if !ok { - // we couldn't find the port, so send err - h.spErr("We could not find the serial port " + portname + " that you were trying to write to.") - return - } - - // see if bufferingMode is valid - switch bufferingMode { - case "send", "sendnobuf", "sendraw": - // valid buffering mode, go ahead - default: - h.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") - return - } - - // send it to the write channel - port.Write(data, bufferingMode) + hub.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") } diff --git a/info.go b/info.go index bf04713e3..2d6296717 100644 --- a/info.go +++ b/info.go @@ -41,12 +41,12 @@ func infoHandler(c *gin.Context) { }) } -func pauseHandler(h *hub, s *systray.Systray) func(c *gin.Context) { +func pauseHandler(hub *hub, s *systray.Systray) func(c *gin.Context) { return func(c *gin.Context) { go func() { ports, _ := serial.GetPortsList() for _, element := range ports { - h.spClose(element) + hub.spClose(element) } *hibernate = true s.Pause() From 1f9b999256c2cbf3ef5a9ca615ec772a810f8399 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 15:28:09 +0200 Subject: [PATCH 31/50] fix: add hub reference to PLogger initialization in uploadHandler --- conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conn.go b/conn.go index ad27c8fa6..42b573bb5 100644 --- a/conn.go +++ b/conn.go @@ -169,7 +169,7 @@ func uploadHandler(hub *hub, pubKey *rsa.PublicKey, tools *tools.Tools) func(*gi return } - l := PLogger{Verbose: true} + l := PLogger{Verbose: true, hub: hub} // Upload if data.Extra.Network { From 4585769d630056aca8f67576a86475d64b27785d Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 15:32:35 +0200 Subject: [PATCH 32/50] move `spErr, spWrite, spClose spHandlerOpen at the end for redaibility --- hub.go | 114 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/hub.go b/hub.go index 495f851c1..5d4b66fa2 100755 --- a/hub.go +++ b/hub.go @@ -282,6 +282,63 @@ func (hub *hub) checkCmd(m []byte) { } } +type logWriter struct { + onWrite func([]byte) +} + +func (hub *hub) logAction(sl string) { + if strings.HasPrefix(sl, "log on") { + *logDump = "on" + + logWriter := logWriter{} + logWriter.onWrite = func(p []byte) { + hub.broadcastSys <- p + } + + multiWriter := io.MultiWriter(&logWriter, os.Stderr) + log.SetOutput(multiWriter) + } else if strings.HasPrefix(sl, "log off") { + *logDump = "off" + log.SetOutput(os.Stderr) + // } else if strings.HasPrefix(sl, "log show") { + // TODO: send all the saved log to websocket + //hub.broadcastSys <- []byte("{\"BufFlowDebug\" : \"" + *logDump + "\"}") + } +} + +func (u *logWriter) Write(p []byte) (n int, err error) { + u.onWrite(p) + return len(p), nil +} + +func (hub *hub) memoryStats() { + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + json, _ := json.Marshal(memStats) + log.Printf("memStats:%v\n", string(json)) + hub.broadcastSys <- json +} + +func (hub *hub) getHostname() { + hub.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") +} + +func (hub *hub) getVersion() { + hub.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") +} + +func (hub *hub) garbageCollection() { + log.Printf("Starting garbageCollection()\n") + hub.broadcastSys <- []byte("{\"gc\":\"starting\"}") + hub.memoryStats() + debug.SetGCPercent(100) + debug.FreeOSMemory() + debug.SetGCPercent(-1) + log.Printf("Done with garbageCollection()\n") + hub.broadcastSys <- []byte("{\"gc\":\"done\"}") + hub.memoryStats() +} + func (hub *hub) spHandlerOpen(portname string, baud int, buftype string) { log.Print("Inside spHandler") @@ -415,63 +472,6 @@ func (hub *hub) spWrite(arg string) { port.Write(data, bufferingMode) } -type logWriter struct { - onWrite func([]byte) -} - -func (hub *hub) logAction(sl string) { - if strings.HasPrefix(sl, "log on") { - *logDump = "on" - - logWriter := logWriter{} - logWriter.onWrite = func(p []byte) { - hub.broadcastSys <- p - } - - multiWriter := io.MultiWriter(&logWriter, os.Stderr) - log.SetOutput(multiWriter) - } else if strings.HasPrefix(sl, "log off") { - *logDump = "off" - log.SetOutput(os.Stderr) - // } else if strings.HasPrefix(sl, "log show") { - // TODO: send all the saved log to websocket - //hub.broadcastSys <- []byte("{\"BufFlowDebug\" : \"" + *logDump + "\"}") - } -} - -func (u *logWriter) Write(p []byte) (n int, err error) { - u.onWrite(p) - return len(p), nil -} - -func (hub *hub) memoryStats() { - var memStats runtime.MemStats - runtime.ReadMemStats(&memStats) - json, _ := json.Marshal(memStats) - log.Printf("memStats:%v\n", string(json)) - hub.broadcastSys <- json -} - -func (hub *hub) getHostname() { - hub.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") -} - -func (hub *hub) getVersion() { - hub.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") -} - -func (hub *hub) garbageCollection() { - log.Printf("Starting garbageCollection()\n") - hub.broadcastSys <- []byte("{\"gc\":\"starting\"}") - hub.memoryStats() - debug.SetGCPercent(100) - debug.FreeOSMemory() - debug.SetGCPercent(-1) - log.Printf("Done with garbageCollection()\n") - hub.broadcastSys <- []byte("{\"gc\":\"done\"}") - hub.memoryStats() -} - func (hub *hub) spErr(err string) { //log.Println("Sending err back: ", err) hub.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") From 283064c82ebafb034cf7206333399d7ec2fc7c7b Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 16:15:15 +0200 Subject: [PATCH 33/50] refactor: clean up serialport.go and serialportlist.go for clarity --- serialport.go | 5 ++++- serialportlist.go | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/serialport.go b/serialport.go index 5fadd87e0..eef4410cc 100755 --- a/serialport.go +++ b/serialport.go @@ -240,7 +240,10 @@ func (p *serport) writerNoBuf() { p.OnMessage([]byte(msgstr)) p.portIo.Close() - // serialPorts.List( + + // The effect of this call is to send in to all the we client the list of serial ports + // TODO: investigate if this is superfluous and it can be removed. + // serialPorts.List() } diff --git a/serialportlist.go b/serialportlist.go index 64f10debb..1c38a49ee 100644 --- a/serialportlist.go +++ b/serialportlist.go @@ -12,11 +12,10 @@ import ( ) type serialPortList struct { - tools *tools.Tools - Ports []*SpPortItem portsLock sync.Mutex + tools *tools.Tools `json:"-"` OnList func([]byte) `json:"-"` OnErr func(string) `json:"-"` } From 14d72e84f97bf26252002380d8447abc0b9fa153 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 16:21:05 +0200 Subject: [PATCH 34/50] refactor: clarify the impact of removing serialPorts.List() in writerNoBuf --- serialport.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/serialport.go b/serialport.go index eef4410cc..00ff1ec90 100755 --- a/serialport.go +++ b/serialport.go @@ -241,8 +241,9 @@ func (p *serport) writerNoBuf() { p.portIo.Close() - // The effect of this call is to send in to all the we client the list of serial ports - // TODO: investigate if this is superfluous and it can be removed. + // NOTE: by removing the 'serialPorts.List()' line, the list of serial ports are NOT sent to the websocket clients. + // after a write is completed. It should not be an issue also because the other two 'writerBuffered' and 'writerRaw' methods + // do not call it. // serialPorts.List() } From 9e13dcb7fc50f23a946dbdae073d1a54eb64faf6 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 17:04:49 +0200 Subject: [PATCH 35/50] move serialportlist inside hub --- hub.go | 6 ++++-- main.go | 6 +----- serialport.go | 7 ++++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/hub.go b/hub.go index 5d4b66fa2..14d25bf8c 100755 --- a/hub.go +++ b/hub.go @@ -60,7 +60,7 @@ type hub struct { systray *systray.Systray } -func newHub(serialList *serialPortList, tools *tools.Tools, systray *systray.Systray) *hub { +func newHub(tools *tools.Tools, systray *systray.Systray) *hub { hub := &hub{ broadcast: make(chan []byte, 1000), broadcastSys: make(chan []byte, 1000), @@ -68,7 +68,7 @@ func newHub(serialList *serialPortList, tools *tools.Tools, systray *systray.Sys unregister: make(chan *connection), connections: make(map[*connection]bool), serialHub: newSerialHub(), - serialPortList: serialList, + serialPortList: newSerialPortList(tools), tools: tools, systray: systray, } @@ -132,6 +132,8 @@ func (hub *hub) sendToRegisteredConnections(data []byte) { } func (hub *hub) run() { + go hub.serialPortList.Run() + for { select { case c := <-hub.register: diff --git a/main.go b/main.go index dfb217c26..51a26450e 100755 --- a/main.go +++ b/main.go @@ -176,9 +176,7 @@ func loop(stray *systray.Systray) { } tools := tools.New(config.GetDataDir(), index, signaturePubKey) - serialPorts := newSerialPortList(tools) - - hub := newHub(serialPorts, tools, stray) + hub := newHub(tools, stray) // Let's handle the config configDir := config.GetDefaultConfigDir() @@ -394,8 +392,6 @@ func loop(stray *systray.Systray) { } } - // launch the discoveries for the running system - go serialPorts.Run() // launch the hub routine which is the singleton for the websocket server go hub.run() // launch our dummy data routine diff --git a/serialport.go b/serialport.go index 00ff1ec90..4385a3e86 100755 --- a/serialport.go +++ b/serialport.go @@ -241,9 +241,10 @@ func (p *serport) writerNoBuf() { p.portIo.Close() - // NOTE: by removing the 'serialPorts.List()' line, the list of serial ports are NOT sent to the websocket clients. - // after a write is completed. It should not be an issue also because the other two 'writerBuffered' and 'writerRaw' methods - // do not call it. + // NOTE: by removing the 'serialPorts.List()' line, + // the list of serial ports are NOT sent to the websocket clients after a write is completed. + // This should not be an issue since the list are periodically called. + // Note also that the 'writerBuffered' and 'writerRaw' methods do not call it. // serialPorts.List() } From a69c0dcf44dfa1bcb1788209f73be5c7752b7d7e Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 2 Apr 2025 17:37:14 +0200 Subject: [PATCH 36/50] refactor on callback --- hub.go | 46 ++++++++++++++++++++++++---------------------- serialhub.go | 14 ++++++++------ serialportlist.go | 9 +++++++-- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/hub.go b/hub.go index 14d25bf8c..46ee81429 100755 --- a/hub.go +++ b/hub.go @@ -61,35 +61,35 @@ type hub struct { } func newHub(tools *tools.Tools, systray *systray.Systray) *hub { - hub := &hub{ - broadcast: make(chan []byte, 1000), - broadcastSys: make(chan []byte, 1000), - register: make(chan *connection), - unregister: make(chan *connection), - connections: make(map[*connection]bool), - serialHub: newSerialHub(), - serialPortList: newSerialPortList(tools), - tools: tools, - systray: systray, - } + broadcastSys := make(chan []byte, 1000) - hub.serialHub.OnRegister = func(port *serport) { - hub.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") + onRegister := func(port *serport) { + broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") } - - hub.serialHub.OnUnregister = func(port *serport) { - hub.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") + onUnregister := func(port *serport) { + broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") } + serialHub := newSerialHub(onRegister, onUnregister) - hub.serialPortList.OnList = func(data []byte) { - hub.broadcastSys <- data + onList := func(data []byte) { + broadcastSys <- data } - - hub.serialPortList.OnErr = func(err string) { - hub.broadcastSys <- []byte("{\"Error\":\"" + err + "\"}") + onErr := func(err string) { + broadcastSys <- []byte("{\"Error\":\"" + err + "\"}") } + serialPortList := newSerialPortList(tools, onList, onErr) - return hub + return &hub{ + broadcast: make(chan []byte, 1000), + broadcastSys: broadcastSys, + register: make(chan *connection), + unregister: make(chan *connection), + connections: make(map[*connection]bool), + serialHub: serialHub, + serialPortList: serialPortList, + tools: tools, + systray: systray, + } } const commands = `{ @@ -216,6 +216,8 @@ func (hub *hub) checkCmd(m []byte) { // will catch send and sendnobuf and sendraw go hub.spWrite(s) } else if strings.HasPrefix(sl, "list") { + // ports := hub.serialPortList.List() + // send to websockets the ports go hub.serialPortList.List() } else if strings.HasPrefix(sl, "downloadtool") { go func() { diff --git a/serialhub.go b/serialhub.go index 6c00718e6..06a29003c 100755 --- a/serialhub.go +++ b/serialhub.go @@ -27,13 +27,15 @@ type serialhub struct { ports map[*serport]bool mu sync.Mutex - OnRegister func(port *serport) - OnUnregister func(port *serport) + onRegister func(port *serport) + onUnregister func(port *serport) } -func newSerialHub() *serialhub { +func newSerialHub(onRegister func(port *serport), onUnregister func(port *serport)) *serialhub { return &serialhub{ - ports: make(map[*serport]bool), + ports: make(map[*serport]bool), + onRegister: onRegister, + onUnregister: onUnregister, } } @@ -41,7 +43,7 @@ func newSerialHub() *serialhub { func (sh *serialhub) Register(port *serport) { sh.mu.Lock() //log.Print("Registering a port: ", p.portConf.Name) - sh.OnRegister(port) + sh.onRegister(port) sh.ports[port] = true sh.mu.Unlock() } @@ -50,7 +52,7 @@ func (sh *serialhub) Register(port *serport) { func (sh *serialhub) Unregister(port *serport) { sh.mu.Lock() //log.Print("Unregistering a port: ", p.portConf.Name) - sh.OnUnregister(port) + sh.onUnregister(port) delete(sh.ports, port) close(port.sendBuffered) close(port.sendNoBuf) diff --git a/serialportlist.go b/serialportlist.go index 1c38a49ee..9b1fdfac6 100644 --- a/serialportlist.go +++ b/serialportlist.go @@ -34,8 +34,12 @@ type SpPortItem struct { ProductID string } -func newSerialPortList(tools *tools.Tools) *serialPortList { - return &serialPortList{tools: tools} +func newSerialPortList(tools *tools.Tools, onList func(data []byte), onErr func(err string)) *serialPortList { + return &serialPortList{ + tools: tools, + OnList: onList, + OnErr: onErr, + } } // List broadcasts a Json representation of the ports found @@ -111,6 +115,7 @@ func (sp *serialPortList) runSerialDiscovery() { logrus.Errorf("Error starting event watcher on serial-discovery: %s", err) panic(err) } + d.List() logrus.Infof("Serial discovery started, watching for events") for ev := range events { From 7e340712d400f9137f66645a6fd283ed3fd745ba Mon Sep 17 00:00:00 2001 From: Davide N Date: Thu, 3 Apr 2025 10:15:42 +0200 Subject: [PATCH 37/50] refactor: simplify hub initialization in upload handler tests --- main_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main_test.go b/main_test.go index bbede14fd..4b0720e0d 100644 --- a/main_test.go +++ b/main_test.go @@ -63,7 +63,7 @@ func TestUploadHandlerAgainstEvilFileNames(t *testing.T) { signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) require.NoError(t, err) tools := tools.New(config.GetDataDir(), index, signaturePubKey) - hub := newHub(newSerialPortList(tools), tools, &systray.Systray{}) + hub := newHub(tools, &systray.Systray{}) pubkey := utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)) r.POST("/", uploadHandler(hub, pubkey, tools)) @@ -107,7 +107,7 @@ func TestUploadHandlerAgainstBase64WithoutPaddingMustFail(t *testing.T) { signaturePubKey, err := utilities.ParseRsaPublicKey([]byte(*signatureKey)) require.NoError(t, err) tools := tools.New(config.GetDataDir(), index, signaturePubKey) - hub := newHub(newSerialPortList(tools), tools, &systray.Systray{}) + hub := newHub(tools, &systray.Systray{}) pubkey := utilities.MustParseRsaPublicKey([]byte(globals.ArduinoSignaturePubKey)) r.POST("/", uploadHandler(hub, pubkey, tools)) From 815a7916fecd0153e8f972fa02409099bd7e4183 Mon Sep 17 00:00:00 2001 From: Davide N Date: Thu, 3 Apr 2025 10:59:39 +0200 Subject: [PATCH 38/50] refactor: remove unused port listing in checkCmd function --- hub.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/hub.go b/hub.go index 46ee81429..9d15240f7 100755 --- a/hub.go +++ b/hub.go @@ -216,8 +216,6 @@ func (hub *hub) checkCmd(m []byte) { // will catch send and sendnobuf and sendraw go hub.spWrite(s) } else if strings.HasPrefix(sl, "list") { - // ports := hub.serialPortList.List() - // send to websockets the ports go hub.serialPortList.List() } else if strings.HasPrefix(sl, "downloadtool") { go func() { From 9535905763ff78a6b43e2fef25a05c0d7754e11e Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:21:52 +0200 Subject: [PATCH 39/50] rename into `h` and revert split of serial.go --- hub.go | 171 ++++++++++++++++----------------- serialportlist.go => serial.go | 140 +++++++++++++++++++++------ serialhub.go | 74 -------------- 3 files changed, 194 insertions(+), 191 deletions(-) rename serialportlist.go => serial.go (58%) delete mode 100755 serialhub.go diff --git a/hub.go b/hub.go index 9d15240f7..0e9ade88d 100755 --- a/hub.go +++ b/hub.go @@ -53,7 +53,7 @@ type hub struct { // Serial hub to communicate with serial ports serialHub *serialhub - serialPortList *serialPortList + serialPortList *SerialPortList tools *tools.Tools @@ -69,7 +69,7 @@ func newHub(tools *tools.Tools, systray *systray.Systray) *hub { onUnregister := func(port *serport) { broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") } - serialHub := newSerialHub(onRegister, onUnregister) + serialHubub := newSerialHub(onRegister, onUnregister) onList := func(data []byte) { broadcastSys <- data @@ -85,7 +85,7 @@ func newHub(tools *tools.Tools, systray *systray.Systray) *hub { register: make(chan *connection), unregister: make(chan *connection), connections: make(map[*connection]bool), - serialHub: serialHub, + serialHub: serialHubub, serialPortList: serialPortList, tools: tools, systray: systray, @@ -110,53 +110,53 @@ const commands = `{ ] }` -func (hub *hub) unregisterConnection(c *connection) { - if _, contains := hub.connections[c]; !contains { +func (h *hub) unregisterConnection(c *connection) { + if _, contains := h.connections[c]; !contains { return } - delete(hub.connections, c) + delete(h.connections, c) close(c.send) } -func (hub *hub) sendToRegisteredConnections(data []byte) { - for c := range hub.connections { +func (h *hub) sendToRegisteredConnections(data []byte) { + for c := range h.connections { select { case c.send <- data: //log.Print("did broadcast to ") //log.Print(c.ws.RemoteAddr()) //c.send <- []byte("hello world") default: - hub.unregisterConnection(c) + h.unregisterConnection(c) } } } -func (hub *hub) run() { - go hub.serialPortList.Run() +func (h *hub) run() { + go h.serialPortList.Run() for { select { - case c := <-hub.register: - hub.connections[c] = true + case c := <-h.register: + h.connections[c] = true // send supported commands c.send <- []byte(fmt.Sprintf(`{"Version" : "%s"} `, version)) c.send <- []byte(html.EscapeString(commands)) c.send <- []byte(fmt.Sprintf(`{"Hostname" : "%s"} `, *hostname)) c.send <- []byte(fmt.Sprintf(`{"OS" : "%s"} `, runtime.GOOS)) - case c := <-hub.unregister: - hub.unregisterConnection(c) - case m := <-hub.broadcast: + case c := <-h.unregister: + h.unregisterConnection(c) + case m := <-h.broadcast: if len(m) > 0 { - hub.checkCmd(m) - hub.sendToRegisteredConnections(m) + h.checkCmd(m) + h.sendToRegisteredConnections(m) } - case m := <-hub.broadcastSys: - hub.sendToRegisteredConnections(m) + case m := <-h.broadcastSys: + h.sendToRegisteredConnections(m) } } } -func (hub *hub) checkCmd(m []byte) { +func (h *hub) checkCmd(m []byte) { //log.Print("Inside checkCmd") s := string(m[:]) @@ -171,18 +171,18 @@ func (hub *hub) checkCmd(m []byte) { args := strings.Split(s, " ") if len(args) < 3 { - go hub.spErr("You did not specify a port and baud rate in your open cmd") + go h.spErr("You did not specify a port and baud rate in your open cmd") return } if len(args[1]) < 1 { - go hub.spErr("You did not specify a serial port") + go h.spErr("You did not specify a serial port") return } baudStr := strings.Replace(args[2], "\n", "", -1) baud, err := strconv.Atoi(baudStr) if err != nil { - go hub.spErr("Problem converting baud rate " + args[2]) + go h.spErr("Problem converting baud rate " + args[2]) return } // pass in buffer type now as string. if user does not @@ -193,30 +193,30 @@ func (hub *hub) checkCmd(m []byte) { buftype := strings.Replace(args[3], "\n", "", -1) bufferAlgorithm = buftype } - go hub.spHandlerOpen(args[1], baud, bufferAlgorithm) + go h.spHandlerOpen(args[1], baud, bufferAlgorithm) } else if strings.HasPrefix(sl, "close") { args := strings.Split(s, " ") if len(args) > 1 { - go hub.spClose(args[1]) + go h.spClose(args[1]) } else { - go hub.spErr("You did not specify a port to close") + go h.spErr("You did not specify a port to close") } } else if strings.HasPrefix(sl, "killupload") { // kill the running process (assumes singleton for now) go func() { upload.Kill() - hub.broadcastSys <- []byte("{\"uploadStatus\": \"Killed\"}") + h.broadcastSys <- []byte("{\"uploadStatus\": \"Killed\"}") log.Println("{\"uploadStatus\": \"Killed\"}") }() } else if strings.HasPrefix(sl, "send") { // will catch send and sendnobuf and sendraw - go hub.spWrite(s) + go h.spWrite(s) } else if strings.HasPrefix(sl, "list") { - go hub.serialPortList.List() + go h.serialPortList.List() } else if strings.HasPrefix(sl, "downloadtool") { go func() { args := strings.Split(s, " ") @@ -227,7 +227,7 @@ func (hub *hub) checkCmd(m []byte) { if len(args) <= 1 { mapD := map[string]string{"DownloadStatus": "Error", "Msg": "Not enough arguments"} mapB, _ := json.Marshal(mapD) - hub.broadcastSys <- mapB + h.broadcastSys <- mapB return } if len(args) > 1 { @@ -250,37 +250,37 @@ func (hub *hub) checkCmd(m []byte) { reportPendingProgress := func(msg string) { mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} mapB, _ := json.Marshal(mapD) - hub.broadcastSys <- mapB + h.broadcastSys <- mapB } - err := hub.tools.Download(pack, tool, toolVersion, behaviour, reportPendingProgress) + err := h.tools.Download(pack, tool, toolVersion, behaviour, reportPendingProgress) if err != nil { mapD := map[string]string{"DownloadStatus": "Error", "Msg": err.Error()} mapB, _ := json.Marshal(mapD) - hub.broadcastSys <- mapB + h.broadcastSys <- mapB } else { mapD := map[string]string{"DownloadStatus": "Success", "Msg": "Map Updated"} mapB, _ := json.Marshal(mapD) - hub.broadcastSys <- mapB + h.broadcastSys <- mapB } }() } else if strings.HasPrefix(sl, "log") { - go hub.logAction(sl) + go h.logAction(sl) } else if strings.HasPrefix(sl, "restart") { - // potentially, the sysStray dependencies can be removed https://github.com/arduino/arduino-create-agent/issues/1013 + // potentially, the sysStray dependencies can be removed https://gith.com/arduino/arduino-create-agent/issues/1013 log.Println("Received restart from the daemon. Why? Boh") - hub.systray.Restart() + h.systray.Restart() } else if strings.HasPrefix(sl, "exit") { - hub.systray.Quit() + h.systray.Quit() } else if strings.HasPrefix(sl, "memstats") { - hub.memoryStats() + h.memoryStats() } else if strings.HasPrefix(sl, "gc") { - hub.garbageCollection() + h.garbageCollection() } else if strings.HasPrefix(sl, "hostname") { - hub.getHostname() + h.getHostname() } else if strings.HasPrefix(sl, "version") { - hub.getVersion() + h.getVersion() } else { - go hub.spErr("Could not understand command.") + go h.spErr("Could not understand command.") } } @@ -288,13 +288,13 @@ type logWriter struct { onWrite func([]byte) } -func (hub *hub) logAction(sl string) { +func (h *hub) logAction(sl string) { if strings.HasPrefix(sl, "log on") { *logDump = "on" logWriter := logWriter{} logWriter.onWrite = func(p []byte) { - hub.broadcastSys <- p + h.broadcastSys <- p } multiWriter := io.MultiWriter(&logWriter, os.Stderr) @@ -304,7 +304,7 @@ func (hub *hub) logAction(sl string) { log.SetOutput(os.Stderr) // } else if strings.HasPrefix(sl, "log show") { // TODO: send all the saved log to websocket - //hub.broadcastSys <- []byte("{\"BufFlowDebug\" : \"" + *logDump + "\"}") + //h.broadcastSys <- []byte("{\"BufFlowDebug\" : \"" + *logDump + "\"}") } } @@ -313,35 +313,35 @@ func (u *logWriter) Write(p []byte) (n int, err error) { return len(p), nil } -func (hub *hub) memoryStats() { +func (h *hub) memoryStats() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) json, _ := json.Marshal(memStats) log.Printf("memStats:%v\n", string(json)) - hub.broadcastSys <- json + h.broadcastSys <- json } -func (hub *hub) getHostname() { - hub.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") +func (h *hub) getHostname() { + h.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}") } -func (hub *hub) getVersion() { - hub.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") +func (h *hub) getVersion() { + h.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}") } -func (hub *hub) garbageCollection() { +func (h *hub) garbageCollection() { log.Printf("Starting garbageCollection()\n") - hub.broadcastSys <- []byte("{\"gc\":\"starting\"}") - hub.memoryStats() + h.broadcastSys <- []byte("{\"gc\":\"starting\"}") + h.memoryStats() debug.SetGCPercent(100) debug.FreeOSMemory() debug.SetGCPercent(-1) log.Printf("Done with garbageCollection()\n") - hub.broadcastSys <- []byte("{\"gc\":\"done\"}") - hub.memoryStats() + h.broadcastSys <- []byte("{\"gc\":\"done\"}") + h.memoryStats() } -func (hub *hub) spHandlerOpen(portname string, baud int, buftype string) { +func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { log.Print("Inside spHandler") @@ -365,8 +365,8 @@ func (hub *hub) spHandlerOpen(portname string, baud int, buftype string) { if err != nil { //log.Fatal(err) log.Print("Error opening port " + err.Error()) - //hub.broadcastSys <- []byte("Error opening port. " + err.Error()) - hub.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}") + //h.broadcastSys <- []byte("Error opening port. " + err.Error()) + h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}") return } @@ -384,22 +384,22 @@ func (hub *hub) spHandlerOpen(portname string, baud int, buftype string) { } p.OnMessage = func(msg []byte) { - hub.broadcastSys <- msg + h.broadcastSys <- msg } p.OnClose = func(port *serport) { - hub.serialPortList.MarkPortAsClosed(p.portName) - hub.serialPortList.List() + h.serialPortList.MarkPortAsClosed(p.portName) + h.serialPortList.List() } var bw Bufferflow switch buftype { case "timed": - bw = NewBufferflowTimed(portname, hub.broadcastSys) + bw = NewBufferflowTimed(portname, h.broadcastSys) case "timedraw": - bw = NewBufferflowTimedRaw(portname, hub.broadcastSys) + bw = NewBufferflowTimedRaw(portname, h.broadcastSys) case "default": - bw = NewBufferflowDefault(portname, hub.broadcastSys) + bw = NewBufferflowDefault(portname, h.broadcastSys) default: log.Panicf("unknown buffer type: %s", buftype) } @@ -407,11 +407,11 @@ func (hub *hub) spHandlerOpen(portname string, baud int, buftype string) { bw.Init() p.bufferwatcher = bw - hub.serialHub.Register(p) - defer hub.serialHub.Unregister(p) + h.serialHub.Register(p) + defer h.serialHub.Unregister(p) - hub.serialPortList.MarkPortAsOpened(portname) - hub.serialPortList.List() + h.serialPortList.MarkPortAsOpened(portname) + h.serialPortList.List() // this is internally buffered thread to not send to serial port if blocked go p.writerBuffered() @@ -422,19 +422,23 @@ func (hub *hub) spHandlerOpen(portname string, baud int, buftype string) { p.reader(buftype) - hub.serialPortList.List() + h.serialPortList.List() } -func (hub *hub) spClose(portname string) { - if myport, ok := hub.serialHub.FindPortByName(portname); ok { - hub.broadcastSys <- []byte("Closing serial port " + portname) +func (h *hub) spErr(err string) { + h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") +} + +func (h *hub) spClose(portname string) { + if myport, ok := h.serialHub.FindPortByName(portname); ok { + h.broadcastSys <- []byte("Closing serial port " + portname) myport.Close() } else { - hub.spErr("We could not find the serial port " + portname + " that you were trying to close.") + h.spErr("We could not find the serial port " + portname + " that you were trying to close.") } } -func (hub *hub) spWrite(arg string) { +func (h *hub) spWrite(arg string) { // we will get a string of comXX asdf asdf asdf //log.Println("Inside spWrite arg: " + arg) arg = strings.TrimPrefix(arg, " ") @@ -443,7 +447,7 @@ func (hub *hub) spWrite(arg string) { if len(args) != 3 { errstr := "Could not parse send command: " + arg //log.Println(errstr) - hub.spErr(errstr) + h.spErr(errstr) return } bufferingMode := args[0] @@ -454,10 +458,10 @@ func (hub *hub) spWrite(arg string) { //log.Println("The data is:" + data + "---") // See if we have this port open - port, ok := hub.serialHub.FindPortByName(portname) + port, ok := h.serialHub.FindPortByName(portname) if !ok { // we couldn't find the port, so send err - hub.spErr("We could not find the serial port " + portname + " that you were trying to write to.") + h.spErr("We could not find the serial port " + portname + " that you were trying to write to.") return } @@ -466,15 +470,10 @@ func (hub *hub) spWrite(arg string) { case "send", "sendnobuf", "sendraw": // valid buffering mode, go ahead default: - hub.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") + h.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") return } // send it to the write channel port.Write(data, bufferingMode) } - -func (hub *hub) spErr(err string) { - //log.Println("Sending err back: ", err) - hub.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") -} diff --git a/serialportlist.go b/serial.go similarity index 58% rename from serialportlist.go rename to serial.go index 9b1fdfac6..845d4fa94 100644 --- a/serialportlist.go +++ b/serial.go @@ -1,8 +1,26 @@ +// Copyright 2022 Arduino SA +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Supports Windows, Linux, Mac, BeagleBone Black, and Raspberry Pi + package main import ( "encoding/json" "slices" + "strings" "sync" "time" @@ -11,7 +29,26 @@ import ( "github.com/sirupsen/logrus" ) -type serialPortList struct { +type serialhub struct { + // Opened serial ports. + ports map[*serport]bool + + mu sync.Mutex + + onRegister func(port *serport) + onUnregister func(port *serport) +} + +func newSerialHub(onRegister func(port *serport), onUnregister func(port *serport)) *serialhub { + return &serialhub{ + ports: make(map[*serport]bool), + onRegister: onRegister, + onUnregister: onUnregister, + } +} + +// SerialPortList is the serial port list +type SerialPortList struct { Ports []*SpPortItem portsLock sync.Mutex @@ -34,8 +71,50 @@ type SpPortItem struct { ProductID string } -func newSerialPortList(tools *tools.Tools, onList func(data []byte), onErr func(err string)) *serialPortList { - return &serialPortList{ +// serialPorts contains the ports attached to the machine +var serialPorts SerialPortList + +var sh = serialhub{ + ports: make(map[*serport]bool), +} + +// Register serial ports from the connections. +func (sh *serialhub) Register(port *serport) { + sh.mu.Lock() + sh.onRegister(port) + // sh.h.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") + sh.ports[port] = true + sh.mu.Unlock() +} + +// Unregister requests from connections. +func (sh *serialhub) Unregister(port *serport) { + sh.mu.Lock() + //log.Print("Unregistering a port: ", p.portConf.Name) + // h.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") + sh.onUnregister(port) + delete(sh.ports, port) + close(port.sendBuffered) + close(port.sendNoBuf) + sh.mu.Unlock() +} + +func (sh *serialhub) FindPortByName(portname string) (*serport, bool) { + sh.mu.Lock() + defer sh.mu.Unlock() + + for port := range sh.ports { + if strings.EqualFold(port.portConf.Name, portname) { + // we found our port + //spHandlerClose(port) + return port, true + } + } + return nil, false +} + +func newSerialPortList(tools *tools.Tools, onList func(data []byte), onErr func(err string)) *SerialPortList { + return &SerialPortList{ tools: tools, OnList: onList, OnErr: onErr, @@ -43,7 +122,7 @@ func newSerialPortList(tools *tools.Tools, onList func(data []byte), onErr func( } // List broadcasts a Json representation of the ports found -func (sp *serialPortList) List() { +func (sp *SerialPortList) List() { sp.portsLock.Lock() ls, err := json.MarshalIndent(sp, "", "\t") sp.portsLock.Unlock() @@ -55,28 +134,8 @@ func (sp *serialPortList) List() { } } -// MarkPortAsOpened marks a port as opened by the user -func (sp *serialPortList) MarkPortAsOpened(portname string) { - sp.portsLock.Lock() - defer sp.portsLock.Unlock() - port := sp.getPortByName(portname) - if port != nil { - port.IsOpen = true - } -} - -// MarkPortAsClosed marks a port as no more opened by the user -func (sp *serialPortList) MarkPortAsClosed(portname string) { - sp.portsLock.Lock() - defer sp.portsLock.Unlock() - port := sp.getPortByName(portname) - if port != nil { - port.IsOpen = false - } -} - // Run is the main loop for port discovery and management -func (sp *serialPortList) Run() { +func (sp *SerialPortList) Run() { for retries := 0; retries < 10; retries++ { sp.runSerialDiscovery() @@ -86,7 +145,7 @@ func (sp *serialPortList) Run() { logrus.Errorf("Failed restarting serial discovery. Giving up...") } -func (sp *serialPortList) runSerialDiscovery() { +func (sp *SerialPortList) runSerialDiscovery() { // First ensure that all the discoveries are available noOpProgress := func(msg string) {} if err := sp.tools.Download("builtin", "serial-discovery", "latest", "keep", noOpProgress); err != nil { @@ -115,7 +174,6 @@ func (sp *serialPortList) runSerialDiscovery() { logrus.Errorf("Error starting event watcher on serial-discovery: %s", err) panic(err) } - d.List() logrus.Infof("Serial discovery started, watching for events") for ev := range events { @@ -132,13 +190,13 @@ func (sp *serialPortList) runSerialDiscovery() { logrus.Errorf("Serial discovery stopped.") } -func (sp *serialPortList) reset() { +func (sp *SerialPortList) reset() { sp.portsLock.Lock() defer sp.portsLock.Unlock() sp.Ports = []*SpPortItem{} } -func (sp *serialPortList) add(addedPort *discovery.Port) { +func (sp *SerialPortList) add(addedPort *discovery.Port) { if addedPort.Protocol != "serial" { return } @@ -181,7 +239,7 @@ func (sp *serialPortList) add(addedPort *discovery.Port) { }) } -func (sp *serialPortList) remove(removedPort *discovery.Port) { +func (sp *SerialPortList) remove(removedPort *discovery.Port) { sp.portsLock.Lock() defer sp.portsLock.Unlock() @@ -191,7 +249,27 @@ func (sp *serialPortList) remove(removedPort *discovery.Port) { }) } -func (sp *serialPortList) getPortByName(portname string) *SpPortItem { +// MarkPortAsOpened marks a port as opened by the user +func (sp *SerialPortList) MarkPortAsOpened(portname string) { + sp.portsLock.Lock() + defer sp.portsLock.Unlock() + port := sp.getPortByName(portname) + if port != nil { + port.IsOpen = true + } +} + +// MarkPortAsClosed marks a port as no more opened by the user +func (sp *SerialPortList) MarkPortAsClosed(portname string) { + sp.portsLock.Lock() + defer sp.portsLock.Unlock() + port := sp.getPortByName(portname) + if port != nil { + port.IsOpen = false + } +} + +func (sp *SerialPortList) getPortByName(portname string) *SpPortItem { for _, port := range sp.Ports { if port.Name == portname { return port diff --git a/serialhub.go b/serialhub.go deleted file mode 100755 index 06a29003c..000000000 --- a/serialhub.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2022 Arduino SA -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -// Supports Windows, Linux, Mac, BeagleBone Black, and Raspberry Pi - -package main - -import ( - "strings" - "sync" -) - -type serialhub struct { - // Opened serial ports. - ports map[*serport]bool - mu sync.Mutex - - onRegister func(port *serport) - onUnregister func(port *serport) -} - -func newSerialHub(onRegister func(port *serport), onUnregister func(port *serport)) *serialhub { - return &serialhub{ - ports: make(map[*serport]bool), - onRegister: onRegister, - onUnregister: onUnregister, - } -} - -// Register serial ports from the connections. -func (sh *serialhub) Register(port *serport) { - sh.mu.Lock() - //log.Print("Registering a port: ", p.portConf.Name) - sh.onRegister(port) - sh.ports[port] = true - sh.mu.Unlock() -} - -// Unregister requests from connections. -func (sh *serialhub) Unregister(port *serport) { - sh.mu.Lock() - //log.Print("Unregistering a port: ", p.portConf.Name) - sh.onUnregister(port) - delete(sh.ports, port) - close(port.sendBuffered) - close(port.sendNoBuf) - sh.mu.Unlock() -} - -func (sh *serialhub) FindPortByName(portname string) (*serport, bool) { - sh.mu.Lock() - defer sh.mu.Unlock() - - for port := range sh.ports { - if strings.EqualFold(port.portConf.Name, portname) { - // we found our port - //spHandlerClose(port) - return port, true - } - } - return nil, false -} From 0699e93365a8908a51bf226e0db2a9f23ceabd73 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:31:19 +0200 Subject: [PATCH 40/50] refactor: rename hub parameter in send and wsHandler functions for clarity --- conn.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/conn.go b/conn.go index 42b573bb5..75486bf9f 100644 --- a/conn.go +++ b/conn.go @@ -211,12 +211,12 @@ func (l PLogger) Info(args ...interface{}) { send(l.hub, map[string]string{uploadStatusStr: "Busy", "Msg": output}) } -func send(hub *hub, args map[string]string) { +func send(h *hub, args map[string]string) { mapB, _ := json.Marshal(args) - hub.broadcastSys <- mapB + h.broadcastSys <- mapB } -func wsHandler(hub *hub) *WsServer { +func wsHandler(h *hub) *WsServer { server, err := socketio.NewServer(nil) if err != nil { log.Fatal(err) @@ -224,13 +224,13 @@ func wsHandler(hub *hub) *WsServer { server.On("connection", func(so socketio.Socket) { c := &connection{send: make(chan []byte, 256*10), ws: so} - hub.register <- c + h.register <- c so.On("command", func(message string) { - hub.broadcast <- []byte(message) + h.broadcast <- []byte(message) }) so.On("disconnection", func() { - hub.unregister <- c + h.unregister <- c }) go c.writer() }) From 062bca0217549009be1ef63972f7e1be688a62f9 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:33:29 +0200 Subject: [PATCH 41/50] refactor: move spHandlerOpen function from serialport.go to hub.go for better organization --- hub.go | 86 --------------------------------------------------- serialport.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/hub.go b/hub.go index 0e9ade88d..32c5f867c 100755 --- a/hub.go +++ b/hub.go @@ -16,7 +16,6 @@ package main import ( - "bytes" "encoding/json" "fmt" "html" @@ -31,7 +30,6 @@ import ( "github.com/arduino/arduino-create-agent/tools" "github.com/arduino/arduino-create-agent/upload" log "github.com/sirupsen/logrus" - "go.bug.st/serial" ) type hub struct { @@ -341,90 +339,6 @@ func (h *hub) garbageCollection() { h.memoryStats() } -func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { - - log.Print("Inside spHandler") - - var out bytes.Buffer - - out.WriteString("Opening serial port ") - out.WriteString(portname) - out.WriteString(" at ") - out.WriteString(strconv.Itoa(baud)) - out.WriteString(" baud") - log.Print(out.String()) - - conf := &SerialConfig{Name: portname, Baud: baud, RtsOn: true} - - mode := &serial.Mode{ - BaudRate: baud, - } - - sp, err := serial.Open(portname, mode) - log.Print("Just tried to open port") - if err != nil { - //log.Fatal(err) - log.Print("Error opening port " + err.Error()) - //h.broadcastSys <- []byte("Error opening port. " + err.Error()) - h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}") - - return - } - log.Print("Opened port successfully") - //p := &serport{send: make(chan []byte, 256), portConf: conf, portIo: sp} - // we can go up to 256,000 lines of gcode in the buffer - p := &serport{ - sendBuffered: make(chan string, 256000), - sendNoBuf: make(chan []byte), - sendRaw: make(chan string), - portConf: conf, - portIo: sp, - portName: portname, - BufferType: buftype, - } - - p.OnMessage = func(msg []byte) { - h.broadcastSys <- msg - } - p.OnClose = func(port *serport) { - h.serialPortList.MarkPortAsClosed(p.portName) - h.serialPortList.List() - } - - var bw Bufferflow - - switch buftype { - case "timed": - bw = NewBufferflowTimed(portname, h.broadcastSys) - case "timedraw": - bw = NewBufferflowTimedRaw(portname, h.broadcastSys) - case "default": - bw = NewBufferflowDefault(portname, h.broadcastSys) - default: - log.Panicf("unknown buffer type: %s", buftype) - } - - bw.Init() - p.bufferwatcher = bw - - h.serialHub.Register(p) - defer h.serialHub.Unregister(p) - - h.serialPortList.MarkPortAsOpened(portname) - h.serialPortList.List() - - // this is internally buffered thread to not send to serial port if blocked - go p.writerBuffered() - // this is thread to send to serial port regardless of block - go p.writerNoBuf() - // this is thread to send to serial port but with base64 decoding - go p.writerRaw() - - p.reader(buftype) - - h.serialPortList.List() -} - func (h *hub) spErr(err string) { h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") } diff --git a/serialport.go b/serialport.go index 4385a3e86..5c8ed5c55 100755 --- a/serialport.go +++ b/serialport.go @@ -25,6 +25,7 @@ import ( "unicode/utf8" log "github.com/sirupsen/logrus" + "go.bug.st/serial" ) // SerialConfig is the serial port configuration @@ -281,6 +282,91 @@ func (p *serport) writerRaw() { p.OnMessage([]byte(msgstr)) } +// FIXME: move this into the `hub.go` file +func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { + + log.Print("Inside spHandler") + + var out bytes.Buffer + + out.WriteString("Opening serial port ") + out.WriteString(portname) + out.WriteString(" at ") + out.WriteString(strconv.Itoa(baud)) + out.WriteString(" baud") + log.Print(out.String()) + + conf := &SerialConfig{Name: portname, Baud: baud, RtsOn: true} + + mode := &serial.Mode{ + BaudRate: baud, + } + + sp, err := serial.Open(portname, mode) + log.Print("Just tried to open port") + if err != nil { + //log.Fatal(err) + log.Print("Error opening port " + err.Error()) + //h.broadcastSys <- []byte("Error opening port. " + err.Error()) + h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}") + + return + } + log.Print("Opened port successfully") + //p := &serport{send: make(chan []byte, 256), portConf: conf, portIo: sp} + // we can go up to 256,000 lines of gcode in the buffer + p := &serport{ + sendBuffered: make(chan string, 256000), + sendNoBuf: make(chan []byte), + sendRaw: make(chan string), + portConf: conf, + portIo: sp, + portName: portname, + BufferType: buftype, + } + + p.OnMessage = func(msg []byte) { + h.broadcastSys <- msg + } + p.OnClose = func(port *serport) { + h.serialPortList.MarkPortAsClosed(p.portName) + h.serialPortList.List() + } + + var bw Bufferflow + + switch buftype { + case "timed": + bw = NewBufferflowTimed(portname, h.broadcastSys) + case "timedraw": + bw = NewBufferflowTimedRaw(portname, h.broadcastSys) + case "default": + bw = NewBufferflowDefault(portname, h.broadcastSys) + default: + log.Panicf("unknown buffer type: %s", buftype) + } + + bw.Init() + p.bufferwatcher = bw + + h.serialHub.Register(p) + defer h.serialHub.Unregister(p) + + h.serialPortList.MarkPortAsOpened(portname) + h.serialPortList.List() + + // this is internally buffered thread to not send to serial port if blocked + go p.writerBuffered() + // this is thread to send to serial port regardless of block + go p.writerNoBuf() + // this is thread to send to serial port but with base64 decoding + go p.writerRaw() + + p.reader(buftype) + + h.serialPortList.List() +} + func (p *serport) Close() { p.isClosing.Store(true) From 2bcc06d4695f5da5e237b9e995c8102aab92fb77 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:39:25 +0200 Subject: [PATCH 42/50] remove comment --- serial.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/serial.go b/serial.go index 845d4fa94..07e0451c7 100644 --- a/serial.go +++ b/serial.go @@ -82,7 +82,6 @@ var sh = serialhub{ func (sh *serialhub) Register(port *serport) { sh.mu.Lock() sh.onRegister(port) - // sh.h.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") sh.ports[port] = true sh.mu.Unlock() } @@ -90,8 +89,6 @@ func (sh *serialhub) Register(port *serport) { // Unregister requests from connections. func (sh *serialhub) Unregister(port *serport) { sh.mu.Lock() - //log.Print("Unregistering a port: ", p.portConf.Name) - // h.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") sh.onUnregister(port) delete(sh.ports, port) close(port.sendBuffered) From 4da1624864d86d0bab985fc8ec5a276777d2eab9 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:42:02 +0200 Subject: [PATCH 43/50] refactor: remove unused serialPorts variable and instance of serialhub for cleaner code --- serial.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/serial.go b/serial.go index 07e0451c7..d198fd5f7 100644 --- a/serial.go +++ b/serial.go @@ -71,13 +71,6 @@ type SpPortItem struct { ProductID string } -// serialPorts contains the ports attached to the machine -var serialPorts SerialPortList - -var sh = serialhub{ - ports: make(map[*serport]bool), -} - // Register serial ports from the connections. func (sh *serialhub) Register(port *serport) { sh.mu.Lock() From effe50d1bfc22064218c62126348b7de718f420a Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:43:51 +0200 Subject: [PATCH 44/50] remove commented-out spHandlerClose call in FindPortByName for cleaner code --- serial.go | 1 - 1 file changed, 1 deletion(-) diff --git a/serial.go b/serial.go index d198fd5f7..f9930f9e6 100644 --- a/serial.go +++ b/serial.go @@ -96,7 +96,6 @@ func (sh *serialhub) FindPortByName(portname string) (*serport, bool) { for port := range sh.ports { if strings.EqualFold(port.portConf.Name, portname) { // we found our port - //spHandlerClose(port) return port, true } } From d68fc7a9e61296a484d71448156de0bb66503e69 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:45:18 +0200 Subject: [PATCH 45/50] fix: correct GitHub link in comment for clarity --- hub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub.go b/hub.go index 32c5f867c..66266046b 100755 --- a/hub.go +++ b/hub.go @@ -264,7 +264,7 @@ func (h *hub) checkCmd(m []byte) { } else if strings.HasPrefix(sl, "log") { go h.logAction(sl) } else if strings.HasPrefix(sl, "restart") { - // potentially, the sysStray dependencies can be removed https://gith.com/arduino/arduino-create-agent/issues/1013 + // potentially, the sysStray dependencies can be removed https://github.com/arduino/arduino-create-agent/issues/1013 log.Println("Received restart from the daemon. Why? Boh") h.systray.Restart() } else if strings.HasPrefix(sl, "exit") { From fd3b2d72af38ae5fe3651c2191250814bf4f665a Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:50:41 +0200 Subject: [PATCH 46/50] refactor: replace logWriter with ChanWriter for improved logging mechanism --- hub.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/hub.go b/hub.go index 66266046b..0a8f1091f 100755 --- a/hub.go +++ b/hub.go @@ -282,20 +282,21 @@ func (h *hub) checkCmd(m []byte) { } } -type logWriter struct { - onWrite func([]byte) +// ChanWriter is a simple io.Writer that sends data to a channel. +type ChanWriter struct { + Ch chan<- []byte +} + +func (u *ChanWriter) Write(p []byte) (n int, err error) { + u.Ch <- p + return len(p), nil } func (h *hub) logAction(sl string) { if strings.HasPrefix(sl, "log on") { *logDump = "on" - logWriter := logWriter{} - logWriter.onWrite = func(p []byte) { - h.broadcastSys <- p - } - - multiWriter := io.MultiWriter(&logWriter, os.Stderr) + multiWriter := io.MultiWriter(&ChanWriter{Ch: h.broadcastSys}, os.Stderr) log.SetOutput(multiWriter) } else if strings.HasPrefix(sl, "log off") { *logDump = "off" From 03705914766579754c28bddf5cc18c1f52819d03 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 15:54:38 +0200 Subject: [PATCH 47/50] refactor: move spErr, spWrite, and spClose functions from serial.go to hub.go for better organization --- hub.go | 58 ------------------------------------------------------- serial.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 58 deletions(-) diff --git a/hub.go b/hub.go index 0a8f1091f..48c1cb1af 100755 --- a/hub.go +++ b/hub.go @@ -307,11 +307,6 @@ func (h *hub) logAction(sl string) { } } -func (u *logWriter) Write(p []byte) (n int, err error) { - u.onWrite(p) - return len(p), nil -} - func (h *hub) memoryStats() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) @@ -339,56 +334,3 @@ func (h *hub) garbageCollection() { h.broadcastSys <- []byte("{\"gc\":\"done\"}") h.memoryStats() } - -func (h *hub) spErr(err string) { - h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") -} - -func (h *hub) spClose(portname string) { - if myport, ok := h.serialHub.FindPortByName(portname); ok { - h.broadcastSys <- []byte("Closing serial port " + portname) - myport.Close() - } else { - h.spErr("We could not find the serial port " + portname + " that you were trying to close.") - } -} - -func (h *hub) spWrite(arg string) { - // we will get a string of comXX asdf asdf asdf - //log.Println("Inside spWrite arg: " + arg) - arg = strings.TrimPrefix(arg, " ") - //log.Println("arg after trim: " + arg) - args := strings.SplitN(arg, " ", 3) - if len(args) != 3 { - errstr := "Could not parse send command: " + arg - //log.Println(errstr) - h.spErr(errstr) - return - } - bufferingMode := args[0] - portname := strings.Trim(args[1], " ") - data := args[2] - - //log.Println("The port to write to is:" + portname + "---") - //log.Println("The data is:" + data + "---") - - // See if we have this port open - port, ok := h.serialHub.FindPortByName(portname) - if !ok { - // we couldn't find the port, so send err - h.spErr("We could not find the serial port " + portname + " that you were trying to write to.") - return - } - - // see if bufferingMode is valid - switch bufferingMode { - case "send", "sendnobuf", "sendraw": - // valid buffering mode, go ahead - default: - h.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") - return - } - - // send it to the write channel - port.Write(data, bufferingMode) -} diff --git a/serial.go b/serial.go index f9930f9e6..8fd74dd72 100644 --- a/serial.go +++ b/serial.go @@ -266,3 +266,57 @@ func (sp *SerialPortList) getPortByName(portname string) *SpPortItem { } return nil } + +// FIXME: the spErr, spWrite, spClose should be moved to the 'hub.go' file +func (h *hub) spErr(err string) { + h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") +} + +func (h *hub) spClose(portname string) { + if myport, ok := h.serialHub.FindPortByName(portname); ok { + h.broadcastSys <- []byte("Closing serial port " + portname) + myport.Close() + } else { + h.spErr("We could not find the serial port " + portname + " that you were trying to close.") + } +} + +func (h *hub) spWrite(arg string) { + // we will get a string of comXX asdf asdf asdf + //log.Println("Inside spWrite arg: " + arg) + arg = strings.TrimPrefix(arg, " ") + //log.Println("arg after trim: " + arg) + args := strings.SplitN(arg, " ", 3) + if len(args) != 3 { + errstr := "Could not parse send command: " + arg + //log.Println(errstr) + h.spErr(errstr) + return + } + bufferingMode := args[0] + portname := strings.Trim(args[1], " ") + data := args[2] + + //log.Println("The port to write to is:" + portname + "---") + //log.Println("The data is:" + data + "---") + + // See if we have this port open + port, ok := h.serialHub.FindPortByName(portname) + if !ok { + // we couldn't find the port, so send err + h.spErr("We could not find the serial port " + portname + " that you were trying to write to.") + return + } + + // see if bufferingMode is valid + switch bufferingMode { + case "send", "sendnobuf", "sendraw": + // valid buffering mode, go ahead + default: + h.spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") + return + } + + // send it to the write channel + port.Write(data, bufferingMode) +} From a50aa86a1c29077743c59737de3ededb951c3486 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 16:00:09 +0200 Subject: [PATCH 48/50] refactor: remove logger from comment --- tools/tools.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/tools.go b/tools/tools.go index 0ad95763a..96265c2d2 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -33,14 +33,13 @@ import ( // // - *directory* contains the location where the tools are downloaded. // - *indexURL* contains the url where the tools description is contained. -// - *logger* is a StdLogger used for reporting debug and info messages // - *installed* contains a map[string]string of the tools installed and their exact location // // Usage: // You have to call the New() function passing it the required parameters: // // index = index.Init("https://downloads.arduino.cc/packages/package_index.json", dataDir) -// tools := tools.New(dataDir, index, logger) +// tools := tools.New(dataDir, index) // Tools will represent the installed tools type Tools struct { @@ -54,7 +53,6 @@ type Tools struct { // New will return a Tool object, allowing the caller to execute operations on it. // The New functions accept the directory to use to host the tools, // an index (used to download the tools), -// and a logger to log the operations func New(directory *paths.Path, index *index.Resource, signPubKey *rsa.PublicKey) *Tools { t := &Tools{ directory: directory, From 65218a666abf117e3a35bd4b0a5e473f0afdee1c Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 16:13:17 +0200 Subject: [PATCH 49/50] refactor: replace OnMessage with ChanWriter for improved message handling --- serialport.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/serialport.go b/serialport.go index 5c8ed5c55..9c46ac823 100755 --- a/serialport.go +++ b/serialport.go @@ -62,8 +62,8 @@ type serport struct { //bufferwatcher *BufferflowDummypause bufferwatcher Bufferflow - OnMessage func([]byte) - OnClose func(*serport) + ChanWriter ChanWriter + OnClose func(*serport) } // SpPortMessage is the serial port message @@ -92,7 +92,7 @@ func (p *serport) reader(buftype string) { if p.isClosing.Load() { strmsg := "Shutting down reader on " + p.portConf.Name log.Println(strmsg) - p.OnMessage([]byte(strmsg)) + p.ChanWriter.Write.Write([]byte(strmsg)) break } @@ -146,14 +146,14 @@ func (p *serport) reader(buftype string) { if err == io.EOF || err == io.ErrUnexpectedEOF { // hit end of file log.Println("Hit end of file on serial port") - p.OnMessage([]byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got EOF (End of File) on port which usually means another app other than Serial Port JSON Server is locking your port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")) + p.ChanWriter.Write([]byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got EOF (End of File) on port which usually means another app other than Serial Port JSON Server is locking your port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")) } if err != nil { log.Println(err) - p.OnMessage([]byte("Error reading on " + p.portConf.Name + " " + err.Error() + " Closing port.")) - p.OnMessage([]byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got error reading on port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")) + p.ChanWriter.Write([]byte("Error reading on " + p.portConf.Name + " " + err.Error() + " Closing port.")) + p.ChanWriter.Write([]byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got error reading on port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")) p.isClosingDueToError = true break } @@ -211,7 +211,7 @@ func (p *serport) writerBuffered() { } msgstr := "writerBuffered just got closed. make sure you make a new one. port:" + p.portConf.Name log.Println(msgstr) - p.OnMessage([]byte(msgstr)) + p.ChanWriter.Write([]byte(msgstr)) } // this method runs as its own thread because it's instantiated @@ -232,13 +232,13 @@ func (p *serport) writerNoBuf() { if err != nil { errstr := "Error writing to " + p.portConf.Name + " " + err.Error() + " Closing port." log.Print(errstr) - p.OnMessage([]byte(errstr)) + p.ChanWriter.Write([]byte(errstr)) break } } msgstr := "Shutting down writer on " + p.portConf.Name log.Println(msgstr) - p.OnMessage([]byte(msgstr)) + p.ChanWriter.Write([]byte(msgstr)) p.portIo.Close() @@ -279,7 +279,7 @@ func (p *serport) writerRaw() { } msgstr := "writerRaw just got closed. make sure you make a new one. port:" + p.portConf.Name log.Println(msgstr) - p.OnMessage([]byte(msgstr)) + p.ChanWriter.Write([]byte(msgstr)) } // FIXME: move this into the `hub.go` file @@ -323,11 +323,9 @@ func (h *hub) spHandlerOpen(portname string, baud int, buftype string) { portIo: sp, portName: portname, BufferType: buftype, + ChanWriter: ChanWriter{h.broadcastSys}, } - p.OnMessage = func(msg []byte) { - h.broadcastSys <- msg - } p.OnClose = func(port *serport) { h.serialPortList.MarkPortAsClosed(p.portName) h.serialPortList.List() From 9cbe49e00fed6e17bed4e67ff18fcb08df1a5971 Mon Sep 17 00:00:00 2001 From: Davide N Date: Wed, 16 Apr 2025 17:00:42 +0200 Subject: [PATCH 50/50] fix tests --- serialport.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serialport.go b/serialport.go index 9c46ac823..614111c71 100755 --- a/serialport.go +++ b/serialport.go @@ -92,7 +92,7 @@ func (p *serport) reader(buftype string) { if p.isClosing.Load() { strmsg := "Shutting down reader on " + p.portConf.Name log.Println(strmsg) - p.ChanWriter.Write.Write([]byte(strmsg)) + p.ChanWriter.Write([]byte(strmsg)) break }