diff --git a/cli/core/search.go b/cli/core/search.go index 7a338d482ae..1ee2f9caeb0 100644 --- a/cli/core/search.go +++ b/cli/core/search.go @@ -18,17 +18,23 @@ package core import ( "context" "os" + "path" "sort" "strings" + "time" + "github.com/arduino/arduino-cli/arduino/utils" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands/core" + "github.com/arduino/arduino-cli/configuration" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/arduino-cli/table" + "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -51,6 +57,9 @@ func initSearchCommand() *cobra.Command { return searchCommand } +// indexUpdateInterval specifies the time threshold over which indexes are updated +const indexUpdateInterval = "24h" + func runSearchCommand(cmd *cobra.Command, args []string) { inst, err := instance.CreateInstance() if err != nil { @@ -58,12 +67,14 @@ func runSearchCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - _, err = commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{ - Instance: inst, - }, output.ProgressBar()) - if err != nil { - feedback.Errorf("Error updating index: %v", err) - os.Exit(errorcodes.ErrGeneric) + if indexesNeedUpdating(indexUpdateInterval) { + _, err = commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{ + Instance: inst, + }, output.ProgressBar()) + if err != nil { + feedback.Errorf("Error updating index: %v", err) + os.Exit(errorcodes.ErrGeneric) + } } arguments := strings.ToLower(strings.Join(args, " ")) @@ -107,3 +118,49 @@ func (sr searchResults) String() string { } return "No platforms matching your search." } + +// indexesNeedUpdating returns whether one or more index files need updating. +// A duration string must be provided to calculate the time threshold +// used to update the indexes, if the duration is not valid a default +// of 24 hours is used. +// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +func indexesNeedUpdating(duration string) bool { + indexpath := paths.New(configuration.Settings.GetString("directories.Data")) + + now := time.Now() + modTimeThreshold, err := time.ParseDuration(duration) + // Not the most elegant way of handling this error + // but it does its job + if err != nil { + modTimeThreshold, _ = time.ParseDuration("24h") + } + + urls := []string{globals.DefaultIndexURL} + urls = append(urls, configuration.Settings.GetStringSlice("board_manager.additional_urls")...) + for _, u := range urls { + URL, err := utils.URLParse(u) + if err != nil { + continue + } + + if URL.Scheme == "file" { + // No need to update local files + continue + } + + coreIndexPath := indexpath.Join(path.Base(URL.Path)) + if coreIndexPath.NotExist() { + return true + } + + info, err := coreIndexPath.Stat() + if err != nil { + return true + } + + if now.After(info.ModTime().Add(modTimeThreshold)) { + return true + } + } + return false +} diff --git a/test/test_core.py b/test/test_core.py index 708a556ea07..72246600c44 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -13,6 +13,8 @@ # software without disclosing the source code of your own applications. To purchase # a commercial license, send an email to license@arduino.cc. import os +import datetime +import time import platform import pytest import simplejson as json @@ -123,9 +125,7 @@ def test_core_search_no_args(run_command, httpserver): assert result.ok num_platforms = 0 lines = [l.strip().split() for l in result.stdout.strip().splitlines()] - # Index update output and the header are printed on the first lines - assert ["Updating", "index:", "package_index.json", "downloaded"] in lines - assert ["Updating", "index:", "package_index.json.sig", "downloaded"] in lines + # The header is printed on the first lines assert ["test:x86", "2.0.0", "test_core"] in lines header_index = lines.index(["ID", "Version", "Name"]) # We use black to format and flake8 to lint .py files but they disagree on certain @@ -147,9 +147,7 @@ def test_core_search_no_args(run_command, httpserver): assert result.ok num_platforms = 0 lines = [l.strip().split() for l in result.stdout.strip().splitlines()] - # Index update output and the header are printed on the first lines - assert ["Updating", "index:", "package_index.json", "downloaded"] in lines - assert ["Updating", "index:", "package_index.json.sig", "downloaded"] in lines + # The header is printed on the first lines assert ["test:x86", "2.0.0", "test_core"] in lines header_index = lines.index(["ID", "Version", "Name"]) # We use black to format and flake8 to lint .py files but they disagree on certain @@ -507,3 +505,28 @@ def test_core_list_with_installed_json(run_command, data_dir): # platform.txt, turns out that this core has different names used in different files # thus the change. assert mapped["adafruit:avr"]["name"] == "Adafruit Boards" + + +def test_core_search_update_index_delay(run_command, data_dir): + assert run_command("update") + + # Verifies index update is not run + res = run_command("core search") + assert res.ok + assert "Updating index" not in res.stdout + + # Change edit time of package index file + index_file = Path(data_dir, "package_index.json") + date = datetime.datetime.now() - datetime.timedelta(hours=25) + mod_time = time.mktime(date.timetuple()) + os.utime(index_file, (mod_time, mod_time)) + + # Verifies index update is run + res = run_command("core search") + assert res.ok + assert "Updating index" in res.stdout + + # Verifies index update is not run again + res = run_command("core search") + assert res.ok + assert "Updating index" not in res.stdout