From 2cb8e3a113c7a540ac8d08f933681772a3bcbfba Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Wed, 4 Sep 2024 16:10:09 +0200 Subject: [PATCH] Cache scan results. --- cmd/jag/commands/device.go | 93 +++++++++++++++++++++++++++++++--- cmd/jag/commands/scan.go | 16 ++++-- cmd/jag/directory/directory.go | 15 ++++++ 3 files changed, 115 insertions(+), 9 deletions(-) diff --git a/cmd/jag/commands/device.go b/cmd/jag/commands/device.go index b4abcc3..c95a399 100644 --- a/cmd/jag/commands/device.go +++ b/cmd/jag/commands/device.go @@ -9,12 +9,14 @@ import ( "fmt" "io" "os" + "path/filepath" "strconv" "time" "unicode/utf8" "github.com/spf13/viper" "github.com/toitlang/jaguar/cmd/jag/directory" + "gopkg.in/yaml.v2" ) const ( @@ -75,6 +77,55 @@ func readDeviceConfig(cfg *viper.Viper) (Device, error) { } } +func readYamlDevices(path string) ([]Device, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var serializable []map[string]interface{} + if err := yaml.Unmarshal(data, &serializable); err != nil { + return nil, err + } + var devices []Device + for _, m := range serializable { + if kind, ok := m["kind"].(string); ok { + switch kind { + case "ble": + d, err := NewDeviceBleFromJson(m, "") + if err != nil { + return nil, err + } + devices = append(devices, d) + case "network": + d, err := NewDeviceNetworkFromJson(m) + if err != nil { + return nil, err + } + devices = append(devices, d) + default: + return nil, fmt.Errorf("unknown device kind: %s", kind) + } + } + } + return devices, nil +} + +func writeYamlDevices(path string, devices []Device) error { + var serializable []interface{} + for _, d := range devices { + serializable = append(serializable, d.ToSerializable()) + } + data, err := yaml.Marshal(serializable) + if err != nil { + return err + } + // Create the directory if it doesn't exist. + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return err + } + return os.WriteFile(path, data, 0644) +} + type Devices struct { Devices []Device } @@ -172,15 +223,18 @@ func GetDevice(ctx context.Context, sdk *SDK, checkPing bool, deviceSelect devic if err != nil { return nil, err } - manualPick := deviceSelect != nil - if deviceCfg.IsSet("device") && !manualPick { + + // Set the manualPick flag before we potentially change the 'deviceSelect' after looking + // at the device-config file. + // In other words: allow to manually pick a device if the device-selection was not set, + // or comes from the device-config file. + manualPick := deviceSelect == nil + + if deviceCfg.IsSet("device") && deviceSelect == nil { d, err := readDeviceConfig(deviceCfg) if err != nil { return nil, err } - if err != nil { - return nil, err - } if checkPing { if d.Ping(ctx, sdk) { return d, nil @@ -192,11 +246,38 @@ func GetDevice(ctx context.Context, sdk *SDK, checkPing bool, deviceSelect devic } } + if deviceSelect != nil { + // Try to find the device in the cached scan if there is one. + scanCache, err := directory.GetScanCachePath() + if err != nil { + return nil, err + } + if _, err := os.Stat(scanCache); err == nil { + devices, err := readYamlDevices(scanCache) + if err == nil { + // We ignore errors, and just handle it as if there wasn't any scan cache. + for _, d := range devices { + if deviceSelect.Match(d) { + if checkPing { + if d.Ping(ctx, sdk) { + return d, nil + } + fmt.Printf("Failed to ping '%s'.\n", d.Name()) + } else { + return d, nil + } + } + } + } + } + } + d, autoSelected, err := scanAndPickDevice(ctx, 0*time.Second, scanPort, deviceSelect, manualPick) if err != nil { return nil, err } - if !manualPick { + if manualPick { + // The user was allowed to manually pick a device. if autoSelected { fmt.Printf("Found device '%s' again\n", d.Name()) } diff --git a/cmd/jag/commands/scan.go b/cmd/jag/commands/scan.go index 9ac1925..11c6dfd 100644 --- a/cmd/jag/commands/scan.go +++ b/cmd/jag/commands/scan.go @@ -32,7 +32,8 @@ func ScanCmd() *cobra.Command { "Unless 'device' is an address, listen for UDP packets broadcasted by the devices.\n" + "In that case you need to be on the same network as the device.\n" + "If a device selection is given, automatically select that device.\n" + - "If the device selection is an address, connect to it using TCP.", + "If the device selection is a BLE address, connect to it using BLE.\n" + + "If the device selection is an IP address, connect to it using TCP.", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() @@ -71,7 +72,7 @@ func ScanCmd() *cobra.Command { return outputter.Encode(Devices{devices}) } - device, _, err := scanAndPickDevice(ctx, timeout, port, autoSelect, false) + device, _, err := scanAndPickDevice(ctx, timeout, port, autoSelect, true) if err != nil { return err } @@ -229,6 +230,15 @@ func scan(ctx context.Context, scanTimeout time.Duration, port uint, autoSelect devices = append(devices, bleDevices...) } + scanCachePath, err := directory.GetScanCachePath() + if err != nil { + return nil, err + } + err = writeYamlDevices(scanCachePath, devices) + if err != nil { + fmt.Printf("Failed to write scan cache: %v\n", err) + } + return devices, nil } @@ -244,7 +254,7 @@ func scanAndPickDevice(ctx context.Context, scanTimeout time.Duration, port uint return d, true, nil } } - if manualPick { + if !manualPick { return nil, false, fmt.Errorf("couldn't find %s", autoSelect) } } diff --git a/cmd/jag/directory/directory.go b/cmd/jag/directory/directory.go index c3afb88..768177c 100644 --- a/cmd/jag/directory/directory.go +++ b/cmd/jag/directory/directory.go @@ -18,6 +18,7 @@ const ( UserConfigPathEnv = "JAG_USER_CONFIG_PATH" DeviceConfigPathEnv = "JAG_DEVICE_CONFIG_PATH" SnapshotCachePathEnv = "JAG_SNAPSHOT_CACHE_PATH" + ScanCachePathEnv = "JAG_SCAN_CACHE_PATH" configFile = ".jaguar" ToitUserConfigPathEnv = "TOIT_CONFIG_FILE" @@ -105,6 +106,20 @@ func GetDeviceConfigPath() (string, error) { return filepath.Join(configDir, "jaguar", "device.yaml"), nil } +func GetScanCachePath() (string, error) { + path, ok := os.LookupEnv(ScanCachePathEnv) + if ok { + return path, nil + } + + cacheDir, err := getCacheDirPath() + if err != nil { + return "", err + } + + return filepath.Join(cacheDir, "jaguar", "scan.yaml"), nil +} + func GetSnapshotsPaths() ([]string, error) { paths := []string{} path, ok := os.LookupEnv(SnapshotCachePathEnv)