cmd/syncthing, gui, lib/config, lib/upgrade: Add option to upgrade to pre-releases

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3939
This commit is contained in:
Jakob Borg 2017-01-27 12:17:06 +00:00 committed by Audrius Butkevicius
parent e03be9158b
commit 35e87e23fd
9 changed files with 78 additions and 51 deletions

View File

@ -1042,7 +1042,8 @@ func (s *apiService) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500) http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
return return
} }
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version) opts := s.cfg.Options()
rel, err := upgrade.LatestRelease(opts.ReleasesURL, Version, opts.UpgradeToPreReleases)
if err != nil { if err != nil {
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
@ -1083,7 +1084,8 @@ func (s *apiService) getLang(w http.ResponseWriter, r *http.Request) {
} }
func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) { func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version) opts := s.cfg.Options()
rel, err := upgrade.LatestRelease(opts.ReleasesURL, Version, opts.UpgradeToPreReleases)
if err != nil { if err != nil {
l.Warnln("getting latest release:", err) l.Warnln("getting latest release:", err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)

View File

@ -481,8 +481,8 @@ func debugFacilities() string {
func checkUpgrade() upgrade.Release { func checkUpgrade() upgrade.Release {
cfg, _ := loadConfig() cfg, _ := loadConfig()
releasesURL := cfg.Options().ReleasesURL opts := cfg.Options()
release, err := upgrade.LatestRelease(releasesURL, Version) release, err := upgrade.LatestRelease(opts.ReleasesURL, Version, opts.UpgradeToPreReleases)
if err != nil { if err != nil {
l.Fatalln("Upgrade:", err) l.Fatalln("Upgrade:", err)
} }
@ -1158,8 +1158,8 @@ func autoUpgrade(cfg *config.Wrapper) {
l.Infof("Connected to device %s with a newer version (current %q < remote %q). Checking for upgrades.", data["id"], Version, data["clientVersion"]) l.Infof("Connected to device %s with a newer version (current %q < remote %q). Checking for upgrades.", data["id"], Version, data["clientVersion"])
case <-timer.C: case <-timer.C:
} }
opts := cfg.Options()
rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version) rel, err := upgrade.LatestRelease(opts.ReleasesURL, Version, opts.UpgradeToPreReleases)
if err == upgrade.ErrUpgradeUnsupported { if err == upgrade.ErrUpgradeUnsupported {
events.Default.Unsubscribe(sub) events.Default.Unsubscribe(sub)
return return

View File

@ -26,6 +26,7 @@
"Be careful!": "Be careful!", "Be careful!": "Be careful!",
"Bugs": "Bugs", "Bugs": "Bugs",
"CPU Utilization": "CPU Utilization", "CPU Utilization": "CPU Utilization",
"Candidate releases": "Candidate releases",
"Changelog": "Changelog", "Changelog": "Changelog",
"Clean out after": "Clean out after", "Clean out after": "Clean out after",
"Close": "Close", "Close": "Close",
@ -81,6 +82,7 @@
"GUI Authentication Password": "GUI Authentication Password", "GUI Authentication Password": "GUI Authentication Password",
"GUI Authentication User": "GUI Authentication User", "GUI Authentication User": "GUI Authentication User",
"GUI Listen Addresses": "GUI Listen Addresses", "GUI Listen Addresses": "GUI Listen Addresses",
"GUI Theme": "GUI Theme",
"Generate": "Generate", "Generate": "Generate",
"Global Changes": "Global Changes", "Global Changes": "Global Changes",
"Global Discovery": "Global Discovery", "Global Discovery": "Global Discovery",
@ -120,6 +122,7 @@
"Newest First": "Newest First", "Newest First": "Newest First",
"No": "No", "No": "No",
"No File Versioning": "No File Versioning", "No File Versioning": "No File Versioning",
"No upgrades": "No upgrades",
"Normal": "Normal", "Normal": "Normal",
"Notice": "Notice", "Notice": "Notice",
"OK": "OK", "OK": "OK",
@ -181,6 +184,7 @@
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)", "Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
"Smallest First": "Smallest First", "Smallest First": "Smallest First",
"Source Code": "Source Code", "Source Code": "Source Code",
"Stable releases only": "Stable releases only",
"Staggered File Versioning": "Staggered File Versioning", "Staggered File Versioning": "Staggered File Versioning",
"Start Browser": "Start Browser", "Start Browser": "Start Browser",
"Statistics": "Statistics", "Statistics": "Statistics",
@ -234,6 +238,7 @@
"Upgrading": "Upgrading", "Upgrading": "Upgrading",
"Upload Rate": "Upload Rate", "Upload Rate": "Upload Rate",
"Uptime": "Uptime", "Uptime": "Uptime",
"Usage reporting is always enabled for candidate releases.": "Usage reporting is always enabled for candidate releases.",
"Use HTTPS for GUI": "Use HTTPS for GUI", "Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Version", "Version": "Version",
"Versions Path": "Versions Path", "Versions Path": "Versions Path",

View File

@ -1004,7 +1004,13 @@ angular.module('syncthing.core')
$scope.tmpOptions = angular.copy($scope.config.options); $scope.tmpOptions = angular.copy($scope.config.options);
$scope.tmpOptions.urEnabled = ($scope.tmpOptions.urAccepted > 0); $scope.tmpOptions.urEnabled = ($scope.tmpOptions.urAccepted > 0);
$scope.tmpOptions.deviceName = $scope.thisDevice().name; $scope.tmpOptions.deviceName = $scope.thisDevice().name;
$scope.tmpOptions.autoUpgradeEnabled = ($scope.tmpOptions.autoUpgradeIntervalH > 0); $scope.tmpOptions.upgrades = "none";
if ($scope.tmpOptions.autoUpgradeIntervalH > 0) {
$scope.tmpOptions.upgrades = "stable";
}
if ($scope.tmpOptions.upgradeToPreReleases) {
$scope.tmpOptions.upgrades = "candidate";
}
$scope.tmpGUI = angular.copy($scope.config.gui); $scope.tmpGUI = angular.copy($scope.config.gui);
$('#settings').modal(); $('#settings').modal();
}; };
@ -1028,6 +1034,20 @@ angular.module('syncthing.core')
var changed = !angular.equals($scope.config.options, $scope.tmpOptions) || !angular.equals($scope.config.gui, $scope.tmpGUI); var changed = !angular.equals($scope.config.options, $scope.tmpOptions) || !angular.equals($scope.config.gui, $scope.tmpGUI);
var themeChanged = $scope.config.gui.theme !== $scope.tmpGUI.theme; var themeChanged = $scope.config.gui.theme !== $scope.tmpGUI.theme;
if (changed) { if (changed) {
// Check if auto-upgrade has been enabled or disabled. This
// also has an effect on usage reporting, so do the check
// for that later.
if ($scope.tmpOptions.upgrades == "candidate") {
$scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
$scope.tmpOptions.upgradeToPreReleases = true;
$scope.tmpOptions.urEnabled = true;
} else if ($scope.tmpOptions.upgrades == "stable") {
$scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
$scope.tmpOptions.upgradeToPreReleases = false;
} else {
$scope.tmpOptions.autoUpgradeIntervalH = 0;
}
// Check if usage reporting has been enabled or disabled // Check if usage reporting has been enabled or disabled
if ($scope.tmpOptions.urEnabled && $scope.tmpOptions.urAccepted <= 0) { if ($scope.tmpOptions.urEnabled && $scope.tmpOptions.urAccepted <= 0) {
$scope.tmpOptions.urAccepted = 1000; $scope.tmpOptions.urAccepted = 1000;
@ -1035,13 +1055,6 @@ angular.module('syncthing.core')
$scope.tmpOptions.urAccepted = -1; $scope.tmpOptions.urAccepted = -1;
} }
// Check if auto-upgrade has been enabled or disabled
if ($scope.tmpOptions.autoUpgradeEnabled) {
$scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
} else {
$scope.tmpOptions.autoUpgradeIntervalH = 0;
}
// Check if protocol will need to be changed on restart // Check if protocol will need to be changed on restart
if ($scope.config.gui.useTLS !== $scope.tmpGUI.useTLS) { if ($scope.config.gui.useTLS !== $scope.tmpGUI.useTLS) {
$scope.protocolChanged = true; $scope.protocolChanged = true;

View File

@ -107,19 +107,24 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group" ng-if="upgradeInfo">
<label translate>Automatic upgrades</label>&emsp;<a href="https://docs.syncthing.net/users/releases.html" target="_blank"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="tmpOptions.upgrades">
<option value="none" translate>No upgrades</option>
<option value="stable" translate>Stable releases only</option>
<option value="candidate" translate>Candidate releases</option>
</select>
</div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox" ng-if="tmpOptions.upgrades != 'candidate'">
<label> <label>
<input id="UREnabled" type="checkbox" ng-model="tmpOptions.urEnabled"> <span translate>Anonymous Usage Reporting</span> (<a href="" translate data-toggle="modal" data-target="#urPreview">Preview</a>) <input id="UREnabled" type="checkbox" ng-model="tmpOptions.urEnabled"> <span translate>Anonymous Usage Reporting</span> (<a href="" translate data-toggle="modal" data-target="#urPreview">Preview</a>)
</label> </label>
</div> </div>
</div> <p class="help-block" ng-if="tmpOptions.upgrades == 'candidate'">
<div class="form-group" ng-if="upgradeInfo"> <span translate>Usage reporting is always enabled for candidate releases.</span> (<a href="" translate data-toggle="modal" data-target="#urPreview">Preview</a>)
<div class="checkbox"> </p>
<label>
<input id="AutoUpgradeEnabled" type="checkbox" ng-model="tmpOptions.autoUpgradeEnabled"> <span translate>Automatic upgrades</span>
</label>
</div>
</div> </div>
<hr /> <hr />
@ -133,7 +138,7 @@
</div> </div>
<div class="form-group" ng-if="themes.length > 1"> <div class="form-group" ng-if="themes.length > 1">
<label>GUI Theme</label> <label translate>GUI Theme</label>
<select class="form-control" ng-model="tmpGUI.theme"> <select class="form-control" ng-model="tmpGUI.theme">
<option ng-repeat="theme in themes.sort()" value="{{ theme }}"> <option ng-repeat="theme in themes.sort()" value="{{ theme }}">
{{ themeName(theme) }} {{ themeName(theme) }}

View File

@ -30,6 +30,7 @@ type OptionsConfiguration struct {
URInitialDelayS int `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"` URInitialDelayS int `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"`
RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"` RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"`
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12"` // 0 for off AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12"` // 0 for off
UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases"` // when auto upgrades are enabled
KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false"` CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false"`
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"` ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`

View File

@ -84,10 +84,10 @@ func insecureGet(url, version string) (*http.Response, error) {
return insecureHTTP.Do(req) return insecureHTTP.Do(req)
} }
// FetchLatestReleases returns the latest releases, including prereleases or // FetchLatestReleases returns the latest releases. The "current" parameter
// not depending on the argument // is used for setting the User-Agent only.
func FetchLatestReleases(releasesURL, version string) []Release { func FetchLatestReleases(releasesURL, current string) []Release {
resp, err := insecureGet(releasesURL, version) resp, err := insecureGet(releasesURL, current)
if err != nil { if err != nil {
l.Infoln("Couldn't fetch release information:", err) l.Infoln("Couldn't fetch release information:", err)
return nil return nil
@ -119,24 +119,22 @@ func (s SortByRelease) Less(i, j int) bool {
return CompareVersions(s[i].Tag, s[j].Tag) > 0 return CompareVersions(s[i].Tag, s[j].Tag) > 0
} }
func LatestRelease(releasesURL, version string) (Release, error) { func LatestRelease(releasesURL, current string, upgradeToPreReleases bool) (Release, error) {
rels := FetchLatestReleases(releasesURL, version) rels := FetchLatestReleases(releasesURL, current)
return SelectLatestRelease(version, rels) return SelectLatestRelease(rels, current, upgradeToPreReleases)
} }
func SelectLatestRelease(version string, rels []Release) (Release, error) { func SelectLatestRelease(rels []Release, current string, upgradeToPreReleases bool) (Release, error) {
if len(rels) == 0 { if len(rels) == 0 {
return Release{}, ErrNoVersionToSelect return Release{}, ErrNoVersionToSelect
} }
// Sort the releases, lowest version number first // Sort the releases, lowest version number first
sort.Sort(sort.Reverse(SortByRelease(rels))) sort.Sort(sort.Reverse(SortByRelease(rels)))
// Check for a beta build
beta := strings.Contains(version, "-")
var selected Release var selected Release
for _, rel := range rels { for _, rel := range rels {
switch CompareVersions(rel.Tag, version) { switch CompareVersions(rel.Tag, current) {
case Older, MajorOlder: case Older, MajorOlder:
// This is older than what we're already running // This is older than what we're already running
continue continue
@ -145,7 +143,7 @@ func SelectLatestRelease(version string, rels []Release) (Release, error) {
// We've found a new major version. That's fine, but if we've // We've found a new major version. That's fine, but if we've
// already found a minor upgrade that is acceptable we should go // already found a minor upgrade that is acceptable we should go
// with that one first and then revisit in the future. // with that one first and then revisit in the future.
if selected.Tag != "" && CompareVersions(selected.Tag, version) == Newer { if selected.Tag != "" && CompareVersions(selected.Tag, current) == Newer {
return selected, nil return selected, nil
} }
// else it may be viable, do the needful below // else it may be viable, do the needful below
@ -154,7 +152,7 @@ func SelectLatestRelease(version string, rels []Release) (Release, error) {
// New minor release, do the usual processing // New minor release, do the usual processing
} }
if rel.Prerelease && !beta { if rel.Prerelease && !upgradeToPreReleases {
continue continue
} }
for _, asset := range rel.Assets { for _, asset := range rel.Assets {

View File

@ -58,7 +58,7 @@ func TestCompareVersions(t *testing.T) {
} }
func TestErrorRelease(t *testing.T) { func TestErrorRelease(t *testing.T) {
_, err := SelectLatestRelease("v0.11.0-beta", nil) _, err := SelectLatestRelease(nil, "v0.11.0-beta", false)
if err == nil { if err == nil {
t.Error("Should return an error when no release were available") t.Error("Should return an error when no release were available")
} }
@ -66,22 +66,25 @@ func TestErrorRelease(t *testing.T) {
func TestSelectedRelease(t *testing.T) { func TestSelectedRelease(t *testing.T) {
testcases := []struct { testcases := []struct {
current string current string
candidates []string upgradeToPre bool
selected string candidates []string
selected string
}{ }{
// Within the same "major" (minor, in this case) select the newest // Within the same "major" (minor, in this case) select the newest
{"v0.12.24", []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.12.26"}, "v0.12.26"}, {"v0.12.24", false, []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.12.26"}, "v0.12.26"},
// Do no select beta versions when we are not a beta // Do no select beta versions when we are not allowed to
{"v0.12.24", []string{"v0.12.26", "v0.12.27-beta.42"}, "v0.12.26"}, {"v0.12.24", false, []string{"v0.12.26", "v0.12.27-beta.42"}, "v0.12.26"},
// Do select beta versions when we are a beta {"v0.12.24-beta.0", false, []string{"v0.12.26", "v0.12.27-beta.42"}, "v0.12.26"},
{"v0.12.24-beta.0", []string{"v0.12.26", "v0.12.27-beta.42"}, "v0.12.27-beta.42"}, // Do select beta versions when we can
{"v0.12.24", true, []string{"v0.12.26", "v0.12.27-beta.42"}, "v0.12.27-beta.42"},
{"v0.12.24-beta.0", true, []string{"v0.12.26", "v0.12.27-beta.42"}, "v0.12.27-beta.42"},
// Select the best within the current major when there is a minor upgrade available // Select the best within the current major when there is a minor upgrade available
{"v0.12.24", []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.13.0"}, "v0.12.25"}, {"v0.12.24", false, []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.13.0"}, "v0.12.25"},
{"v1.12.24", []string{"v1.12.23", "v1.12.24", "v1.14.2", "v2.0.0"}, "v1.14.2"}, {"v1.12.24", false, []string{"v1.12.23", "v1.12.24", "v1.14.2", "v2.0.0"}, "v1.14.2"},
// Select the next major when we are at the best minor // Select the next major when we are at the best minor
{"v0.12.25", []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.13.0"}, "v0.13.0"}, {"v0.12.25", true, []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.13.0"}, "v0.13.0"},
{"v1.14.2", []string{"v0.12.23", "v0.12.24", "v1.14.2", "v2.0.0"}, "v2.0.0"}, {"v1.14.2", true, []string{"v0.12.23", "v0.12.24", "v1.14.2", "v2.0.0"}, "v2.0.0"},
} }
for i, tc := range testcases { for i, tc := range testcases {
@ -99,7 +102,7 @@ func TestSelectedRelease(t *testing.T) {
} }
// Check the selection // Check the selection
sel, err := SelectLatestRelease(tc.current, rels) sel, err := SelectLatestRelease(rels, tc.current, tc.upgradeToPre)
if err != nil { if err != nil {
t.Fatal("Unexpected error:", err) t.Fatal("Unexpected error:", err)
} }

View File

@ -18,6 +18,6 @@ func upgradeToURL(archiveName, binary, url string) error {
return ErrUpgradeUnsupported return ErrUpgradeUnsupported
} }
func LatestRelease(releasesURL, version string) (Release, error) { func LatestRelease(releasesURL, current string, upgradeToPreRelease bool) (Release, error) {
return Release{}, ErrUpgradeUnsupported return Release{}, ErrUpgradeUnsupported
} }