瀏覽代碼

all: Add configurable defaults (fixes #4224, fixes #6086) (#7131)

Simon Frei 4 年之前
父節點
當前提交
ffc14a77c6
共有 41 個文件被更改,包括 1708 次插入1046 次删除
  1. 1 0
      go.mod
  2. 2 2
      gui/default/index.html
  3. 230 149
      gui/default/syncthing/core/syncthingController.js
  4. 6 6
      gui/default/syncthing/device/editDeviceModalView.html
  5. 37 35
      gui/default/syncthing/folder/editFolderModalView.html
  6. 9 7
      gui/default/syncthing/settings/settingsModalView.html
  7. 2 2
      gui/default/untrusted/index.html
  8. 235 153
      gui/default/untrusted/syncthing/core/syncthingController.js
  9. 6 6
      gui/default/untrusted/syncthing/device/editDeviceModalView.html
  10. 37 35
      gui/default/untrusted/syncthing/folder/editFolderModalView.html
  11. 2 0
      lib/api/api.go
  12. 45 9
      lib/api/confighandler.go
  13. 16 1
      lib/api/mocked_config_test.go
  14. 19 2
      lib/config/config.go
  15. 302 37
      lib/config/config.pb.go
  16. 109 46
      lib/config/config_test.go
  17. 0 18
      lib/config/deviceconfiguration.go
  18. 68 67
      lib/config/deviceconfiguration.pb.go
  19. 0 15
      lib/config/folderconfiguration.go
  20. 133 132
      lib/config/folderconfiguration.pb.go
  21. 6 0
      lib/config/migrations.go
  22. 1 1
      lib/config/observed.pb.go
  23. 259 258
      lib/config/optionsconfiguration.pb.go
  24. 42 2
      lib/config/testdata/overridenvalues.xml
  25. 1 1
      lib/config/versioningconfiguration.go
  26. 14 0
      lib/config/wrapper.go
  27. 13 6
      lib/connections/limiter_test.go
  28. 1 1
      lib/db/structs.pb.go
  29. 1 2
      lib/model/folder_sendrecv_test.go
  30. 27 10
      lib/model/model.go
  31. 6 6
      lib/model/model_test.go
  32. 17 7
      lib/model/testutils_test.go
  33. 6 1
      lib/syncthing/utils.go
  34. 2 1
      lib/ur/usage_report.go
  35. 1 0
      proto/ext.proto
  36. 32 21
      proto/ext/ext.pb.go
  37. 6 0
      proto/lib/config/config.proto
  38. 2 2
      proto/lib/config/deviceconfiguration.proto
  39. 4 4
      proto/lib/config/folderconfiguration.proto
  40. 1 1
      proto/lib/config/optionsconfiguration.proto
  41. 7 0
      proto/scripts/protoc_plugin.go

+ 1 - 0
go.mod

@@ -17,6 +17,7 @@ require (
 	github.com/gobwas/glob v0.2.3
 	github.com/gogo/protobuf v1.3.1
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
+	github.com/golang/protobuf v1.4.3
 	github.com/greatroar/blobloom v0.5.0
 	github.com/hashicorp/golang-lru v0.5.1
 	github.com/jackpal/gateway v1.0.6

+ 2 - 2
gui/default/index.html

@@ -554,7 +554,7 @@
                     <button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-disabled="['idle', 'stopped', 'unshared', 'outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) < 0">
                       <span class="fas fa-refresh"></span>&nbsp;<span translate>Rescan</span>
                     </button>
-                    <button type="button" class="btn btn-sm btn-default" ng-click="editFolder(folder)">
+                    <button type="button" class="btn btn-sm btn-default" ng-click="editFolderExisting(folder)">
                       <span class="fas fa-pencil-alt"></span>&nbsp;<span translate>Edit</span>
                     </button>
                   </span>
@@ -821,7 +821,7 @@
                     <button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
                       <span class="fas fa-play"></span>&nbsp;<span translate>Resume</span>
                     </button>
-                    <button type="button" class="btn btn-sm btn-default" ng-click="editDevice(deviceCfg)">
+                    <button type="button" class="btn btn-sm btn-default" ng-click="editDeviceExisting(deviceCfg)">
                       <span class="fas fa-pencil-alt"></span>&nbsp;<span translate>Edit</span>
                     </button>
                   </span>

+ 230 - 149
gui/default/syncthing/core/syncthingController.js

@@ -52,6 +52,7 @@ angular.module('syncthing.core')
         $scope.metricRates = false;
         $scope.folderPathErrors = {};
         $scope.currentFolder = {};
+        $scope.currentDevice = {};
         $scope.ignores = {
             text: '',
             error: null,
@@ -63,17 +64,8 @@ angular.module('syncthing.core')
             $scope.metricRates = (window.localStorage["metricRates"] == "true");
         } catch (exception) { }
 
-        $scope.folderDefaults = {
-            devices: [],
-            type: "sendreceive",
-            rescanIntervalS: 3600,
-            fsWatcherDelayS: 10,
-            fsWatcherEnabled: true,
-            minDiskFree: { value: 1, unit: "%" },
-            maxConflicts: 10,
-            fsync: true,
-            order: "random",
-            fileVersioningSelector: "none",
+        $scope.versioningDefaults = {
+            selector: "none",
             trashcanClean: 0,
             versioningCleanupIntervalS: 3600,
             simpleKeep: 5,
@@ -81,8 +73,6 @@ angular.module('syncthing.core')
             staggeredCleanInterval: 3600,
             staggeredVersionsPath: "",
             externalCommand: "",
-            autoNormalize: true,
-            path: "",
         };
 
         $scope.localStateTotal = {
@@ -727,7 +717,7 @@ angular.module('syncthing.core')
         }
 
         function shouldSetDefaultFolderPath() {
-            return $scope.config.options && $scope.config.options.defaultFolderPath && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine
+            return $scope.config.defaults.folder.path && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine && !$scope.editingDefaults;
         }
 
         function resetRemoteNeed() {
@@ -769,8 +759,23 @@ angular.module('syncthing.core')
             $scope.currentSharing.shared = [];
             $scope.currentSharing.unrelated = [];
             $scope.currentSharing.selected = {};
+            if (editing === 'folder') {
+                initShareEditingFolder();
+            }
         };
 
+        function initShareEditingFolder() {
+            $scope.currentFolder.devices.forEach(function (n) {
+                if (n.deviceID !== $scope.myID) {
+                    $scope.currentSharing.shared.push($scope.devices[n.deviceID]);
+                }
+                $scope.currentSharing.selected[n.deviceID] = true;
+            });
+            $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
+                return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID];
+            });
+        }
+
         $scope.refreshFailed = function (page, perpage) {
             if (!$scope.failed || !$scope.failed.folder) {
                 return;
@@ -1493,9 +1498,40 @@ angular.module('syncthing.core')
             $scope.configInSync = true;
         };
 
-        $scope.editDevice = function (deviceCfg) {
+        function editDeviceModal() {
+            $scope.currentDevice._addressesStr = $scope.currentDevice.addresses.join(', ');
+            $scope.deviceEditor.$setPristine();
+            $('#editDevice').modal();
+        }
+
+        $scope.editDeviceModalTitle = function() {
+            if ($scope.editingDefaults) {
+                return $translate.instant("Edit Device Defaults");
+            }
+            var title = '';
+            if ($scope.editingExisting) {
+                title += $translate.instant("Edit Device");
+            } else {
+                title += $translate.instant("Add Device");
+            }
+            var name = $scope.deviceName($scope.currentDevice);
+            if (name !== '') {
+                title += ' (' + name + ')';
+            }
+            return title;
+        };
+
+        $scope.editDeviceModalIcon = function() {
+            if ($scope.editingDefaults || $scope.editingExisting) {
+                return 'fas fa-pencil-alt';
+            }
+            return 'fas fa-desktop';
+        };
+
+        $scope.editDeviceExisting = function (deviceCfg) {
             $scope.currentDevice = $.extend({}, deviceCfg);
             $scope.editingExisting = true;
+            $scope.editingDefaults = false;
             $scope.willBeReintroducedBy = undefined;
             if (deviceCfg.introducedBy) {
                 var introducerDevice = $scope.devices[deviceCfg.introducedBy];
@@ -1503,7 +1539,6 @@ angular.module('syncthing.core')
                     $scope.willBeReintroducedBy = $scope.deviceName(introducerDevice);
                 }
             }
-            $scope.currentDevice._addressesStr = deviceCfg.addresses.join(', ');
             initShareEditing('device');
             $scope.deviceFolders($scope.currentDevice).forEach(function (folderID) {
                 $scope.currentSharing.shared.push($scope.folders[folderID]);
@@ -1512,8 +1547,15 @@ angular.module('syncthing.core')
             $scope.currentSharing.unrelated = $scope.folderList().filter(function (n) {
                 return !$scope.currentSharing.selected[n.id];
             });
-            $scope.deviceEditor.$setPristine();
-            $('#editDevice').modal();
+            editDeviceModal();
+        };
+
+        $scope.editDeviceDefaults = function () {
+            $http.get(urlbase + '/config/defaults/device').then(function (p) {
+                $scope.currentDevice = p.data;
+                $scope.editingDefaults = true;
+                editDeviceModal();
+            }, $scope.emitHTTPError);
         };
 
         $scope.selectAllSharedFolders = function (state) {
@@ -1545,19 +1587,16 @@ angular.module('syncthing.core')
                     }
                 })
                 .then(function () {
-                    $scope.currentDevice = {
-                        name: name,
-                        deviceID: deviceID,
-                        _addressesStr: 'dynamic',
-                        compression: 'metadata',
-                        introducer: false,
-                        ignoredFolders: []
-                    };
-                    $scope.editingExisting = false;
-                    initShareEditing('device');
-                    $scope.currentSharing.unrelated = $scope.folderList();
-                    $scope.deviceEditor.$setPristine();
-                    $('#editDevice').modal();
+                    $http.get(urlbase + '/config/defaults/device').then(function (p) {
+                        $scope.currentDevice = p.data;
+                        $scope.currentDevice.name = name;
+                        $scope.currentDevice.deviceID = deviceID;
+                        $scope.editingExisting = false;
+                        $scope.editingDefaults = false;
+                        initShareEditing('device');
+                        $scope.currentSharing.unrelated = $scope.folderList();
+                        editDeviceModal();
+                    }, $scope.emitHTTPError);
                 });
         };
 
@@ -1582,22 +1621,30 @@ angular.module('syncthing.core')
 
         $scope.saveDevice = function () {
             $('#editDevice').modal('hide');
-            $scope.saveDeviceConfig($scope.currentDevice);
-        };
-
-        $scope.saveDeviceConfig = function (deviceCfg) {
-            deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
+            $scope.currentDevice.addresses = $scope.currentDevice._addressesStr.split(',').map(function (x) {
                 return x.trim();
             });
+            delete $scope.currentDevice._addressesStr;
+            if ($scope.editingDefaults) {
+                $scope.config.defaults.device = $scope.currentDevice;
+            } else {
+                setDeviceConfig();
+            }
+            delete $scope.currentSharing;
+            delete $scope.currentDevice;
+            $scope.saveConfig();
+        };
 
-            $scope.devices[deviceCfg.deviceID] = deviceCfg;
+        function setDeviceConfig() {
+            var currentID = $scope.currentDevice.DeviceID;
+            $scope.devices[currentID] = $scope.currentDevice;
             $scope.config.devices = deviceList($scope.devices);
 
             for (var id in $scope.currentSharing.selected) {
                 if ($scope.currentSharing.selected[id]) {
                     var found = false;
                     for (i = 0; i < $scope.folders[id].devices.length; i++) {
-                        if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) {
+                        if ($scope.folders[id].devices[i].deviceID === currentID) {
                             found = true;
                             break;
                         }
@@ -1606,21 +1653,18 @@ angular.module('syncthing.core')
                     if (!found) {
                         // Add device to folder
                         $scope.folders[id].devices.push({
-                            deviceID: deviceCfg.deviceID
+                            deviceID: currentID,
                         });
                     }
                 } else {
                     // Remove device from folder
                     $scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
-                        return n.deviceID !== deviceCfg.deviceID;
+                        return n.deviceID !== currentID;
                     });
                 }
             }
 
-            delete $scope.currentSharing;
-
             $scope.config.folders = folderList($scope.folders);
-            $scope.saveConfig();
         };
 
         $scope.ignoreDevice = function (deviceID, pendingDevice) {
@@ -1757,14 +1801,14 @@ angular.module('syncthing.core')
             if (!newvalue || !shouldSetDefaultFolderPath()) {
                 return;
             }
-            $scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
+            $scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
         });
 
         $scope.$watch('currentFolder.id', function (newvalue) {
             if (!newvalue || !shouldSetDefaultFolderPath() || $scope.currentFolder.label) {
                 return;
             }
-            $scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
+            $scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
         });
 
         $scope.fsWatcherToggled = function () {
@@ -1791,7 +1835,8 @@ angular.module('syncthing.core')
             $('#globalChanges').modal();
         };
 
-        $scope.editFolderModal = function () {
+        function editFolderModal() {
+            initVersioningEditing();
             $scope.folderPathErrors = {};
             $scope.folderEditor.$setPristine();
             $('#editFolder').modal().one('shown.bs.tab', function (e) {
@@ -1804,64 +1849,78 @@ angular.module('syncthing.core')
             });
         };
 
-        $scope.editFolder = function (folderCfg) {
-            $scope.editingExisting = true;
-            $scope.currentFolder = angular.copy(folderCfg);
+        $scope.editFolderModalTitle = function() {
+            if ($scope.editingDefaults) {
+                return $translate.instant("Edit Folder Defaults");
+            }
+            var title = '';
+            if ($scope.editingExisting) {
+                title += $translate.instant("Edit Folder");
+            } else {
+                title += $translate.instant("Add Folder");
+            }
+            if ($scope.currentFolder.id !== '') {
+                title += ' (' + $scope.folderLabel($scope.currentFolder.id) + ')';
+            }
+            return title;
+        };
+
+        $scope.editFolderModalIcon = function() {
+            if ($scope.editingDefaults || $scope.editingExisting) {
+                return 'fas fa-pencil-alt';
+            }
+            return 'fas fa-folder';
+        };
+
+        function editFolder() {
             if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
                 $scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
+            } else if (!$scope.currentFolder.path) {
+                // undefined path leads to invalid input field
+                $scope.currentFolder.path = '';
             }
-            // Cache complete device objects indexed by ID for lookups
             initShareEditing('folder');
-            $scope.currentFolder.devices.forEach(function (n) {
-                if (n.deviceID !== $scope.myID) {
-                    $scope.currentSharing.shared.push($scope.devices[n.deviceID]);
-                }
-                $scope.currentSharing.selected[n.deviceID] = true;
-            });
-            $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
-                return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID];
-            });
-            if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
-                $scope.currentFolder.trashcanFileVersioning = true;
-                $scope.currentFolder.fileVersioningSelector = "trashcan";
-                $scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
-                $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
-            } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
-                $scope.currentFolder.simpleFileVersioning = true;
-                $scope.currentFolder.fileVersioningSelector = "simple";
-                $scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
-                $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
-                $scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
-            } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "staggered") {
-                $scope.currentFolder.staggeredFileVersioning = true;
-                $scope.currentFolder.fileVersioningSelector = "staggered";
-                $scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
-                $scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
-                $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
-                $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
-            } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "external") {
-                $scope.currentFolder.externalFileVersioning = true;
-                $scope.currentFolder.fileVersioningSelector = "external";
-                $scope.currentFolder.externalCommand = $scope.currentFolder.versioning.params.command;
-            } else {
-                $scope.currentFolder.fileVersioningSelector = "none";
-            }
-            $scope.currentFolder.trashcanClean = $scope.currentFolder.trashcanClean || 0; // weeds out nulls and undefineds
-            $scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
-            $scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
-            $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.staggeredVersionsPath || "";
-            // Zero is a valid, non-default value (disabled)
-            if ($scope.currentFolder.versioningCleanupIntervalS !== 0) {
-                $scope.currentFolder.versioningCleanupIntervalS = $scope.currentFolder.versioningCleanupIntervalS || 3600;
+            editFolderModal();
+        }
+
+        function initVersioningEditing() {
+            $scope.currentFolder._guiVersioning = angular.copy($scope.versioningDefaults);
+
+            if (!$scope.currentFolder.versioning) {
+                return;
             }
 
-            // staggeredMaxAge can validly be zero, which we should not replace
-            // with the default value of 365. So only set the default if it's
-            // actually undefined.
-            if (typeof $scope.currentFolder.staggeredMaxAge === 'undefined') {
-                $scope.currentFolder.staggeredMaxAge = 365;
+            var currentVersioning = $scope.currentFolder.versioning;
+
+            $scope.currentFolder._guiVersioning.cleanupIntervalS = +currentVersioning.cleanupIntervalS;
+
+            // Apply parameters currently in use
+            switch (currentVersioning.type) {
+            case "trashcan":
+                $scope.currentFolder._guiVersioning.selector = "trashcan";
+                $scope.currentFolder._guiVersioning.trashcanClean = +currentVersioning.params.cleanoutDays;
+                break;
+            case "simple":
+                $scope.currentFolder._guiVersioning.selector = "simple";
+                $scope.currentFolder._guiVersioning.simpleKeep = +currentVersioning.params.keep;
+                $scope.currentFolder._guiVersioning.trashcanClean = +currentVersioning.params.cleanoutDays;
+                break;
+            case "staggered":
+                $scope.currentFolder._guiVersioning.selector = "staggered";
+                $scope.currentFolder._guiVersioning.staggeredMaxAge = Math.floor(+currentVersioning.params.maxAge / 86400);
+                $scope.currentFolder._guiVersioning.staggeredCleanInterval = +currentVersioning.params.cleanInterval;
+                $scope.currentFolder._guiVersioning.staggeredVersionsPath = currentVersioning.params.versionsPath;
+                break;
+            case "external":
+                $scope.currentFolder._guiVersioning.selector = "external";
+                $scope.currentFolder.externalCommand = currentVersioning.params.command;
+                break;
             }
-            $scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
+        };
+
+        $scope.editFolderExisting = function(folderCfg) {
+            $scope.editingExisting = true;
+            $scope.currentFolder = angular.copy(folderCfg);
 
             $scope.ignores.text = 'Loading...';
             $scope.ignores.error = null;
@@ -1878,7 +1937,18 @@ angular.module('syncthing.core')
                     $scope.emitHTTPError(err);
                 });
 
-            $scope.editFolderModal();
+            editFolder();
+        };
+
+        $scope.editFolderDefaults = function() {
+            $http.get(urlbase + '/config/defaults/folder')
+                 .success(function (data) {
+                     $scope.currentFolder = data;
+                     $scope.editingExisting = false;
+                     $scope.editingDefaults = true;
+                     editFolder();
+                 })
+                 .error($scope.emitHTTPError);
         };
 
         $scope.selectAllSharedDevices = function (state) {
@@ -1897,37 +1967,43 @@ angular.module('syncthing.core')
 
         $scope.addFolder = function () {
             $http.get(urlbase + '/svc/random/string?length=10').success(function (data) {
-                $scope.editingExisting = false;
-                $scope.currentFolder = angular.copy($scope.folderDefaults);
-                initShareEditing('folder');
-                $scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
-                $scope.currentSharing.unrelated = $scope.otherDevices();
-                $scope.ignores.text = '';
-                $scope.ignores.error = null;
-                $scope.ignores.disabled = false;
-                $scope.editFolderModal();
+                var folderID = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
+                addFolderInit(folderID).then(function() {
+                    // Triggers the watch that sets the path
+                    $scope.currentFolder.label = $scope.currentFolder.label;
+                    editFolderModal();
+                });
             });
         };
 
-        $scope.addFolderAndShare = function (folder, folderLabel, device) {
-            $scope.editingExisting = false;
-            $scope.currentFolder = angular.copy($scope.folderDefaults);
-            $scope.currentFolder.id = folder;
-            $scope.currentFolder.label = folderLabel;
-            $scope.currentFolder.viewFlags = {
-                importFromOtherDevice: true
-            };
-            initShareEditing('folder');
-            $scope.currentSharing.selected[device] = true;
-            $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
-                return n.deviceID !== $scope.myID;
+        $scope.addFolderAndShare = function (folderID, folderLabel, device) {
+            addFolderInit(folderID).then(function() {
+                $scope.currentFolder.viewFlags = {
+                    importFromOtherDevice: true
+                };
+                $scope.currentSharing.selected[device] = true;
+                $scope.currentFolder.label = folderLabel;
+                editFolderModal();
             });
-            $scope.ignores.text = '';
-            $scope.ignores.error = null;
-            $scope.ignores.disabled = false;
-            $scope.editFolderModal();
         };
 
+        function addFolderInit(folderID) {
+            $scope.editingExisting = false;
+            $scope.editingDefaults = false;
+            return $http.get(urlbase + '/config/defaults/folder').then(function(p) {
+                $scope.currentFolder = p.data;
+                $scope.currentFolder.id = folderID;
+
+                initShareEditing('folder');
+                $scope.currentSharing.unrelated = $scope.currentSharing.unrelated.concat($scope.currentSharing.shared);
+                $scope.currentSharing.shared = [];
+
+                $scope.ignores.text = '';
+                $scope.ignores.error = null;
+                $scope.ignores.disabled = false;
+            }, $scope.emitHTTPError);
+        }
+
         $scope.shareFolderWithDevice = function (folder, device) {
             $scope.folders[folder].devices.push({
                 deviceID: device
@@ -1957,55 +2033,60 @@ angular.module('syncthing.core')
             folderCfg.devices = newDevices;
             delete $scope.currentSharing;
 
-            if (folderCfg.fileVersioningSelector === "trashcan") {
+            switch (folderCfg._guiVersioning.selector) {
+            case "trashcan":
                 folderCfg.versioning = {
                     'type': 'trashcan',
                     'params': {
-                        'cleanoutDays': '' + folderCfg.trashcanClean
+                        'cleanoutDays': '' + folderCfg._guiVersioning.trashcanClean
                     },
-                    'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+                    'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
                 };
-                delete folderCfg.trashcanFileVersioning;
-                delete folderCfg.trashcanClean;
-            } else if (folderCfg.fileVersioningSelector === "simple") {
+                break;
+            case "simple":
                 folderCfg.versioning = {
                     'type': 'simple',
                     'params': {
-                        'keep': '' + folderCfg.simpleKeep,
-                        'cleanoutDays': '' + folderCfg.trashcanClean
+                        'keep': '' + folderCfg._guiVersioning.simpleKeep,
+                        'cleanoutDays': '' + folderCfg._guiVersioning.trashcanClean
                     },
-                    'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+                    'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
                 };
-                delete folderCfg.simpleFileVersioning;
-                delete folderCfg.simpleKeep;
-            } else if (folderCfg.fileVersioningSelector === "staggered") {
+                break;
+            case "staggered":
                 folderCfg.versioning = {
                     'type': 'staggered',
                     'params': {
-                        'maxAge': '' + (folderCfg.staggeredMaxAge * 86400),
-                        'cleanInterval': '' + folderCfg.staggeredCleanInterval,
-                        'versionsPath': '' + folderCfg.staggeredVersionsPath
+                        'maxAge': '' + (folderCfg._guiVersioning.staggeredMaxAge * 86400),
+                        'cleanInterval': '' + folderCfg._guiVersioning.staggeredCleanInterval,
+                        'versionsPath': '' + folderCfg._guiVersioning.staggeredVersionsPath
                     },
-                    'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+                    'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
                 };
-                delete folderCfg.staggeredFileVersioning;
-                delete folderCfg.staggeredMaxAge;
-                delete folderCfg.staggeredCleanInterval;
-                delete folderCfg.staggeredVersionsPath;
-            } else if (folderCfg.fileVersioningSelector === "external") {
+                break;
+            case "external":
                 folderCfg.versioning = {
                     'type': 'external',
                     'params': {
-                        'command': '' + folderCfg.externalCommand
+                        'command': '' + folderCfg._guiVersioning.externalCommand
                     },
-                    'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+                    'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
                 };
-                delete folderCfg.externalFileVersioning;
-                delete folderCfg.externalCommand;
-            } else {
+                break;
+            default:
                 delete folderCfg.versioning;
             }
+            delete folderCfg._guiVersioning;
+
+            if ($scope.editingDefaults) {
+                $scope.config.defaults.folder = folderCfg;
+                $scope.saveConfig();
+            } else {
+                saveFolderExisting(folderCfg);
+            }
+        };
 
+        function saveFolderExisting(folderCfg) {
             var ignoresLoaded = !$scope.ignores.disabled;
             var ignores = $scope.ignores.text.split('\n');
             // Split always returns a minimum 1-length array even for no patterns

+ 6 - 6
gui/default/syncthing/device/editDeviceModalView.html

@@ -1,14 +1,14 @@
-<modal id="editDevice" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-desktop'}}" heading="{{editingExisting ? 'Edit Device' : 'Add Device' | translate}} {{currentDevice.name}}" large="yes" closeable="yes">
+<modal id="editDevice" status="default" icon="{{editDeviceModalIcon()}}" heading="{{editDeviceModalTitle()}}" large="yes" closeable="yes">
   <div class="modal-body">
     <form role="form" name="deviceEditor">
       <ul class="nav nav-tabs" ng-init="loadFormIntoScope(deviceEditor)">
         <li class="active"><a data-toggle="tab" href="#device-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
-        <li><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
+        <li ng-if="!editingDefaults"><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
         <li><a data-toggle="tab" href="#device-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
       </ul>
       <div class="tab-content">
         <div id="device-general" class="tab-pane in active">
-          <div class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
+          <div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
             <label translate for="deviceID">Device ID</label>
             <div ng-if="!editingExisting">
               <input name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required="" valid-deviceid list="discovery-list" aria-required="true" />
@@ -38,7 +38,7 @@
             <p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
           </div>
         </div>
-        <div id="device-sharing" class="tab-pane">
+        <div ng-if="!editingDefaults" id="device-sharing" class="tab-pane">
           <div class="row">
             <div class="col-md-6">
               <div class="form-group">
@@ -164,13 +164,13 @@
     <button type="button" class="btn btn-primary btn-sm" ng-click="saveDevice()" ng-disabled="deviceEditor.$invalid">
       <span class="fas fa-check"></span>&nbsp;<span translate>Save</span>
     </button>
-    <button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
+    <button ng-if="!editingDefaults" type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
       <span class="fas fa-qrcode"></span>&nbsp;<span translate>Show QR</span>
     </button>
     <button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
       <span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
     </button>
-    <div ng-if="editingExisting" class="pull-left">
+    <div ng-if="editingExisting && !editingDefaults" class="pull-left">
       <button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation">
         <span class="fas fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
       </button>

+ 37 - 35
gui/default/syncthing/folder/editFolderModalView.html

@@ -1,24 +1,24 @@
-<modal id="editFolder" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-folder'}}" heading="{{editingExisting ? 'Edit Folder' : 'Add Folder' | translate}} ({{folderLabel(currentFolder.id)}})" large="yes" closeable="yes">
+<modal id="editFolder" status="default" icon="{{editFolderModalIcon()}}" heading="{{editFolderModalTitle()}}" large="yes" closeable="yes">
   <div class="modal-body">
     <form role="form" name="folderEditor">
       <ul class="nav nav-tabs" ng-init="loadFormIntoScope(folderEditor)">
         <li class="active"><a data-toggle="tab" href="#folder-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
         <li><a data-toggle="tab" href="#folder-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
         <li><a data-toggle="tab" href="#folder-versioning"><span class="fas fa-copy"></span> <span translate>File Versioning</span></a></li>
-        <li><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
+        <li ng-if="!editingDefaults"><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
         <li><a data-toggle="tab" href="#folder-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
       </ul>
       <div class="tab-content">
 
         <div id="folder-general" class="tab-pane in active">
-          <div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty}">
+          <div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty && !editingDefaults}">
             <label for="folderLabel"><span translate>Folder Label</span></label>
             <input name="folderLabel" id="folderLabel" class="form-control" type="text" ng-model="currentFolder.label" value="{{currentFolder.label}}" />
             <p class="help-block">
               <span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
             </p>
           </div>
-          <div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
+          <div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
             <label for="folderID"><span translate>Folder ID</span></label>
             <input name="folderID" ng-readonly="editingExisting || (!editingExisting && currentFolder.viewFlags.importFromOtherDevice)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />
             <p class="help-block">
@@ -28,19 +28,21 @@
               <span translate ng-show="!editingExisting">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</span>
             </p>
           </div>
-          <div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
+          <div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty && !editingDefaults}">
             <label translate for="folderPath">Folder Path</label>
-            <input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required="" aria-required="true" path-is-sub-dir />
+            <input name="folderPath" ng-readonly="editingExisting && !editingDefaults" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" ng-required="!editingDefaults" ng-aria-required="!editingDefaults" path-is-sub-dir />
             <datalist id="directory-list">
               <option ng-repeat="directory in directoryList" value="{{ directory }}" />
             </datalist>
             <p class="help-block">
               <span ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine"><span translate>Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.</br></span>
-              <span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty">The folder path cannot be blank.</span>
+              <span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty && !editingDefaults">The folder path cannot be blank.</span>
               <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length == 0">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
               <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length != 0">Warning, this path is a subdirectory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
-              <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
-              <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
+              <span ng-if="folderPathErrors.isParent && !editingDefaults">
+                <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
+                <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
+              </span>
             </p>
           </div>
         </div>
@@ -88,7 +90,7 @@
         <div id="folder-versioning" class="tab-pane">
           <div class="form-group">
             <label translate>File Versioning</label>&emsp;<a href="https://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
-            <select class="form-control" ng-model="currentFolder.fileVersioningSelector">
+            <select class="form-control" ng-model="currentFolder._guiVersioning.selector">
               <option value="none" translate>No File Versioning</option>
               <option value="trashcan" translate>Trash Can File Versioning</option>
               <option value="simple" translate>Simple File Versioning</option>
@@ -96,46 +98,46 @@
               <option value="external" translate>External File Versioning</option>
             </select>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='trashcan' || currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selectorector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.trashcanClean.$invalid && folderEditor._guiVersioning.trashcanClean.$dirty}">
             <p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
             <label translate for="trashcanClean">Clean out after</label>
             <div class="input-group">
-              <input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required="" aria-required="true" min="0" />
+              <input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.trashcanClean" required="" aria-required="true" min="0" />
               <div class="input-group-addon" translate>days</div>
             </div>
             <p class="help-block">
-              <span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
-              <span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
-              <span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
+              <span translate ng-if="folderEditor._guiVersioning.trashcanClean.$valid || folderEditor._guiVersioning.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
+              <span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.required && folderEditor._guiVersioning.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
+              <span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.min && folderEditor._guiVersioning.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
             </p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.simpleKeep.$invalid && folderEditor._guiVersioning.simpleKeep.$dirty}">
             <p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
             <label translate for="simpleKeep">Keep Versions</label>
-            <input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required="" aria-required="true" min="1" />
+            <input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder._guiVersioning.simpleKeep" required="" aria-required="true" min="1" />
             <p class="help-block">
-              <span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
-              <span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
-              <span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
+              <span translate ng-if="folderEditor._guiVersioning.simpleKeep.$valid || folderEditor._guiVersioning.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
+              <span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.required && folderEditor._guiVersioning.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
+              <span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.min && folderEditor._guiVersioning.simpleKeep.$dirty">You must keep at least one version.</span>
             </p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor._guiVersioning.staggeredMaxAge.$invalid && folderEditor._guiVersioning.staggeredMaxAge.$dirty}">
             <p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
             <p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
             <label translate for="staggeredMaxAge">Maximum Age</label>
-            <input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder.staggeredMaxAge" required="" aria-required="true" min="0" />
+            <input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder._guiVersioning.staggeredMaxAge" required="" aria-required="true" min="0" />
             <p class="help-block">
-              <span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
-              <span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
-              <span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
+              <span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$valid || folderEditor._guiVersioning.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
+              <span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.required && folderEditor._guiVersioning.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
+              <span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.min && folderEditor._guiVersioning.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
             </p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector == 'staggered'">
             <label translate for="staggeredVersionsPath">Versions Path</label>
-            <input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath" />
+            <input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder._guiVersioning.staggeredVersionsPath" />
             <p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).</p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
             <p translate class="help-block">An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.</p>
             <label translate for="externalCommand">Command</label>
             <input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required="" aria-required="true" />
@@ -144,21 +146,21 @@
               <span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
             </p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector != 'none'" ng-class="{'has-error': folderEditor.versioningCleanupIntervalS.$invalid && folderEditor.versioningCleanupIntervalS.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector != 'none'" ng-class="{'has-error': folderEditor._guiVersioning.cleanupIntervalS.$invalid && folderEditor._guiVersioning.cleanupIntervalS.$dirty}">
             <label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
             <div class="input-group">
-              <input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder.versioningCleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
+              <input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
               <div class="input-group-addon" translate>seconds</div>
             </div>
             <p class="help-block">
-              <span translate ng-if="folderEditor.versioningCleanupIntervalS.$valid || folderEditor.versioningCleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
-              <span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.required && folderEditor.versioningCleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
-              <span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.min && folderEditor.versioningCleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
+              <span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$valid || folderEditor._guiVersioning.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
+              <span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.required && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
+              <span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.min && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
             </p>
           </div>
         </div>
 
-        <div id="folder-ignores" class="tab-pane">
+        <div ng-if="!editingDefaults" id="folder-ignores" class="tab-pane">
           <p translate>Enter ignore patterns, one per line.</p>
           <div ng-class="{'has-error': ignores.error != null}">
             <textarea class="form-control" rows="5" ng-model="ignores.text" ng-disabled="ignores.disabled"></textarea>
@@ -280,7 +282,7 @@
     <button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
       <span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
     </button>
-    <button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting">
+    <button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting && !editingDefaults">
       <span class="fas fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
     </button>
   </div>

+ 9 - 7
gui/default/syncthing/settings/settingsModalView.html

@@ -100,13 +100,15 @@
               </div>
             </div>
           </div>
-          <div class="form-group">
-            <label translate for="urVersion">Default Folder Path</label>
-            <input id="DefaultFolderPath" class="form-control" type="text" ng-model="tmpOptions.defaultFolderPath" />
-            <p class="help-block">
-              <span translate translate-value-tilde="{{system.tilde}}">
-                Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.
-              </span>
+          <div>
+            <label translate>Default Configuration</label>
+            <p>
+              <button type="button" class="btn btn-default btn-secondary" ng-click="editFolderDefaults()">
+                <span translate>Edit Folder Defaults</span>
+              </button>
+              <button type="button" class="btn btn-default btn-secondary" ng-click="editDeviceDefaults()">
+                <span translate>Edit Device Defaults</span>
+              </button>
             </p>
           </div>
         </div>

+ 2 - 2
gui/default/untrusted/index.html

@@ -566,7 +566,7 @@
                     <button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-disabled="['idle', 'stopped', 'unshared', 'outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) < 0">
                       <span class="fas fa-refresh"></span>&nbsp;<span translate>Rescan</span>
                     </button>
-                    <button type="button" class="btn btn-sm btn-default" ng-click="editFolder(folder)">
+                    <button type="button" class="btn btn-sm btn-default" ng-click="editFolderExisting(folder)">
                       <span class="fas fa-pencil-alt"></span>&nbsp;<span translate>Edit</span>
                     </button>
                   </span>
@@ -833,7 +833,7 @@
                     <button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
                       <span class="fas fa-play"></span>&nbsp;<span translate>Resume</span>
                     </button>
-                    <button type="button" class="btn btn-sm btn-default" ng-click="editDevice(deviceCfg)">
+                    <button type="button" class="btn btn-sm btn-default" ng-click="editDeviceExisting(deviceCfg)">
                       <span class="fas fa-pencil-alt"></span>&nbsp;<span translate>Edit</span>
                     </button>
                   </span>

+ 235 - 153
gui/default/untrusted/syncthing/core/syncthingController.js

@@ -52,6 +52,7 @@ angular.module('syncthing.core')
         $scope.metricRates = false;
         $scope.folderPathErrors = {};
         $scope.currentFolder = {};
+        $scope.currentDevice = {};
         $scope.ignores = {
             text: '',
             error: null,
@@ -63,17 +64,8 @@ angular.module('syncthing.core')
             $scope.metricRates = (window.localStorage["metricRates"] == "true");
         } catch (exception) { }
 
-        $scope.folderDefaults = {
-            devices: [],
-            type: "sendreceive",
-            rescanIntervalS: 3600,
-            fsWatcherDelayS: 10,
-            fsWatcherEnabled: true,
-            minDiskFree: { value: 1, unit: "%" },
-            maxConflicts: 10,
-            fsync: true,
-            order: "random",
-            fileVersioningSelector: "none",
+        $scope.versioningDefaults = {
+            selector: "none",
             trashcanClean: 0,
             versioningCleanupIntervalS: 3600,
             simpleKeep: 5,
@@ -81,8 +73,6 @@ angular.module('syncthing.core')
             staggeredCleanInterval: 3600,
             staggeredVersionsPath: "",
             externalCommand: "",
-            autoNormalize: true,
-            path: "",
         };
 
         $scope.localStateTotal = {
@@ -727,7 +717,7 @@ angular.module('syncthing.core')
         }
 
         function shouldSetDefaultFolderPath() {
-            return $scope.config.options && $scope.config.options.defaultFolderPath && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine
+            return $scope.config.defaults.folder.path && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine && !$scope.editingDefaults;
         }
 
         function resetRemoteNeed() {
@@ -770,8 +760,26 @@ angular.module('syncthing.core')
             $scope.currentSharing.unrelated = [];
             $scope.currentSharing.selected = {};
             $scope.currentSharing.encryptionPasswords = {};
+            if (editing === 'folder') {
+                initShareEditingFolder();
+            }
         };
 
+        function initShareEditingFolder() {
+            $scope.currentFolder.devices.forEach(function (n) {
+                if (n.deviceID !== $scope.myID) {
+                    $scope.currentSharing.shared.push($scope.devices[n.deviceID]);
+                }
+                if (n.encryptionPassword !== '') {
+                    $scope.currentSharing.encryptionPasswords[n.deviceID] = n.encryptionPassword;
+                }
+                $scope.currentSharing.selected[n.deviceID] = true;
+            });
+            $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
+                return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID];
+            });
+        }
+
         $scope.refreshFailed = function (page, perpage) {
             if (!$scope.failed || !$scope.failed.folder) {
                 return;
@@ -1239,6 +1247,7 @@ angular.module('syncthing.core')
                 }).error($scope.emitHTTPError);
             },
             show: function () {
+                $scope.logging.paused = false;
                 $scope.logging.refreshFacilities();
                 $scope.logging.timer = $timeout($scope.logging.fetch);
                 var textArea = $('#logViewerText');
@@ -1496,9 +1505,40 @@ angular.module('syncthing.core')
             $scope.configInSync = true;
         };
 
-        $scope.editDevice = function (deviceCfg) {
+        function editDeviceModal() {
+            $scope.currentDevice._addressesStr = $scope.currentDevice.addresses.join(', ');
+            $scope.deviceEditor.$setPristine();
+            $('#editDevice').modal();
+        }
+
+        $scope.editDeviceModalTitle = function() {
+            if ($scope.editingDefaults) {
+                return $translate.instant("Edit Device Defaults");
+            }
+            var title = '';
+            if ($scope.editingExisting) {
+                title += $translate.instant("Edit Device");
+            } else {
+                title += $translate.instant("Add Device");
+            }
+            var name = $scope.deviceName($scope.currentDevice);
+            if (name !== '') {
+                title += ' (' + name + ')';
+            }
+            return title;
+        };
+
+        $scope.editDeviceModalIcon = function() {
+            if ($scope.editingDefaults || $scope.editingExisting) {
+                return 'fas fa-pencil-alt';
+            }
+            return 'fas fa-desktop';
+        };
+
+        $scope.editDeviceExisting = function (deviceCfg) {
             $scope.currentDevice = $.extend({}, deviceCfg);
             $scope.editingExisting = true;
+            $scope.editingDefaults = false;
             $scope.willBeReintroducedBy = undefined;
             if (deviceCfg.introducedBy) {
                 var introducerDevice = $scope.devices[deviceCfg.introducedBy];
@@ -1506,7 +1546,6 @@ angular.module('syncthing.core')
                     $scope.willBeReintroducedBy = $scope.deviceName(introducerDevice);
                 }
             }
-            $scope.currentDevice._addressesStr = deviceCfg.addresses.join(', ');
             initShareEditing('device');
             $scope.deviceFolders($scope.currentDevice).forEach(function (folderID) {
                 $scope.currentSharing.shared.push($scope.folders[folderID]);
@@ -1522,8 +1561,15 @@ angular.module('syncthing.core')
             $scope.currentSharing.unrelated = $scope.folderList().filter(function (n) {
                 return !$scope.currentSharing.selected[n.id];
             });
-            $scope.deviceEditor.$setPristine();
-            $('#editDevice').modal();
+            editDeviceModal();
+        };
+
+        $scope.editDeviceDefaults = function () {
+            $http.get(urlbase + '/config/defaults/device').then(function (p) {
+                $scope.currentDevice = p.data;
+                $scope.editingDefaults = true;
+                editDeviceModal();
+            }, $scope.emitHTTPError);
         };
 
         $scope.selectAllSharedFolders = function (state) {
@@ -1555,19 +1601,16 @@ angular.module('syncthing.core')
                     }
                 })
                 .then(function () {
-                    $scope.currentDevice = {
-                        name: name,
-                        deviceID: deviceID,
-                        _addressesStr: 'dynamic',
-                        compression: 'metadata',
-                        introducer: false,
-                        ignoredFolders: []
-                    };
-                    $scope.editingExisting = false;
-                    initShareEditing('device');
-                    $scope.currentSharing.unrelated = $scope.folderList();
-                    $scope.deviceEditor.$setPristine();
-                    $('#editDevice').modal();
+                    $http.get(urlbase + '/config/defaults/device').then(function (p) {
+                        $scope.currentDevice = p.data;
+                        $scope.currentDevice.name = name;
+                        $scope.currentDevice.deviceID = deviceID;
+                        $scope.editingExisting = false;
+                        $scope.editingDefaults = false;
+                        initShareEditing('device');
+                        $scope.currentSharing.unrelated = $scope.folderList();
+                        editDeviceModal();
+                    }, $scope.emitHTTPError);
                 });
         };
 
@@ -1592,22 +1635,30 @@ angular.module('syncthing.core')
 
         $scope.saveDevice = function () {
             $('#editDevice').modal('hide');
-            $scope.saveDeviceConfig($scope.currentDevice);
-        };
-
-        $scope.saveDeviceConfig = function (deviceCfg) {
-            deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
+            $scope.currentDevice.addresses = $scope.currentDevice._addressesStr.split(',').map(function (x) {
                 return x.trim();
             });
+            delete $scope.currentDevice._addressesStr;
+            if ($scope.editingDefaults) {
+                $scope.config.defaults.device = $scope.currentDevice;
+            } else {
+                setDeviceConfig();
+            }
+            delete $scope.currentSharing;
+            delete $scope.currentDevice;
+            $scope.saveConfig();
+        };
 
-            $scope.devices[deviceCfg.deviceID] = deviceCfg;
+        function setDeviceConfig() {
+            var currentID = $scope.currentDevice.DeviceID;
+            $scope.devices[currentID] = $scope.currentDevice;
             $scope.config.devices = deviceList($scope.devices);
 
             for (var id in $scope.currentSharing.selected) {
                 if ($scope.currentSharing.selected[id]) {
                     var found = false;
                     for (i = 0; i < $scope.folders[id].devices.length; i++) {
-                        if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) {
+                        if ($scope.folders[id].devices[i].deviceID === currentID) {
                             found = true;
                             // Update encryption pw
                             $scope.folders[id].devices[i].encryptionPassword = $scope.currentSharing.encryptionPasswords[id];
@@ -1618,22 +1669,19 @@ angular.module('syncthing.core')
                     if (!found) {
                         // Add device to folder
                         $scope.folders[id].devices.push({
-                            deviceID: deviceCfg.deviceID,
-                            encryptionPassword: $scope.currentSharing.encryptionPasswords[id]
+                            deviceID: currentID,
+                            encryptionPassword: $scope.currentSharing.encryptionPasswords[id],
                         });
                     }
                 } else {
                     // Remove device from folder
                     $scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
-                        return n.deviceID !== deviceCfg.deviceID;
+                        return n.deviceID !== currentID;
                     });
                 }
             }
 
-            delete $scope.currentSharing;
-
             $scope.config.folders = folderList($scope.folders);
-            $scope.saveConfig();
         };
 
         $scope.ignoreDevice = function (deviceID, pendingDevice) {
@@ -1770,14 +1818,14 @@ angular.module('syncthing.core')
             if (!newvalue || !shouldSetDefaultFolderPath()) {
                 return;
             }
-            $scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
+            $scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
         });
 
         $scope.$watch('currentFolder.id', function (newvalue) {
             if (!newvalue || !shouldSetDefaultFolderPath() || $scope.currentFolder.label) {
                 return;
             }
-            $scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
+            $scope.currentFolder.path = pathJoin($scope.config.defaults.folder.path, newvalue);
         });
 
         $scope.fsWatcherToggled = function () {
@@ -1804,7 +1852,8 @@ angular.module('syncthing.core')
             $('#globalChanges').modal();
         };
 
-        $scope.editFolderModal = function () {
+        function editFolderModal() {
+            initVersioningEditing();
             $scope.folderPathErrors = {};
             $scope.folderEditor.$setPristine();
             $('#editFolder').modal().one('shown.bs.tab', function (e) {
@@ -1817,67 +1866,78 @@ angular.module('syncthing.core')
             });
         };
 
-        $scope.editFolder = function (folderCfg) {
-            $scope.editingExisting = true;
-            $scope.currentFolder = angular.copy(folderCfg);
+        $scope.editFolderModalTitle = function() {
+            if ($scope.editingDefaults) {
+                return $translate.instant("Edit Folder Defaults");
+            }
+            var title = '';
+            if ($scope.editingExisting) {
+                title += $translate.instant("Edit Folder");
+            } else {
+                title += $translate.instant("Add Folder");
+            }
+            if ($scope.currentFolder.id !== '') {
+                title += ' (' + $scope.folderLabel($scope.currentFolder.id) + ')';
+            }
+            return title;
+        };
+
+        $scope.editFolderModalIcon = function() {
+            if ($scope.editingDefaults || $scope.editingExisting) {
+                return 'fas fa-pencil-alt';
+            }
+            return 'fas fa-folder';
+        };
+
+        function editFolder() {
             if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
                 $scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
+            } else if (!$scope.currentFolder.path) {
+                // undefined path leads to invalid input field
+                $scope.currentFolder.path = '';
             }
-            // Cache complete device objects indexed by ID for lookups
             initShareEditing('folder');
-            $scope.currentFolder.devices.forEach(function (n) {
-                if (n.deviceID !== $scope.myID) {
-                    $scope.currentSharing.shared.push($scope.devices[n.deviceID]);
-                }
-                if (n.encryptionPassword !== '') {
-                    $scope.currentSharing.encryptionPasswords[n.deviceID] = n.encryptionPassword;
-                }
-                $scope.currentSharing.selected[n.deviceID] = true;
-            });
-            $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
-                return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID];
-            });
-            if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
-                $scope.currentFolder.trashcanFileVersioning = true;
-                $scope.currentFolder.fileVersioningSelector = "trashcan";
-                $scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
-                $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
-            } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
-                $scope.currentFolder.simpleFileVersioning = true;
-                $scope.currentFolder.fileVersioningSelector = "simple";
-                $scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
-                $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
-                $scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
-            } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "staggered") {
-                $scope.currentFolder.staggeredFileVersioning = true;
-                $scope.currentFolder.fileVersioningSelector = "staggered";
-                $scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
-                $scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
-                $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
-                $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
-            } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "external") {
-                $scope.currentFolder.externalFileVersioning = true;
-                $scope.currentFolder.fileVersioningSelector = "external";
-                $scope.currentFolder.externalCommand = $scope.currentFolder.versioning.params.command;
-            } else {
-                $scope.currentFolder.fileVersioningSelector = "none";
-            }
-            $scope.currentFolder.trashcanClean = $scope.currentFolder.trashcanClean || 0; // weeds out nulls and undefineds
-            $scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
-            $scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
-            $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.staggeredVersionsPath || "";
-            // Zero is a valid, non-default value (disabled)
-            if ($scope.currentFolder.versioningCleanupIntervalS !== 0) {
-                $scope.currentFolder.versioningCleanupIntervalS = $scope.currentFolder.versioningCleanupIntervalS || 3600;
+            editFolderModal();
+        }
+
+        function initVersioningEditing() {
+            $scope.currentFolder._guiVersioning = angular.copy($scope.versioningDefaults);
+
+            if (!$scope.currentFolder.versioning) {
+                return;
             }
 
-            // staggeredMaxAge can validly be zero, which we should not replace
-            // with the default value of 365. So only set the default if it's
-            // actually undefined.
-            if (typeof $scope.currentFolder.staggeredMaxAge === 'undefined') {
-                $scope.currentFolder.staggeredMaxAge = 365;
+            var currentVersioning = $scope.currentFolder.versioning;
+
+            $scope.currentFolder._guiVersioning.cleanupIntervalS = +currentVersioning.cleanupIntervalS;
+
+            // Apply parameters currently in use
+            switch (currentVersioning.type) {
+            case "trashcan":
+                $scope.currentFolder._guiVersioning.selector = "trashcan";
+                $scope.currentFolder._guiVersioning.trashcanClean = +currentVersioning.params.cleanoutDays;
+                break;
+            case "simple":
+                $scope.currentFolder._guiVersioning.selector = "simple";
+                $scope.currentFolder._guiVersioning.simpleKeep = +currentVersioning.params.keep;
+                $scope.currentFolder._guiVersioning.trashcanClean = +currentVersioning.params.cleanoutDays;
+                break;
+            case "staggered":
+                $scope.currentFolder._guiVersioning.selector = "staggered";
+                $scope.currentFolder._guiVersioning.staggeredMaxAge = Math.floor(+currentVersioning.params.maxAge / 86400);
+                $scope.currentFolder._guiVersioning.staggeredCleanInterval = +currentVersioning.params.cleanInterval;
+                $scope.currentFolder._guiVersioning.staggeredVersionsPath = currentVersioning.params.versionsPath;
+                break;
+            case "external":
+                $scope.currentFolder._guiVersioning.selector = "external";
+                $scope.currentFolder.externalCommand = currentVersioning.params.command;
+                break;
             }
-            $scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
+        };
+
+        $scope.editFolderExisting = function(folderCfg) {
+            $scope.editingExisting = true;
+            $scope.currentFolder = angular.copy(folderCfg);
 
             $scope.ignores.text = 'Loading...';
             $scope.ignores.error = null;
@@ -1894,7 +1954,18 @@ angular.module('syncthing.core')
                     $scope.emitHTTPError(err);
                 });
 
-            $scope.editFolderModal();
+            editFolder();
+        };
+
+        $scope.editFolderDefaults = function() {
+            $http.get(urlbase + '/config/defaults/folder')
+                 .success(function (data) {
+                     $scope.currentFolder = data;
+                     $scope.editingExisting = false;
+                     $scope.editingDefaults = true;
+                     editFolder();
+                 })
+                 .error($scope.emitHTTPError);
         };
 
         $scope.selectAllSharedDevices = function (state) {
@@ -1913,37 +1984,43 @@ angular.module('syncthing.core')
 
         $scope.addFolder = function () {
             $http.get(urlbase + '/svc/random/string?length=10').success(function (data) {
-                $scope.editingExisting = false;
-                $scope.currentFolder = angular.copy($scope.folderDefaults);
-                initShareEditing('folder');
-                $scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
-                $scope.currentSharing.unrelated = $scope.otherDevices();
-                $scope.ignores.text = '';
-                $scope.ignores.error = null;
-                $scope.ignores.disabled = false;
-                $scope.editFolderModal();
+                var folderID = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
+                addFolderInit(folderID).then(function() {
+                    // Triggers the watch that sets the path
+                    $scope.currentFolder.label = $scope.currentFolder.label;
+                    editFolderModal();
+                });
             });
         };
 
-        $scope.addFolderAndShare = function (folder, folderLabel, device) {
-            $scope.editingExisting = false;
-            $scope.currentFolder = angular.copy($scope.folderDefaults);
-            $scope.currentFolder.id = folder;
-            $scope.currentFolder.label = folderLabel;
-            $scope.currentFolder.viewFlags = {
-                importFromOtherDevice: true
-            };
-            initShareEditing('folder');
-            $scope.currentSharing.selected[device] = true;
-            $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
-                return n.deviceID !== $scope.myID;
+        $scope.addFolderAndShare = function (folderID, folderLabel, device) {
+            addFolderInit(folderID).then(function() {
+                $scope.currentFolder.viewFlags = {
+                    importFromOtherDevice: true
+                };
+                $scope.currentSharing.selected[device] = true;
+                $scope.currentFolder.label = folderLabel;
+                editFolderModal();
             });
-            $scope.ignores.text = '';
-            $scope.ignores.error = null;
-            $scope.ignores.disabled = false;
-            $scope.editFolderModal();
         };
 
+        function addFolderInit(folderID) {
+            $scope.editingExisting = false;
+            $scope.editingDefaults = false;
+            return $http.get(urlbase + '/config/defaults/folder').then(function(p) {
+                $scope.currentFolder = p.data;
+                $scope.currentFolder.id = folderID;
+
+                initShareEditing('folder');
+                $scope.currentSharing.unrelated = $scope.currentSharing.unrelated.concat($scope.currentSharing.shared);
+                $scope.currentSharing.shared = [];
+
+                $scope.ignores.text = '';
+                $scope.ignores.error = null;
+                $scope.ignores.disabled = false;
+            }, $scope.emitHTTPError);
+        }
+
         $scope.shareFolderWithDevice = function (folder, device) {
             $scope.folders[folder].devices.push({
                 deviceID: device
@@ -1975,55 +2052,60 @@ angular.module('syncthing.core')
             folderCfg.devices = newDevices;
             delete $scope.currentSharing;
 
-            if (folderCfg.fileVersioningSelector === "trashcan") {
+            switch (folderCfg._guiVersioning.selector) {
+            case "trashcan":
                 folderCfg.versioning = {
                     'type': 'trashcan',
                     'params': {
-                        'cleanoutDays': '' + folderCfg.trashcanClean
+                        'cleanoutDays': '' + folderCfg._guiVersioning.trashcanClean
                     },
-                    'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+                    'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
                 };
-                delete folderCfg.trashcanFileVersioning;
-                delete folderCfg.trashcanClean;
-            } else if (folderCfg.fileVersioningSelector === "simple") {
+                break;
+            case "simple":
                 folderCfg.versioning = {
                     'type': 'simple',
                     'params': {
-                        'keep': '' + folderCfg.simpleKeep,
-                        'cleanoutDays': '' + folderCfg.trashcanClean
+                        'keep': '' + folderCfg._guiVersioning.simpleKeep,
+                        'cleanoutDays': '' + folderCfg._guiVersioning.trashcanClean
                     },
-                    'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+                    'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
                 };
-                delete folderCfg.simpleFileVersioning;
-                delete folderCfg.simpleKeep;
-            } else if (folderCfg.fileVersioningSelector === "staggered") {
+                break;
+            case "staggered":
                 folderCfg.versioning = {
                     'type': 'staggered',
                     'params': {
-                        'maxAge': '' + (folderCfg.staggeredMaxAge * 86400),
-                        'cleanInterval': '' + folderCfg.staggeredCleanInterval,
-                        'versionsPath': '' + folderCfg.staggeredVersionsPath
+                        'maxAge': '' + (folderCfg._guiVersioning.staggeredMaxAge * 86400),
+                        'cleanInterval': '' + folderCfg._guiVersioning.staggeredCleanInterval,
+                        'versionsPath': '' + folderCfg._guiVersioning.staggeredVersionsPath
                     },
-                    'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+                    'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
                 };
-                delete folderCfg.staggeredFileVersioning;
-                delete folderCfg.staggeredMaxAge;
-                delete folderCfg.staggeredCleanInterval;
-                delete folderCfg.staggeredVersionsPath;
-            } else if (folderCfg.fileVersioningSelector === "external") {
+                break;
+            case "external":
                 folderCfg.versioning = {
                     'type': 'external',
                     'params': {
-                        'command': '' + folderCfg.externalCommand
+                        'command': '' + folderCfg._guiVersioning.externalCommand
                     },
-                    'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+                    'cleanupIntervalS': folderCfg._guiVersioning.cleanupIntervalS
                 };
-                delete folderCfg.externalFileVersioning;
-                delete folderCfg.externalCommand;
-            } else {
+                break;
+            default:
                 delete folderCfg.versioning;
             }
+            delete folderCfg._guiVersioning;
+
+            if ($scope.editingDefaults) {
+                $scope.config.defaults.folder = folderCfg;
+                $scope.saveConfig();
+            } else {
+                saveFolderExisting(folderCfg);
+            }
+        };
 
+        function saveFolderExisting(folderCfg) {
             var ignoresLoaded = !$scope.ignores.disabled;
             var ignores = $scope.ignores.text.split('\n');
             // Split always returns a minimum 1-length array even for no patterns

+ 6 - 6
gui/default/untrusted/syncthing/device/editDeviceModalView.html

@@ -1,14 +1,14 @@
-<modal id="editDevice" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-desktop'}}" heading="{{editingExisting ? 'Edit Device' : 'Add Device' | translate}} {{currentDevice.name}}" large="yes" closeable="yes">
+<modal id="editDevice" status="default" icon="{{editDeviceModalIcon()}}" heading="{{editDeviceModalTitle()}}" large="yes" closeable="yes">
   <div class="modal-body">
     <form role="form" name="deviceEditor">
       <ul class="nav nav-tabs" ng-init="loadFormIntoScope(deviceEditor)">
         <li class="active"><a data-toggle="tab" href="#device-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
-        <li><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
+        <li ng-if="!editingDefaults"><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
         <li><a data-toggle="tab" href="#device-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
       </ul>
       <div class="tab-content">
         <div id="device-general" class="tab-pane in active">
-          <div class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
+          <div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
             <label translate for="deviceID">Device ID</label>
             <div ng-if="!editingExisting">
               <input name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required="" valid-deviceid list="discovery-list" aria-required="true" />
@@ -38,7 +38,7 @@
             <p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
           </div>
         </div>
-        <div id="device-sharing" class="tab-pane">
+        <div ng-if="!editingDefaults" id="device-sharing" class="tab-pane">
           <div class="row">
             <div class="col-md-6">
               <div class="form-group">
@@ -151,13 +151,13 @@
     <button type="button" class="btn btn-primary btn-sm" ng-click="saveDevice()" ng-disabled="deviceEditor.$invalid">
       <span class="fas fa-check"></span>&nbsp;<span translate>Save</span>
     </button>
-    <button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
+    <button ng-if="!editingDefaults" type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
       <span class="fas fa-qrcode"></span>&nbsp;<span translate>Show QR</span>
     </button>
     <button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
       <span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
     </button>
-    <div ng-if="editingExisting" class="pull-left">
+    <div ng-if="editingExisting && !editingDefaults" class="pull-left">
       <button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation">
         <span class="fas fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
       </button>

+ 37 - 35
gui/default/untrusted/syncthing/folder/editFolderModalView.html

@@ -1,24 +1,24 @@
-<modal id="editFolder" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-folder'}}" heading="{{editingExisting ? 'Edit Folder' : 'Add Folder' | translate}} ({{folderLabel(currentFolder.id)}})" large="yes" closeable="yes">
+<modal id="editFolder" status="default" icon="{{editFolderModalIcon()}}" heading="{{editFolderModalTitle()}}" large="yes" closeable="yes">
   <div class="modal-body">
     <form role="form" name="folderEditor">
       <ul class="nav nav-tabs" ng-init="loadFormIntoScope(folderEditor)">
         <li class="active"><a data-toggle="tab" href="#folder-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
         <li><a data-toggle="tab" href="#folder-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
         <li><a data-toggle="tab" href="#folder-versioning"><span class="fas fa-copy"></span> <span translate>File Versioning</span></a></li>
-        <li><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
+        <li ng-if="!editingDefaults"><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
         <li><a data-toggle="tab" href="#folder-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
       </ul>
       <div class="tab-content">
 
         <div id="folder-general" class="tab-pane in active">
-          <div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty}">
+          <div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty && !editingDefaults}">
             <label for="folderLabel"><span translate>Folder Label</span></label>
             <input name="folderLabel" id="folderLabel" class="form-control" type="text" ng-model="currentFolder.label" value="{{currentFolder.label}}" />
             <p class="help-block">
               <span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
             </p>
           </div>
-          <div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
+          <div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
             <label for="folderID"><span translate>Folder ID</span></label>
             <input name="folderID" ng-readonly="editingExisting || (!editingExisting && currentFolder.viewFlags.importFromOtherDevice)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />
             <p class="help-block">
@@ -28,19 +28,21 @@
               <span translate ng-show="!editingExisting">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</span>
             </p>
           </div>
-          <div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
+          <div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty && !editingDefaults}">
             <label translate for="folderPath">Folder Path</label>
-            <input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required="" aria-required="true" path-is-sub-dir />
+            <input name="folderPath" ng-readonly="editingExisting && !editingDefaults" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" ng-required="!editingDefaults" ng-aria-required="!editingDefaults" path-is-sub-dir />
             <datalist id="directory-list">
               <option ng-repeat="directory in directoryList" value="{{ directory }}" />
             </datalist>
             <p class="help-block">
               <span ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine"><span translate>Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.</br></span>
-              <span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty">The folder path cannot be blank.</span>
+              <span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty && !editingDefaults">The folder path cannot be blank.</span>
               <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length == 0">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
               <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length != 0">Warning, this path is a subdirectory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
-              <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
-              <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
+              <span ng-if="folderPathErrors.isParent && !editingDefaults">
+                <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
+                <span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
+              </span>
             </p>
           </div>
         </div>
@@ -76,7 +78,7 @@
         <div id="folder-versioning" class="tab-pane">
           <div class="form-group">
             <label translate>File Versioning</label>&emsp;<a href="https://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
-            <select class="form-control" ng-model="currentFolder.fileVersioningSelector">
+            <select class="form-control" ng-model="currentFolder._guiVersioning.selector">
               <option value="none" translate>No File Versioning</option>
               <option value="trashcan" translate>Trash Can File Versioning</option>
               <option value="simple" translate>Simple File Versioning</option>
@@ -84,46 +86,46 @@
               <option value="external" translate>External File Versioning</option>
             </select>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='trashcan' || currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selectorector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.trashcanClean.$invalid && folderEditor._guiVersioning.trashcanClean.$dirty}">
             <p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
             <label translate for="trashcanClean">Clean out after</label>
             <div class="input-group">
-              <input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required="" aria-required="true" min="0" />
+              <input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.trashcanClean" required="" aria-required="true" min="0" />
               <div class="input-group-addon" translate>days</div>
             </div>
             <p class="help-block">
-              <span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
-              <span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
-              <span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
+              <span translate ng-if="folderEditor._guiVersioning.trashcanClean.$valid || folderEditor._guiVersioning.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
+              <span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.required && folderEditor._guiVersioning.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
+              <span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.min && folderEditor._guiVersioning.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
             </p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.simpleKeep.$invalid && folderEditor._guiVersioning.simpleKeep.$dirty}">
             <p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
             <label translate for="simpleKeep">Keep Versions</label>
-            <input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required="" aria-required="true" min="1" />
+            <input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder._guiVersioning.simpleKeep" required="" aria-required="true" min="1" />
             <p class="help-block">
-              <span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
-              <span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
-              <span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
+              <span translate ng-if="folderEditor._guiVersioning.simpleKeep.$valid || folderEditor._guiVersioning.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
+              <span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.required && folderEditor._guiVersioning.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
+              <span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.min && folderEditor._guiVersioning.simpleKeep.$dirty">You must keep at least one version.</span>
             </p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor._guiVersioning.staggeredMaxAge.$invalid && folderEditor._guiVersioning.staggeredMaxAge.$dirty}">
             <p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
             <p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
             <label translate for="staggeredMaxAge">Maximum Age</label>
-            <input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder.staggeredMaxAge" required="" aria-required="true" min="0" />
+            <input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder._guiVersioning.staggeredMaxAge" required="" aria-required="true" min="0" />
             <p class="help-block">
-              <span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
-              <span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
-              <span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
+              <span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$valid || folderEditor._guiVersioning.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
+              <span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.required && folderEditor._guiVersioning.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
+              <span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.min && folderEditor._guiVersioning.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
             </p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector == 'staggered'">
             <label translate for="staggeredVersionsPath">Versions Path</label>
-            <input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath" />
+            <input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder._guiVersioning.staggeredVersionsPath" />
             <p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).</p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
             <p translate class="help-block">An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.</p>
             <label translate for="externalCommand">Command</label>
             <input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required="" aria-required="true" />
@@ -132,21 +134,21 @@
               <span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
             </p>
           </div>
-          <div class="form-group" ng-if="currentFolder.fileVersioningSelector != 'none'" ng-class="{'has-error': folderEditor.versioningCleanupIntervalS.$invalid && folderEditor.versioningCleanupIntervalS.$dirty}">
+          <div class="form-group" ng-if="currentFolder._guiVersioning.selector != 'none'" ng-class="{'has-error': folderEditor._guiVersioning.cleanupIntervalS.$invalid && folderEditor._guiVersioning.cleanupIntervalS.$dirty}">
             <label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
             <div class="input-group">
-              <input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder.versioningCleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
+              <input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
               <div class="input-group-addon" translate>seconds</div>
             </div>
             <p class="help-block">
-              <span translate ng-if="folderEditor.versioningCleanupIntervalS.$valid || folderEditor.versioningCleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
-              <span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.required && folderEditor.versioningCleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
-              <span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.min && folderEditor.versioningCleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
+              <span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$valid || folderEditor._guiVersioning.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
+              <span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.required && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
+              <span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.min && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
             </p>
           </div>
         </div>
 
-        <div id="folder-ignores" class="tab-pane">
+        <div ng-if="!editingDefaults" id="folder-ignores" class="tab-pane">
           <p translate>Enter ignore patterns, one per line.</p>
           <div ng-class="{'has-error': ignores.error != null}">
             <textarea class="form-control" rows="5" ng-model="ignores.text" ng-disabled="ignores.disabled"></textarea>
@@ -271,7 +273,7 @@
     <button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
       <span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
     </button>
-    <button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting">
+    <button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting && !editingDefaults">
       <span class="fas fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
     </button>
   </div>

+ 2 - 0
lib/api/api.go

@@ -302,6 +302,8 @@ func (s *service) Serve(ctx context.Context) error {
 	configBuilder.registerDevices("/rest/config/devices")
 	configBuilder.registerFolder("/rest/config/folders/:id")
 	configBuilder.registerDevice("/rest/config/devices/:id")
+	configBuilder.registerDefaultFolder("/rest/config/defaults/folder")
+	configBuilder.registerDefaultDevice("/rest/config/defaults/device")
 	configBuilder.registerOptions("/rest/config/options")
 	configBuilder.registerLDAP("/rest/config/ldap")
 	configBuilder.registerGUI("/rest/config/gui")

+ 45 - 9
lib/api/confighandler.go

@@ -73,7 +73,7 @@ func (c *configMuxBuilder) registerFolders(path string) {
 	})
 
 	c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) {
-		c.adjustFolder(w, r, config.FolderConfiguration{})
+		c.adjustFolder(w, r, config.FolderConfiguration{}, false)
 	})
 }
 
@@ -126,7 +126,7 @@ func (c *configMuxBuilder) registerFolder(path string) {
 	})
 
 	c.Handle(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
-		c.adjustFolder(w, r, config.FolderConfiguration{})
+		c.adjustFolder(w, r, config.FolderConfiguration{}, false)
 	})
 
 	c.Handle(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
@@ -135,7 +135,7 @@ func (c *configMuxBuilder) registerFolder(path string) {
 			http.Error(w, "No folder with given ID", http.StatusNotFound)
 			return
 		}
-		c.adjustFolder(w, r, folder)
+		c.adjustFolder(w, r, folder, false)
 	})
 
 	c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
@@ -170,12 +170,12 @@ func (c *configMuxBuilder) registerDevice(path string) {
 	})
 
 	c.Handle(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
-		c.adjustDevice(w, r, config.DeviceConfiguration{})
+		c.adjustDevice(w, r, config.DeviceConfiguration{}, false)
 	})
 
 	c.Handle(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
 		if device, ok := deviceFromParams(w, p); ok {
-			c.adjustDevice(w, r, device)
+			c.adjustDevice(w, r, device, false)
 		}
 	})
 
@@ -190,6 +190,34 @@ func (c *configMuxBuilder) registerDevice(path string) {
 	})
 }
 
+func (c *configMuxBuilder) registerDefaultFolder(path string) {
+	c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
+		sendJSON(w, c.cfg.DefaultFolder())
+	})
+
+	c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
+		c.adjustFolder(w, r, config.FolderConfiguration{}, true)
+	})
+
+	c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
+		c.adjustFolder(w, r, c.cfg.DefaultFolder(), true)
+	})
+}
+
+func (c *configMuxBuilder) registerDefaultDevice(path string) {
+	c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
+		sendJSON(w, c.cfg.DefaultDevice())
+	})
+
+	c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
+		c.adjustDevice(w, r, config.DeviceConfiguration{}, true)
+	})
+
+	c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
+		c.adjustDevice(w, r, c.cfg.DefaultDevice(), true)
+	})
+}
+
 func (c *configMuxBuilder) registerOptions(path string) {
 	c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
 		sendJSON(w, c.cfg.Options())
@@ -260,13 +288,17 @@ func (c *configMuxBuilder) adjustConfig(w http.ResponseWriter, r *http.Request)
 	c.finish(w, waiter)
 }
 
-func (c *configMuxBuilder) adjustFolder(w http.ResponseWriter, r *http.Request, folder config.FolderConfiguration) {
+func (c *configMuxBuilder) adjustFolder(w http.ResponseWriter, r *http.Request, folder config.FolderConfiguration, defaults bool) {
 	if err := unmarshalTo(r.Body, &folder); err != nil {
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
 	waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
-		cfg.SetFolder(folder)
+		if defaults {
+			cfg.Defaults.Folder = folder
+		} else {
+			cfg.SetFolder(folder)
+		}
 	})
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -275,13 +307,17 @@ func (c *configMuxBuilder) adjustFolder(w http.ResponseWriter, r *http.Request,
 	c.finish(w, waiter)
 }
 
-func (c *configMuxBuilder) adjustDevice(w http.ResponseWriter, r *http.Request, device config.DeviceConfiguration) {
+func (c *configMuxBuilder) adjustDevice(w http.ResponseWriter, r *http.Request, device config.DeviceConfiguration, defaults bool) {
 	if err := unmarshalTo(r.Body, &device); err != nil {
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
 	waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
-		cfg.SetDevice(device)
+		if defaults {
+			cfg.Defaults.Device = device
+		} else {
+			cfg.SetDevice(device)
+		}
 	})
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)

+ 16 - 1
lib/api/mocked_config_test.go

@@ -91,7 +91,6 @@ func (c *mockedConfig) RemoveFolder(id string) (config.Waiter, error) {
 func (c *mockedConfig) FolderPasswords(device protocol.DeviceID) map[string]string {
 	return nil
 }
-
 func (c *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
 	return config.DeviceConfiguration{}, false
 }
@@ -112,6 +111,22 @@ func (c *mockedConfig) IgnoredFolder(device protocol.DeviceID, folder string) bo
 	return false
 }
 
+func (c *mockedConfig) DefaultFolder() config.FolderConfiguration {
+	return config.FolderConfiguration{}
+}
+
+func (c *mockedConfig) SetDefaultFolder(config.FolderConfiguration) (config.Waiter, error) {
+	return noopWaiter{}, nil
+}
+
+func (c *mockedConfig) DefaultDevice() config.DeviceConfiguration {
+	return config.DeviceConfiguration{}
+}
+
+func (c *mockedConfig) SetDefaultDevice(config.DeviceConfiguration) (config.Waiter, error) {
+	return noopWaiter{}, nil
+}
+
 func (c *mockedConfig) GlobalDiscoveryServers() []string {
 	return nil
 }

+ 19 - 2
lib/config/config.go

@@ -30,7 +30,7 @@ import (
 
 const (
 	OldestHandledVersion = 10
-	CurrentVersion       = 33
+	CurrentVersion       = 34
 	MaxRescanIntervalS   = 365 * 24 * 60 * 60
 )
 
@@ -234,6 +234,8 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
 
 	cfg.prepareIgnoredDevices(existingDevices)
 
+	cfg.Defaults.prepare(myID, existingDevices)
+
 	cfg.removeDeprecatedProtocols()
 
 	util.FillNilExceptDeprecated(cfg)
@@ -245,7 +247,6 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) error {
 }
 
 func (cfg *Configuration) ensureMyDevice(myID protocol.DeviceID) {
-	// Ensure this device is present in the config
 	for _, device := range cfg.Devices {
 		if device.DeviceID == myID {
 			return
@@ -586,3 +587,19 @@ func getFreePort(host string, ports ...int) (int, error) {
 	c.Close()
 	return addr.Port, nil
 }
+
+func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
+	ensureZeroForNodefault(&FolderConfiguration{}, &defaults.Folder)
+	ensureZeroForNodefault(&DeviceConfiguration{}, &defaults.Device)
+	defaults.Folder.prepare(myID, existingDevices)
+	defaults.Device.prepare(nil)
+}
+
+func ensureZeroForNodefault(empty interface{}, target interface{}) {
+	util.CopyMatchingTag(empty, target, "nodefault", func(v string) bool {
+		if len(v) > 0 && v != "true" {
+			panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "true"`, v))
+		}
+		return len(v) > 0
+	})
+}

+ 302 - 37
lib/config/config.pb.go

@@ -32,6 +32,7 @@ type Configuration struct {
 	Options                  OptionsConfiguration  `protobuf:"bytes,6,opt,name=options,proto3" json:"options" xml:"options"`
 	IgnoredDevices           []ObservedDevice      `protobuf:"bytes,7,rep,name=ignored_devices,json=ignoredDevices,proto3" json:"remoteIgnoredDevices" xml:"remoteIgnoredDevice"`
 	DeprecatedPendingDevices []ObservedDevice      `protobuf:"bytes,8,rep,name=pending_devices,json=pendingDevices,proto3" json:"-" xml:"pendingDevice,omitempty"` // Deprecated: Do not use.
+	Defaults                 Defaults              `protobuf:"bytes,9,opt,name=defaults,proto3" json:"defaults" xml:"defaults"`
 }
 
 func (m *Configuration) Reset()         { *m = Configuration{} }
@@ -67,50 +68,94 @@ func (m *Configuration) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_Configuration proto.InternalMessageInfo
 
+type Defaults struct {
+	Folder FolderConfiguration `protobuf:"bytes,1,opt,name=folder,proto3" json:"folder" xml:"folder"`
+	Device DeviceConfiguration `protobuf:"bytes,2,opt,name=device,proto3" json:"device" xml:"device"`
+}
+
+func (m *Defaults) Reset()         { *m = Defaults{} }
+func (m *Defaults) String() string { return proto.CompactTextString(m) }
+func (*Defaults) ProtoMessage()    {}
+func (*Defaults) Descriptor() ([]byte, []int) {
+	return fileDescriptor_baadf209193dc627, []int{1}
+}
+func (m *Defaults) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Defaults) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Defaults.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Defaults) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Defaults.Merge(m, src)
+}
+func (m *Defaults) XXX_Size() int {
+	return m.ProtoSize()
+}
+func (m *Defaults) XXX_DiscardUnknown() {
+	xxx_messageInfo_Defaults.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Defaults proto.InternalMessageInfo
+
 func init() {
 	proto.RegisterType((*Configuration)(nil), "config.Configuration")
+	proto.RegisterType((*Defaults)(nil), "config.Defaults")
 }
 
 func init() { proto.RegisterFile("lib/config/config.proto", fileDescriptor_baadf209193dc627) }
 
 var fileDescriptor_baadf209193dc627 = []byte{
-	// 575 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0x4d, 0x8b, 0xd3, 0x5e,
-	0x14, 0xc6, 0x93, 0x7f, 0x3b, 0xed, 0x7f, 0x32, 0x6f, 0x10, 0x45, 0x53, 0x5f, 0x72, 0x6b, 0xa8,
-	0x52, 0x65, 0xec, 0xc0, 0xb8, 0x11, 0x77, 0xd6, 0xe2, 0x58, 0x14, 0x1c, 0x02, 0x23, 0xea, 0x46,
-	0xda, 0xe6, 0x4e, 0x7a, 0xa1, 0xcd, 0x0d, 0xc9, 0x4d, 0x99, 0xf9, 0x08, 0xee, 0xc4, 0x4f, 0xe0,
-	0xd6, 0x6f, 0xd2, 0x5d, 0xbb, 0x70, 0xe1, 0xea, 0xc2, 0xb4, 0xbb, 0x2c, 0xb3, 0x74, 0x25, 0xf7,
-	0xad, 0x26, 0x10, 0x5d, 0x35, 0xe7, 0x3c, 0xcf, 0xf9, 0x9d, 0xc3, 0xd3, 0xc4, 0xb8, 0x39, 0x41,
-	0xc3, 0xa3, 0x11, 0x0e, 0xce, 0x91, 0x2f, 0x7f, 0x3a, 0x61, 0x84, 0x09, 0x36, 0x6b, 0xa2, 0xba,
-	0xd5, 0xca, 0x19, 0xce, 0xf1, 0xc4, 0x83, 0x91, 0x28, 0x92, 0x68, 0x40, 0x10, 0x0e, 0x84, 0xbb,
-	0xe0, 0xf2, 0xe0, 0x0c, 0x8d, 0x60, 0x99, 0xeb, 0x5e, 0xce, 0xe5, 0x27, 0xa8, 0xcc, 0xe2, 0xe4,
-	0x2c, 0x13, 0x6f, 0x10, 0x96, 0x79, 0xee, 0xe7, 0x3c, 0x38, 0x64, 0x42, 0x5c, 0x66, 0x6b, 0xe4,
-	0x6d, 0xc3, 0x18, 0x46, 0x33, 0xe8, 0x49, 0x69, 0x1b, 0x5e, 0x10, 0xf1, 0xe8, 0xfc, 0xa8, 0x19,
-	0x7b, 0x2f, 0xf2, 0xd3, 0xa6, 0x6b, 0xd4, 0x67, 0x30, 0x8a, 0x11, 0x0e, 0x2c, 0xbd, 0xa9, 0xb7,
-	0xb7, 0xba, 0x4f, 0x53, 0x0a, 0x54, 0x2b, 0xa3, 0xc0, 0xbc, 0x98, 0x4e, 0x9e, 0x39, 0xb2, 0x3e,
-	0x1c, 0x10, 0x12, 0x39, 0xbf, 0x28, 0xa8, 0xa0, 0x80, 0xa4, 0x8b, 0xd6, 0x6e, 0xbe, 0xef, 0xaa,
-	0x29, 0xf3, 0x9d, 0x51, 0x17, 0xe1, 0xc5, 0xd6, 0x7f, 0xcd, 0x4a, 0x7b, 0xe7, 0xf8, 0x76, 0x47,
-	0xa6, 0xfd, 0x92, 0xb7, 0x0b, 0x17, 0x74, 0xc1, 0x9c, 0x02, 0x8d, 0x2d, 0x95, 0x33, 0x19, 0x05,
-	0xbb, 0x7c, 0xa9, 0xa8, 0x1d, 0x57, 0x09, 0x8c, 0x2b, 0xe2, 0x8e, 0xad, 0x4a, 0x91, 0xdb, 0xe3,
-	0xed, 0xbf, 0x70, 0xe5, 0xcc, 0x86, 0x2b, 0x6a, 0xc7, 0x55, 0x82, 0xe9, 0x1a, 0x15, 0x3f, 0x41,
-	0x56, 0xb5, 0xa9, 0xb7, 0x77, 0x8e, 0x2d, 0xc5, 0x3c, 0x39, 0xeb, 0x17, 0x81, 0x0f, 0x18, 0x70,
-	0x45, 0x41, 0xe5, 0xe4, 0xac, 0x9f, 0x52, 0xc0, 0x66, 0x32, 0x0a, 0xb6, 0x39, 0xd3, 0x4f, 0x90,
-	0xf3, 0x75, 0xd9, 0x62, 0x92, 0xcb, 0x04, 0xf3, 0x83, 0x51, 0x65, 0xff, 0xa8, 0xb5, 0xc5, 0xa1,
-	0x0d, 0x05, 0x7d, 0xd3, 0x7b, 0x7e, 0x5a, 0xa4, 0x3e, 0x92, 0xd4, 0x2a, 0x93, 0x52, 0x0a, 0xf8,
-	0x58, 0x46, 0x81, 0xc1, 0xb9, 0xac, 0x60, 0x60, 0xae, 0xba, 0x5c, 0x33, 0xdf, 0x1b, 0x75, 0xf9,
-	0x22, 0x58, 0x35, 0x4e, 0xbf, 0xa3, 0xe8, 0x6f, 0x45, 0xbb, 0xb8, 0xa0, 0xa9, 0x72, 0x90, 0x43,
-	0x19, 0x05, 0x7b, 0x9c, 0x2d, 0x6b, 0xc7, 0x55, 0x8a, 0xf9, 0x5d, 0x37, 0x0e, 0x90, 0x1f, 0xe0,
-	0x08, 0x7a, 0x9f, 0x54, 0xd2, 0x75, 0x9e, 0xf4, 0x8d, 0xcd, 0x0a, 0xf9, 0x6e, 0x89, 0xc4, 0xbb,
-	0x63, 0x09, 0xbf, 0x1e, 0xc1, 0x29, 0x26, 0xb0, 0x2f, 0x86, 0x7b, 0x9b, 0xc4, 0x1b, 0x7c, 0x53,
-	0x89, 0xe8, 0xa4, 0x8b, 0xd6, 0xb5, 0x92, 0x7e, 0xb6, 0x68, 0x95, 0xb2, 0xdc, 0x7d, 0x54, 0xa8,
-	0xcd, 0xcf, 0xba, 0x71, 0x10, 0xc2, 0xc0, 0x43, 0x81, 0xbf, 0xb9, 0xf5, 0xff, 0x7f, 0xde, 0xfa,
-	0x4a, 0x26, 0x6d, 0xf5, 0x60, 0x18, 0xc1, 0xd1, 0x80, 0x40, 0xef, 0x54, 0x00, 0x24, 0x33, 0xa5,
-	0x40, 0x7f, 0x9c, 0x51, 0x70, 0x97, 0x1f, 0x1d, 0xe6, 0xb5, 0x43, 0x3c, 0x45, 0x04, 0x4e, 0x43,
-	0x72, 0xe9, 0x58, 0xba, 0xbb, 0x5f, 0xd0, 0xe2, 0xee, 0xeb, 0xf9, 0x95, 0xad, 0x2d, 0xaf, 0x6c,
-	0x6d, 0xbe, 0xb2, 0xf5, 0xe5, 0xca, 0xd6, 0xbf, 0xac, 0x6d, 0xed, 0xdb, 0xda, 0xd6, 0x97, 0x6b,
-	0x5b, 0xfb, 0xb9, 0xb6, 0xb5, 0x8f, 0x0f, 0x7d, 0x44, 0xc6, 0xc9, 0xb0, 0x33, 0xc2, 0xd3, 0xa3,
-	0xf8, 0x32, 0x18, 0x91, 0x31, 0x0a, 0xfc, 0xdc, 0xd3, 0x9f, 0xaf, 0x77, 0x58, 0xe3, 0x9f, 0xea,
-	0x93, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x97, 0x39, 0xe5, 0x72, 0xad, 0x04, 0x00, 0x00,
+	// 654 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xcd, 0x6e, 0xd3, 0x40,
+	0x10, 0xc7, 0xed, 0xa6, 0x4d, 0xda, 0xed, 0x17, 0x32, 0x08, 0x5c, 0x3e, 0xbc, 0x61, 0x15, 0x50,
+	0x41, 0xa5, 0x95, 0xca, 0x05, 0x71, 0x23, 0x44, 0x94, 0x0a, 0x24, 0x2a, 0xa3, 0x22, 0xe0, 0x82,
+	0x92, 0x78, 0xeb, 0xae, 0x94, 0xd8, 0x96, 0xbd, 0xae, 0xda, 0x47, 0xe0, 0x86, 0x78, 0x02, 0x4e,
+	0x48, 0xdc, 0x79, 0x88, 0xdc, 0x92, 0x23, 0xa7, 0x95, 0x9a, 0xdc, 0x7c, 0xf4, 0x91, 0x13, 0xda,
+	0x0f, 0xbb, 0xb6, 0x6a, 0xe0, 0x64, 0xcf, 0xfc, 0xff, 0xf3, 0x9b, 0xd5, 0x78, 0xc7, 0xe0, 0xc6,
+	0x80, 0xf4, 0x76, 0xfa, 0xbe, 0x77, 0x44, 0x5c, 0xf5, 0xd8, 0x0e, 0x42, 0x9f, 0xfa, 0x46, 0x5d,
+	0x46, 0x37, 0x5b, 0x05, 0xc3, 0x91, 0x3f, 0x70, 0x70, 0x28, 0x83, 0x38, 0xec, 0x52, 0xe2, 0x7b,
+	0xd2, 0x5d, 0x72, 0x39, 0xf8, 0x84, 0xf4, 0x71, 0x95, 0xeb, 0x6e, 0xc1, 0xe5, 0xc6, 0xa4, 0xca,
+	0x82, 0x0a, 0x96, 0x81, 0xd3, 0x0d, 0xaa, 0x3c, 0xf7, 0x0a, 0x1e, 0x3f, 0xe0, 0x42, 0x54, 0x65,
+	0xdb, 0x28, 0xda, 0x7a, 0x11, 0x0e, 0x4f, 0xb0, 0xa3, 0xa4, 0x25, 0x7c, 0x4a, 0xe5, 0x2b, 0xfa,
+	0xde, 0x00, 0xab, 0xcf, 0x8b, 0xd5, 0x86, 0x0d, 0x1a, 0x27, 0x38, 0x8c, 0x88, 0xef, 0x99, 0x7a,
+	0x53, 0xdf, 0x5c, 0x68, 0x3f, 0x49, 0x18, 0xcc, 0x52, 0x29, 0x83, 0xc6, 0xe9, 0x70, 0xf0, 0x14,
+	0xa9, 0x78, 0xab, 0x4b, 0x69, 0x88, 0x7e, 0x33, 0x58, 0x23, 0x1e, 0x4d, 0xc6, 0xad, 0x95, 0x62,
+	0xde, 0xce, 0xaa, 0x8c, 0x77, 0xa0, 0x21, 0x87, 0x17, 0x99, 0x73, 0xcd, 0xda, 0xe6, 0xf2, 0xee,
+	0xad, 0x6d, 0x35, 0xed, 0x17, 0x22, 0x5d, 0x3a, 0x41, 0x1b, 0x8e, 0x18, 0xd4, 0x78, 0x53, 0x55,
+	0x93, 0x32, 0xb8, 0x22, 0x9a, 0xca, 0x18, 0xd9, 0x99, 0xc0, 0xb9, 0x72, 0xdc, 0x91, 0x59, 0x2b,
+	0x73, 0x3b, 0x22, 0xfd, 0x17, 0xae, 0xaa, 0xc9, 0xb9, 0x32, 0x46, 0x76, 0x26, 0x18, 0x36, 0xa8,
+	0xb9, 0x31, 0x31, 0xe7, 0x9b, 0xfa, 0xe6, 0xf2, 0xae, 0x99, 0x31, 0xf7, 0x0e, 0xf7, 0xcb, 0xc0,
+	0xfb, 0x1c, 0x38, 0x65, 0xb0, 0xb6, 0x77, 0xb8, 0x9f, 0x30, 0xc8, 0x6b, 0x52, 0x06, 0x97, 0x04,
+	0xd3, 0x8d, 0x09, 0xfa, 0x3a, 0x69, 0x71, 0xc9, 0xe6, 0x82, 0xf1, 0x01, 0xcc, 0xf3, 0x2f, 0x6a,
+	0x2e, 0x08, 0xe8, 0x46, 0x06, 0x7d, 0xdd, 0x79, 0x76, 0x50, 0xa6, 0x3e, 0x54, 0xd4, 0x79, 0x2e,
+	0x25, 0x0c, 0x8a, 0xb2, 0x94, 0x41, 0x20, 0xb8, 0x3c, 0xe0, 0x60, 0xa1, 0xda, 0x42, 0x33, 0xde,
+	0x83, 0x86, 0xba, 0x08, 0x66, 0x5d, 0xd0, 0x6f, 0x67, 0xf4, 0x37, 0x32, 0x5d, 0x6e, 0xd0, 0xcc,
+	0xe6, 0xa0, 0x8a, 0x52, 0x06, 0x57, 0x05, 0x5b, 0xc5, 0xc8, 0xce, 0x14, 0xe3, 0x87, 0x0e, 0xd6,
+	0x89, 0xeb, 0xf9, 0x21, 0x76, 0x3e, 0x65, 0x93, 0x6e, 0x88, 0x49, 0x5f, 0xcf, 0x5b, 0xa8, 0xbb,
+	0x25, 0x27, 0xde, 0x3e, 0x56, 0xf0, 0x6b, 0x21, 0x1e, 0xfa, 0x14, 0xef, 0xcb, 0xe2, 0x4e, 0x3e,
+	0xf1, 0x0d, 0xd1, 0xa9, 0x42, 0x44, 0xc9, 0xb8, 0x75, 0xb5, 0x22, 0x9f, 0x8e, 0x5b, 0x95, 0x2c,
+	0x7b, 0x8d, 0x94, 0x62, 0xe3, 0xb3, 0x0e, 0xd6, 0x03, 0xec, 0x39, 0xc4, 0x73, 0xf3, 0xb3, 0x2e,
+	0xfe, 0xf3, 0xac, 0x2f, 0xd5, 0xa4, 0xcd, 0x0e, 0x0e, 0x42, 0xdc, 0xef, 0x52, 0xec, 0x1c, 0x48,
+	0x80, 0x62, 0x26, 0x0c, 0xea, 0x8f, 0x52, 0x06, 0xef, 0x88, 0x43, 0x07, 0x45, 0x6d, 0xcb, 0x1f,
+	0x12, 0x8a, 0x87, 0x01, 0x3d, 0x43, 0xa6, 0x6e, 0xaf, 0x95, 0xb4, 0xc8, 0x38, 0x00, 0x8b, 0x0e,
+	0x3e, 0xea, 0xc6, 0x03, 0x1a, 0x99, 0x4b, 0xe2, 0x93, 0x5c, 0xb9, 0xb8, 0x99, 0x32, 0xdf, 0x46,
+	0x6a, 0x52, 0xb9, 0x33, 0x65, 0x70, 0x4d, 0xdd, 0x47, 0x99, 0x40, 0x76, 0xae, 0xa1, 0x9f, 0x3a,
+	0x58, 0xcc, 0x4a, 0x8d, 0xb7, 0xa0, 0x2e, 0x57, 0x40, 0xac, 0xe8, 0x7f, 0xd6, 0xc9, 0x52, 0x7d,
+	0x54, 0xc9, 0xa5, 0x6d, 0x52, 0x79, 0x0e, 0x95, 0x63, 0x33, 0xe7, 0xca, 0xd0, 0xaa, 0x5d, 0xca,
+	0xa1, 0xb2, 0xe4, 0xd2, 0x2a, 0xa9, 0x7c, 0xfb, 0xd5, 0xe8, 0xdc, 0xd2, 0x26, 0xe7, 0x96, 0x36,
+	0x9a, 0x5a, 0xfa, 0x64, 0x6a, 0xe9, 0x5f, 0x66, 0x96, 0xf6, 0x6d, 0x66, 0xe9, 0x93, 0x99, 0xa5,
+	0xfd, 0x9a, 0x59, 0xda, 0xc7, 0x07, 0x2e, 0xa1, 0xc7, 0x71, 0x6f, 0xbb, 0xef, 0x0f, 0x77, 0xa2,
+	0x33, 0xaf, 0x4f, 0x8f, 0x89, 0xe7, 0x16, 0xde, 0x2e, 0x7e, 0x63, 0xbd, 0xba, 0xf8, 0x67, 0x3d,
+	0xfe, 0x13, 0x00, 0x00, 0xff, 0xff, 0x50, 0xe9, 0xd5, 0x50, 0xb6, 0x05, 0x00, 0x00,
 }
 
 func (m *Configuration) Marshal() (dAtA []byte, err error) {
@@ -133,6 +178,16 @@ func (m *Configuration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	{
+		size, err := m.Defaults.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintConfig(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0x4a
 	if len(m.DeprecatedPendingDevices) > 0 {
 		for iNdEx := len(m.DeprecatedPendingDevices) - 1; iNdEx >= 0; iNdEx-- {
 			{
@@ -227,6 +282,49 @@ func (m *Configuration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *Defaults) Marshal() (dAtA []byte, err error) {
+	size := m.ProtoSize()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Defaults) MarshalTo(dAtA []byte) (int, error) {
+	size := m.ProtoSize()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Defaults) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	{
+		size, err := m.Device.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintConfig(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0x12
+	{
+		size, err := m.Folder.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintConfig(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
 func encodeVarintConfig(dAtA []byte, offset int, v uint64) int {
 	offset -= sovConfig(v)
 	base := offset
@@ -277,6 +375,21 @@ func (m *Configuration) ProtoSize() (n int) {
 			n += 1 + l + sovConfig(uint64(l))
 		}
 	}
+	l = m.Defaults.ProtoSize()
+	n += 1 + l + sovConfig(uint64(l))
+	return n
+}
+
+func (m *Defaults) ProtoSize() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Folder.ProtoSize()
+	n += 1 + l + sovConfig(uint64(l))
+	l = m.Device.ProtoSize()
+	n += 1 + l + sovConfig(uint64(l))
 	return n
 }
 
@@ -569,6 +682,158 @@ func (m *Configuration) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 9:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Defaults", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowConfig
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthConfig
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthConfig
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Defaults.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipConfig(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthConfig
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthConfig
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *Defaults) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowConfig
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Defaults: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Defaults: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Folder", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowConfig
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthConfig
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthConfig
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Folder.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowConfig
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthConfig
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthConfig
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Device.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipConfig(dAtA[iNdEx:])

+ 109 - 46
lib/config/config_test.go

@@ -40,52 +40,93 @@ func init() {
 }
 
 func TestDefaultValues(t *testing.T) {
-	expected := OptionsConfiguration{
-		RawListenAddresses:      []string{"default"},
-		RawGlobalAnnServers:     []string{"default"},
-		GlobalAnnEnabled:        true,
-		LocalAnnEnabled:         true,
-		LocalAnnPort:            21027,
-		LocalAnnMCAddr:          "[ff12::8384]:21027",
-		MaxSendKbps:             0,
-		MaxRecvKbps:             0,
-		ReconnectIntervalS:      60,
-		RelaysEnabled:           true,
-		RelayReconnectIntervalM: 10,
-		StartBrowser:            true,
-		NATEnabled:              true,
-		NATLeaseM:               60,
-		NATRenewalM:             30,
-		NATTimeoutS:             10,
-		RestartOnWakeup:         true,
-		AutoUpgradeIntervalH:    12,
-		KeepTemporariesH:        24,
-		CacheIgnoredFiles:       false,
-		ProgressUpdateIntervalS: 5,
-		LimitBandwidthInLan:     false,
-		MinHomeDiskFree:         Size{1, "%"},
-		URURL:                   "https://data.syncthing.net/newdata",
-		URInitialDelayS:         1800,
-		URPostInsecurely:        false,
-		ReleasesURL:             "https://upgrades.syncthing.net/meta.json",
-		AlwaysLocalNets:         []string{},
-		OverwriteRemoteDevNames: false,
-		TempIndexMinBlocks:      10,
-		UnackedNotificationIDs:  []string{"authenticationUserAndPassword"},
-		DefaultFolderPath:       "~",
-		SetLowPriority:          true,
-		CRURL:                   "https://crash.syncthing.net/newcrash",
-		CREnabled:               true,
-		StunKeepaliveStartS:     180,
-		StunKeepaliveMinS:       20,
-		RawStunServers:          []string{"default"},
-		AnnounceLANAddresses:    true,
-		FeatureFlags:            []string{},
+	size, err := ParseSize("1%")
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := Configuration{
+		Version: CurrentVersion,
+		Folders: []FolderConfiguration{},
+		Options: OptionsConfiguration{
+			RawListenAddresses:      []string{"default"},
+			RawGlobalAnnServers:     []string{"default"},
+			GlobalAnnEnabled:        true,
+			LocalAnnEnabled:         true,
+			LocalAnnPort:            21027,
+			LocalAnnMCAddr:          "[ff12::8384]:21027",
+			MaxSendKbps:             0,
+			MaxRecvKbps:             0,
+			ReconnectIntervalS:      60,
+			RelaysEnabled:           true,
+			RelayReconnectIntervalM: 10,
+			StartBrowser:            true,
+			NATEnabled:              true,
+			NATLeaseM:               60,
+			NATRenewalM:             30,
+			NATTimeoutS:             10,
+			RestartOnWakeup:         true,
+			AutoUpgradeIntervalH:    12,
+			KeepTemporariesH:        24,
+			CacheIgnoredFiles:       false,
+			ProgressUpdateIntervalS: 5,
+			LimitBandwidthInLan:     false,
+			MinHomeDiskFree:         Size{1, "%"},
+			URURL:                   "https://data.syncthing.net/newdata",
+			URInitialDelayS:         1800,
+			URPostInsecurely:        false,
+			ReleasesURL:             "https://upgrades.syncthing.net/meta.json",
+			AlwaysLocalNets:         []string{},
+			OverwriteRemoteDevNames: false,
+			TempIndexMinBlocks:      10,
+			UnackedNotificationIDs:  []string{"authenticationUserAndPassword"},
+			SetLowPriority:          true,
+			CRURL:                   "https://crash.syncthing.net/newcrash",
+			CREnabled:               true,
+			StunKeepaliveStartS:     180,
+			StunKeepaliveMinS:       20,
+			RawStunServers:          []string{"default"},
+			AnnounceLANAddresses:    true,
+			FeatureFlags:            []string{},
+		},
+		Defaults: Defaults{
+			Folder: FolderConfiguration{
+				FilesystemType:   fs.FilesystemTypeBasic,
+				Path:             "~",
+				Type:             FolderTypeSendReceive,
+				Devices:          []FolderDeviceConfiguration{{DeviceID: device1}},
+				RescanIntervalS:  3600,
+				FSWatcherEnabled: true,
+				FSWatcherDelayS:  10,
+				IgnorePerms:      false,
+				AutoNormalize:    true,
+				MinDiskFree:      size,
+				Versioning: VersioningConfiguration{
+					CleanupIntervalS: 3600,
+					Params:           map[string]string{},
+				},
+				MaxConflicts:         10,
+				WeakHashThresholdPct: 25,
+				MarkerName:           ".stfolder",
+				MaxConcurrentWrites:  2,
+			},
+			Device: DeviceConfiguration{
+				Addresses:       []string{"dynamic"},
+				AllowedNetworks: []string{},
+				Compression:     protocol.CompressionMetadata,
+				IgnoredFolders:  []ObservedFolder{},
+			},
+		},
+		IgnoredDevices: []ObservedDevice{},
 	}
+	expected.Devices = []DeviceConfiguration{expected.Defaults.Device.Copy()}
+	expected.Devices[0].DeviceID = device1
+	expected.Devices[0].Name, _ = os.Hostname()
 
 	cfg := New(device1)
+	cfg.GUI = GUIConfiguration{}
+	cfg.LDAP = LDAPConfiguration{}
 
-	if diff, equal := messagediff.PrettyDiff(expected, cfg.Options); !equal {
+	if diff, equal := messagediff.PrettyDiff(expected, cfg); !equal {
 		t.Errorf("Default config differs. Diff:\n%s", diff)
 	}
 }
@@ -222,7 +263,6 @@ func TestOverriddenValues(t *testing.T) {
 		OverwriteRemoteDevNames: true,
 		TempIndexMinBlocks:      100,
 		UnackedNotificationIDs:  []string{"asdfasdf"},
-		DefaultFolderPath:       "/media/syncthing",
 		SetLowPriority:          false,
 		CRURL:                   "https://localhost/newcrash",
 		CREnabled:               false,
@@ -231,6 +271,7 @@ func TestOverriddenValues(t *testing.T) {
 		RawStunServers:          []string{"foo"},
 		FeatureFlags:            []string{"feature"},
 	}
+	expectedPath := "/media/syncthing"
 
 	os.Unsetenv("STNOUPGRADE")
 	cfg, cfgCancel, err := copyAndLoad("testdata/overridenvalues.xml", device1)
@@ -242,6 +283,10 @@ func TestOverriddenValues(t *testing.T) {
 	if diff, equal := messagediff.PrettyDiff(expected, cfg.Options()); !equal {
 		t.Errorf("Overridden config differs. Diff:\n%s", diff)
 	}
+
+	if path := cfg.DefaultFolder().Path; path != expectedPath {
+		t.Errorf("Default folder path is %v, expected %v", path, expectedPath)
+	}
 }
 
 func TestDeviceAddressesDynamic(t *testing.T) {
@@ -1159,13 +1204,29 @@ func TestMaxConcurrentFolders(t *testing.T) {
 	}
 }
 
+func adjustDeviceConfiguration(cfg *DeviceConfiguration, id protocol.DeviceID, name string) {
+	cfg.DeviceID = id
+	cfg.Name = name
+}
+
+func adjustFolderConfiguration(cfg *FolderConfiguration, id, label string, fsType fs.FilesystemType, path string) {
+	cfg.ID = id
+	cfg.Label = label
+	cfg.FilesystemType = fsType
+	cfg.Path = path
+}
+
 // defaultConfigAsMap returns a valid default config as a JSON-decoded
 // map[string]interface{}. This is useful to override random elements and
 // re-encode into JSON.
 func defaultConfigAsMap() map[string]interface{} {
 	cfg := New(device1)
-	cfg.Devices = append(cfg.Devices, NewDeviceConfiguration(device2, "name"))
-	cfg.Folders = append(cfg.Folders, NewFolderConfiguration(device1, "default", "default", fs.FilesystemTypeBasic, "/tmp"))
+	dev := cfg.Defaults.Device.Copy()
+	adjustDeviceConfiguration(&dev, device2, "name")
+	cfg.Devices = append(cfg.Devices, dev)
+	folder := cfg.Defaults.Folder.Copy()
+	adjustFolderConfiguration(&folder, "default", "default", fs.FilesystemTypeBasic, "/tmp")
+	cfg.Folders = append(cfg.Folders, folder)
 	bs, err := json.Marshal(cfg)
 	if err != nil {
 		// can't happen
@@ -1260,7 +1321,9 @@ func TestInternalVersioningConfiguration(t *testing.T) {
 	// reasonable.
 
 	cfg := New(device1)
-	cfg.Folders = append(cfg.Folders, NewFolderConfiguration(device1, "default", "default", fs.FilesystemTypeBasic, "/tmp"))
+	folder := cfg.Defaults.Folder.Copy()
+	adjustFolderConfiguration(&folder, "default", "default", fs.FilesystemTypeBasic, "/tmp")
+	cfg.Folders = append(cfg.Folders, folder)
 	cfg.Folders[0].Versioning = VersioningConfiguration{
 		Type:             "foo",
 		Params:           map[string]string{"bar": "baz"},

+ 0 - 18
lib/config/deviceconfiguration.go

@@ -8,23 +8,8 @@ package config
 
 import (
 	"sort"
-
-	"github.com/syncthing/syncthing/lib/protocol"
-	"github.com/syncthing/syncthing/lib/util"
 )
 
-func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfiguration {
-	d := DeviceConfiguration{
-		DeviceID: id,
-		Name:     name,
-	}
-
-	util.SetDefaults(&d)
-
-	d.prepare(nil)
-	return d
-}
-
 func (cfg DeviceConfiguration) Copy() DeviceConfiguration {
 	c := cfg
 	c.Addresses = make([]string, len(cfg.Addresses))
@@ -40,9 +25,6 @@ func (cfg *DeviceConfiguration) prepare(sharedFolders []string) {
 	if len(cfg.Addresses) == 0 || len(cfg.Addresses) == 1 && cfg.Addresses[0] == "" {
 		cfg.Addresses = []string{"dynamic"}
 	}
-	if len(cfg.AllowedNetworks) == 0 {
-		cfg.AllowedNetworks = []string{}
-	}
 
 	ignoredFolders := deduplicateObservedFoldersToMap(cfg.IgnoredFolders)
 

+ 68 - 67
lib/config/deviceconfiguration.pb.go

@@ -26,14 +26,14 @@ var _ = math.Inf
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
 type DeviceConfiguration struct {
-	DeviceID                 github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"deviceID" xml:"id,attr"`
+	DeviceID                 github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"deviceID" xml:"id,attr" nodefault:"true"`
 	Name                     string                                               `protobuf:"bytes,2,opt,name=name,proto3" json:"name" xml:"name,attr,omitempty"`
 	Addresses                []string                                             `protobuf:"bytes,3,rep,name=addresses,proto3" json:"addresses" xml:"address,omitempty" default:"dynamic"`
 	Compression              protocol.Compression                                 `protobuf:"varint,4,opt,name=compression,proto3,enum=protocol.Compression" json:"compression" xml:"compression,attr"`
 	CertName                 string                                               `protobuf:"bytes,5,opt,name=cert_name,json=certName,proto3" json:"certName" xml:"certName,attr,omitempty"`
 	Introducer               bool                                                 `protobuf:"varint,6,opt,name=introducer,proto3" json:"introducer" xml:"introducer,attr"`
 	SkipIntroductionRemovals bool                                                 `protobuf:"varint,7,opt,name=skip_introduction_removals,json=skipIntroductionRemovals,proto3" json:"skipIntroductionRemovals" xml:"skipIntroductionRemovals,attr"`
-	IntroducedBy             github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,8,opt,name=introduced_by,json=introducedBy,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"introducedBy" xml:"introducedBy,attr"`
+	IntroducedBy             github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,8,opt,name=introduced_by,json=introducedBy,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"introducedBy" xml:"introducedBy,attr" nodefault:"true"`
 	Paused                   bool                                                 `protobuf:"varint,9,opt,name=paused,proto3" json:"paused" xml:"paused"`
 	AllowedNetworks          []string                                             `protobuf:"bytes,10,rep,name=allowed_networks,json=allowedNetworks,proto3" json:"allowedNetworks" xml:"allowedNetwork,omitempty"`
 	AutoAcceptFolders        bool                                                 `protobuf:"varint,11,opt,name=auto_accept_folders,json=autoAcceptFolders,proto3" json:"autoAcceptFolders" xml:"autoAcceptFolders"`
@@ -88,71 +88,72 @@ func init() {
 }
 
 var fileDescriptor_744b782bd13071dd = []byte{
-	// 1009 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xbd, 0x6f, 0xdb, 0x46,
-	0x1c, 0x15, 0xeb, 0xc4, 0xb6, 0xce, 0x96, 0x65, 0xd3, 0x88, 0xc3, 0x18, 0x88, 0x4e, 0x50, 0x35,
-	0x28, 0x68, 0x22, 0x17, 0x6e, 0x27, 0xa3, 0x2d, 0x50, 0xc6, 0x68, 0x63, 0x18, 0x4d, 0xdc, 0x2b,
-	0xba, 0x78, 0x61, 0x49, 0xde, 0x59, 0x39, 0x58, 0xfc, 0x28, 0x79, 0x54, 0x2c, 0xa0, 0x43, 0xc7,
-	0x16, 0xe8, 0x50, 0x64, 0xed, 0x52, 0x74, 0xe8, 0xd0, 0xff, 0xa3, 0x80, 0x37, 0x6b, 0x2c, 0x3a,
-	0x1c, 0x10, 0x7b, 0xe3, 0xc8, 0x31, 0x53, 0x71, 0x47, 0x8a, 0x3a, 0xca, 0x71, 0x50, 0x20, 0xdb,
-	0xdd, 0x7b, 0xef, 0xde, 0xef, 0x83, 0xbf, 0x3b, 0x82, 0xee, 0x90, 0x3a, 0x3b, 0x6e, 0xe0, 0x9f,
-	0xd0, 0xc1, 0x0e, 0x26, 0x23, 0xea, 0x92, 0x7c, 0x93, 0x44, 0x36, 0xa3, 0x81, 0xdf, 0x0f, 0xa3,
-	0x80, 0x05, 0xfa, 0x62, 0x0e, 0x6e, 0x6f, 0x09, 0xb5, 0x84, 0xdc, 0x60, 0xb8, 0xe3, 0x90, 0x30,
-	0xe7, 0xb7, 0xef, 0x29, 0x2e, 0x81, 0x13, 0x93, 0x68, 0x44, 0x70, 0x41, 0xd5, 0xc9, 0x19, 0xcb,
-	0x97, 0x9d, 0xbf, 0xd7, 0xc1, 0xe6, 0xbe, 0x8c, 0xf1, 0x58, 0x8d, 0xa1, 0xff, 0xa5, 0x81, 0x7a,
-	0x1e, 0xdb, 0xa2, 0xd8, 0xd0, 0xda, 0x5a, 0x6f, 0xd5, 0xfc, 0x45, 0x3b, 0xe7, 0xb0, 0xf6, 0x2f,
-	0x87, 0x1f, 0x0f, 0x28, 0x7b, 0x9e, 0x38, 0x7d, 0x37, 0xf0, 0x76, 0xe2, 0xb1, 0xef, 0xb2, 0xe7,
-	0xd4, 0x1f, 0x28, 0x2b, 0x35, 0xa3, 0x7e, 0xee, 0x7e, 0xb0, 0x7f, 0xc9, 0xe1, 0xf2, 0x74, 0x9d,
-	0x72, 0xb8, 0x8c, 0x8b, 0x75, 0xc6, 0x61, 0xe3, 0xcc, 0x1b, 0xee, 0x75, 0x28, 0x7e, 0x68, 0x33,
-	0x16, 0x75, 0xd2, 0x8b, 0xee, 0x52, 0xb1, 0xce, 0x2e, 0xba, 0xa5, 0xee, 0xa7, 0x49, 0x57, 0x7b,
-	0x39, 0xe9, 0x96, 0x1e, 0x68, 0xca, 0x60, 0xfd, 0x08, 0xdc, 0xf2, 0x6d, 0x8f, 0x18, 0xef, 0xb5,
-	0xb5, 0x5e, 0xdd, 0xfc, 0x24, 0xe5, 0x50, 0xee, 0x33, 0x0e, 0xef, 0x49, 0x67, 0xb1, 0x91, 0x7e,
-	0x0f, 0x03, 0x8f, 0x32, 0xe2, 0x85, 0x6c, 0x2c, 0xa2, 0x6c, 0xbe, 0x01, 0x47, 0xf2, 0xa4, 0x7e,
-	0x06, 0xea, 0x36, 0xc6, 0x11, 0x89, 0x63, 0x12, 0x1b, 0x0b, 0xed, 0x85, 0x5e, 0xdd, 0x3c, 0x4e,
-	0x39, 0x9c, 0x81, 0x19, 0x87, 0x0f, 0xa4, 0x77, 0x81, 0x28, 0xce, 0x6d, 0x4c, 0x4e, 0xec, 0x64,
-	0xc8, 0xf6, 0x3a, 0x78, 0xec, 0xdb, 0x1e, 0x75, 0x45, 0xac, 0x8d, 0x6b, 0xba, 0xd7, 0x17, 0xdd,
-	0xa5, 0x42, 0x80, 0x66, 0xbe, 0xfa, 0x08, 0xac, 0xb8, 0x81, 0x17, 0x8a, 0x1d, 0x0d, 0x7c, 0xe3,
-	0x56, 0x5b, 0xeb, 0xad, 0xed, 0xde, 0xe9, 0x97, 0xed, 0x7c, 0x3c, 0x23, 0xcd, 0x4f, 0x53, 0x0e,
-	0x55, 0x75, 0xc6, 0xe1, 0x96, 0x4c, 0x4a, 0xc1, 0xca, 0x9e, 0xae, 0xcf, 0x83, 0x48, 0x3d, 0xaa,
-	0x13, 0x50, 0x77, 0x49, 0xc4, 0x2c, 0xd9, 0xc8, 0xdb, 0xb2, 0x91, 0x4f, 0xc4, 0x67, 0x12, 0xe0,
-	0xd3, 0xbc, 0x99, 0xf7, 0x73, 0xef, 0x02, 0x78, 0x43, 0x43, 0xef, 0xde, 0xc0, 0xa1, 0xd2, 0x45,
-	0x3f, 0x06, 0x80, 0xfa, 0x2c, 0x0a, 0x70, 0xe2, 0x92, 0xc8, 0x58, 0x6c, 0x6b, 0xbd, 0x65, 0x73,
-	0x2f, 0xe5, 0x50, 0x41, 0x33, 0x0e, 0xef, 0xe4, 0x03, 0x51, 0x42, 0x65, 0x11, 0xcd, 0x39, 0x0c,
-	0x29, 0xe7, 0xf4, 0x3f, 0x34, 0xb0, 0x1d, 0x9f, 0xd2, 0xd0, 0x9a, 0x62, 0x62, 0x92, 0xad, 0x88,
-	0x78, 0xc1, 0xc8, 0x1e, 0xc6, 0xc6, 0x92, 0x0c, 0x86, 0x53, 0x0e, 0x0d, 0xa1, 0x3a, 0x50, 0x44,
-	0xa8, 0xd0, 0x64, 0x1c, 0xbe, 0x2f, 0x43, 0xdf, 0x24, 0x28, 0x13, 0xb9, 0xff, 0x56, 0x05, 0xba,
-	0x31, 0x82, 0xfe, 0xa7, 0x06, 0x1a, 0x65, 0xce, 0xd8, 0x72, 0xc6, 0xc6, 0xb2, 0xbc, 0x5c, 0x3f,
-	0xbe, 0xd3, 0xe5, 0x4a, 0x39, 0x5c, 0x9d, 0xb9, 0x9a, 0xe3, 0x8c, 0xc3, 0xbb, 0xd5, 0x1e, 0x62,
-	0x73, 0x5c, 0x26, 0xbf, 0x71, 0x0d, 0x15, 0x97, 0x0b, 0x55, 0x1c, 0xf4, 0x5d, 0xb0, 0x18, 0xda,
-	0x49, 0x4c, 0xb0, 0x51, 0x97, 0x8d, 0xdb, 0x4e, 0x39, 0x2c, 0x90, 0x8c, 0xc3, 0x55, 0xe9, 0x9e,
-	0x6f, 0x3b, 0xa8, 0xc0, 0xf5, 0x1f, 0xc0, 0xba, 0x3d, 0x1c, 0x06, 0x2f, 0x08, 0xb6, 0x7c, 0xc2,
-	0x5e, 0x04, 0xd1, 0x69, 0x6c, 0x00, 0x79, 0x7b, 0xbe, 0x4e, 0x39, 0x6c, 0x16, 0xdc, 0xd3, 0x82,
-	0xca, 0x38, 0x6c, 0xe5, 0x77, 0xa8, 0x82, 0x57, 0x67, 0xca, 0xb8, 0x89, 0x44, 0xf3, 0x76, 0xfa,
-	0x77, 0x60, 0xd3, 0x4e, 0x58, 0x60, 0xd9, 0xae, 0x4b, 0x42, 0x66, 0x9d, 0x04, 0x43, 0x4c, 0xa2,
-	0xd8, 0x58, 0x91, 0xe9, 0x7f, 0x98, 0x72, 0xb8, 0x21, 0xe8, 0xcf, 0x25, 0xfb, 0x45, 0x4e, 0x96,
-	0x7d, 0xba, 0xc6, 0x74, 0xd0, 0x75, 0xb5, 0xfe, 0x0c, 0x34, 0x3c, 0xfb, 0xcc, 0x8a, 0x89, 0x8f,
-	0xad, 0x53, 0x27, 0x8c, 0x8d, 0xd5, 0xb6, 0xd6, 0xbb, 0x6d, 0x7e, 0x20, 0xee, 0xa1, 0x67, 0x9f,
-	0x7d, 0x43, 0x7c, 0x7c, 0xe8, 0x84, 0xc2, 0x75, 0x43, 0xba, 0x2a, 0x58, 0xe7, 0x35, 0x87, 0x0b,
-	0xd4, 0x67, 0x48, 0x15, 0x4e, 0x0d, 0x23, 0xe2, 0x8e, 0x72, 0xc3, 0x46, 0xc5, 0x10, 0x11, 0x77,
-	0x34, 0x6f, 0x38, 0xc5, 0x2a, 0x86, 0x53, 0x50, 0xf7, 0x41, 0x93, 0x0e, 0xfc, 0x20, 0x22, 0xb8,
-	0xac, 0x7f, 0xad, 0xbd, 0xd0, 0x5b, 0xd9, 0xdd, 0xea, 0xe7, 0xff, 0x82, 0xfe, 0xb3, 0xe2, 0x5f,
-	0x90, 0xd7, 0x64, 0x3e, 0x12, 0x63, 0x97, 0x72, 0xb8, 0x56, 0x1c, 0x9b, 0x35, 0x66, 0x33, 0x1f,
-	0x20, 0x15, 0xee, 0xa0, 0x39, 0x99, 0xfe, 0xb3, 0x06, 0x9a, 0x21, 0xf1, 0x31, 0xf5, 0x07, 0x65,
-	0xc0, 0xe6, 0x5b, 0x03, 0x3e, 0x11, 0x01, 0x2f, 0x39, 0x34, 0xf6, 0x49, 0x18, 0x11, 0xd7, 0x66,
-	0x04, 0x1f, 0xe5, 0x06, 0x85, 0x67, 0xca, 0xa1, 0xf6, 0xa8, 0x7c, 0x6e, 0x42, 0x95, 0x53, 0x46,
-	0xc3, 0xd0, 0xd0, 0x5a, 0x85, 0x8b, 0xf5, 0xdf, 0x34, 0xd0, 0xcc, 0xbb, 0xf9, 0x7d, 0x42, 0x62,
-	0x66, 0x9d, 0x52, 0xc7, 0x58, 0x97, 0xfd, 0x8c, 0x2f, 0x39, 0x6c, 0x7c, 0x25, 0xda, 0x24, 0x99,
-	0x43, 0x6a, 0xa6, 0x1c, 0x36, 0x3c, 0x15, 0x28, 0x0b, 0xae, 0xa0, 0xd3, 0x26, 0xa7, 0x17, 0xdd,
-	0x39, 0xf9, 0x3c, 0xf0, 0x72, 0xd2, 0xad, 0x46, 0x40, 0x15, 0xde, 0xd1, 0x3f, 0x03, 0xf5, 0xc4,
-	0x67, 0x51, 0x12, 0x33, 0x82, 0x8d, 0x0d, 0x39, 0x93, 0x6d, 0xf1, 0x4b, 0x29, 0xc1, 0x8c, 0xc3,
-	0xa6, 0xcc, 0xa0, 0x44, 0x3a, 0x68, 0xc6, 0xca, 0xea, 0xc4, 0x5b, 0xc6, 0x88, 0x35, 0x48, 0xa8,
-	0x15, 0x06, 0x11, 0x33, 0xf4, 0x59, 0x75, 0x48, 0x52, 0x5f, 0x7e, 0x7b, 0x70, 0x14, 0x44, 0x4c,
-	0x54, 0x17, 0xa9, 0x40, 0x59, 0x5d, 0x05, 0x55, 0xab, 0xab, 0xca, 0xe7, 0x01, 0x51, 0x5d, 0x25,
-	0x02, 0x9a, 0xf2, 0x09, 0x15, 0x5b, 0xf3, 0xf0, 0xfc, 0x55, 0xab, 0x36, 0x79, 0xd5, 0xaa, 0x9d,
-	0x5f, 0xb6, 0xb4, 0xc9, 0x65, 0x4b, 0xfb, 0xf5, 0xaa, 0x55, 0xfb, 0xfd, 0xaa, 0xa5, 0x4d, 0xae,
-	0x5a, 0xb5, 0x7f, 0xae, 0x5a, 0xb5, 0xe3, 0x07, 0xff, 0xe3, 0x5d, 0xcb, 0x27, 0xc6, 0x59, 0x94,
-	0xef, 0xdb, 0x47, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x01, 0x84, 0xd4, 0x09, 0x09, 0x00,
-	0x00,
+	// 1026 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xbf, 0x6f, 0xdb, 0x46,
+	0x18, 0x15, 0xeb, 0xc4, 0xb6, 0xce, 0x3f, 0x64, 0xd3, 0x88, 0xc3, 0x18, 0x88, 0x4e, 0x50, 0x35,
+	0x28, 0x68, 0x22, 0x17, 0x6e, 0x27, 0xa3, 0x2d, 0x50, 0xc6, 0x68, 0x63, 0x18, 0x4d, 0x5c, 0x16,
+	0x5d, 0xbc, 0xb0, 0x24, 0xef, 0xac, 0x1c, 0x2c, 0xf2, 0x58, 0xf2, 0xa8, 0x58, 0x40, 0xff, 0x80,
+	0x76, 0x2b, 0x02, 0x74, 0xea, 0x92, 0xf6, 0xdf, 0xe8, 0xd0, 0xd5, 0x9b, 0x35, 0x16, 0x1d, 0x0e,
+	0x88, 0xbd, 0x71, 0x29, 0xc0, 0x31, 0x53, 0x71, 0x77, 0x14, 0x45, 0xca, 0x51, 0x50, 0xa0, 0x1b,
+	0xef, 0xbd, 0x77, 0xef, 0xdd, 0xf7, 0xe9, 0xbb, 0x13, 0xe8, 0x0c, 0x88, 0xbb, 0xeb, 0xd1, 0xe0,
+	0x94, 0xf4, 0x77, 0x11, 0x1e, 0x12, 0x0f, 0xab, 0x45, 0x12, 0x39, 0x8c, 0xd0, 0xa0, 0x17, 0x46,
+	0x94, 0x51, 0x7d, 0x51, 0x81, 0x3b, 0xdb, 0x42, 0x2d, 0x21, 0x8f, 0x0e, 0x76, 0x5d, 0x1c, 0x2a,
+	0x7e, 0xe7, 0x5e, 0xc9, 0x85, 0xba, 0x31, 0x8e, 0x86, 0x18, 0xe5, 0x54, 0x1d, 0x9f, 0x33, 0xf5,
+	0xd9, 0xfe, 0x67, 0x03, 0x6c, 0x1d, 0xc8, 0x8c, 0xc7, 0xe5, 0x0c, 0xfd, 0x4f, 0x0d, 0xd4, 0x55,
+	0xb6, 0x4d, 0x90, 0xa1, 0xb5, 0xb4, 0xee, 0xaa, 0xf9, 0x9b, 0x76, 0xc1, 0x61, 0xed, 0x6f, 0x0e,
+	0x3f, 0xee, 0x13, 0xf6, 0x3c, 0x71, 0x7b, 0x1e, 0xf5, 0x77, 0xe3, 0x51, 0xe0, 0xb1, 0xe7, 0x24,
+	0xe8, 0x97, 0xbe, 0xca, 0x27, 0xea, 0x29, 0xf7, 0xc3, 0x83, 0x2b, 0x0e, 0x97, 0x27, 0xdf, 0x29,
+	0x87, 0xcb, 0x28, 0xff, 0xce, 0x38, 0x6c, 0x9e, 0xfb, 0x83, 0xfd, 0x36, 0x41, 0x0f, 0x1d, 0xc6,
+	0xa2, 0x76, 0x2b, 0xa0, 0x08, 0x9f, 0x3a, 0xc9, 0x80, 0xed, 0xb7, 0x59, 0x94, 0xe0, 0x76, 0x7a,
+	0xd9, 0x59, 0xca, 0xc9, 0xec, 0xb2, 0x53, 0x6c, 0xfc, 0x71, 0xdc, 0xd1, 0x5e, 0x8e, 0x3b, 0x85,
+	0xe9, 0xab, 0x71, 0x47, 0xb3, 0x26, 0x2c, 0xd2, 0x8f, 0xc1, 0xad, 0xc0, 0xf1, 0xb1, 0xf1, 0x5e,
+	0x4b, 0xeb, 0xd6, 0xcd, 0x4f, 0x52, 0x0e, 0xe5, 0x3a, 0xe3, 0xf0, 0x9e, 0x8c, 0x13, 0x0b, 0xe9,
+	0xf9, 0x90, 0xfa, 0x84, 0x61, 0x3f, 0x64, 0x23, 0x91, 0xb4, 0xf5, 0x16, 0xdc, 0x92, 0x3b, 0xf5,
+	0x73, 0x50, 0x77, 0x10, 0x8a, 0x70, 0x1c, 0xe3, 0xd8, 0x58, 0x68, 0x2d, 0x74, 0xeb, 0xe6, 0x49,
+	0xca, 0xe1, 0x14, 0xcc, 0x38, 0x7c, 0x20, 0xbd, 0x73, 0xa4, 0xe4, 0xdc, 0x2a, 0x4a, 0x42, 0xa3,
+	0xc0, 0xf1, 0x89, 0x27, 0xb2, 0x36, 0x6f, 0xe8, 0xde, 0x5c, 0x76, 0x96, 0x72, 0x81, 0x35, 0xf5,
+	0xd5, 0x87, 0x60, 0xc5, 0xa3, 0x7e, 0x28, 0x56, 0x84, 0x06, 0xc6, 0xad, 0x96, 0xd6, 0x5d, 0xdf,
+	0xbb, 0xd3, 0x2b, 0x7a, 0xfc, 0x78, 0x4a, 0x9a, 0x9f, 0xa6, 0x1c, 0x96, 0xd5, 0x19, 0x87, 0xdb,
+	0xf2, 0x50, 0x25, 0x4c, 0x35, 0x3a, 0xbd, 0xec, 0x6c, 0xcc, 0x82, 0x56, 0x79, 0xab, 0x8e, 0x41,
+	0xdd, 0xc3, 0x11, 0xb3, 0x65, 0x23, 0x6f, 0xcb, 0x46, 0x3e, 0x11, 0xbf, 0x9d, 0x00, 0x9f, 0xaa,
+	0x66, 0xde, 0x57, 0xde, 0x39, 0xf0, 0x96, 0x86, 0xde, 0x9d, 0xc3, 0x59, 0x85, 0x8b, 0x7e, 0x02,
+	0x00, 0x09, 0x58, 0x44, 0x51, 0xe2, 0xe1, 0xc8, 0x58, 0x6c, 0x69, 0xdd, 0x65, 0x73, 0x3f, 0xe5,
+	0xb0, 0x84, 0x66, 0x1c, 0xde, 0x51, 0x53, 0x52, 0x40, 0x45, 0x11, 0x8d, 0x19, 0xcc, 0x2a, 0xed,
+	0xd3, 0x7f, 0xd7, 0xc0, 0x4e, 0x7c, 0x46, 0x42, 0x7b, 0x82, 0x89, 0xf1, 0xb6, 0x23, 0xec, 0xd3,
+	0xa1, 0x33, 0x88, 0x8d, 0x25, 0x19, 0x86, 0x52, 0x0e, 0x0d, 0xa1, 0x3a, 0x2c, 0x89, 0xac, 0x5c,
+	0x93, 0x71, 0xf8, 0xbe, 0x8c, 0x9e, 0x27, 0x28, 0x0e, 0x72, 0xff, 0x9d, 0x0a, 0x6b, 0x6e, 0x82,
+	0xfe, 0x87, 0x06, 0xd6, 0x8a, 0x33, 0x23, 0xdb, 0x1d, 0x19, 0xcb, 0xf2, 0xc6, 0xfd, 0xf2, 0xbf,
+	0x6e, 0x5c, 0xca, 0xe1, 0xea, 0xd4, 0xd5, 0x1c, 0x65, 0x1c, 0x76, 0xab, 0x3d, 0x44, 0xe6, 0x68,
+	0xfe, 0x9d, 0xdb, 0xbc, 0x21, 0x13, 0x37, 0x4e, 0xde, 0xb2, 0x8a, 0xad, 0xbe, 0x07, 0x16, 0x43,
+	0x27, 0x89, 0x31, 0x32, 0xea, 0xb2, 0x9b, 0x3b, 0x29, 0x87, 0x39, 0x92, 0x71, 0xb8, 0x2a, 0x23,
+	0xd5, 0xb2, 0x6d, 0xe5, 0xb8, 0xfe, 0x03, 0xd8, 0x70, 0x06, 0x03, 0xfa, 0x02, 0x23, 0x3b, 0xc0,
+	0xec, 0x05, 0x8d, 0xce, 0x62, 0x03, 0xc8, 0x2b, 0xf5, 0x75, 0xca, 0x61, 0x23, 0xe7, 0x9e, 0xe6,
+	0x54, 0xf1, 0x46, 0x54, 0xf1, 0xea, 0xa0, 0x19, 0xf3, 0x48, 0x6b, 0xd6, 0x4e, 0xff, 0x0e, 0x6c,
+	0x39, 0x09, 0xa3, 0xb6, 0xe3, 0x79, 0x38, 0x64, 0xf6, 0x29, 0x1d, 0x20, 0x1c, 0xc5, 0xc6, 0x8a,
+	0x3c, 0xfe, 0x87, 0x29, 0x87, 0x9b, 0x82, 0xfe, 0x5c, 0xb2, 0x5f, 0x28, 0x32, 0xe3, 0xf0, 0xae,
+	0x3a, 0xc2, 0x2c, 0xd3, 0xb6, 0x6e, 0xaa, 0xf5, 0x67, 0x60, 0xcd, 0x77, 0xce, 0xed, 0x18, 0x07,
+	0xc8, 0x3e, 0x73, 0xc3, 0xd8, 0x58, 0x6d, 0x69, 0xdd, 0xdb, 0xe6, 0x07, 0xe2, 0x72, 0xfa, 0xce,
+	0xf9, 0x37, 0x38, 0x40, 0x47, 0x6e, 0x28, 0x5c, 0x37, 0xa5, 0x6b, 0x09, 0x6b, 0xbf, 0xe1, 0x70,
+	0x81, 0x04, 0xcc, 0x2a, 0x0b, 0x27, 0x86, 0x11, 0xf6, 0x86, 0xca, 0x70, 0xad, 0x62, 0x68, 0x61,
+	0x6f, 0x38, 0x6b, 0x38, 0xc1, 0x2a, 0x86, 0x13, 0x50, 0x0f, 0x40, 0x83, 0xf4, 0x03, 0x1a, 0x61,
+	0x54, 0xd4, 0xbf, 0xde, 0x5a, 0xe8, 0xae, 0xec, 0x6d, 0xf7, 0xd4, 0xbf, 0x46, 0xef, 0x59, 0xfe,
+	0xaf, 0xa1, 0x6a, 0x32, 0x1f, 0x89, 0x59, 0x4c, 0x39, 0x5c, 0xcf, 0xb7, 0x4d, 0x1b, 0xb3, 0xa5,
+	0xa6, 0xaa, 0x0c, 0xb7, 0xad, 0x19, 0x99, 0xfe, 0x93, 0x06, 0x1a, 0x21, 0x0e, 0x10, 0x09, 0xfa,
+	0x45, 0x60, 0xe3, 0x9d, 0x81, 0x4f, 0x44, 0xe0, 0x15, 0x87, 0xc6, 0x01, 0x0e, 0x23, 0xec, 0x39,
+	0x0c, 0xa3, 0x63, 0x65, 0x90, 0x7b, 0xa6, 0x1c, 0x6a, 0x8f, 0x8a, 0x37, 0x28, 0x2c, 0x73, 0xa5,
+	0xd1, 0x30, 0x34, 0x6b, 0xbd, 0xc2, 0xc5, 0xfa, 0xaf, 0x1a, 0x68, 0xa8, 0x6e, 0x7e, 0x9f, 0xe0,
+	0x98, 0xd9, 0x67, 0xc4, 0x35, 0x36, 0x64, 0x3f, 0xe3, 0x2b, 0x0e, 0xd7, 0xbe, 0x12, 0x6d, 0x92,
+	0xcc, 0x11, 0x31, 0x53, 0x0e, 0xd7, 0xfc, 0x32, 0x50, 0x14, 0x5c, 0x41, 0x27, 0x4d, 0x4e, 0x2f,
+	0x3b, 0x33, 0xf2, 0x59, 0xe0, 0xe5, 0xb8, 0x53, 0x4d, 0xb0, 0x2a, 0xbc, 0xab, 0x7f, 0x06, 0xea,
+	0x49, 0xc0, 0xa2, 0x24, 0x66, 0x18, 0x19, 0x9b, 0x72, 0x26, 0x5b, 0xe2, 0x7f, 0xa6, 0x00, 0x33,
+	0x0e, 0x1b, 0xf2, 0x04, 0x05, 0xd2, 0xb6, 0xa6, 0xac, 0xac, 0x4e, 0x3c, 0x70, 0x0c, 0xdb, 0xfd,
+	0x84, 0xd8, 0x21, 0x8d, 0x98, 0xa1, 0x4f, 0xab, 0xb3, 0x24, 0xf5, 0xe5, 0xb7, 0x87, 0xc7, 0x34,
+	0x62, 0xa2, 0xba, 0xa8, 0x0c, 0x14, 0xd5, 0x55, 0xd0, 0x72, 0x75, 0x55, 0xf9, 0x2c, 0x20, 0xaa,
+	0xab, 0x24, 0x58, 0x13, 0x3e, 0x21, 0x62, 0x69, 0x1e, 0x5d, 0xbc, 0x6e, 0xd6, 0xc6, 0xaf, 0x9b,
+	0xb5, 0x8b, 0xab, 0xa6, 0x36, 0xbe, 0x6a, 0x6a, 0x3f, 0x5f, 0x37, 0x6b, 0xaf, 0xae, 0x9b, 0xda,
+	0xf8, 0xba, 0x59, 0xfb, 0xeb, 0xba, 0x59, 0x3b, 0x79, 0xf0, 0x1f, 0x1e, 0x3b, 0x35, 0x31, 0xee,
+	0xa2, 0x7c, 0xf4, 0x3e, 0xfa, 0x37, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x4a, 0x4f, 0x60, 0x33, 0x09,
+	0x00, 0x00,
 }
 
 func (m *DeviceConfiguration) Marshal() (dAtA []byte, err error) {

+ 0 - 15
lib/config/folderconfiguration.go

@@ -33,21 +33,6 @@ const (
 	maxConcurrentWritesLimit   = 64
 )
 
-func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.FilesystemType, path string) FolderConfiguration {
-	f := FolderConfiguration{
-		ID:             id,
-		Label:          label,
-		Devices:        []FolderDeviceConfiguration{{DeviceID: myID}},
-		FilesystemType: fsType,
-		Path:           path,
-	}
-
-	util.SetDefaults(&f)
-
-	f.prepare(myID, nil)
-	return f
-}
-
 func (f FolderConfiguration) Copy() FolderConfiguration {
 	c := f
 	c.Devices = make([]FolderDeviceConfiguration, len(f.Devices))

+ 133 - 132
lib/config/folderconfiguration.pb.go

@@ -66,10 +66,10 @@ func (m *FolderDeviceConfiguration) XXX_DiscardUnknown() {
 var xxx_messageInfo_FolderDeviceConfiguration proto.InternalMessageInfo
 
 type FolderConfiguration struct {
-	ID                      string                      `protobuf:"bytes,1,opt,name=id,proto3" json:"id" xml:"id,attr"`
+	ID                      string                      `protobuf:"bytes,1,opt,name=id,proto3" json:"id" xml:"id,attr" nodefault:"true"`
 	Label                   string                      `protobuf:"bytes,2,opt,name=label,proto3" json:"label" xml:"label,attr" restart:"false"`
 	FilesystemType          fs.FilesystemType           `protobuf:"varint,3,opt,name=filesystem_type,json=filesystemType,proto3,enum=fs.FilesystemType" json:"filesystemType" xml:"filesystemType"`
-	Path                    string                      `protobuf:"bytes,4,opt,name=path,proto3" json:"path" xml:"path,attr"`
+	Path                    string                      `protobuf:"bytes,4,opt,name=path,proto3" json:"path" xml:"path,attr" default:"~"`
 	Type                    FolderType                  `protobuf:"varint,5,opt,name=type,proto3,enum=config.FolderType" json:"type" xml:"type,attr"`
 	Devices                 []FolderDeviceConfiguration `protobuf:"bytes,6,rep,name=devices,proto3" json:"devices" xml:"device"`
 	RescanIntervalS         int                         `protobuf:"varint,7,opt,name=rescan_interval_s,json=rescanIntervalS,proto3,casttype=int" json:"rescanIntervalS" xml:"rescanIntervalS,attr" default:"3600"`
@@ -77,7 +77,7 @@ type FolderConfiguration struct {
 	FSWatcherDelayS         int                         `protobuf:"varint,9,opt,name=fs_watcher_delay_s,json=fsWatcherDelayS,proto3,casttype=int" json:"fsWatcherDelayS" xml:"fsWatcherDelayS,attr" default:"10"`
 	IgnorePerms             bool                        `protobuf:"varint,10,opt,name=ignore_perms,json=ignorePerms,proto3" json:"ignorePerms" xml:"ignorePerms,attr"`
 	AutoNormalize           bool                        `protobuf:"varint,11,opt,name=auto_normalize,json=autoNormalize,proto3" json:"autoNormalize" xml:"autoNormalize,attr" default:"true"`
-	MinDiskFree             Size                        `protobuf:"bytes,12,opt,name=min_disk_free,json=minDiskFree,proto3" json:"minDiskFree" xml:"minDiskFree"`
+	MinDiskFree             Size                        `protobuf:"bytes,12,opt,name=min_disk_free,json=minDiskFree,proto3" json:"minDiskFree" xml:"minDiskFree" default:"1 %"`
 	Versioning              VersioningConfiguration     `protobuf:"bytes,13,opt,name=versioning,proto3" json:"versioning" xml:"versioning"`
 	Copiers                 int                         `protobuf:"varint,14,opt,name=copiers,proto3,casttype=int" json:"copiers" xml:"copiers"`
 	PullerMaxPendingKiB     int                         `protobuf:"varint,15,opt,name=puller_max_pending_kib,json=pullerMaxPendingKib,proto3,casttype=int" json:"pullerMaxPendingKiB" xml:"pullerMaxPendingKiB"`
@@ -86,7 +86,7 @@ type FolderConfiguration struct {
 	IgnoreDelete            bool                        `protobuf:"varint,18,opt,name=ignore_delete,json=ignoreDelete,proto3" json:"ignoreDelete" xml:"ignoreDelete"`
 	ScanProgressIntervalS   int                         `protobuf:"varint,19,opt,name=scan_progress_interval_s,json=scanProgressIntervalS,proto3,casttype=int" json:"scanProgressIntervalS" xml:"scanProgressIntervalS"`
 	PullerPauseS            int                         `protobuf:"varint,20,opt,name=puller_pause_s,json=pullerPauseS,proto3,casttype=int" json:"pullerPauseS" xml:"pullerPauseS"`
-	MaxConflicts            int                         `protobuf:"varint,21,opt,name=max_conflicts,json=maxConflicts,proto3,casttype=int" json:"maxConflicts" xml:"maxConflicts" default:"-1"`
+	MaxConflicts            int                         `protobuf:"varint,21,opt,name=max_conflicts,json=maxConflicts,proto3,casttype=int" json:"maxConflicts" xml:"maxConflicts" default:"10"`
 	DisableSparseFiles      bool                        `protobuf:"varint,22,opt,name=disable_sparse_files,json=disableSparseFiles,proto3" json:"disableSparseFiles" xml:"disableSparseFiles"`
 	DisableTempIndexes      bool                        `protobuf:"varint,23,opt,name=disable_temp_indexes,json=disableTempIndexes,proto3" json:"disableTempIndexes" xml:"disableTempIndexes"`
 	Paused                  bool                        `protobuf:"varint,24,opt,name=paused,proto3" json:"paused" xml:"paused"`
@@ -149,134 +149,135 @@ func init() {
 }
 
 var fileDescriptor_44a9785876ed3afa = []byte{
-	// 2018 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0xcd, 0x6f, 0x1c, 0x49,
-	0x15, 0x77, 0x3b, 0x5f, 0x76, 0xf9, 0xbb, 0x1c, 0x27, 0x15, 0x67, 0xd7, 0xe5, 0x6d, 0x86, 0xc5,
-	0xbb, 0xda, 0x38, 0x89, 0x17, 0x71, 0x88, 0x76, 0x17, 0x76, 0xe2, 0xb5, 0x08, 0x21, 0x9b, 0x51,
-	0x3b, 0x10, 0x11, 0x90, 0x9a, 0x76, 0x77, 0xcd, 0x4c, 0xad, 0xfb, 0x8b, 0xaa, 0x9e, 0xd8, 0x93,
-	0x53, 0xb8, 0x20, 0x10, 0x7b, 0x40, 0xe6, 0xc0, 0x15, 0x09, 0x84, 0x60, 0xff, 0x01, 0x24, 0xfe,
-	0x82, 0x5c, 0x90, 0xe7, 0x84, 0x10, 0x87, 0x92, 0xd6, 0xbe, 0xcd, 0xb1, 0x8f, 0x39, 0xa1, 0xaa,
-	0xea, 0xee, 0xe9, 0xee, 0x99, 0x45, 0x48, 0x7b, 0x9b, 0xfa, 0xfd, 0x7e, 0xf5, 0xde, 0xeb, 0x57,
-	0xf5, 0x5e, 0xbf, 0x1e, 0xd0, 0xf0, 0xe9, 0xc1, 0x6d, 0x37, 0x0a, 0xdb, 0xb4, 0x73, 0xbb, 0x1d,
-	0xf9, 0x1e, 0x61, 0x7a, 0xd1, 0x63, 0x4e, 0x42, 0xa3, 0x70, 0x3b, 0x66, 0x51, 0x12, 0xc1, 0xcb,
-	0x1a, 0x5c, 0xbf, 0x39, 0xa6, 0x4e, 0xfa, 0x31, 0xd1, 0xa2, 0xf5, 0xb5, 0x12, 0xc9, 0xe9, 0x8b,
-	0x1c, 0x5e, 0x2f, 0xc1, 0x71, 0xcf, 0xf7, 0x23, 0xe6, 0x11, 0x96, 0x71, 0x5b, 0x25, 0xee, 0x39,
-	0x61, 0x9c, 0x46, 0x21, 0x0d, 0x3b, 0x13, 0x22, 0x58, 0xc7, 0x25, 0xe5, 0x81, 0x1f, 0xb9, 0x87,
-	0x75, 0x53, 0x50, 0x0a, 0xda, 0xfc, 0xb6, 0x0c, 0x88, 0x67, 0xd8, 0x1b, 0x19, 0xe6, 0x46, 0x71,
-	0x9f, 0x39, 0x61, 0x87, 0x04, 0x24, 0xe9, 0x46, 0x5e, 0xc6, 0xce, 0x92, 0xe3, 0x44, 0xff, 0x34,
-	0xff, 0x75, 0x01, 0xdc, 0xd8, 0x53, 0xcf, 0xb3, 0x4b, 0x9e, 0x53, 0x97, 0xdc, 0x2f, 0x47, 0x00,
-	0xbf, 0x30, 0xc0, 0xac, 0xa7, 0x70, 0x9b, 0x7a, 0xc8, 0xd8, 0x34, 0xb6, 0xe6, 0x9b, 0x9f, 0x1b,
-	0xaf, 0x04, 0x9e, 0xfa, 0x8f, 0xc0, 0xdf, 0xee, 0xd0, 0xa4, 0xdb, 0x3b, 0xd8, 0x76, 0xa3, 0xe0,
-	0x36, 0xef, 0x87, 0x6e, 0xd2, 0xa5, 0x61, 0xa7, 0xf4, 0x4b, 0x86, 0xa0, 0x9c, 0xb8, 0x91, 0xbf,
-	0xad, 0xad, 0x3f, 0xd8, 0x3d, 0x13, 0x78, 0x26, 0xff, 0x3d, 0x14, 0x78, 0xc6, 0xcb, 0x7e, 0xa7,
-	0x02, 0x2f, 0x1c, 0x07, 0xfe, 0x3d, 0x93, 0x7a, 0xef, 0x39, 0x49, 0xc2, 0xcc, 0xe1, 0x69, 0xe3,
-	0x4a, 0xf6, 0x3b, 0x3d, 0x6d, 0x14, 0xba, 0x5f, 0x0f, 0x1a, 0xc6, 0xc9, 0xa0, 0x51, 0xd8, 0xb0,
-	0x72, 0xc6, 0x83, 0x7f, 0x31, 0xc0, 0x02, 0x0d, 0x13, 0x16, 0x79, 0x3d, 0x97, 0x78, 0xf6, 0x41,
-	0x1f, 0x4d, 0xab, 0x80, 0x5f, 0x7e, 0xad, 0x80, 0x87, 0x02, 0xcf, 0x8f, 0xac, 0x36, 0xfb, 0xa9,
-	0xc0, 0xd7, 0x75, 0xa0, 0x25, 0xb0, 0x08, 0x79, 0x65, 0x0c, 0x95, 0x01, 0x5b, 0x15, 0x0b, 0xd0,
-	0x05, 0xab, 0x24, 0x74, 0x59, 0x3f, 0x96, 0x39, 0xb6, 0x63, 0x87, 0xf3, 0xa3, 0x88, 0x79, 0xe8,
-	0xc2, 0xa6, 0xb1, 0x35, 0xdb, 0xdc, 0x19, 0x0a, 0x0c, 0x47, 0x74, 0x2b, 0x63, 0x53, 0x81, 0x91,
-	0x72, 0x3b, 0x4e, 0x99, 0xd6, 0x04, 0xbd, 0x79, 0x8e, 0xc1, 0xaa, 0x3e, 0xd8, 0xea, 0x91, 0x7e,
-	0x04, 0xa6, 0xb3, 0xa3, 0x9c, 0x6d, 0x6e, 0x9f, 0x09, 0x3c, 0xad, 0x1e, 0x71, 0x9a, 0x7a, 0xff,
-	0xeb, 0x04, 0x4e, 0x06, 0x8d, 0xe9, 0x07, 0xbb, 0xd6, 0x34, 0xf5, 0xe0, 0x8f, 0xc0, 0x25, 0xdf,
-	0x39, 0x20, 0xbe, 0x4a, 0xee, 0x6c, 0xf3, 0xbb, 0x43, 0x81, 0x35, 0x90, 0x0a, 0xbc, 0xa9, 0xf6,
-	0xab, 0x95, 0x36, 0xb1, 0xc9, 0x08, 0x4f, 0x1c, 0x96, 0xdc, 0x33, 0xdb, 0x8e, 0xcf, 0x89, 0x34,
-	0x09, 0x46, 0xf4, 0xcb, 0x41, 0x63, 0xca, 0xd2, 0x9b, 0x61, 0x07, 0x2c, 0xb5, 0xa9, 0x4f, 0x78,
-	0x9f, 0x27, 0x24, 0xb0, 0xe5, 0x55, 0x56, 0xf9, 0x58, 0xdc, 0x81, 0xdb, 0x6d, 0xbe, 0xbd, 0x57,
-	0x50, 0x4f, 0xfa, 0x31, 0x69, 0xbe, 0x3b, 0x14, 0x78, 0xb1, 0x5d, 0xc1, 0x52, 0x81, 0xaf, 0x2a,
-	0xef, 0x55, 0xd8, 0xb4, 0x6a, 0x3a, 0xf8, 0x01, 0xb8, 0x18, 0x3b, 0x49, 0x17, 0x5d, 0x54, 0xe1,
-	0x6f, 0x0d, 0x05, 0x56, 0xeb, 0x54, 0xe0, 0x25, 0xb5, 0x5f, 0x2e, 0x8a, 0xe7, 0x9f, 0x2d, 0x56,
-	0x96, 0x52, 0xc1, 0x16, 0xb8, 0xa8, 0x62, 0xbb, 0x94, 0xc5, 0xa6, 0xeb, 0x72, 0x5b, 0x27, 0x5a,
-	0xc5, 0xa6, 0x2c, 0x26, 0x3a, 0x22, 0x6d, 0x51, 0x2e, 0x46, 0x16, 0x8b, 0x95, 0xa5, 0x54, 0xf0,
-	0x67, 0xe0, 0x8a, 0xbe, 0xc1, 0x1c, 0x5d, 0xde, 0xbc, 0xb0, 0x35, 0xb7, 0xf3, 0x56, 0xd5, 0xe8,
-	0x84, 0xb2, 0x6c, 0x62, 0x79, 0xa1, 0x87, 0x02, 0xe7, 0x3b, 0x53, 0x81, 0xe7, 0x95, 0x2b, 0xbd,
-	0x36, 0xad, 0x9c, 0x80, 0xbf, 0x37, 0xc0, 0x0a, 0x23, 0xdc, 0x75, 0x42, 0x9b, 0x86, 0x09, 0x61,
-	0xcf, 0x1d, 0xdf, 0xe6, 0xe8, 0xca, 0xa6, 0xb1, 0x75, 0xa9, 0xd9, 0x19, 0x0a, 0xbc, 0xa4, 0xc9,
-	0x07, 0x19, 0xb7, 0x9f, 0x0a, 0xfc, 0x8e, 0xb2, 0x54, 0xc3, 0xb3, 0xe3, 0xf4, 0x48, 0xdb, 0xe9,
-	0xf9, 0xc9, 0x3d, 0xf3, 0xfd, 0xef, 0xdc, 0xb9, 0x63, 0xbe, 0x16, 0xf8, 0x02, 0x0d, 0x93, 0xe1,
-	0x69, 0xe3, 0xea, 0x24, 0xf9, 0xeb, 0xd3, 0xc6, 0x45, 0xa9, 0xb3, 0xea, 0x4e, 0xe0, 0x3f, 0x0c,
-	0x00, 0xdb, 0xdc, 0x3e, 0x72, 0x12, 0xb7, 0x4b, 0x98, 0x4d, 0x42, 0xe7, 0xc0, 0x27, 0x1e, 0x9a,
-	0xd9, 0x34, 0xb6, 0x66, 0x9a, 0xbf, 0x35, 0xce, 0x04, 0x5e, 0xde, 0xdb, 0x7f, 0xaa, 0xd9, 0x4f,
-	0x34, 0x39, 0x14, 0x78, 0xb9, 0xcd, 0xab, 0x58, 0x2a, 0xf0, 0xbb, 0xfa, 0xcc, 0x6b, 0x44, 0x3d,
-	0xda, 0x84, 0xf5, 0xd4, 0xdd, 0x5b, 0x9b, 0x28, 0x94, 0x71, 0x4a, 0xc5, 0xc9, 0xa0, 0x31, 0xe6,
-	0xd6, 0x1a, 0x73, 0x0a, 0xff, 0x5e, 0x0d, 0xde, 0x23, 0xbe, 0xd3, 0xb7, 0x39, 0x9a, 0x55, 0x39,
-	0xfd, 0x8d, 0x0c, 0x7e, 0xa9, 0xb0, 0xb2, 0x2b, 0xc9, 0x7d, 0x99, 0xe7, 0xc2, 0x8c, 0x86, 0x52,
-	0x81, 0xbf, 0x55, 0x0d, 0x5d, 0xe3, 0xf5, 0xc8, 0xef, 0x56, 0xb2, 0x3c, 0x49, 0xfc, 0xfa, 0xb4,
-	0x31, 0x7d, 0xf7, 0xce, 0xc9, 0xa0, 0x51, 0xf7, 0x6a, 0xd5, 0x7d, 0xc2, 0x9f, 0x83, 0x79, 0xda,
-	0x09, 0x23, 0x46, 0xec, 0x98, 0xb0, 0x80, 0x23, 0xa0, 0xf2, 0xfd, 0xe1, 0x50, 0xe0, 0x39, 0x8d,
-	0xb7, 0x24, 0x9c, 0x0a, 0x7c, 0x4d, 0xf7, 0x81, 0x11, 0x56, 0x5c, 0xdf, 0xe5, 0x3a, 0x68, 0x95,
-	0xb7, 0xc2, 0x5f, 0x1a, 0x60, 0xd1, 0xe9, 0x25, 0x91, 0x1d, 0x46, 0x2c, 0x70, 0x7c, 0xfa, 0x82,
-	0xa0, 0x39, 0xe5, 0xe4, 0xd9, 0x50, 0xe0, 0x05, 0xc9, 0x7c, 0x9a, 0x13, 0x45, 0x06, 0x2a, 0xe8,
-	0x57, 0x9d, 0x1c, 0x1c, 0x57, 0xe5, 0xc7, 0x66, 0x55, 0xed, 0xc2, 0x67, 0x60, 0x21, 0xa0, 0xa1,
-	0xed, 0x51, 0x7e, 0x68, 0xb7, 0x19, 0x21, 0x68, 0x7e, 0xd3, 0xd8, 0x9a, 0xdb, 0x99, 0xcf, 0xcb,
-	0x6a, 0x9f, 0xbe, 0x20, 0xcd, 0xad, 0xac, 0x82, 0xe6, 0x02, 0x1a, 0xee, 0x52, 0x7e, 0xb8, 0xc7,
-	0x88, 0x8c, 0x68, 0x45, 0x45, 0x54, 0xc2, 0x4c, 0xab, 0xac, 0x80, 0x1d, 0x00, 0x46, 0x2f, 0x6b,
-	0xb4, 0xa0, 0x0c, 0xe3, 0xdc, 0xf0, 0x8f, 0x0b, 0xa6, 0x5a, 0xad, 0x6f, 0x67, 0xbe, 0x4a, 0x5b,
-	0x53, 0x81, 0x97, 0x95, 0xab, 0x11, 0x64, 0x5a, 0x25, 0x1e, 0x7e, 0x08, 0xae, 0xb8, 0x51, 0x4c,
-	0x09, 0xe3, 0x68, 0x51, 0x5d, 0xac, 0x6f, 0xc8, 0x72, 0xcf, 0xa0, 0xa2, 0x53, 0x67, 0xeb, 0xfc,
-	0x8a, 0x58, 0xb9, 0x00, 0xfe, 0xd3, 0x00, 0xd7, 0xe4, 0x98, 0x40, 0x98, 0x1d, 0x38, 0xc7, 0x76,
-	0x4c, 0x42, 0x8f, 0x86, 0x1d, 0xfb, 0x90, 0x1e, 0xa0, 0x25, 0x65, 0xee, 0x0f, 0xf2, 0x9e, 0xae,
-	0xb6, 0x94, 0xe4, 0x91, 0x73, 0xdc, 0xd2, 0x82, 0x87, 0xb4, 0x39, 0x14, 0x78, 0x35, 0x1e, 0x87,
-	0x53, 0x81, 0x6f, 0xe8, 0xf6, 0x38, 0xce, 0x95, 0x6e, 0xe8, 0xc4, 0xad, 0x93, 0xe1, 0x93, 0x41,
-	0x63, 0x92, 0x7f, 0x6b, 0x82, 0xf6, 0x40, 0xa6, 0xa3, 0xeb, 0xf0, 0xae, 0x4c, 0xc7, 0xf2, 0x28,
-	0x1d, 0x19, 0x54, 0xa4, 0x23, 0x5b, 0x8f, 0xd2, 0x91, 0x01, 0xf0, 0x63, 0x70, 0x49, 0x0d, 0x4c,
-	0x68, 0x45, 0xb5, 0xed, 0x95, 0xfc, 0xc4, 0xa4, 0xff, 0xc7, 0x92, 0x68, 0x22, 0xf9, 0x1a, 0x53,
-	0x9a, 0x54, 0xe0, 0x39, 0x65, 0x4d, 0xad, 0x4c, 0x4b, 0xa3, 0xf0, 0x21, 0x58, 0xc8, 0x6a, 0xc7,
-	0x23, 0x3e, 0x49, 0x08, 0x82, 0xea, 0x5e, 0xbf, 0xad, 0xc6, 0x03, 0x45, 0xec, 0x2a, 0x3c, 0x15,
-	0x18, 0x96, 0xaa, 0x47, 0x83, 0xa6, 0x55, 0xd1, 0xc0, 0x63, 0x80, 0x54, 0x4b, 0x8e, 0x59, 0xd4,
-	0x61, 0x84, 0xf3, 0x72, 0x6f, 0x5e, 0x55, 0xcf, 0x27, 0x5f, 0xab, 0x6b, 0x52, 0xd3, 0xca, 0x24,
-	0xe5, 0x0e, 0x7d, 0x53, 0x39, 0x98, 0xc8, 0x16, 0xcf, 0x3e, 0x79, 0x33, 0xdc, 0x07, 0x8b, 0xd9,
-	0xbd, 0x88, 0x9d, 0x1e, 0x27, 0x36, 0x47, 0x57, 0x95, 0xbf, 0x5b, 0xf2, 0x39, 0x34, 0xd3, 0x92,
-	0xc4, 0x7e, 0xf1, 0x1c, 0x65, 0xb0, 0xb0, 0x5e, 0x91, 0x42, 0x02, 0x16, 0xe4, 0x2d, 0x93, 0x49,
-	0xf5, 0xa9, 0x9b, 0x70, 0xb4, 0xa6, 0x6c, 0x7e, 0x4f, 0xda, 0x0c, 0x9c, 0xe3, 0xfb, 0x39, 0x9e,
-	0x0a, 0x8c, 0x75, 0x81, 0x95, 0xc0, 0x52, 0xb1, 0xdf, 0xba, 0x9b, 0x3b, 0x90, 0x4d, 0xed, 0xd6,
-	0x5d, 0xab, 0xb2, 0x1b, 0x7a, 0xe0, 0xaa, 0x47, 0xb9, 0x6c, 0xc2, 0x36, 0x8f, 0x1d, 0xc6, 0x89,
-	0xad, 0x5e, 0xed, 0xe8, 0x9a, 0x3a, 0x09, 0x35, 0x37, 0x65, 0xfc, 0xbe, 0xa2, 0xd5, 0xd0, 0x50,
-	0xcc, 0x4d, 0xe3, 0x94, 0x69, 0x4d, 0xd0, 0x97, 0xbd, 0x24, 0x24, 0x88, 0x6d, 0x1a, 0x7a, 0xe4,
-	0x98, 0x70, 0x74, 0x7d, 0xcc, 0xcb, 0x13, 0x12, 0xc4, 0x0f, 0x34, 0x5b, 0xf7, 0x52, 0xa2, 0x46,
-	0x5e, 0x4a, 0x20, 0xdc, 0x01, 0x97, 0xd5, 0x01, 0x78, 0x08, 0x29, 0xbb, 0xeb, 0x43, 0x81, 0x33,
-	0xa4, 0x78, 0x99, 0xeb, 0xa5, 0x69, 0x65, 0x38, 0x4c, 0xc0, 0xf5, 0x23, 0xe2, 0x1c, 0xda, 0xf2,
-	0x56, 0xdb, 0x49, 0x97, 0x11, 0xde, 0x8d, 0x7c, 0xcf, 0x8e, 0xdd, 0x04, 0xdd, 0x50, 0x09, 0x97,
-	0x9d, 0xfc, 0xaa, 0x94, 0x7c, 0xdf, 0xe1, 0xdd, 0x27, 0xb9, 0xa0, 0xe5, 0x26, 0xa9, 0xc0, 0xeb,
-	0xca, 0xe4, 0x24, 0xb2, 0x38, 0xd4, 0x89, 0x5b, 0xe1, 0x7d, 0x30, 0x17, 0x38, 0xec, 0x90, 0x30,
-	0x3b, 0x74, 0x02, 0x82, 0xd6, 0xd5, 0xd8, 0x64, 0xca, 0x76, 0xa6, 0xe1, 0x4f, 0x9d, 0x80, 0x14,
-	0xed, 0x6c, 0x04, 0x99, 0x56, 0x89, 0x87, 0x7d, 0xb0, 0x2e, 0xbf, 0x44, 0xec, 0xe8, 0x28, 0x24,
-	0x8c, 0x77, 0x69, 0x6c, 0xb7, 0x59, 0x14, 0xd8, 0xb1, 0xc3, 0x48, 0x98, 0xa0, 0x9b, 0x2a, 0x05,
-	0x1f, 0x0c, 0x05, 0xbe, 0x2e, 0x55, 0x8f, 0x73, 0xd1, 0x1e, 0x8b, 0x82, 0x96, 0x92, 0xa4, 0x02,
-	0xbf, 0x99, 0x77, 0xbc, 0x49, 0xbc, 0x69, 0x7d, 0xd5, 0x4e, 0xf8, 0x2b, 0x03, 0xac, 0x04, 0x91,
-	0x67, 0x27, 0x34, 0x20, 0xf6, 0x11, 0x0d, 0xbd, 0xe8, 0xc8, 0xe6, 0xe8, 0x0d, 0x95, 0xb0, 0x9f,
-	0x9e, 0x09, 0xbc, 0x62, 0x39, 0x47, 0x8f, 0x22, 0xef, 0x09, 0x0d, 0xc8, 0x53, 0xc5, 0xca, 0xd7,
-	0xf5, 0x62, 0x50, 0x41, 0x8a, 0xe1, 0xb2, 0x0a, 0xe7, 0x99, 0x3b, 0x19, 0x34, 0xc6, 0xad, 0x58,
-	0x35, 0x1b, 0xf0, 0xa5, 0x01, 0xd6, 0xb2, 0x32, 0x71, 0x7b, 0x4c, 0xc6, 0x66, 0x1f, 0x31, 0x9a,
-	0x10, 0x8e, 0xde, 0x54, 0xc1, 0xfc, 0x50, 0xb6, 0x5e, 0x7d, 0xe1, 0x33, 0xfe, 0xa9, 0xa2, 0x53,
-	0x81, 0xbf, 0x59, 0xaa, 0x9a, 0x0a, 0x57, 0x2a, 0x9e, 0x9d, 0x52, 0xed, 0x18, 0x3b, 0xd6, 0x24,
-	0x4b, 0xb2, 0x89, 0xe5, 0x77, 0xbb, 0x2d, 0x3f, 0x7b, 0xd0, 0xc6, 0xa8, 0x89, 0x65, 0xc4, 0x9e,
-	0xc4, 0x8b, 0xe2, 0x2f, 0x83, 0xa6, 0x55, 0xd1, 0x40, 0x1f, 0x2c, 0xab, 0xcf, 0x51, 0x5b, 0xf6,
-	0x02, 0x5b, 0xf7, 0x57, 0xac, 0xfa, 0xeb, 0xb5, 0xbc, 0xbf, 0x36, 0x25, 0x3f, 0x6a, 0xb2, 0x6a,
-	0x6c, 0x3f, 0xa8, 0x60, 0x45, 0x66, 0xab, 0xb0, 0x69, 0xd5, 0x74, 0xf0, 0x73, 0x03, 0xac, 0xa8,
-	0x2b, 0xa4, 0xbe, 0x66, 0x6d, 0xfd, 0x39, 0x8b, 0x36, 0x95, 0xbf, 0x55, 0xf9, 0x89, 0x70, 0x3f,
-	0x8a, 0xfb, 0x96, 0xe4, 0x1e, 0x29, 0xaa, 0xf9, 0x50, 0x4e, 0x5d, 0x6e, 0x15, 0x4c, 0x05, 0xde,
-	0x2a, 0xae, 0x51, 0x09, 0x2f, 0xa5, 0x91, 0x27, 0x4e, 0xe8, 0x39, 0xcc, 0x33, 0x5f, 0x9f, 0x36,
-	0x66, 0xf2, 0x85, 0x55, 0x37, 0x04, 0xff, 0x2c, 0xc3, 0x71, 0x64, 0x03, 0x25, 0x21, 0xa7, 0x09,
-	0x7d, 0x2e, 0x33, 0x8a, 0xde, 0x52, 0xe9, 0x3c, 0x96, 0x23, 0xe0, 0x7d, 0x87, 0x93, 0xfd, 0x9c,
-	0xdb, 0x53, 0x23, 0xa0, 0x5b, 0x85, 0x52, 0x81, 0xd7, 0x74, 0x30, 0x55, 0x5c, 0x8e, 0x3b, 0x63,
-	0xda, 0x71, 0x48, 0x4e, 0x7c, 0x35, 0x27, 0x56, 0x4d, 0xc3, 0xe1, 0x9f, 0x0c, 0xb0, 0xdc, 0x8e,
-	0x7c, 0x3f, 0x3a, 0xb2, 0x3f, 0xeb, 0x85, 0xae, 0x1c, 0x47, 0x38, 0x32, 0x47, 0x51, 0xfe, 0x20,
-	0x07, 0x3f, 0xe6, 0xbb, 0x94, 0x71, 0x19, 0xe5, 0x67, 0x55, 0xa8, 0x88, 0xb2, 0x86, 0xab, 0x28,
-	0xeb, 0xda, 0x71, 0x48, 0x46, 0x59, 0x73, 0x62, 0x2d, 0xe9, 0x88, 0x0a, 0x18, 0x1e, 0x82, 0x59,
-	0x46, 0x1c, 0xcf, 0x8e, 0x42, 0xbf, 0x8f, 0xfe, 0xba, 0xa7, 0xc2, 0x7b, 0x74, 0x26, 0x30, 0xdc,
-	0x25, 0x31, 0x23, 0xae, 0x93, 0x10, 0xcf, 0x22, 0x8e, 0xf7, 0x38, 0xf4, 0xfb, 0x43, 0x81, 0x8d,
-	0x5b, 0xc5, 0x27, 0x38, 0x8b, 0xd4, 0x24, 0xf8, 0x5e, 0x14, 0x50, 0xd9, 0xab, 0x93, 0xbe, 0xfa,
-	0x04, 0x1f, 0x43, 0x91, 0x61, 0xcd, 0xb0, 0xcc, 0x00, 0xfc, 0x05, 0x58, 0xa9, 0x8c, 0x87, 0xaa,
-	0x7f, 0xfe, 0x4d, 0x3a, 0x35, 0x9a, 0x9f, 0x9c, 0x09, 0x8c, 0x46, 0x4e, 0x1f, 0x8d, 0x26, 0xbf,
-	0x96, 0x9b, 0xe4, 0xae, 0x37, 0xea, 0x33, 0x62, 0xcb, 0x4d, 0x4a, 0x11, 0x20, 0xc3, 0x5a, 0xac,
-	0x92, 0xf0, 0x27, 0xe0, 0x8a, 0x7e, 0x5f, 0x72, 0xf4, 0xc5, 0x9e, 0xaa, 0xf5, 0x8f, 0x64, 0xe3,
-	0x19, 0x39, 0xd2, 0x73, 0x10, 0xaf, 0x3e, 0x5c, 0xb6, 0xa5, 0x64, 0x3a, 0x2b, 0x70, 0x64, 0x58,
-	0xb9, 0xbd, 0xe6, 0xc3, 0x57, 0x5f, 0x6e, 0x4c, 0x0d, 0xbe, 0xdc, 0x98, 0x7a, 0x75, 0xb6, 0x61,
-	0x0c, 0xce, 0x36, 0x8c, 0xdf, 0x9d, 0x6f, 0x4c, 0xfd, 0xf1, 0x7c, 0xc3, 0x18, 0x9c, 0x6f, 0x4c,
-	0xfd, 0xfb, 0x7c, 0x63, 0xea, 0xd9, 0x3b, 0xff, 0xc7, 0x9f, 0x1e, 0xba, 0x5c, 0x0f, 0x2e, 0xab,
-	0x3f, 0x3f, 0xde, 0xff, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xe5, 0x04, 0x99, 0x1a, 0x13,
-	0x00, 0x00,
+	// 2043 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0xcd, 0x6f, 0x24, 0x47,
+	0x15, 0x77, 0x7b, 0xbf, 0xec, 0xf2, 0x77, 0x79, 0xbd, 0xdb, 0xf1, 0x26, 0x53, 0x93, 0x66, 0x36,
+	0x38, 0x51, 0xe2, 0xdd, 0x75, 0x10, 0x12, 0x2b, 0x16, 0xc8, 0xd8, 0xb1, 0x58, 0x16, 0x67, 0x47,
+	0xed, 0x85, 0x15, 0x01, 0xa9, 0xe9, 0xe9, 0xae, 0x99, 0xa9, 0xb8, 0xbf, 0xa8, 0xea, 0x59, 0x7b,
+	0xf6, 0x10, 0x2d, 0x17, 0x04, 0x22, 0x07, 0x64, 0x0e, 0xdc, 0x50, 0x24, 0x10, 0x82, 0xfc, 0x03,
+	0x48, 0xfc, 0x05, 0x7b, 0x41, 0x9e, 0x13, 0x42, 0x1c, 0x4a, 0x8a, 0xf7, 0x36, 0xc7, 0x3e, 0xfa,
+	0x84, 0xaa, 0xaa, 0xbb, 0xa7, 0xbb, 0x67, 0x22, 0x21, 0x71, 0x9b, 0xfa, 0xfd, 0x5e, 0xbd, 0xf7,
+	0xeb, 0x57, 0xaf, 0x5e, 0xbf, 0x1e, 0xd0, 0xf0, 0x48, 0xfb, 0x8e, 0x13, 0x06, 0x1d, 0xd2, 0xbd,
+	0xd3, 0x09, 0x3d, 0x17, 0x53, 0xb5, 0xe8, 0x53, 0x3b, 0x26, 0x61, 0xb0, 0x1d, 0xd1, 0x30, 0x0e,
+	0xe1, 0x55, 0x05, 0x6e, 0xde, 0x9a, 0xb0, 0x8e, 0x07, 0x11, 0x56, 0x46, 0x9b, 0x1b, 0x05, 0x92,
+	0x91, 0xe7, 0x19, 0xbc, 0x59, 0x80, 0xa3, 0xbe, 0xe7, 0x85, 0xd4, 0xc5, 0x34, 0xe5, 0xb6, 0x0a,
+	0xdc, 0x33, 0x4c, 0x19, 0x09, 0x03, 0x12, 0x74, 0xa7, 0x28, 0xd8, 0x44, 0x05, 0xcb, 0xb6, 0x17,
+	0x3a, 0x47, 0x55, 0x57, 0x50, 0x18, 0x74, 0xd8, 0x1d, 0x21, 0x88, 0xa5, 0xd8, 0xeb, 0x29, 0xe6,
+	0x84, 0xd1, 0x80, 0xda, 0x41, 0x17, 0xfb, 0x38, 0xee, 0x85, 0x6e, 0xca, 0xce, 0xe3, 0x93, 0x58,
+	0xfd, 0x34, 0xfe, 0x75, 0x09, 0xbc, 0xb6, 0x2f, 0x9f, 0x67, 0x0f, 0x3f, 0x23, 0x0e, 0xde, 0x2d,
+	0x2a, 0x80, 0x5f, 0x68, 0x60, 0xde, 0x95, 0xb8, 0x45, 0x5c, 0x5d, 0xab, 0x6b, 0x5b, 0x8b, 0xcd,
+	0xcf, 0xb4, 0x97, 0x1c, 0xcd, 0xfc, 0x87, 0xa3, 0x6f, 0x74, 0x49, 0xdc, 0xeb, 0xb7, 0xb7, 0x9d,
+	0xd0, 0xbf, 0xc3, 0x06, 0x81, 0x13, 0xf7, 0x48, 0xd0, 0x2d, 0xfc, 0x12, 0x12, 0x64, 0x10, 0x27,
+	0xf4, 0xb6, 0x95, 0xf7, 0x87, 0x7b, 0xe7, 0x1c, 0xcd, 0x65, 0xbf, 0x47, 0x1c, 0xcd, 0xb9, 0xe9,
+	0xef, 0x84, 0xa3, 0xa5, 0x13, 0xdf, 0xbb, 0x6f, 0x10, 0xf7, 0x5d, 0x3b, 0x8e, 0xa9, 0x31, 0x3a,
+	0x6b, 0x5c, 0x4b, 0x7f, 0x27, 0x67, 0x8d, 0xdc, 0xee, 0xd7, 0xc3, 0x86, 0x76, 0x3a, 0x6c, 0xe4,
+	0x3e, 0xcc, 0x8c, 0x71, 0xe1, 0x5f, 0x34, 0xb0, 0x44, 0x82, 0x98, 0x86, 0x6e, 0xdf, 0xc1, 0xae,
+	0xd5, 0x1e, 0xe8, 0xb3, 0x52, 0xf0, 0x8b, 0xff, 0x4b, 0xf0, 0x88, 0xa3, 0xc5, 0xb1, 0xd7, 0xe6,
+	0x20, 0xe1, 0xe8, 0xa6, 0x12, 0x5a, 0x00, 0x73, 0xc9, 0x6b, 0x13, 0xa8, 0x10, 0x6c, 0x96, 0x3c,
+	0x40, 0x07, 0xac, 0xe3, 0xc0, 0xa1, 0x83, 0x48, 0xe4, 0xd8, 0x8a, 0x6c, 0xc6, 0x8e, 0x43, 0xea,
+	0xea, 0x97, 0xea, 0xda, 0xd6, 0x7c, 0x73, 0x67, 0xc4, 0x11, 0x1c, 0xd3, 0xad, 0x94, 0x4d, 0x38,
+	0xd2, 0x65, 0xd8, 0x49, 0xca, 0x30, 0xa7, 0xd8, 0x1b, 0x7f, 0xac, 0x83, 0x75, 0x75, 0xb0, 0xe5,
+	0x23, 0x3d, 0x04, 0xb3, 0xe9, 0x51, 0xce, 0x37, 0x77, 0xcf, 0x39, 0x9a, 0x95, 0x8f, 0x38, 0x4b,
+	0x44, 0x84, 0x5a, 0xe9, 0x04, 0xea, 0x41, 0xe8, 0xe2, 0x8e, 0xdd, 0xf7, 0xe2, 0xfb, 0x46, 0x4c,
+	0xfb, 0xb8, 0x78, 0x24, 0xa7, 0xc3, 0xc6, 0xec, 0xc3, 0xbd, 0xcf, 0xc5, 0xb3, 0xcd, 0x12, 0x17,
+	0xfe, 0x08, 0x5c, 0xf1, 0xec, 0x36, 0xf6, 0x64, 0xc6, 0xe7, 0x9b, 0xdf, 0x1d, 0x71, 0xa4, 0x80,
+	0x84, 0xa3, 0xba, 0x74, 0x2a, 0x57, 0xa9, 0x5f, 0x8a, 0x59, 0x6c, 0xd3, 0xf8, 0xbe, 0xd1, 0xb1,
+	0x3d, 0x26, 0xdd, 0x82, 0x31, 0xfd, 0x62, 0xd8, 0x98, 0x31, 0xd5, 0x66, 0xd8, 0x05, 0x2b, 0x1d,
+	0xe2, 0x61, 0x36, 0x60, 0x31, 0xf6, 0x2d, 0x51, 0xdf, 0x32, 0x49, 0xcb, 0x3b, 0x70, 0xbb, 0xc3,
+	0xb6, 0xf7, 0x73, 0xea, 0xc9, 0x20, 0xc2, 0xcd, 0x77, 0x46, 0x1c, 0x2d, 0x77, 0x4a, 0x58, 0xc2,
+	0xd1, 0x75, 0x19, 0xbd, 0x0c, 0x1b, 0x66, 0xc5, 0x0e, 0x1e, 0x80, 0xcb, 0x91, 0x1d, 0xf7, 0xf4,
+	0xcb, 0x52, 0xfe, 0xb7, 0x46, 0x1c, 0xc9, 0x75, 0xc2, 0xd1, 0x2d, 0xb9, 0x5f, 0x2c, 0x52, 0xf1,
+	0x79, 0x4a, 0x3e, 0x15, 0xc2, 0xe7, 0x73, 0xe6, 0xe2, 0xac, 0xa1, 0x7d, 0x6a, 0xca, 0x6d, 0xb0,
+	0x05, 0x2e, 0x4b, 0xb1, 0x57, 0x52, 0xb1, 0xea, 0xf6, 0x6e, 0xab, 0xe3, 0x90, 0x62, 0xb7, 0x44,
+	0x88, 0x58, 0x49, 0x5c, 0x91, 0x21, 0xc4, 0x22, 0x2f, 0xa3, 0xf9, 0x7c, 0x65, 0x4a, 0x2b, 0xf8,
+	0x33, 0x70, 0x4d, 0xd5, 0x39, 0xd3, 0xaf, 0xd6, 0x2f, 0x6d, 0x2d, 0xec, 0xbc, 0x59, 0x76, 0x3a,
+	0xe5, 0xf2, 0x36, 0x91, 0x28, 0xfb, 0x11, 0x47, 0xd9, 0xce, 0x84, 0xa3, 0x45, 0x19, 0x4a, 0xad,
+	0x0d, 0x33, 0x23, 0xe0, 0xef, 0x35, 0xb0, 0x46, 0x31, 0x73, 0xec, 0xc0, 0x22, 0x41, 0x8c, 0xe9,
+	0x33, 0xdb, 0xb3, 0x98, 0x7e, 0xad, 0xae, 0x6d, 0x5d, 0x69, 0x76, 0x47, 0x1c, 0xad, 0x28, 0xf2,
+	0x61, 0xca, 0x1d, 0x26, 0x1c, 0xbd, 0x2d, 0x3d, 0x55, 0xf0, 0x6a, 0x8a, 0xde, 0xff, 0xe6, 0xdd,
+	0xbb, 0xc6, 0x05, 0x47, 0x97, 0x48, 0x10, 0x8f, 0xce, 0x1a, 0xd7, 0xa7, 0x99, 0x5f, 0x9c, 0x35,
+	0x2e, 0x0b, 0x3b, 0xb3, 0x1a, 0x04, 0xfe, 0x43, 0x03, 0xb0, 0xc3, 0xac, 0x63, 0x3b, 0x76, 0x7a,
+	0x98, 0x5a, 0x38, 0xb0, 0xdb, 0x1e, 0x76, 0xf5, 0xb9, 0xba, 0xb6, 0x35, 0xd7, 0xfc, 0xad, 0x76,
+	0xce, 0xd1, 0xea, 0xfe, 0xe1, 0x53, 0xc5, 0x7e, 0xa8, 0xc8, 0x11, 0x47, 0xab, 0x1d, 0x56, 0xc6,
+	0x12, 0x8e, 0xde, 0x51, 0x45, 0x50, 0x21, 0xaa, 0x6a, 0xb3, 0x1a, 0xdf, 0x98, 0x6a, 0x28, 0x74,
+	0x0a, 0x8b, 0xd3, 0x61, 0x63, 0x22, 0xac, 0x39, 0x11, 0x14, 0xfe, 0xbd, 0x2c, 0xde, 0xc5, 0x9e,
+	0x3d, 0xb0, 0x98, 0x3e, 0x2f, 0x73, 0xfa, 0x1b, 0x21, 0x7e, 0x25, 0xf7, 0xb2, 0x27, 0xc8, 0x43,
+	0x91, 0xe7, 0xdc, 0x8d, 0x82, 0x12, 0x8e, 0xbe, 0x5e, 0x96, 0xae, 0xf0, 0xaa, 0xf2, 0x7b, 0xa5,
+	0x2c, 0x4f, 0x33, 0xbe, 0x38, 0x6b, 0xcc, 0xde, 0xbb, 0x7b, 0x3a, 0x6c, 0x54, 0xa3, 0x9a, 0xd5,
+	0x98, 0xf0, 0xe7, 0x60, 0x91, 0x74, 0x83, 0x90, 0x62, 0x2b, 0xc2, 0xd4, 0x67, 0x3a, 0x90, 0xf9,
+	0x7e, 0x30, 0xe2, 0x68, 0x41, 0xe1, 0x2d, 0x01, 0x27, 0x1c, 0xdd, 0x50, 0xdd, 0x62, 0x8c, 0xe5,
+	0xe5, 0xbb, 0x5a, 0x05, 0xcd, 0xe2, 0x56, 0xf8, 0x4b, 0x0d, 0x2c, 0xdb, 0xfd, 0x38, 0xb4, 0x82,
+	0x90, 0xfa, 0xb6, 0x47, 0x9e, 0x63, 0x7d, 0x41, 0x06, 0xf9, 0x78, 0xc4, 0xd1, 0x92, 0x60, 0x3e,
+	0xca, 0x88, 0x3c, 0x03, 0x25, 0xf4, 0xab, 0x4e, 0x0e, 0x4e, 0x5a, 0x65, 0xc7, 0x66, 0x96, 0xfd,
+	0xc2, 0x10, 0x2c, 0xf9, 0x24, 0xb0, 0x5c, 0xc2, 0x8e, 0xac, 0x0e, 0xc5, 0x58, 0x5f, 0xac, 0x6b,
+	0x5b, 0x0b, 0x3b, 0x8b, 0xd9, 0xb5, 0x3a, 0x24, 0xcf, 0x71, 0xf3, 0x41, 0x7a, 0x83, 0x16, 0x7c,
+	0x12, 0xec, 0x11, 0x76, 0xb4, 0x4f, 0xb1, 0x50, 0x84, 0xa4, 0xa2, 0x02, 0x56, 0x3c, 0x8a, 0xfa,
+	0x6d, 0xe3, 0xe2, 0xac, 0x71, 0xe9, 0x5e, 0xfd, 0xb6, 0x59, 0xdc, 0x06, 0xbb, 0x00, 0x8c, 0xdf,
+	0xf3, 0xfa, 0x92, 0x8c, 0x86, 0xb2, 0x68, 0x3f, 0xce, 0x99, 0xf2, 0x15, 0x7e, 0x2b, 0x15, 0x50,
+	0xd8, 0x9a, 0x70, 0xb4, 0x2a, 0xe3, 0x8f, 0x21, 0xc3, 0x2c, 0xf0, 0xf0, 0x01, 0xb8, 0xe6, 0x84,
+	0x11, 0xc1, 0x94, 0xe9, 0xcb, 0xb2, 0xda, 0xbe, 0x26, 0x7a, 0x40, 0x0a, 0xe5, 0xaf, 0xd9, 0x74,
+	0x9d, 0xd5, 0x8d, 0x99, 0x19, 0xc0, 0x7f, 0x6a, 0xe0, 0x86, 0x98, 0x30, 0x30, 0xb5, 0x7c, 0xfb,
+	0xc4, 0x8a, 0x70, 0xe0, 0x92, 0xa0, 0x6b, 0x1d, 0x91, 0xb6, 0xbe, 0x22, 0xdd, 0xfd, 0x41, 0x14,
+	0xef, 0x7a, 0x4b, 0x9a, 0x1c, 0xd8, 0x27, 0x2d, 0x65, 0xf0, 0x88, 0x34, 0x47, 0x1c, 0xad, 0x47,
+	0x93, 0x70, 0xc2, 0xd1, 0x6b, 0xaa, 0x89, 0x4e, 0x72, 0x85, 0xb2, 0x9d, 0xba, 0x75, 0x3a, 0x7c,
+	0x3a, 0x6c, 0x4c, 0x8b, 0x6f, 0x4e, 0xb1, 0x6d, 0x8b, 0x74, 0xf4, 0x6c, 0xd6, 0x13, 0xe9, 0x58,
+	0x1d, 0xa7, 0x23, 0x85, 0xf2, 0x74, 0xa4, 0xeb, 0x71, 0x3a, 0x52, 0x00, 0x7e, 0x00, 0xae, 0xc8,
+	0x59, 0x4b, 0x5f, 0x93, 0xbd, 0x7c, 0x2d, 0x3b, 0x31, 0x11, 0xff, 0xb1, 0x20, 0x9a, 0xba, 0x78,
+	0xd9, 0x49, 0x9b, 0x84, 0xa3, 0x05, 0xe9, 0x4d, 0xae, 0x0c, 0x53, 0xa1, 0xf0, 0x11, 0x58, 0x4a,
+	0x2f, 0x94, 0x8b, 0x3d, 0x1c, 0x63, 0x1d, 0xca, 0x62, 0x7f, 0x4b, 0x4e, 0x16, 0x92, 0xd8, 0x93,
+	0x78, 0xc2, 0x11, 0x2c, 0x5c, 0x29, 0x05, 0x1a, 0x66, 0xc9, 0x06, 0x9e, 0x00, 0x5d, 0xf6, 0xe9,
+	0x88, 0x86, 0x5d, 0x8a, 0x19, 0x2b, 0x36, 0xec, 0x75, 0xf9, 0x7c, 0xe2, 0xe5, 0xbb, 0x21, 0x6c,
+	0x5a, 0xa9, 0x49, 0xb1, 0x6d, 0xab, 0xd7, 0xd9, 0x54, 0x36, 0x7f, 0xf6, 0xe9, 0x9b, 0xe1, 0x21,
+	0x58, 0x4e, 0xeb, 0x22, 0xb2, 0xfb, 0x0c, 0x5b, 0x4c, 0xbf, 0x2e, 0xe3, 0xbd, 0x27, 0x9e, 0x43,
+	0x31, 0x2d, 0x41, 0x1c, 0xe6, 0xcf, 0x51, 0x04, 0x73, 0xef, 0x25, 0x53, 0x88, 0xc1, 0x92, 0xa8,
+	0x32, 0x91, 0x54, 0x8f, 0x38, 0x31, 0xd3, 0x37, 0xa4, 0xcf, 0xef, 0x09, 0x9f, 0xbe, 0x7d, 0xb2,
+	0x9b, 0xe1, 0xe3, 0x5b, 0x57, 0x00, 0xa7, 0x76, 0x40, 0xd5, 0xe9, 0xcc, 0xd2, 0x6e, 0xe8, 0x82,
+	0xeb, 0x2e, 0x61, 0xa2, 0x33, 0x5b, 0x2c, 0xb2, 0x29, 0xc3, 0x96, 0x1c, 0x00, 0xf4, 0x1b, 0xf2,
+	0x24, 0xe4, 0xc8, 0x95, 0xf2, 0x87, 0x92, 0x96, 0xa3, 0x45, 0x3e, 0x72, 0x4d, 0x52, 0x86, 0x39,
+	0xc5, 0xbe, 0x18, 0x25, 0xc6, 0x7e, 0x64, 0x91, 0xc0, 0xc5, 0x27, 0x98, 0xe9, 0x37, 0x27, 0xa2,
+	0x3c, 0xc1, 0x7e, 0xf4, 0x50, 0xb1, 0xd5, 0x28, 0x05, 0x6a, 0x1c, 0xa5, 0x00, 0xc2, 0x1d, 0x70,
+	0x55, 0x1e, 0x80, 0xab, 0xeb, 0xd2, 0xef, 0xe6, 0x88, 0xa3, 0x14, 0xc9, 0xdf, 0xf0, 0x6a, 0x69,
+	0x98, 0x29, 0x0e, 0x63, 0x70, 0xf3, 0x18, 0xdb, 0x47, 0x96, 0xa8, 0x6a, 0x2b, 0xee, 0x51, 0xcc,
+	0x7a, 0xa1, 0xe7, 0x5a, 0x91, 0x13, 0xeb, 0xaf, 0xc9, 0x84, 0x8b, 0xf6, 0x7e, 0x5d, 0x98, 0x7c,
+	0xdf, 0x66, 0xbd, 0x27, 0x99, 0x41, 0xcb, 0x89, 0x13, 0x8e, 0x36, 0xa5, 0xcb, 0x69, 0x64, 0x7e,
+	0xa8, 0x53, 0xb7, 0xc2, 0x5d, 0xb0, 0xe0, 0xdb, 0xf4, 0x08, 0x53, 0x2b, 0xb0, 0x7d, 0xac, 0x6f,
+	0xca, 0xe1, 0xca, 0x10, 0xed, 0x4c, 0xc1, 0x1f, 0xd9, 0x3e, 0xce, 0xdb, 0xd9, 0x18, 0x32, 0xcc,
+	0x02, 0x0f, 0x07, 0x60, 0x53, 0x7c, 0xc4, 0x58, 0xe1, 0x71, 0x80, 0x29, 0xeb, 0x91, 0xc8, 0xea,
+	0xd0, 0xd0, 0xb7, 0x22, 0x9b, 0xe2, 0x20, 0xd6, 0x6f, 0xc9, 0x14, 0x7c, 0x7b, 0xc4, 0xd1, 0x4d,
+	0x61, 0xf5, 0x38, 0x33, 0xda, 0xa7, 0xa1, 0xdf, 0x92, 0x26, 0x09, 0x47, 0x6f, 0x64, 0x1d, 0x6f,
+	0x1a, 0x6f, 0x98, 0x5f, 0xb5, 0x13, 0xfe, 0x4a, 0x03, 0x6b, 0x7e, 0xe8, 0x5a, 0x31, 0xf1, 0xb1,
+	0x75, 0x4c, 0x02, 0x37, 0x3c, 0xb6, 0x98, 0xfe, 0xba, 0x4c, 0xd8, 0x4f, 0xcf, 0x39, 0x5a, 0x33,
+	0xed, 0xe3, 0x83, 0xd0, 0x7d, 0x42, 0x7c, 0xfc, 0x54, 0xb2, 0xe2, 0x1d, 0xbe, 0xec, 0x97, 0x90,
+	0x7c, 0x04, 0x2d, 0xc3, 0x59, 0xe6, 0x4e, 0x87, 0x8d, 0x49, 0x2f, 0x66, 0xc5, 0x07, 0x7c, 0xa1,
+	0x81, 0x8d, 0xf4, 0x9a, 0x38, 0x7d, 0x2a, 0xb4, 0x59, 0xc7, 0x94, 0xc4, 0x98, 0xe9, 0x6f, 0x48,
+	0x31, 0x3f, 0x14, 0xad, 0x57, 0x15, 0x7c, 0xca, 0x3f, 0x95, 0x74, 0xc2, 0xd1, 0xed, 0xc2, 0xad,
+	0x29, 0x71, 0x85, 0xcb, 0xb3, 0x53, 0xb8, 0x3b, 0xda, 0x8e, 0x39, 0xcd, 0x93, 0x68, 0x62, 0x59,
+	0x6d, 0x77, 0xc4, 0x17, 0x93, 0x5e, 0x1b, 0x37, 0xb1, 0x94, 0xd8, 0x17, 0x78, 0x7e, 0xf9, 0x8b,
+	0xa0, 0x61, 0x96, 0x6c, 0xa0, 0x07, 0x56, 0xe5, 0x97, 0xac, 0x25, 0x7a, 0x81, 0xa5, 0xfa, 0x2b,
+	0x92, 0xfd, 0xf5, 0x46, 0xd6, 0x5f, 0x9b, 0x82, 0x1f, 0x37, 0x59, 0x39, 0xdc, 0xb7, 0x4b, 0x58,
+	0x9e, 0xd9, 0x32, 0x6c, 0x98, 0x15, 0x3b, 0xf8, 0x99, 0x06, 0xd6, 0x64, 0x09, 0xc9, 0x0f, 0x61,
+	0x4b, 0x7d, 0x09, 0xeb, 0x75, 0x19, 0x6f, 0x5d, 0x7c, 0x48, 0xec, 0x86, 0xd1, 0xc0, 0x14, 0xdc,
+	0x81, 0xa4, 0x9a, 0x8f, 0xc4, 0x28, 0xe6, 0x94, 0xc1, 0x84, 0xa3, 0xad, 0xbc, 0x8c, 0x0a, 0x78,
+	0x21, 0x8d, 0x2c, 0xb6, 0x03, 0xd7, 0xa6, 0xae, 0x78, 0xff, 0xcf, 0x65, 0x0b, 0xb3, 0xea, 0x08,
+	0xfe, 0x59, 0xc8, 0xb1, 0x45, 0x03, 0xc5, 0x01, 0x23, 0x31, 0x79, 0x26, 0x32, 0xaa, 0xbf, 0x29,
+	0xd3, 0x79, 0x22, 0xe6, 0xc2, 0x5d, 0x9b, 0xe1, 0xc3, 0x8c, 0xdb, 0x97, 0x73, 0xa1, 0x53, 0x86,
+	0x12, 0x8e, 0x36, 0x94, 0x98, 0x32, 0x2e, 0x66, 0xa0, 0x09, 0xdb, 0x49, 0x48, 0x8c, 0x81, 0x95,
+	0x20, 0x66, 0xc5, 0x86, 0xc1, 0x3f, 0x69, 0x60, 0xb5, 0x13, 0x7a, 0x5e, 0x78, 0x6c, 0x7d, 0xd2,
+	0x0f, 0x1c, 0x31, 0x8e, 0x30, 0xdd, 0x18, 0xab, 0xfc, 0x41, 0x06, 0x7e, 0xc0, 0xf6, 0x08, 0x65,
+	0x42, 0xe5, 0x27, 0x65, 0x28, 0x57, 0x59, 0xc1, 0xa5, 0xca, 0xaa, 0xed, 0x24, 0x24, 0x54, 0x56,
+	0x82, 0x98, 0x2b, 0x4a, 0x51, 0x0e, 0xc3, 0x23, 0x30, 0x4f, 0xb1, 0xed, 0x5a, 0x61, 0xe0, 0x0d,
+	0xf4, 0xbf, 0xee, 0x4b, 0x79, 0x07, 0xe7, 0x1c, 0xc1, 0x3d, 0x1c, 0x51, 0xec, 0xd8, 0x31, 0x76,
+	0x4d, 0x6c, 0xbb, 0x8f, 0x03, 0x6f, 0x30, 0xe2, 0x48, 0x7b, 0x2f, 0xff, 0x7a, 0xa7, 0xa1, 0x1c,
+	0x0f, 0xdf, 0x0d, 0x7d, 0x22, 0x7a, 0x75, 0x3c, 0x90, 0x5f, 0xef, 0x13, 0xa8, 0xae, 0x99, 0x73,
+	0x34, 0x75, 0x00, 0x7f, 0x01, 0xd6, 0x4a, 0x33, 0xa3, 0xec, 0x9f, 0x7f, 0x13, 0x41, 0xb5, 0xe6,
+	0x87, 0xe7, 0x1c, 0xe9, 0xe3, 0xa0, 0x07, 0xe3, 0xc9, 0xaf, 0xe5, 0xc4, 0x59, 0xe8, 0x5a, 0x75,
+	0x70, 0x6c, 0x39, 0x71, 0x41, 0x81, 0xae, 0x99, 0xcb, 0x65, 0x12, 0xfe, 0x04, 0x5c, 0x53, 0xef,
+	0x4b, 0xa6, 0x7f, 0xb1, 0x2f, 0xef, 0xfa, 0x77, 0x44, 0xe3, 0x19, 0x07, 0x52, 0x73, 0x10, 0x2b,
+	0x3f, 0x5c, 0xba, 0xa5, 0xe0, 0x3a, 0xbd, 0xe0, 0xba, 0x66, 0x66, 0xfe, 0x9a, 0x8f, 0x5e, 0x7e,
+	0x59, 0x9b, 0x19, 0x7e, 0x59, 0x9b, 0x79, 0x79, 0x5e, 0xd3, 0x86, 0xe7, 0x35, 0xed, 0x77, 0xaf,
+	0x6a, 0x33, 0x9f, 0xbf, 0xaa, 0x69, 0xc3, 0x57, 0xb5, 0x99, 0x7f, 0xbf, 0xaa, 0xcd, 0x7c, 0xfc,
+	0xf6, 0xff, 0xf0, 0x7f, 0x89, 0xba, 0xae, 0xed, 0xab, 0xf2, 0x7f, 0x93, 0xf7, 0xff, 0x1b, 0x00,
+	0x00, 0xff, 0xff, 0x3e, 0xb6, 0x85, 0xe6, 0x55, 0x13, 0x00, 0x00,
 }
 
 func (m *FolderDeviceConfiguration) Marshal() (dAtA []byte, err error) {

+ 6 - 0
lib/config/migrations.go

@@ -27,6 +27,7 @@ import (
 // put the newest on top for readability.
 var (
 	migrations = migrationSet{
+		{34, migrateToConfigV34},
 		{33, migrateToConfigV33},
 		{32, migrateToConfigV32},
 		{31, migrateToConfigV31},
@@ -92,6 +93,11 @@ func (m migration) apply(cfg *Configuration) {
 	cfg.Version = m.targetVersion
 }
 
+func migrateToConfigV34(cfg *Configuration) {
+	cfg.Defaults.Folder.Path = cfg.Options.DeprecatedDefaultFolderPath
+	cfg.Options.DeprecatedDefaultFolderPath = ""
+}
+
 func migrateToConfigV33(cfg *Configuration) {
 	for i := range cfg.Devices {
 		cfg.Devices[i].DeprecatedPendingFolders = nil

+ 1 - 1
lib/config/observed.pb.go

@@ -7,9 +7,9 @@ import (
 	fmt "fmt"
 	proto "github.com/gogo/protobuf/proto"
 	github_com_gogo_protobuf_types "github.com/gogo/protobuf/types"
+	_ "github.com/golang/protobuf/ptypes/timestamp"
 	github_com_syncthing_syncthing_lib_protocol "github.com/syncthing/syncthing/lib/protocol"
 	_ "github.com/syncthing/syncthing/proto/ext"
-	_ "google.golang.org/protobuf/types/known/timestamppb"
 	io "io"
 	math "math"
 	math_bits "math/bits"

+ 259 - 258
lib/config/optionsconfiguration.pb.go

@@ -25,55 +25,55 @@ var _ = math.Inf
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
 type OptionsConfiguration struct {
-	RawListenAddresses      []string `protobuf:"bytes,1,rep,name=listen_addresses,json=listenAddresses,proto3" json:"listenAddresses" xml:"listenAddress" default:"default"`
-	RawGlobalAnnServers     []string `protobuf:"bytes,2,rep,name=global_discovery_servers,json=globalDiscoveryServers,proto3" json:"globalAnnounceServers" xml:"globalAnnounceServer" default:"default"`
-	GlobalAnnEnabled        bool     `protobuf:"varint,3,opt,name=global_discovery_enabled,json=globalDiscoveryEnabled,proto3" json:"globalAnnounceEnabled" xml:"globalAnnounceEnabled" default:"true"`
-	LocalAnnEnabled         bool     `protobuf:"varint,4,opt,name=local_discovery_enabled,json=localDiscoveryEnabled,proto3" json:"localAnnounceEnabled" xml:"localAnnounceEnabled" default:"true"`
-	LocalAnnPort            int      `protobuf:"varint,5,opt,name=local_announce_port,json=localAnnouncePort,proto3,casttype=int" json:"localAnnouncePort" xml:"localAnnouncePort" default:"21027"`
-	LocalAnnMCAddr          string   `protobuf:"bytes,6,opt,name=local_announce_multicast_address,json=localAnnounceMulticastAddress,proto3" json:"localAnnounceMCAddr" xml:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
-	MaxSendKbps             int      `protobuf:"varint,7,opt,name=max_send_kbps,json=maxSendKbps,proto3,casttype=int" json:"maxSendKbps" xml:"maxSendKbps"`
-	MaxRecvKbps             int      `protobuf:"varint,8,opt,name=max_recv_kbps,json=maxRecvKbps,proto3,casttype=int" json:"maxRecvKbps" xml:"maxRecvKbps"`
-	ReconnectIntervalS      int      `protobuf:"varint,9,opt,name=reconnection_interval_s,json=reconnectionIntervalS,proto3,casttype=int" json:"reconnectionIntervalS" xml:"reconnectionIntervalS" default:"60"`
-	RelaysEnabled           bool     `protobuf:"varint,10,opt,name=relays_enabled,json=relaysEnabled,proto3" json:"relaysEnabled" xml:"relaysEnabled" default:"true"`
-	RelayReconnectIntervalM int      `protobuf:"varint,11,opt,name=relays_reconnect_interval_m,json=relaysReconnectIntervalM,proto3,casttype=int" json:"relayReconnectIntervalM" xml:"relayReconnectIntervalM" default:"10"`
-	StartBrowser            bool     `protobuf:"varint,12,opt,name=start_browser,json=startBrowser,proto3" json:"startBrowser" xml:"startBrowser" default:"true"`
-	NATEnabled              bool     `protobuf:"varint,14,opt,name=nat_traversal_enabled,json=natTraversalEnabled,proto3" json:"natEnabled" xml:"natEnabled" default:"true"`
-	NATLeaseM               int      `protobuf:"varint,15,opt,name=nat_traversal_lease_m,json=natTraversalLeaseM,proto3,casttype=int" json:"natLeaseMinutes" xml:"natLeaseMinutes" default:"60"`
-	NATRenewalM             int      `protobuf:"varint,16,opt,name=nat_traversal_renewal_m,json=natTraversalRenewalM,proto3,casttype=int" json:"natRenewalMinutes" xml:"natRenewalMinutes" default:"30"`
-	NATTimeoutS             int      `protobuf:"varint,17,opt,name=nat_traversal_timeout_s,json=natTraversalTimeoutS,proto3,casttype=int" json:"natTimeoutSeconds" xml:"natTimeoutSeconds" default:"10"`
-	URAccepted              int      `protobuf:"varint,18,opt,name=usage_reporting_accepted,json=usageReportingAccepted,proto3,casttype=int" json:"urAccepted" xml:"urAccepted"`
-	URSeen                  int      `protobuf:"varint,19,opt,name=usage_reporting_seen,json=usageReportingSeen,proto3,casttype=int" json:"urSeen" xml:"urSeen"`
-	URUniqueID              string   `protobuf:"bytes,20,opt,name=usage_reporting_unique_id,json=usageReportingUniqueId,proto3" json:"urUniqueId" xml:"urUniqueID"`
-	URURL                   string   `protobuf:"bytes,21,opt,name=usage_reporting_url,json=usageReportingUrl,proto3" json:"urURL" xml:"urURL" default:"https://data.syncthing.net/newdata"`
-	URPostInsecurely        bool     `protobuf:"varint,22,opt,name=usage_reporting_post_insecurely,json=usageReportingPostInsecurely,proto3" json:"urPostInsecurely" xml:"urPostInsecurely" default:"false"`
-	URInitialDelayS         int      `protobuf:"varint,23,opt,name=usage_reporting_initial_delay_s,json=usageReportingInitialDelayS,proto3,casttype=int" json:"urInitialDelayS" xml:"urInitialDelayS" default:"1800"`
-	RestartOnWakeup         bool     `protobuf:"varint,24,opt,name=restart_on_wakeup,json=restartOnWakeup,proto3" json:"restartOnWakeup" xml:"restartOnWakeup" default:"true"`
-	AutoUpgradeIntervalH    int      `protobuf:"varint,25,opt,name=auto_upgrade_interval_h,json=autoUpgradeIntervalH,proto3,casttype=int" json:"autoUpgradeIntervalH" xml:"autoUpgradeIntervalH" default:"12"`
-	UpgradeToPreReleases    bool     `protobuf:"varint,26,opt,name=upgrade_to_pre_releases,json=upgradeToPreReleases,proto3" json:"upgradeToPreReleases" xml:"upgradeToPreReleases"`
-	KeepTemporariesH        int      `protobuf:"varint,27,opt,name=keep_temporaries_h,json=keepTemporariesH,proto3,casttype=int" json:"keepTemporariesH" xml:"keepTemporariesH" default:"24"`
-	CacheIgnoredFiles       bool     `protobuf:"varint,28,opt,name=cache_ignored_files,json=cacheIgnoredFiles,proto3" json:"cacheIgnoredFiles" xml:"cacheIgnoredFiles" default:"false"`
-	ProgressUpdateIntervalS int      `protobuf:"varint,29,opt,name=progress_update_interval_s,json=progressUpdateIntervalS,proto3,casttype=int" json:"progressUpdateIntervalS" xml:"progressUpdateIntervalS" default:"5"`
-	LimitBandwidthInLan     bool     `protobuf:"varint,30,opt,name=limit_bandwidth_in_lan,json=limitBandwidthInLan,proto3" json:"limitBandwidthInLan" xml:"limitBandwidthInLan" default:"false"`
-	MinHomeDiskFree         Size     `protobuf:"bytes,31,opt,name=min_home_disk_free,json=minHomeDiskFree,proto3" json:"minHomeDiskFree" xml:"minHomeDiskFree" default:"1 %"`
-	ReleasesURL             string   `protobuf:"bytes,32,opt,name=releases_url,json=releasesUrl,proto3" json:"releasesURL" xml:"releasesURL" default:"https://upgrades.syncthing.net/meta.json"`
-	AlwaysLocalNets         []string `protobuf:"bytes,33,rep,name=always_local_nets,json=alwaysLocalNets,proto3" json:"alwaysLocalNets" xml:"alwaysLocalNet"`
-	OverwriteRemoteDevNames bool     `protobuf:"varint,34,opt,name=overwrite_remote_device_names_on_connect,json=overwriteRemoteDeviceNamesOnConnect,proto3" json:"overwriteRemoteDeviceNamesOnConnect" xml:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
-	TempIndexMinBlocks      int      `protobuf:"varint,35,opt,name=temp_index_min_blocks,json=tempIndexMinBlocks,proto3,casttype=int" json:"tempIndexMinBlocks" xml:"tempIndexMinBlocks" default:"10"`
-	UnackedNotificationIDs  []string `protobuf:"bytes,36,rep,name=unacked_notification_ids,json=unackedNotificationIds,proto3" json:"unackedNotificationIDs" xml:"unackedNotificationID"`
-	TrafficClass            int      `protobuf:"varint,37,opt,name=traffic_class,json=trafficClass,proto3,casttype=int" json:"trafficClass" xml:"trafficClass"`
-	DefaultFolderPath       string   `protobuf:"bytes,38,opt,name=default_folder_path,json=defaultFolderPath,proto3" json:"defaultFolderPath" xml:"defaultFolderPath" default:"~"`
-	SetLowPriority          bool     `protobuf:"varint,39,opt,name=set_low_priority,json=setLowPriority,proto3" json:"setLowPriority" xml:"setLowPriority" default:"true"`
-	RawMaxFolderConcurrency int      `protobuf:"varint,40,opt,name=max_folder_concurrency,json=maxFolderConcurrency,proto3,casttype=int" json:"maxFolderConcurrency" xml:"maxFolderConcurrency"`
-	CRURL                   string   `protobuf:"bytes,41,opt,name=crash_reporting_url,json=crashReportingUrl,proto3" json:"crURL" xml:"crashReportingURL" default:"https://crash.syncthing.net/newcrash"`
-	CREnabled               bool     `protobuf:"varint,42,opt,name=crash_reporting_enabled,json=crashReportingEnabled,proto3" json:"crashReportingEnabled" xml:"crashReportingEnabled" default:"true"`
-	StunKeepaliveStartS     int      `protobuf:"varint,43,opt,name=stun_keepalive_start_s,json=stunKeepaliveStartS,proto3,casttype=int" json:"stunKeepaliveStartS" xml:"stunKeepaliveStartS" default:"180"`
-	StunKeepaliveMinS       int      `protobuf:"varint,44,opt,name=stun_keepalive_min_s,json=stunKeepaliveMinS,proto3,casttype=int" json:"stunKeepaliveMinS" xml:"stunKeepaliveMinS" default:"20"`
-	RawStunServers          []string `protobuf:"bytes,45,rep,name=stun_servers,json=stunServers,proto3" json:"stunServers" xml:"stunServer" default:"default"`
-	DatabaseTuning          Tuning   `protobuf:"varint,46,opt,name=database_tuning,json=databaseTuning,proto3,enum=config.Tuning" json:"databaseTuning" xml:"databaseTuning" restart:"true"`
-	RawMaxCIRequestKiB      int      `protobuf:"varint,47,opt,name=max_concurrent_incoming_request_kib,json=maxConcurrentIncomingRequestKib,proto3,casttype=int" json:"maxConcurrentIncomingRequestKiB" xml:"maxConcurrentIncomingRequestKiB"`
-	AnnounceLANAddresses    bool     `protobuf:"varint,48,opt,name=announce_lan_addresses,json=announceLanAddresses,proto3" json:"announceLANAddresses" xml:"announceLANAddresses" default:"true"`
-	SendFullIndexOnUpgrade  bool     `protobuf:"varint,49,opt,name=send_full_index_on_upgrade,json=sendFullIndexOnUpgrade,proto3" json:"sendFullIndexOnUpgrade" xml:"sendFullIndexOnUpgrade"`
-	FeatureFlags            []string `protobuf:"bytes,50,rep,name=feature_flags,json=featureFlags,proto3" json:"featureFlags" xml:"featureFlag"`
+	RawListenAddresses          []string `protobuf:"bytes,1,rep,name=listen_addresses,json=listenAddresses,proto3" json:"listenAddresses" xml:"listenAddress" default:"default"`
+	RawGlobalAnnServers         []string `protobuf:"bytes,2,rep,name=global_discovery_servers,json=globalDiscoveryServers,proto3" json:"globalAnnounceServers" xml:"globalAnnounceServer" default:"default"`
+	GlobalAnnEnabled            bool     `protobuf:"varint,3,opt,name=global_discovery_enabled,json=globalDiscoveryEnabled,proto3" json:"globalAnnounceEnabled" xml:"globalAnnounceEnabled" default:"true"`
+	LocalAnnEnabled             bool     `protobuf:"varint,4,opt,name=local_discovery_enabled,json=localDiscoveryEnabled,proto3" json:"localAnnounceEnabled" xml:"localAnnounceEnabled" default:"true"`
+	LocalAnnPort                int      `protobuf:"varint,5,opt,name=local_announce_port,json=localAnnouncePort,proto3,casttype=int" json:"localAnnouncePort" xml:"localAnnouncePort" default:"21027"`
+	LocalAnnMCAddr              string   `protobuf:"bytes,6,opt,name=local_announce_multicast_address,json=localAnnounceMulticastAddress,proto3" json:"localAnnounceMCAddr" xml:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
+	MaxSendKbps                 int      `protobuf:"varint,7,opt,name=max_send_kbps,json=maxSendKbps,proto3,casttype=int" json:"maxSendKbps" xml:"maxSendKbps"`
+	MaxRecvKbps                 int      `protobuf:"varint,8,opt,name=max_recv_kbps,json=maxRecvKbps,proto3,casttype=int" json:"maxRecvKbps" xml:"maxRecvKbps"`
+	ReconnectIntervalS          int      `protobuf:"varint,9,opt,name=reconnection_interval_s,json=reconnectionIntervalS,proto3,casttype=int" json:"reconnectionIntervalS" xml:"reconnectionIntervalS" default:"60"`
+	RelaysEnabled               bool     `protobuf:"varint,10,opt,name=relays_enabled,json=relaysEnabled,proto3" json:"relaysEnabled" xml:"relaysEnabled" default:"true"`
+	RelayReconnectIntervalM     int      `protobuf:"varint,11,opt,name=relays_reconnect_interval_m,json=relaysReconnectIntervalM,proto3,casttype=int" json:"relayReconnectIntervalM" xml:"relayReconnectIntervalM" default:"10"`
+	StartBrowser                bool     `protobuf:"varint,12,opt,name=start_browser,json=startBrowser,proto3" json:"startBrowser" xml:"startBrowser" default:"true"`
+	NATEnabled                  bool     `protobuf:"varint,14,opt,name=nat_traversal_enabled,json=natTraversalEnabled,proto3" json:"natEnabled" xml:"natEnabled" default:"true"`
+	NATLeaseM                   int      `protobuf:"varint,15,opt,name=nat_traversal_lease_m,json=natTraversalLeaseM,proto3,casttype=int" json:"natLeaseMinutes" xml:"natLeaseMinutes" default:"60"`
+	NATRenewalM                 int      `protobuf:"varint,16,opt,name=nat_traversal_renewal_m,json=natTraversalRenewalM,proto3,casttype=int" json:"natRenewalMinutes" xml:"natRenewalMinutes" default:"30"`
+	NATTimeoutS                 int      `protobuf:"varint,17,opt,name=nat_traversal_timeout_s,json=natTraversalTimeoutS,proto3,casttype=int" json:"natTimeoutSeconds" xml:"natTimeoutSeconds" default:"10"`
+	URAccepted                  int      `protobuf:"varint,18,opt,name=usage_reporting_accepted,json=usageReportingAccepted,proto3,casttype=int" json:"urAccepted" xml:"urAccepted"`
+	URSeen                      int      `protobuf:"varint,19,opt,name=usage_reporting_seen,json=usageReportingSeen,proto3,casttype=int" json:"urSeen" xml:"urSeen"`
+	URUniqueID                  string   `protobuf:"bytes,20,opt,name=usage_reporting_unique_id,json=usageReportingUniqueId,proto3" json:"urUniqueId" xml:"urUniqueID"`
+	URURL                       string   `protobuf:"bytes,21,opt,name=usage_reporting_url,json=usageReportingUrl,proto3" json:"urURL" xml:"urURL" default:"https://data.syncthing.net/newdata"`
+	URPostInsecurely            bool     `protobuf:"varint,22,opt,name=usage_reporting_post_insecurely,json=usageReportingPostInsecurely,proto3" json:"urPostInsecurely" xml:"urPostInsecurely" default:"false"`
+	URInitialDelayS             int      `protobuf:"varint,23,opt,name=usage_reporting_initial_delay_s,json=usageReportingInitialDelayS,proto3,casttype=int" json:"urInitialDelayS" xml:"urInitialDelayS" default:"1800"`
+	RestartOnWakeup             bool     `protobuf:"varint,24,opt,name=restart_on_wakeup,json=restartOnWakeup,proto3" json:"restartOnWakeup" xml:"restartOnWakeup" default:"true"`
+	AutoUpgradeIntervalH        int      `protobuf:"varint,25,opt,name=auto_upgrade_interval_h,json=autoUpgradeIntervalH,proto3,casttype=int" json:"autoUpgradeIntervalH" xml:"autoUpgradeIntervalH" default:"12"`
+	UpgradeToPreReleases        bool     `protobuf:"varint,26,opt,name=upgrade_to_pre_releases,json=upgradeToPreReleases,proto3" json:"upgradeToPreReleases" xml:"upgradeToPreReleases"`
+	KeepTemporariesH            int      `protobuf:"varint,27,opt,name=keep_temporaries_h,json=keepTemporariesH,proto3,casttype=int" json:"keepTemporariesH" xml:"keepTemporariesH" default:"24"`
+	CacheIgnoredFiles           bool     `protobuf:"varint,28,opt,name=cache_ignored_files,json=cacheIgnoredFiles,proto3" json:"cacheIgnoredFiles" xml:"cacheIgnoredFiles" default:"false"`
+	ProgressUpdateIntervalS     int      `protobuf:"varint,29,opt,name=progress_update_interval_s,json=progressUpdateIntervalS,proto3,casttype=int" json:"progressUpdateIntervalS" xml:"progressUpdateIntervalS" default:"5"`
+	LimitBandwidthInLan         bool     `protobuf:"varint,30,opt,name=limit_bandwidth_in_lan,json=limitBandwidthInLan,proto3" json:"limitBandwidthInLan" xml:"limitBandwidthInLan" default:"false"`
+	MinHomeDiskFree             Size     `protobuf:"bytes,31,opt,name=min_home_disk_free,json=minHomeDiskFree,proto3" json:"minHomeDiskFree" xml:"minHomeDiskFree" default:"1 %"`
+	ReleasesURL                 string   `protobuf:"bytes,32,opt,name=releases_url,json=releasesUrl,proto3" json:"releasesURL" xml:"releasesURL" default:"https://upgrades.syncthing.net/meta.json"`
+	AlwaysLocalNets             []string `protobuf:"bytes,33,rep,name=always_local_nets,json=alwaysLocalNets,proto3" json:"alwaysLocalNets" xml:"alwaysLocalNet"`
+	OverwriteRemoteDevNames     bool     `protobuf:"varint,34,opt,name=overwrite_remote_device_names_on_connect,json=overwriteRemoteDeviceNamesOnConnect,proto3" json:"overwriteRemoteDeviceNamesOnConnect" xml:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
+	TempIndexMinBlocks          int      `protobuf:"varint,35,opt,name=temp_index_min_blocks,json=tempIndexMinBlocks,proto3,casttype=int" json:"tempIndexMinBlocks" xml:"tempIndexMinBlocks" default:"10"`
+	UnackedNotificationIDs      []string `protobuf:"bytes,36,rep,name=unacked_notification_ids,json=unackedNotificationIds,proto3" json:"unackedNotificationIDs" xml:"unackedNotificationID"`
+	TrafficClass                int      `protobuf:"varint,37,opt,name=traffic_class,json=trafficClass,proto3,casttype=int" json:"trafficClass" xml:"trafficClass"`
+	DeprecatedDefaultFolderPath string   `protobuf:"bytes,38,opt,name=default_folder_path,json=defaultFolderPath,proto3" json:"-" xml:"defaultFolderPath,omitempty"` // Deprecated: Do not use.
+	SetLowPriority              bool     `protobuf:"varint,39,opt,name=set_low_priority,json=setLowPriority,proto3" json:"setLowPriority" xml:"setLowPriority" default:"true"`
+	RawMaxFolderConcurrency     int      `protobuf:"varint,40,opt,name=max_folder_concurrency,json=maxFolderConcurrency,proto3,casttype=int" json:"maxFolderConcurrency" xml:"maxFolderConcurrency"`
+	CRURL                       string   `protobuf:"bytes,41,opt,name=crash_reporting_url,json=crashReportingUrl,proto3" json:"crURL" xml:"crashReportingURL" default:"https://crash.syncthing.net/newcrash"`
+	CREnabled                   bool     `protobuf:"varint,42,opt,name=crash_reporting_enabled,json=crashReportingEnabled,proto3" json:"crashReportingEnabled" xml:"crashReportingEnabled" default:"true"`
+	StunKeepaliveStartS         int      `protobuf:"varint,43,opt,name=stun_keepalive_start_s,json=stunKeepaliveStartS,proto3,casttype=int" json:"stunKeepaliveStartS" xml:"stunKeepaliveStartS" default:"180"`
+	StunKeepaliveMinS           int      `protobuf:"varint,44,opt,name=stun_keepalive_min_s,json=stunKeepaliveMinS,proto3,casttype=int" json:"stunKeepaliveMinS" xml:"stunKeepaliveMinS" default:"20"`
+	RawStunServers              []string `protobuf:"bytes,45,rep,name=stun_servers,json=stunServers,proto3" json:"stunServers" xml:"stunServer" default:"default"`
+	DatabaseTuning              Tuning   `protobuf:"varint,46,opt,name=database_tuning,json=databaseTuning,proto3,enum=config.Tuning" json:"databaseTuning" xml:"databaseTuning" restart:"true"`
+	RawMaxCIRequestKiB          int      `protobuf:"varint,47,opt,name=max_concurrent_incoming_request_kib,json=maxConcurrentIncomingRequestKib,proto3,casttype=int" json:"maxConcurrentIncomingRequestKiB" xml:"maxConcurrentIncomingRequestKiB"`
+	AnnounceLANAddresses        bool     `protobuf:"varint,48,opt,name=announce_lan_addresses,json=announceLanAddresses,proto3" json:"announceLANAddresses" xml:"announceLANAddresses" default:"true"`
+	SendFullIndexOnUpgrade      bool     `protobuf:"varint,49,opt,name=send_full_index_on_upgrade,json=sendFullIndexOnUpgrade,proto3" json:"sendFullIndexOnUpgrade" xml:"sendFullIndexOnUpgrade"`
+	FeatureFlags                []string `protobuf:"bytes,50,rep,name=feature_flags,json=featureFlags,proto3" json:"featureFlags" xml:"featureFlag"`
 	// The number of connections at which we stop trying to connect to more
 	// devices, zero meaning no limit. Does not affect incoming connections.
 	ConnectionLimitEnough int `protobuf:"varint,51,opt,name=connection_limit_enough,json=connectionLimitEnough,proto3,casttype=int" json:"connectionLimitEnough" xml:"connectionLimitEnough"`
@@ -133,208 +133,209 @@ func init() {
 }
 
 var fileDescriptor_d09882599506ca03 = []byte{
-	// 3211 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x5a, 0x5b, 0x6c, 0xdc, 0xc6,
-	0xd5, 0x36, 0xed, 0xd8, 0x89, 0x69, 0x59, 0xb6, 0x28, 0x59, 0x62, 0x6c, 0x47, 0x54, 0xd6, 0xeb,
-	0x44, 0xb9, 0xd9, 0x92, 0xec, 0xf8, 0x77, 0x0c, 0xfc, 0xc8, 0xaf, 0x4b, 0xf4, 0x5b, 0xb1, 0x64,
-	0x0b, 0x23, 0x09, 0xf9, 0x91, 0x1f, 0x05, 0x31, 0xcb, 0x9d, 0x95, 0x58, 0x71, 0x87, 0x6b, 0x72,
-	0xa8, 0x95, 0xd2, 0x22, 0x0d, 0x52, 0xf4, 0xf2, 0xd6, 0x56, 0xe8, 0x05, 0x68, 0x81, 0x22, 0x45,
-	0x5b, 0xa0, 0x69, 0x9a, 0xa2, 0x40, 0x81, 0x02, 0xed, 0x4b, 0x8b, 0x02, 0x05, 0x82, 0x16, 0xa8,
-	0xf4, 0x58, 0xa0, 0x29, 0x8b, 0xc8, 0x79, 0xda, 0x87, 0x3e, 0xec, 0xa3, 0xfa, 0x52, 0x9c, 0xe1,
-	0x6d, 0x48, 0xce, 0xc6, 0x7e, 0x5b, 0x9e, 0xef, 0xcc, 0x99, 0xef, 0xcc, 0xe5, 0xcc, 0x39, 0x33,
-	0xab, 0x5e, 0x76, 0xec, 0xda, 0x55, 0xcb, 0xa5, 0x0d, 0x7b, 0xfd, 0xaa, 0xdb, 0x62, 0xb6, 0x4b,
-	0xfd, 0xe8, 0x2b, 0xf0, 0x30, 0x7c, 0x5d, 0x69, 0x79, 0x2e, 0x73, 0xb5, 0x13, 0x91, 0xf0, 0xfc,
-	0x88, 0xa0, 0xce, 0x02, 0x6a, 0xd3, 0xf5, 0x48, 0xe1, 0xfc, 0x39, 0x01, 0xf0, 0xed, 0xb7, 0x48,
-	0x2c, 0x3e, 0x49, 0xb6, 0x59, 0xf4, 0xb3, 0xf2, 0xd7, 0xdb, 0xea, 0xd0, 0xbd, 0xa8, 0x87, 0x59,
-	0xb1, 0x07, 0xed, 0x87, 0x8a, 0x7a, 0xd6, 0xb1, 0x7d, 0x46, 0xa8, 0x89, 0xeb, 0x75, 0x8f, 0xf8,
-	0x3e, 0xf1, 0x75, 0x65, 0xec, 0xd8, 0xf8, 0xc9, 0x19, 0xff, 0x20, 0x34, 0x34, 0x84, 0xdb, 0x8b,
-	0x1c, 0x9e, 0x4e, 0xd0, 0x4e, 0x68, 0x9c, 0x71, 0xf2, 0xa2, 0x6e, 0x68, 0x5c, 0xde, 0x6e, 0x3a,
-	0xb7, 0x2a, 0x39, 0x79, 0x65, 0xac, 0x4e, 0x1a, 0x38, 0x70, 0xd8, 0xad, 0x4a, 0xfc, 0xa3, 0x72,
-	0xb8, 0x57, 0x7d, 0x3c, 0xfe, 0xbd, 0xbb, 0x5f, 0x95, 0x18, 0x47, 0x45, 0xd3, 0xda, 0xbf, 0x14,
-	0x55, 0x5f, 0x77, 0xdc, 0x1a, 0x76, 0xcc, 0xba, 0xed, 0x5b, 0xee, 0x16, 0xf1, 0x76, 0x4c, 0x9f,
-	0x78, 0x5b, 0xc4, 0xf3, 0xf5, 0xa3, 0x9c, 0xe8, 0xaf, 0x95, 0x83, 0xd0, 0x18, 0x44, 0xb8, 0xfd,
-	0xbf, 0x5c, 0x6f, 0x9a, 0xd2, 0x95, 0x08, 0xef, 0x84, 0xc6, 0xb9, 0xf5, 0x44, 0xe6, 0x06, 0xd4,
-	0x22, 0x31, 0xd0, 0x0d, 0x8d, 0x17, 0x39, 0x61, 0x19, 0x2a, 0xe1, 0xdd, 0xd9, 0xab, 0x0e, 0xc9,
-	0x54, 0xbb, 0x7b, 0x55, 0x79, 0x07, 0x79, 0x47, 0x65, 0xdc, 0xd0, 0x70, 0xd4, 0x70, 0x2e, 0x71,
-	0x2a, 0x96, 0x6b, 0x9f, 0xca, 0x1c, 0x26, 0x14, 0xd7, 0x1c, 0x52, 0xd7, 0x8f, 0x8d, 0x29, 0xe3,
-	0x4f, 0xcc, 0xbc, 0x0f, 0x0e, 0x9f, 0x4d, 0x2d, 0xbe, 0x16, 0x81, 0x65, 0x6f, 0x63, 0xa0, 0x1b,
-	0x1a, 0xcf, 0x4b, 0xbc, 0x8d, 0x51, 0xc1, 0x5d, 0xe6, 0x05, 0x04, 0x7c, 0xed, 0x61, 0xa6, 0x17,
-	0x70, 0xb8, 0x57, 0x7d, 0x0c, 0x9a, 0xee, 0xee, 0x57, 0x4b, 0xa4, 0x4a, 0x6e, 0xc6, 0x72, 0xed,
-	0x63, 0x45, 0x1d, 0x71, 0x5c, 0x4b, 0xea, 0xe5, 0x63, 0xdc, 0xcb, 0x1f, 0x83, 0x97, 0x67, 0x16,
-	0x41, 0x27, 0xe7, 0xe4, 0x90, 0x13, 0x8b, 0x0a, 0x3e, 0x3e, 0x17, 0x2d, 0x41, 0x09, 0x28, 0x71,
-	0x51, 0x6e, 0xa4, 0x87, 0x5c, 0x70, 0xb0, 0xc8, 0x07, 0x9d, 0xe3, 0x0d, 0x4a, 0xee, 0xfd, 0x45,
-	0x51, 0x07, 0x23, 0xf7, 0x70, 0x6c, 0xcb, 0x6c, 0xb9, 0x1e, 0xd3, 0x8f, 0x8f, 0x29, 0xe3, 0xc7,
-	0x67, 0xbe, 0x0f, 0xae, 0xf5, 0x25, 0xa6, 0x96, 0x5d, 0x8f, 0x75, 0x42, 0x63, 0x20, 0xd7, 0x35,
-	0x08, 0xbb, 0xa1, 0xf1, 0x6c, 0xd9, 0x29, 0x40, 0x04, 0x8f, 0xa6, 0x26, 0x27, 0xa6, 0xfe, 0xab,
-	0x72, 0x18, 0x1a, 0xc7, 0x6c, 0xca, 0x3a, 0x7b, 0x55, 0x89, 0x19, 0x99, 0xf0, 0x70, 0xaf, 0x7a,
-	0x9c, 0x37, 0xdd, 0xdd, 0xaf, 0xe6, 0x98, 0xa0, 0xb2, 0xae, 0xf6, 0xe5, 0xa3, 0xea, 0x58, 0xc1,
-	0x9b, 0x66, 0xe0, 0x30, 0xdb, 0xc2, 0x3e, 0x4b, 0xe2, 0x86, 0x7e, 0x62, 0x4c, 0x19, 0x3f, 0x39,
-	0xf3, 0x5b, 0x70, 0xad, 0x3f, 0x31, 0xb8, 0x34, 0x0b, 0x3b, 0xb9, 0x13, 0x1a, 0x83, 0x39, 0xa3,
-	0x91, 0xb8, 0x1b, 0x1a, 0x37, 0xca, 0xee, 0x45, 0x98, 0xe0, 0xe0, 0xff, 0x37, 0x1a, 0x93, 0x53,
-	0xb7, 0x6e, 0xdd, 0xbc, 0x76, 0xf3, 0xfa, 0xe7, 0x6e, 0x45, 0xde, 0x76, 0xf6, 0xaa, 0x52, 0x83,
-	0x72, 0xf1, 0xe1, 0x5e, 0x55, 0x2b, 0x1b, 0xd9, 0xdd, 0xaf, 0x16, 0x68, 0xa2, 0xa7, 0xf2, 0x8d,
-	0x13, 0x0f, 0xe3, 0x60, 0xa4, 0xdd, 0x53, 0x4f, 0x37, 0xf1, 0xb6, 0xe9, 0x13, 0x5a, 0x37, 0x37,
-	0x6b, 0x2d, 0x5f, 0x7f, 0x9c, 0x4f, 0xe6, 0x0b, 0x9d, 0xd0, 0x38, 0xd5, 0xc4, 0xdb, 0x2b, 0x84,
-	0xd6, 0xef, 0xd4, 0x5a, 0x10, 0x5c, 0x06, 0xb8, 0x5b, 0x82, 0x2c, 0x99, 0x1f, 0x24, 0x2a, 0x26,
-	0x06, 0x3d, 0x62, 0x6d, 0x45, 0x06, 0x9f, 0xc8, 0x19, 0x44, 0xc4, 0xda, 0x2a, 0x1a, 0x4c, 0x64,
-	0x39, 0x83, 0x89, 0x50, 0xfb, 0x8d, 0xa2, 0x8e, 0x78, 0xc4, 0x72, 0x29, 0x25, 0x16, 0x84, 0x77,
-	0xd3, 0xa6, 0x8c, 0x78, 0x5b, 0xd8, 0x31, 0x7d, 0xfd, 0x24, 0xb7, 0xfd, 0x36, 0x0f, 0xea, 0x89,
-	0xca, 0x42, 0x0c, 0xaf, 0x40, 0xec, 0x10, 0x1b, 0xa6, 0x40, 0x37, 0x34, 0xc6, 0x79, 0xdf, 0x52,
-	0x54, 0x98, 0xa5, 0x1b, 0x13, 0x09, 0xa5, 0xc3, 0xbd, 0xea, 0xd1, 0x1b, 0x13, 0x3c, 0xbe, 0x97,
-	0xfa, 0x41, 0xf2, 0x5e, 0xb4, 0x86, 0xda, 0xef, 0x11, 0x07, 0xef, 0xf8, 0x69, 0x0c, 0x50, 0x79,
-	0x0c, 0x78, 0xb5, 0x13, 0x1a, 0xa7, 0x23, 0x24, 0xdb, 0xe8, 0x95, 0x98, 0x90, 0x20, 0x2d, 0xee,
-	0xf0, 0x64, 0xc7, 0xa2, 0x7c, 0x63, 0xed, 0xdd, 0xa3, 0xea, 0x85, 0xb8, 0xa3, 0x94, 0x48, 0x36,
-	0x48, 0x4d, 0xfd, 0x14, 0x1f, 0xa4, 0x3f, 0xc2, 0x1a, 0x1e, 0x41, 0xa0, 0x57, 0x72, 0x61, 0xa9,
-	0x13, 0x1a, 0x23, 0x9e, 0x1c, 0x4a, 0x03, 0x6d, 0x0f, 0x5c, 0x60, 0x39, 0x39, 0x21, 0x6c, 0xd9,
-	0x9e, 0xf6, 0x7a, 0x43, 0x30, 0xc8, 0x93, 0x30, 0xc8, 0xbd, 0x68, 0x22, 0x3d, 0xf2, 0xb3, 0x8c,
-	0x68, 0x35, 0xf5, 0xb4, 0xcf, 0xb0, 0xc7, 0xcc, 0x9a, 0xe7, 0xb6, 0x7d, 0xe2, 0xe9, 0x7d, 0x7c,
-	0xac, 0xff, 0xbb, 0x13, 0x1a, 0x7d, 0x1c, 0x98, 0x89, 0xe4, 0xdd, 0xd0, 0x78, 0x9a, 0xbb, 0x23,
-	0x0a, 0x7b, 0x8e, 0x74, 0xae, 0xa9, 0xf6, 0x53, 0x45, 0x3d, 0x47, 0x31, 0x33, 0x99, 0x87, 0xe1,
-	0x54, 0xc3, 0x4e, 0x3a, 0xb1, 0xfd, 0xbc, 0xb3, 0xfb, 0x07, 0xa1, 0xa1, 0xde, 0x9d, 0x5e, 0xcd,
-	0xc2, 0xba, 0x4a, 0x31, 0xcb, 0xe6, 0xd8, 0xe0, 0x1d, 0x67, 0x22, 0x49, 0x08, 0x17, 0x1b, 0xe4,
-	0xbe, 0x84, 0x70, 0x2d, 0x74, 0x81, 0x06, 0x29, 0x66, 0xab, 0x09, 0x9d, 0x64, 0x41, 0xfc, 0xae,
-	0xc4, 0xd3, 0x21, 0xd8, 0x27, 0x66, 0x53, 0x3f, 0xc3, 0x97, 0xc2, 0x57, 0x61, 0x29, 0x9c, 0xbc,
-	0x3b, 0xbd, 0xba, 0x08, 0x62, 0x98, 0xfc, 0x33, 0x14, 0xb3, 0xe8, 0xc3, 0xa6, 0x01, 0xe3, 0xc9,
-	0x4f, 0x25, 0x21, 0x2b, 0xca, 0xa5, 0x7b, 0xa3, 0xb3, 0x57, 0x2d, 0xb5, 0x2f, 0x8b, 0xd2, 0x1d,
-	0x94, 0x75, 0x8c, 0x34, 0x91, 0x7d, 0x24, 0xd3, 0xfe, 0xac, 0xa8, 0x23, 0x79, 0xf2, 0x1e, 0xa1,
-	0xa4, 0xcd, 0x57, 0xf2, 0x59, 0x4e, 0x7f, 0x17, 0xe8, 0x9f, 0xba, 0x3b, 0xbd, 0x8a, 0x22, 0x00,
-	0x1c, 0x18, 0xa0, 0x98, 0x25, 0x9f, 0xa9, 0x0b, 0xd5, 0xc4, 0x85, 0x3c, 0x22, 0x38, 0x71, 0x4d,
-	0x74, 0x42, 0x62, 0x43, 0x26, 0x04, 0x47, 0xae, 0x81, 0x23, 0x22, 0x05, 0x34, 0x24, 0xba, 0x92,
-	0x48, 0x25, 0xce, 0x30, 0xbb, 0x49, 0xdc, 0x80, 0x99, 0xbe, 0x3e, 0x90, 0x77, 0x66, 0x35, 0x02,
-	0x56, 0x62, 0x67, 0x92, 0x4f, 0x58, 0xe9, 0xf5, 0x9c, 0x33, 0x79, 0xa4, 0xd7, 0xf6, 0x93, 0xd8,
-	0x90, 0x09, 0xd3, 0x2d, 0x27, 0x52, 0xc8, 0x3b, 0x93, 0x48, 0xb5, 0x1f, 0x28, 0xaa, 0x1e, 0xf8,
-	0x78, 0x9d, 0x98, 0x1e, 0x81, 0x73, 0xdf, 0xa6, 0xeb, 0x26, 0xb6, 0x2c, 0xd2, 0x62, 0xa4, 0xae,
-	0x6b, 0xdc, 0x1b, 0x0c, 0x3b, 0x60, 0x0d, 0x4d, 0xc7, 0x52, 0xd8, 0x01, 0x81, 0x97, 0x7c, 0x75,
-	0x43, 0xe3, 0x2c, 0x77, 0x22, 0x13, 0x09, 0x84, 0x45, 0xc5, 0xdc, 0x17, 0xac, 0xf8, 0xcc, 0x24,
-	0x1a, 0xe6, 0x14, 0x50, 0xc2, 0x20, 0x91, 0x6b, 0x5f, 0x50, 0x87, 0x8a, 0xe4, 0x7c, 0x42, 0xa8,
-	0x3e, 0xc8, 0x89, 0x2d, 0x1c, 0x84, 0xc6, 0x89, 0x35, 0xb4, 0x42, 0x08, 0xed, 0x84, 0xc6, 0x89,
-	0xc0, 0x83, 0x5f, 0xdd, 0xd0, 0xe8, 0x8b, 0x09, 0xc1, 0xa7, 0x40, 0x26, 0x51, 0x48, 0x7f, 0xed,
-	0xee, 0x57, 0xe3, 0xe6, 0x48, 0xcb, 0x13, 0x00, 0x99, 0xf6, 0x1d, 0x45, 0x7d, 0xb2, 0xd8, 0x7b,
-	0x40, 0xed, 0xfb, 0x01, 0x31, 0xed, 0xba, 0x3e, 0xc4, 0x93, 0x88, 0x37, 0xa3, 0xb1, 0x59, 0xe3,
-	0xe2, 0x85, 0xb9, 0x68, 0x6c, 0xe2, 0x2f, 0x71, 0x6c, 0x12, 0x85, 0x4a, 0x34, 0x28, 0xc9, 0x67,
-	0x57, 0xfc, 0x8a, 0x07, 0x25, 0xc1, 0x8a, 0x83, 0x92, 0x68, 0x69, 0x7f, 0x50, 0xd4, 0xc1, 0x12,
-	0x2f, 0xcf, 0xd1, 0xcf, 0x71, 0x46, 0xdf, 0x80, 0xb5, 0x77, 0x7c, 0x0d, 0xad, 0xa1, 0xc5, 0x4e,
-	0x68, 0x1c, 0x0f, 0xbc, 0x35, 0xb4, 0xd8, 0x0d, 0x8d, 0x9b, 0x09, 0x11, 0xb4, 0x28, 0xac, 0xae,
-	0x0d, 0xc6, 0x5a, 0xfe, 0xad, 0xab, 0x57, 0xeb, 0x98, 0xe1, 0x2b, 0xfe, 0x0e, 0xb5, 0xd8, 0x06,
-	0x14, 0x6b, 0x94, 0xb0, 0xab, 0x94, 0xb4, 0x41, 0x0a, 0x84, 0x63, 0x23, 0xc9, 0x8f, 0xc3, 0xbd,
-	0xea, 0x23, 0x34, 0xdc, 0xdd, 0xaf, 0x46, 0x2c, 0xd0, 0x40, 0xc1, 0x0f, 0xcf, 0xd1, 0xfe, 0xa9,
-	0xa8, 0x46, 0xd1, 0x85, 0x96, 0xeb, 0xc3, 0x09, 0xe7, 0x13, 0x2b, 0xf0, 0x88, 0xb3, 0xa3, 0x0f,
-	0xf3, 0xf0, 0xfb, 0x3d, 0x5e, 0x41, 0xac, 0xa1, 0x65, 0xd7, 0x67, 0x0b, 0x29, 0xd8, 0x09, 0x8d,
-	0xb3, 0x81, 0x97, 0x97, 0x75, 0x43, 0xe3, 0x99, 0xd8, 0xc9, 0x3c, 0x20, 0xf8, 0xdb, 0xc0, 0x8e,
-	0xcf, 0x43, 0x72, 0xb9, 0xb5, 0x44, 0x06, 0x99, 0x27, 0x6f, 0x01, 0xf5, 0x42, 0x91, 0x02, 0xba,
-	0x98, 0x77, 0x2b, 0x8f, 0x6a, 0xff, 0x90, 0x78, 0x68, 0x53, 0x9b, 0xd9, 0x50, 0x47, 0xc0, 0x79,
-	0x67, 0xfa, 0xfa, 0x08, 0x5f, 0xc5, 0xdf, 0xe5, 0xd5, 0xc3, 0x1a, 0x5a, 0x88, 0xd0, 0x39, 0x00,
-	0x21, 0x60, 0x9c, 0x09, 0xbc, 0x9c, 0x28, 0x0d, 0x17, 0x05, 0xb9, 0x18, 0x2c, 0x6e, 0x4e, 0xe4,
-	0x02, 0x78, 0xd1, 0x42, 0x59, 0x04, 0x27, 0x10, 0xb4, 0x82, 0x82, 0xa1, 0x40, 0x01, 0x5d, 0xc8,
-	0x3b, 0x98, 0x03, 0x35, 0x57, 0x1d, 0xf0, 0x48, 0x74, 0x38, 0xbb, 0xd4, 0x6c, 0xe3, 0x4d, 0x12,
-	0xb4, 0x74, 0x9d, 0x4f, 0xd9, 0x2c, 0x90, 0x8f, 0xc1, 0x7b, 0xf4, 0x0d, 0x0e, 0xa5, 0xe4, 0x0b,
-	0xf2, 0x9e, 0x87, 0x74, 0xd1, 0x80, 0xf6, 0x35, 0x45, 0x1d, 0xc1, 0x01, 0x73, 0xcd, 0xa0, 0xb5,
-	0xee, 0xe1, 0x3a, 0xc9, 0x92, 0xa1, 0x0d, 0xfd, 0x49, 0x3e, 0x90, 0xcb, 0x50, 0x72, 0x81, 0xca,
-	0x5a, 0xa4, 0x91, 0xe4, 0x11, 0xb7, 0xd3, 0xea, 0x44, 0x06, 0x8a, 0xc3, 0x37, 0x25, 0x66, 0x86,
-	0x93, 0x53, 0x48, 0x6a, 0x4d, 0x6b, 0xaa, 0x23, 0x09, 0x07, 0xe6, 0x9a, 0x2d, 0x0f, 0xa6, 0x98,
-	0x9f, 0xc5, 0xbe, 0x7e, 0x9e, 0x0f, 0xc0, 0x0d, 0x20, 0x12, 0xab, 0xac, 0xba, 0xcb, 0x1e, 0x41,
-	0x31, 0xde, 0x0d, 0x8d, 0xf3, 0xd1, 0x14, 0x4a, 0xc0, 0x0a, 0x92, 0xb6, 0xd1, 0xb6, 0x54, 0x6d,
-	0x93, 0x90, 0x96, 0xc9, 0x48, 0xb3, 0xe5, 0x7a, 0xd8, 0xb3, 0x89, 0x6f, 0x6e, 0xe8, 0x17, 0xb8,
-	0xcb, 0xb7, 0x61, 0x23, 0x00, 0xba, 0x9a, 0x81, 0xe0, 0xee, 0x25, 0xde, 0x4b, 0x11, 0x10, 0x6b,
-	0xb1, 0xeb, 0xa2, 0xab, 0x53, 0xd7, 0x51, 0xc9, 0x8a, 0xb6, 0xa3, 0x0e, 0x5a, 0xd8, 0xda, 0x20,
-	0xa6, 0xbd, 0x4e, 0x5d, 0x8f, 0xd4, 0xcd, 0x86, 0xed, 0x10, 0x5f, 0xbf, 0xc8, 0x5d, 0x5c, 0x80,
-	0x13, 0x8d, 0xc3, 0x0b, 0x11, 0x3a, 0x0f, 0x60, 0x3a, 0xd0, 0x25, 0xa4, 0xb4, 0x07, 0xd3, 0xbd,
-	0x85, 0xca, 0x66, 0xb4, 0x6f, 0x29, 0xea, 0xf9, 0x96, 0xe7, 0xae, 0x43, 0x31, 0x63, 0x06, 0xad,
-	0x3a, 0x66, 0x44, 0x2c, 0x10, 0x9e, 0xe2, 0xbe, 0xaf, 0x42, 0x7e, 0x9b, 0x68, 0xad, 0x71, 0x25,
-	0xb1, 0x18, 0x88, 0x8a, 0xec, 0x1e, 0xb8, 0x40, 0xe7, 0x65, 0x61, 0x20, 0x94, 0x97, 0x51, 0x2f,
-	0x8b, 0xda, 0xbb, 0x8a, 0x3a, 0xec, 0xd8, 0x4d, 0x9b, 0x99, 0x35, 0x4c, 0xeb, 0x6d, 0xbb, 0xce,
-	0x36, 0x4c, 0x9b, 0x9a, 0x0e, 0xa6, 0xfa, 0x28, 0x1f, 0x92, 0x25, 0x5e, 0x3c, 0x82, 0xc6, 0x4c,
-	0xa2, 0xb0, 0x40, 0x17, 0x31, 0xcd, 0x0a, 0xfe, 0x32, 0xf6, 0x19, 0xc3, 0x22, 0x33, 0xa5, 0xbd,
-	0xa3, 0xa8, 0x5a, 0xd3, 0xa6, 0xe6, 0x86, 0xdb, 0x24, 0x66, 0xdd, 0xf6, 0x37, 0xcd, 0x86, 0x47,
-	0x88, 0x6e, 0x8c, 0x29, 0xe3, 0xa7, 0xa6, 0xfa, 0xae, 0x44, 0x37, 0x6b, 0x57, 0x56, 0xec, 0xb7,
-	0xc8, 0xcc, 0x6b, 0x1f, 0x85, 0xc6, 0x11, 0xd8, 0x89, 0x4d, 0x9b, 0xde, 0x76, 0x9b, 0x64, 0xce,
-	0xf6, 0x37, 0xe7, 0x3d, 0x42, 0xd2, 0xd5, 0x51, 0x90, 0x8b, 0xfb, 0x60, 0xec, 0x32, 0x10, 0x39,
-	0x36, 0x39, 0x76, 0x19, 0x15, 0x9b, 0x6b, 0x0f, 0x14, 0xb5, 0x2f, 0x59, 0xef, 0xfc, 0xd8, 0x19,
-	0xe3, 0xc7, 0xce, 0xef, 0x79, 0xca, 0x93, 0x2c, 0xda, 0xe8, 0xf0, 0x39, 0xe5, 0x65, 0x9f, 0xdd,
-	0xd0, 0x98, 0x4b, 0x2a, 0x8e, 0x44, 0x26, 0x39, 0x88, 0xe2, 0x1d, 0xe0, 0x17, 0xce, 0x94, 0x26,
-	0x61, 0xf8, 0xca, 0xe7, 0x7d, 0x97, 0x42, 0xec, 0xce, 0x99, 0xcd, 0x7f, 0x1e, 0xee, 0x55, 0xc7,
-	0x1f, 0xd5, 0x14, 0xe4, 0x47, 0x02, 0x5f, 0x94, 0xd9, 0xf1, 0x1c, 0xed, 0x0d, 0x75, 0x00, 0x3b,
-	0x6d, 0xa8, 0xbe, 0xa2, 0xdb, 0x04, 0x4a, 0x98, 0xaf, 0x3f, 0xcd, 0x2f, 0xf1, 0xa0, 0xe8, 0x3d,
-	0x13, 0x81, 0xbc, 0x2a, 0xbf, 0x4b, 0x18, 0x2c, 0xfc, 0xa1, 0x28, 0xc2, 0xe4, 0xe4, 0x15, 0x54,
-	0x54, 0xd4, 0xfe, 0xad, 0xa8, 0xe3, 0xee, 0x16, 0xf1, 0xda, 0x9e, 0xcd, 0x20, 0x70, 0x34, 0x5d,
-	0x46, 0xcc, 0x3a, 0xd9, 0xb2, 0x2d, 0x62, 0x52, 0xdc, 0x24, 0x3e, 0x84, 0xd3, 0xb8, 0x10, 0xd2,
-	0x2b, 0xd9, 0xf5, 0xd2, 0xc8, 0xbd, 0xa4, 0x11, 0xe2, 0x6d, 0xe6, 0xc8, 0xd6, 0x5d, 0x50, 0xef,
-	0x84, 0xc6, 0x25, 0xb7, 0x04, 0xd9, 0x16, 0xe1, 0xe8, 0x3d, 0x3a, 0x1b, 0x99, 0xea, 0x86, 0xc6,
-	0x2b, 0x9c, 0xe0, 0x23, 0xe8, 0xf6, 0x5e, 0x94, 0x50, 0xc5, 0xf5, 0xe0, 0x81, 0x1e, 0x85, 0x85,
-	0xf6, 0x25, 0xf5, 0x1c, 0x84, 0x31, 0xd3, 0xa6, 0x75, 0xb2, 0x6d, 0xc2, 0x4a, 0xae, 0x39, 0xae,
-	0xb5, 0xe9, 0xeb, 0x97, 0xf8, 0x96, 0x86, 0x45, 0xa3, 0x81, 0xc2, 0x02, 0xe0, 0x4b, 0x36, 0x9d,
-	0xe1, 0x68, 0x7a, 0x6b, 0x5b, 0x86, 0xa4, 0x99, 0x72, 0x94, 0xff, 0x22, 0x89, 0x25, 0xed, 0xef,
-	0x90, 0xee, 0x52, 0x6c, 0x6d, 0x92, 0xba, 0x49, 0x5d, 0x66, 0x37, 0x6c, 0x0b, 0x47, 0xf7, 0x0f,
-	0x75, 0x5f, 0xaf, 0xf2, 0xf9, 0x7d, 0x0f, 0x86, 0x7b, 0x78, 0x2d, 0x52, 0xba, 0x2b, 0xe8, 0x2c,
-	0xcc, 0xc1, 0x68, 0x0f, 0x07, 0x52, 0xa4, 0x1b, 0x1a, 0x17, 0xa2, 0xd0, 0x2e, 0x83, 0xf9, 0x5d,
-	0xa5, 0x14, 0xe9, 0xee, 0x55, 0x7b, 0x58, 0xdc, 0xdd, 0xaf, 0xf6, 0x60, 0x81, 0xa4, 0x2d, 0xea,
-	0xbe, 0x86, 0xd4, 0xd3, 0xcc, 0xc3, 0x8d, 0x86, 0x6d, 0x99, 0x96, 0x83, 0x7d, 0x5f, 0xbf, 0xcc,
-	0x87, 0xf5, 0x25, 0xa8, 0x97, 0x63, 0x60, 0x16, 0xe4, 0xdd, 0xd0, 0xd0, 0xa2, 0x01, 0x15, 0x84,
-	0xe9, 0x45, 0x4d, 0x4e, 0x55, 0xbb, 0xaf, 0x0e, 0xc6, 0x43, 0x6c, 0x36, 0x5c, 0xa7, 0x4e, 0x3c,
-	0xb3, 0x85, 0xd9, 0x86, 0xfe, 0x0c, 0xdf, 0xf5, 0xd3, 0x70, 0x0c, 0xc4, 0xf0, 0x3c, 0x47, 0x97,
-	0x31, 0xdb, 0x48, 0x43, 0x4c, 0x09, 0x11, 0xa6, 0xeb, 0x6d, 0x58, 0x56, 0xca, 0xdb, 0xa8, 0xdc,
-	0x5c, 0xdb, 0x54, 0xcf, 0xfa, 0x84, 0x99, 0x8e, 0xdb, 0x36, 0x5b, 0x9e, 0xed, 0x7a, 0x36, 0xdb,
-	0xd1, 0x9f, 0xe5, 0x5b, 0x01, 0xfa, 0xeb, 0xf7, 0x09, 0x5b, 0x74, 0xdb, 0xcb, 0x31, 0x92, 0x76,
-	0x96, 0x17, 0xf7, 0x4c, 0x2c, 0x0a, 0xcd, 0xb5, 0xf7, 0x15, 0x75, 0xb8, 0x89, 0xb7, 0x13, 0xe7,
-	0x2c, 0x97, 0x5a, 0x81, 0xe7, 0x11, 0x6a, 0xed, 0xe8, 0xe3, 0x7c, 0xf4, 0x7c, 0x7e, 0xc5, 0x82,
-	0xdb, 0x4b, 0x78, 0x3b, 0xe2, 0x38, 0x9b, 0xa9, 0xc0, 0x41, 0xdf, 0x94, 0xc8, 0xd3, 0x83, 0x5e,
-	0x06, 0x26, 0x03, 0xcd, 0xef, 0x44, 0xe4, 0x76, 0x91, 0xd4, 0xaa, 0xf6, 0xb1, 0xa2, 0x0e, 0x5a,
-	0x1e, 0xf6, 0x37, 0x0a, 0x99, 0xff, 0x73, 0x7c, 0x32, 0x3e, 0xe0, 0x99, 0xff, 0x6c, 0x92, 0xf9,
-	0x5b, 0x71, 0xe6, 0x3f, 0x1f, 0x9d, 0xc8, 0xd0, 0x2c, 0xcb, 0xc1, 0xa5, 0xc1, 0x97, 0xeb, 0x94,
-	0xb3, 0x79, 0x2e, 0x86, 0x15, 0x3c, 0x50, 0x32, 0x02, 0x35, 0x81, 0x15, 0xd7, 0x04, 0xd5, 0x47,
-	0x31, 0x03, 0x55, 0xc1, 0x6c, 0x54, 0x15, 0x14, 0x8c, 0x79, 0x8e, 0xf6, 0x23, 0x45, 0x1d, 0x29,
-	0xba, 0x97, 0x5c, 0xc6, 0x3c, 0xcf, 0xe7, 0xdf, 0x3e, 0x08, 0x8d, 0x93, 0xb3, 0x48, 0x78, 0x47,
-	0xc8, 0x5b, 0x29, 0xbe, 0x23, 0x48, 0xd1, 0x5e, 0x4b, 0x63, 0x77, 0xbf, 0x9a, 0xd9, 0x46, 0x72,
-	0xcb, 0xda, 0x57, 0x14, 0x75, 0xd8, 0x67, 0x01, 0x35, 0x21, 0x5f, 0xc2, 0x8e, 0xbd, 0x45, 0xcc,
-	0x28, 0x0b, 0xf6, 0xf5, 0x17, 0xd2, 0x2c, 0x74, 0x10, 0x34, 0xee, 0x24, 0x0a, 0x2b, 0x80, 0xaf,
-	0xa4, 0xb9, 0x91, 0x04, 0xcb, 0xa7, 0xf0, 0x42, 0x18, 0x3b, 0x36, 0x79, 0x73, 0x02, 0xc9, 0xac,
-	0x41, 0x65, 0x5c, 0xa0, 0x01, 0xd1, 0xd4, 0xd7, 0x5f, 0xe4, 0x24, 0x5e, 0x87, 0x7d, 0x99, 0x6b,
-	0xb6, 0x64, 0xd3, 0xac, 0x82, 0x28, 0x21, 0x62, 0x66, 0x98, 0x0b, 0xa3, 0x53, 0x13, 0xa8, 0x6c,
-	0x07, 0x72, 0xf1, 0x3e, 0xde, 0x7b, 0xf2, 0xbc, 0xf5, 0x12, 0x8f, 0x9c, 0xf5, 0x83, 0xd0, 0xe8,
-	0x47, 0xb8, 0xbd, 0xc2, 0x02, 0xe1, 0x61, 0xeb, 0x94, 0x9f, 0x7d, 0xa6, 0x57, 0x50, 0x99, 0xec,
-	0xa1, 0x8f, 0x6f, 0x05, 0x8b, 0x48, 0xb4, 0xa7, 0x6d, 0xa9, 0x67, 0xa0, 0xd8, 0xac, 0x61, 0x9f,
-	0x98, 0xd1, 0x4b, 0xa3, 0x7e, 0x65, 0x4c, 0x19, 0xef, 0x9f, 0xea, 0x4f, 0x92, 0xa1, 0x55, 0x2e,
-	0xe5, 0x77, 0x86, 0xfd, 0x89, 0x6a, 0x24, 0xcb, 0xc2, 0x54, 0x4e, 0x5c, 0x19, 0x8b, 0x4b, 0x8f,
-	0x78, 0x79, 0xbc, 0xb3, 0x5f, 0x55, 0x50, 0xa1, 0xa9, 0xf6, 0xed, 0xa3, 0xea, 0x25, 0x88, 0x1a,
-	0x69, 0xb8, 0x80, 0xd2, 0xd5, 0x72, 0x9b, 0xb0, 0x64, 0x3d, 0x72, 0x3f, 0x20, 0x3e, 0x33, 0x37,
-	0xed, 0x9a, 0x7e, 0x95, 0x4f, 0xc7, 0x9f, 0x94, 0xf8, 0x85, 0x72, 0x09, 0x6f, 0xcf, 0x2e, 0xa0,
-	0x08, 0xbf, 0x63, 0xcf, 0x74, 0x42, 0xc3, 0x68, 0xe2, 0xed, 0x74, 0x8b, 0xb3, 0x85, 0xd8, 0x46,
-	0xa6, 0x92, 0x9e, 0x7d, 0x0f, 0xd1, 0x13, 0xca, 0xbe, 0x87, 0x9a, 0x7c, 0xb8, 0x4a, 0xfc, 0xe6,
-	0x59, 0xa0, 0x8b, 0x1e, 0xd2, 0xac, 0xa6, 0x7d, 0xaa, 0xa8, 0xc3, 0xe9, 0xc3, 0x8b, 0x83, 0xc5,
-	0xa7, 0xda, 0x09, 0xbe, 0x81, 0x3f, 0x84, 0x91, 0x18, 0x4a, 0x1e, 0x2e, 0x16, 0xa7, 0xef, 0x8a,
-	0xaf, 0xb5, 0x43, 0x58, 0x22, 0x4f, 0xd3, 0x67, 0x19, 0x28, 0x7b, 0x2f, 0x93, 0x1a, 0xe9, 0x21,
-	0x17, 0xb6, 0xbe, 0x94, 0x14, 0xca, 0x5a, 0x61, 0xe1, 0xa9, 0x77, 0x4b, 0x3d, 0xcf, 0xdf, 0x56,
-	0x1a, 0x81, 0xe3, 0xc4, 0xb9, 0x8c, 0x4b, 0x93, 0xc2, 0x54, 0x9f, 0xe4, 0x9e, 0xde, 0x82, 0x5c,
-	0x01, 0xb4, 0xe6, 0x03, 0xc7, 0xe1, 0x59, 0xc8, 0x3d, 0x1a, 0x97, 0x92, 0xdd, 0xd0, 0xb8, 0x18,
-	0x1f, 0x59, 0x32, 0xb8, 0x82, 0x7a, 0xb4, 0xd3, 0x5e, 0x57, 0x4f, 0x37, 0x08, 0x66, 0x81, 0x47,
-	0xcc, 0x86, 0x83, 0xd7, 0x7d, 0x7d, 0x8a, 0xef, 0xbb, 0xcb, 0x70, 0xbe, 0xc7, 0xc0, 0x3c, 0xc8,
-	0xd3, 0x77, 0x18, 0x41, 0x58, 0x41, 0x39, 0x15, 0xad, 0xad, 0x8e, 0x08, 0xcf, 0x2f, 0x51, 0x65,
-	0x43, 0xa8, 0x1b, 0xac, 0x6f, 0xe8, 0xd7, 0xf8, 0xa2, 0x7d, 0x95, 0x87, 0xd7, 0x54, 0x65, 0x11,
-	0x34, 0x5e, 0xe3, 0x0a, 0x69, 0xae, 0x23, 0x45, 0xd3, 0x3c, 0x42, 0xde, 0x58, 0xdb, 0x54, 0x87,
-	0x4a, 0x1d, 0x37, 0xf1, 0xb6, 0x7e, 0x9d, 0xf7, 0xfa, 0x0a, 0xa4, 0x80, 0x85, 0x86, 0x4b, 0x78,
-	0xbb, 0x1b, 0x1a, 0xba, 0xac, 0xcb, 0x25, 0xbc, 0x9d, 0xf6, 0x27, 0x69, 0xa6, 0x7d, 0x51, 0xed,
-	0x0b, 0x5a, 0xb4, 0x95, 0x1e, 0x23, 0x3f, 0x9b, 0xe7, 0x93, 0xf3, 0x7f, 0x07, 0xa1, 0x71, 0x6e,
-	0x8e, 0xb4, 0x3c, 0x62, 0x61, 0x46, 0xea, 0x6b, 0xcb, 0x74, 0x39, 0x3b, 0x53, 0x94, 0x97, 0xb2,
-	0x64, 0xae, 0x45, 0x5b, 0x31, 0xf0, 0xa2, 0xdb, 0xb4, 0x21, 0xa1, 0x64, 0x3b, 0x95, 0xdd, 0xfd,
-	0xaa, 0xbc, 0xb1, 0xae, 0xa0, 0x53, 0x42, 0x13, 0xed, 0x27, 0x4a, 0xdc, 0x7d, 0x72, 0x55, 0xff,
-	0xfe, 0x3c, 0x77, 0xf2, 0x1d, 0xbe, 0x0b, 0xf2, 0x26, 0xd2, 0x6b, 0x7b, 0xde, 0xfd, 0x58, 0xda,
-	0xbd, 0x78, 0xdd, 0x2e, 0x70, 0xc8, 0xb6, 0xfb, 0xf9, 0xde, 0x5a, 0xb0, 0xac, 0x65, 0xbd, 0xe8,
-	0x0a, 0x52, 0xb3, 0x56, 0xda, 0xaf, 0x14, 0xb5, 0x9f, 0xd3, 0xcc, 0x2e, 0xe5, 0x7f, 0x1e, 0x11,
-	0xfd, 0x3a, 0xcf, 0x85, 0xf3, 0x26, 0x84, 0x0b, 0x7a, 0x4e, 0xb5, 0x92, 0x52, 0xcd, 0x5f, 0xa9,
-	0x4b, 0xc9, 0x5e, 0xfc, 0x2c, 0x3d, 0xc8, 0x78, 0xe5, 0x7d, 0xe9, 0x0a, 0xea, 0x13, 0x5b, 0x66,
-	0x94, 0xb3, 0xab, 0xf7, 0x0f, 0x7a, 0x53, 0x16, 0xae, 0xe1, 0x0b, 0x94, 0xf3, 0x17, 0xe7, 0xbd,
-	0x29, 0xf7, 0xd2, 0x2b, 0x53, 0x4e, 0x34, 0x13, 0xca, 0xe9, 0x4d, 0x7b, 0x43, 0x8d, 0x9e, 0xf8,
-	0xd2, 0x43, 0xf3, 0x17, 0xf3, 0x7c, 0xf7, 0xfe, 0x4f, 0x9e, 0x2f, 0x7f, 0x25, 0xcb, 0x4e, 0x4f,
-	0x61, 0x31, 0x7a, 0x19, 0x22, 0x10, 0x85, 0x7e, 0x04, 0xc4, 0xe7, 0x17, 0x15, 0xe5, 0x3b, 0x02,
-	0xb3, 0x65, 0x31, 0xfd, 0x43, 0x18, 0x22, 0x65, 0x66, 0xe9, 0x20, 0x34, 0x2e, 0x66, 0x3d, 0x2e,
-	0xe5, 0x2b, 0xfc, 0x65, 0x8b, 0xe5, 0xc7, 0xa9, 0x59, 0xc2, 0xf3, 0xdd, 0x6b, 0x65, 0x05, 0xc8,
-	0x10, 0x86, 0x0a, 0xe7, 0xa3, 0x6f, 0x61, 0xea, 0xeb, 0xbf, 0x8c, 0x66, 0x69, 0xb5, 0x40, 0x41,
-	0x3c, 0x57, 0x56, 0x40, 0xb1, 0x40, 0xa1, 0x84, 0x97, 0xa7, 0x8a, 0x33, 0x29, 0xe9, 0xcd, 0xdc,
-	0xf9, 0xe8, 0x93, 0xd1, 0x23, 0xfb, 0x9f, 0x8c, 0x1e, 0xf9, 0xe8, 0x60, 0x54, 0xd9, 0x3f, 0x18,
-	0x55, 0xbe, 0xf9, 0x60, 0xf4, 0xc8, 0x7b, 0x0f, 0x46, 0x95, 0xfd, 0x07, 0xa3, 0x47, 0xfe, 0xf6,
-	0x60, 0xf4, 0xc8, 0x9b, 0xcf, 0xad, 0xdb, 0x6c, 0x23, 0xa8, 0x5d, 0xb1, 0xdc, 0xe6, 0xd5, 0x34,
-	0x6b, 0x15, 0x7e, 0x65, 0xff, 0x59, 0xaa, 0x9d, 0xe0, 0x7f, 0x52, 0xba, 0xf6, 0x9f, 0x00, 0x00,
-	0x00, 0xff, 0xff, 0x43, 0x37, 0xb7, 0x54, 0x10, 0x25, 0x00, 0x00,
+	// 3218 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x5a, 0x5d, 0x6c, 0x1d, 0x47,
+	0xf5, 0xcf, 0x26, 0x4d, 0xda, 0x6c, 0x1c, 0x27, 0x1e, 0x3b, 0xf6, 0x36, 0x49, 0xbd, 0xee, 0xcd,
+	0x4d, 0xeb, 0x7e, 0x25, 0xb6, 0x93, 0xe6, 0x9f, 0x46, 0xfa, 0xab, 0xf8, 0xa3, 0x26, 0x6e, 0xec,
+	0xc4, 0x1a, 0xdb, 0x2a, 0x2a, 0x42, 0xab, 0xb9, 0x7b, 0xe7, 0xda, 0x8b, 0xf7, 0xce, 0xde, 0xee,
+	0xcc, 0xfa, 0xda, 0x2d, 0x82, 0xaa, 0x88, 0x8f, 0x37, 0xc0, 0xe2, 0x43, 0x02, 0x09, 0x15, 0x01,
+	0x12, 0xa5, 0x14, 0x21, 0x21, 0x21, 0xc1, 0x0b, 0x08, 0x09, 0xa9, 0x82, 0x07, 0xfb, 0xb1, 0x12,
+	0x65, 0x51, 0x9d, 0x3e, 0xdd, 0x07, 0x1e, 0xee, 0xa3, 0x79, 0x41, 0x33, 0xfb, 0x35, 0xbb, 0x3b,
+	0xb7, 0xc9, 0xdb, 0xdd, 0xf3, 0x3b, 0x73, 0xe6, 0x77, 0xe6, 0xe3, 0xcc, 0x39, 0x33, 0x57, 0xbf,
+	0xec, 0x3a, 0xb5, 0xab, 0xb6, 0x47, 0x1a, 0xce, 0xfa, 0x55, 0xaf, 0xc5, 0x1c, 0x8f, 0xd0, 0xe8,
+	0x2b, 0xf0, 0x11, 0xff, 0xba, 0xd2, 0xf2, 0x3d, 0xe6, 0x81, 0x13, 0x91, 0xf0, 0xfc, 0x88, 0xa4,
+	0xce, 0x02, 0xe2, 0x90, 0xf5, 0x48, 0xe1, 0xfc, 0x39, 0x09, 0xa0, 0xce, 0x9b, 0x38, 0x16, 0x9f,
+	0xc4, 0xdb, 0x2c, 0xfa, 0x59, 0xf9, 0xe8, 0xb6, 0x3e, 0x74, 0x2f, 0xea, 0x61, 0x56, 0xee, 0x01,
+	0xfc, 0x54, 0xd3, 0xcf, 0xba, 0x0e, 0x65, 0x98, 0x58, 0xa8, 0x5e, 0xf7, 0x31, 0xa5, 0x98, 0x1a,
+	0xda, 0xd8, 0xb1, 0xf1, 0x93, 0x33, 0xf4, 0x20, 0x34, 0x01, 0x44, 0xed, 0x45, 0x01, 0x4f, 0x27,
+	0x68, 0x27, 0x34, 0xcf, 0xb8, 0x79, 0x51, 0x37, 0x34, 0x2f, 0x6f, 0x37, 0xdd, 0x5b, 0x95, 0x9c,
+	0xbc, 0x32, 0x56, 0xc7, 0x0d, 0x14, 0xb8, 0xec, 0x56, 0x25, 0xfe, 0x51, 0x39, 0xdc, 0xab, 0x3e,
+	0x1a, 0xff, 0xde, 0xdd, 0xaf, 0x2a, 0x8c, 0xc3, 0xa2, 0x69, 0xf0, 0x1f, 0x4d, 0x37, 0xd6, 0x5d,
+	0xaf, 0x86, 0x5c, 0xab, 0xee, 0x50, 0xdb, 0xdb, 0xc2, 0xfe, 0x8e, 0x45, 0xb1, 0xbf, 0x85, 0x7d,
+	0x6a, 0x1c, 0x15, 0x44, 0x7f, 0xaf, 0x1d, 0x84, 0xe6, 0x20, 0x44, 0xed, 0xcf, 0x0b, 0xbd, 0x69,
+	0x42, 0x56, 0x22, 0xbc, 0x13, 0x9a, 0xe7, 0xd6, 0x13, 0x99, 0x17, 0x10, 0x1b, 0xc7, 0x40, 0x37,
+	0x34, 0x9f, 0x17, 0x84, 0x55, 0xa8, 0x82, 0x77, 0x67, 0xaf, 0x3a, 0xa4, 0x52, 0xed, 0xee, 0x55,
+	0xd5, 0x1d, 0xe4, 0x1d, 0x55, 0x71, 0x83, 0xc3, 0x51, 0xc3, 0xb9, 0xc4, 0xa9, 0x58, 0x0e, 0x3e,
+	0x55, 0x39, 0x8c, 0x09, 0xaa, 0xb9, 0xb8, 0x6e, 0x1c, 0x1b, 0xd3, 0xc6, 0x1f, 0x9b, 0x79, 0x8f,
+	0x3b, 0x7c, 0x36, 0xb5, 0xf8, 0x4a, 0x04, 0x96, 0xbd, 0x8d, 0x81, 0x6e, 0x68, 0x3e, 0xab, 0xf0,
+	0x36, 0x46, 0x25, 0x77, 0x99, 0x1f, 0x60, 0xee, 0x6b, 0x0f, 0x33, 0xbd, 0x80, 0xc3, 0xbd, 0xea,
+	0x23, 0xbc, 0xe9, 0xee, 0x7e, 0xb5, 0x44, 0xaa, 0xe4, 0x66, 0x2c, 0x07, 0x1f, 0x6b, 0xfa, 0x88,
+	0xeb, 0xd9, 0x4a, 0x2f, 0x1f, 0x11, 0x5e, 0xfe, 0x9c, 0x7b, 0x79, 0x66, 0x91, 0xeb, 0xe4, 0x9c,
+	0x1c, 0x72, 0x63, 0x51, 0xc1, 0xc7, 0x67, 0xa2, 0x25, 0xa8, 0x00, 0x15, 0x2e, 0xaa, 0x8d, 0xf4,
+	0x90, 0x4b, 0x0e, 0x16, 0xf9, 0xc0, 0x73, 0xa2, 0x41, 0xc9, 0xbd, 0x7f, 0x68, 0xfa, 0x60, 0xe4,
+	0x1e, 0x8a, 0x6d, 0x59, 0x2d, 0xcf, 0x67, 0xc6, 0xf1, 0x31, 0x6d, 0xfc, 0xf8, 0xcc, 0x8f, 0xb9,
+	0x6b, 0x7d, 0x89, 0xa9, 0x65, 0xcf, 0x67, 0x9d, 0xd0, 0x1c, 0xc8, 0x75, 0xcd, 0x85, 0xdd, 0xd0,
+	0x7c, 0xba, 0xec, 0x14, 0x47, 0x24, 0x8f, 0xa6, 0x26, 0x27, 0xa6, 0xfe, 0xaf, 0x72, 0x18, 0x9a,
+	0xc7, 0x1c, 0xc2, 0x3a, 0x7b, 0x55, 0x85, 0x19, 0x95, 0xf0, 0x70, 0xaf, 0x7a, 0x5c, 0x34, 0xdd,
+	0xdd, 0xaf, 0xe6, 0x98, 0xc0, 0xb2, 0x2e, 0xf8, 0xfa, 0x51, 0x7d, 0xac, 0xe0, 0x4d, 0x33, 0x70,
+	0x99, 0x63, 0x23, 0xca, 0x92, 0xb8, 0x61, 0x9c, 0x18, 0xd3, 0xc6, 0x4f, 0xce, 0xfc, 0x91, 0xbb,
+	0xd6, 0x9f, 0x18, 0x5c, 0x9a, 0xe5, 0x3b, 0xb9, 0x13, 0x9a, 0x83, 0x39, 0xa3, 0x91, 0xb8, 0x1b,
+	0x9a, 0x37, 0xca, 0xee, 0x45, 0x98, 0xe4, 0xe0, 0x17, 0x1b, 0x8d, 0xc9, 0xa9, 0x5b, 0xb7, 0x6e,
+	0x5e, 0xbb, 0x79, 0xfd, 0x4b, 0xb7, 0x22, 0x6f, 0x3b, 0x7b, 0x55, 0xa5, 0x41, 0xb5, 0xf8, 0x70,
+	0xaf, 0x0a, 0xca, 0x46, 0x76, 0xf7, 0xab, 0x05, 0x9a, 0xf0, 0x89, 0x7c, 0xe3, 0xc4, 0xc3, 0x38,
+	0x18, 0x81, 0x7b, 0xfa, 0xe9, 0x26, 0xda, 0xb6, 0x28, 0x26, 0x75, 0x6b, 0xb3, 0xd6, 0xa2, 0xc6,
+	0xa3, 0x62, 0x32, 0x9f, 0xeb, 0x84, 0xe6, 0xa9, 0x26, 0xda, 0x5e, 0xc1, 0xa4, 0x7e, 0xa7, 0xd6,
+	0xe2, 0xc1, 0x65, 0x40, 0xb8, 0x25, 0xc9, 0x92, 0xf9, 0x81, 0xb2, 0x62, 0x62, 0xd0, 0xc7, 0xf6,
+	0x56, 0x64, 0xf0, 0xb1, 0x9c, 0x41, 0x88, 0xed, 0xad, 0xa2, 0xc1, 0x44, 0x96, 0x33, 0x98, 0x08,
+	0xc1, 0x1f, 0x34, 0x7d, 0xc4, 0xc7, 0xb6, 0x47, 0x08, 0xb6, 0x79, 0x78, 0xb7, 0x1c, 0xc2, 0xb0,
+	0xbf, 0x85, 0x5c, 0x8b, 0x1a, 0x27, 0x85, 0xed, 0xaf, 0x8a, 0xa0, 0x9e, 0xa8, 0x2c, 0xc4, 0xf0,
+	0x0a, 0x8f, 0x1d, 0x72, 0xc3, 0x14, 0xe8, 0x86, 0xe6, 0xb8, 0xe8, 0x5b, 0x89, 0x4a, 0xb3, 0x74,
+	0x63, 0x22, 0xa1, 0x74, 0xb8, 0x57, 0x3d, 0x7a, 0x63, 0x42, 0xc4, 0xf7, 0x52, 0x3f, 0x50, 0xdd,
+	0x0b, 0x68, 0xe8, 0xfd, 0x3e, 0x76, 0xd1, 0x0e, 0x4d, 0x63, 0x80, 0x2e, 0x62, 0xc0, 0xcb, 0x9d,
+	0xd0, 0x3c, 0x1d, 0x21, 0xd9, 0x46, 0xaf, 0xc4, 0x84, 0x24, 0x69, 0x71, 0x87, 0x27, 0x3b, 0x16,
+	0xe6, 0x1b, 0x83, 0x77, 0x8e, 0xea, 0x17, 0xe2, 0x8e, 0x52, 0x22, 0xd9, 0x20, 0x35, 0x8d, 0x53,
+	0x62, 0x90, 0xfe, 0xca, 0xd7, 0xf0, 0x08, 0xe4, 0x7a, 0x25, 0x17, 0x96, 0x3a, 0xa1, 0x39, 0xe2,
+	0xab, 0xa1, 0x34, 0xd0, 0xf6, 0xc0, 0x25, 0x96, 0x93, 0x13, 0xd2, 0x96, 0xed, 0x69, 0xaf, 0x37,
+	0xc4, 0x07, 0x79, 0x92, 0x0f, 0x72, 0x2f, 0x9a, 0xd0, 0x88, 0xfc, 0x2c, 0x23, 0xa0, 0xa6, 0x9f,
+	0xa6, 0x0c, 0xf9, 0xcc, 0xaa, 0xf9, 0x5e, 0x9b, 0x62, 0xdf, 0xe8, 0x13, 0x63, 0xfd, 0xff, 0x9d,
+	0xd0, 0xec, 0x13, 0xc0, 0x4c, 0x24, 0xef, 0x86, 0xe6, 0x93, 0xc2, 0x1d, 0x59, 0xd8, 0x73, 0xa4,
+	0x73, 0x4d, 0xc1, 0x2f, 0x35, 0xfd, 0x1c, 0x41, 0xcc, 0x62, 0x3e, 0xe2, 0xa7, 0x1a, 0x72, 0xd3,
+	0x89, 0xed, 0x17, 0x9d, 0xbd, 0x71, 0x10, 0x9a, 0xfa, 0xdd, 0xe9, 0xd5, 0x2c, 0xac, 0xeb, 0x04,
+	0xb1, 0x6c, 0x8e, 0x4d, 0xd1, 0x71, 0x26, 0x52, 0x84, 0x70, 0xb9, 0x41, 0xee, 0x4b, 0x0a, 0xd7,
+	0x52, 0x17, 0x70, 0x90, 0x20, 0xb6, 0x9a, 0xd0, 0x49, 0x16, 0xc4, 0x9f, 0x4a, 0x3c, 0x5d, 0x8c,
+	0x28, 0xb6, 0x9a, 0xc6, 0x19, 0xb1, 0x14, 0xbe, 0xc9, 0x97, 0xc2, 0xc9, 0xbb, 0xd3, 0xab, 0x8b,
+	0x5c, 0xcc, 0x27, 0xff, 0x0c, 0x41, 0x2c, 0xfa, 0x70, 0x48, 0xc0, 0x44, 0xf2, 0x53, 0x49, 0xc8,
+	0xca, 0x72, 0xe5, 0xde, 0xe8, 0xec, 0x55, 0x4b, 0xed, 0xcb, 0xa2, 0x74, 0x07, 0x65, 0x1d, 0x43,
+	0x20, 0xb3, 0x8f, 0x64, 0xe0, 0xef, 0x9a, 0x3e, 0x92, 0x27, 0xef, 0x63, 0x82, 0xdb, 0x62, 0x25,
+	0x9f, 0x15, 0xf4, 0x77, 0x39, 0xfd, 0x53, 0x77, 0xa7, 0x57, 0x61, 0x04, 0x70, 0x07, 0x06, 0x08,
+	0x62, 0xc9, 0x67, 0xea, 0x42, 0x35, 0x71, 0x21, 0x8f, 0x48, 0x4e, 0x5c, 0x93, 0x9d, 0x50, 0xd8,
+	0x50, 0x09, 0xb9, 0x23, 0xd7, 0xb8, 0x23, 0x32, 0x05, 0x38, 0x24, 0xbb, 0x92, 0x48, 0x15, 0xce,
+	0x30, 0xa7, 0x89, 0xbd, 0x80, 0x59, 0xd4, 0x18, 0xc8, 0x3b, 0xb3, 0x1a, 0x01, 0x2b, 0xb1, 0x33,
+	0xc9, 0x27, 0x5f, 0xe9, 0xf5, 0x9c, 0x33, 0x79, 0xa4, 0xd7, 0xf6, 0x53, 0xd8, 0x50, 0x09, 0xd3,
+	0x2d, 0x27, 0x53, 0xc8, 0x3b, 0x93, 0x48, 0xc1, 0x4f, 0x34, 0xdd, 0x08, 0x28, 0x5a, 0xc7, 0x96,
+	0x8f, 0xf9, 0xb9, 0xef, 0x90, 0x75, 0x0b, 0xd9, 0x36, 0x6e, 0x31, 0x5c, 0x37, 0x80, 0xf0, 0x06,
+	0xf1, 0x1d, 0xb0, 0x06, 0xa7, 0x63, 0x29, 0xdf, 0x01, 0x81, 0x9f, 0x7c, 0x75, 0x43, 0xf3, 0xac,
+	0x70, 0x22, 0x13, 0x49, 0x84, 0x65, 0xc5, 0xdc, 0x17, 0x5f, 0xf1, 0x99, 0x49, 0x38, 0x2c, 0x28,
+	0xc0, 0x84, 0x41, 0x22, 0x07, 0x6f, 0xe9, 0x43, 0x45, 0x72, 0x14, 0x63, 0x62, 0x0c, 0x0a, 0x62,
+	0x0b, 0x07, 0xa1, 0x79, 0x62, 0x0d, 0xae, 0x60, 0x4c, 0x3a, 0xa1, 0x79, 0x22, 0xf0, 0xf9, 0xaf,
+	0x6e, 0x68, 0xf6, 0xc5, 0x84, 0xf8, 0xa7, 0x44, 0x26, 0x51, 0x48, 0x7f, 0xed, 0xee, 0x57, 0xe3,
+	0xe6, 0x10, 0xe4, 0x09, 0x70, 0x19, 0xf8, 0x81, 0xa6, 0x3f, 0x5e, 0xec, 0x3d, 0x20, 0xce, 0x1b,
+	0x01, 0xb6, 0x9c, 0xba, 0x31, 0x24, 0x92, 0x88, 0xd7, 0xa3, 0xb1, 0x59, 0x13, 0xe2, 0x85, 0xb9,
+	0x68, 0x6c, 0xe2, 0x2f, 0x79, 0x6c, 0x12, 0x85, 0x4a, 0x34, 0x28, 0xc9, 0x67, 0x57, 0xfe, 0x8a,
+	0x07, 0x25, 0xc1, 0x8a, 0x83, 0x92, 0x68, 0x81, 0xbf, 0x68, 0xfa, 0x60, 0x89, 0x97, 0xef, 0x1a,
+	0xe7, 0x04, 0xa3, 0xef, 0xf0, 0xb5, 0x77, 0x7c, 0x0d, 0xae, 0xc1, 0xc5, 0x4e, 0x68, 0x1e, 0x0f,
+	0xfc, 0x35, 0xb8, 0xd8, 0x0d, 0xcd, 0x9b, 0x09, 0x11, 0xb8, 0x28, 0xad, 0xae, 0x0d, 0xc6, 0x5a,
+	0xf4, 0xd6, 0xd5, 0xab, 0x75, 0xc4, 0xd0, 0x15, 0xba, 0x43, 0x6c, 0xb6, 0xc1, 0x8b, 0x35, 0x82,
+	0xd9, 0x55, 0x82, 0xdb, 0x5c, 0xca, 0x09, 0xc7, 0x46, 0x92, 0x1f, 0x87, 0x7b, 0xd5, 0x87, 0x68,
+	0xb8, 0xbb, 0x5f, 0x8d, 0x58, 0xc0, 0x81, 0x82, 0x1f, 0xbe, 0x0b, 0xfe, 0xad, 0xe9, 0x66, 0xd1,
+	0x85, 0x96, 0x47, 0xf9, 0x09, 0x47, 0xb1, 0x1d, 0xf8, 0xd8, 0xdd, 0x31, 0x86, 0x45, 0xf8, 0xfd,
+	0x91, 0xa8, 0x20, 0xd6, 0xe0, 0xb2, 0x47, 0xd9, 0x42, 0x0a, 0x76, 0x42, 0xf3, 0x6c, 0xe0, 0xe7,
+	0x65, 0xdd, 0xd0, 0x7c, 0x2a, 0x76, 0x32, 0x0f, 0x48, 0xfe, 0x36, 0x90, 0x4b, 0x45, 0x48, 0x2e,
+	0xb7, 0x56, 0xc8, 0x78, 0xe6, 0x29, 0x5a, 0xf0, 0x7a, 0xa1, 0x48, 0x01, 0x5e, 0xcc, 0xbb, 0x95,
+	0x47, 0xc1, 0xbf, 0x14, 0x1e, 0x3a, 0xc4, 0x61, 0x0e, 0xaf, 0x23, 0xf8, 0x79, 0x67, 0x51, 0x63,
+	0x44, 0xac, 0xe2, 0x1f, 0x8a, 0xea, 0x61, 0x0d, 0x2e, 0x44, 0xe8, 0x1c, 0x07, 0x79, 0xc0, 0x38,
+	0x13, 0xf8, 0x39, 0x51, 0x1a, 0x2e, 0x0a, 0x72, 0x39, 0x58, 0xdc, 0x9c, 0xc8, 0x05, 0xf0, 0xa2,
+	0x85, 0xb2, 0x88, 0x9f, 0x40, 0xbc, 0x15, 0x2f, 0x18, 0x0a, 0x14, 0xe0, 0x85, 0xbc, 0x83, 0x39,
+	0x10, 0x78, 0xfa, 0x80, 0x8f, 0xa3, 0xc3, 0xd9, 0x23, 0x56, 0x1b, 0x6d, 0xe2, 0xa0, 0x65, 0x18,
+	0x62, 0xca, 0x66, 0x39, 0xf9, 0x18, 0xbc, 0x47, 0x5e, 0x13, 0x50, 0x4a, 0xbe, 0x20, 0xef, 0x79,
+	0x48, 0x17, 0x0d, 0x80, 0x6f, 0x69, 0xfa, 0x08, 0x0a, 0x98, 0x67, 0x05, 0xad, 0x75, 0x1f, 0xd5,
+	0x71, 0x96, 0x0c, 0x6d, 0x18, 0x8f, 0x8b, 0x81, 0x5c, 0xe6, 0x25, 0x17, 0x57, 0x59, 0x8b, 0x34,
+	0x92, 0x3c, 0xe2, 0x76, 0x5a, 0x9d, 0xa8, 0x40, 0x79, 0xf8, 0xa6, 0xe4, 0xcc, 0x70, 0x72, 0x0a,
+	0x2a, 0xad, 0x81, 0xa6, 0x3e, 0x92, 0x70, 0x60, 0x9e, 0xd5, 0xf2, 0xf9, 0x14, 0x8b, 0xb3, 0x98,
+	0x1a, 0xe7, 0xc5, 0x00, 0xdc, 0xe0, 0x44, 0x62, 0x95, 0x55, 0x6f, 0xd9, 0xc7, 0x30, 0xc6, 0xbb,
+	0xa1, 0x79, 0x3e, 0x9a, 0x42, 0x05, 0x58, 0x81, 0xca, 0x36, 0x60, 0x4b, 0x07, 0x9b, 0x18, 0xb7,
+	0x2c, 0x86, 0x9b, 0x2d, 0xcf, 0x47, 0xbe, 0x83, 0xa9, 0xb5, 0x61, 0x5c, 0x10, 0x2e, 0xdf, 0xe6,
+	0x1b, 0x81, 0xa3, 0xab, 0x19, 0xc8, 0xdd, 0xbd, 0x24, 0x7a, 0x29, 0x02, 0x72, 0x2d, 0x76, 0x5d,
+	0x76, 0x75, 0xea, 0x3a, 0x2c, 0x59, 0x01, 0x3b, 0xfa, 0xa0, 0x8d, 0xec, 0x0d, 0x6c, 0x39, 0xeb,
+	0xc4, 0xf3, 0x71, 0xdd, 0x6a, 0x38, 0x2e, 0xa6, 0xc6, 0x45, 0xe1, 0xe2, 0x02, 0x3f, 0xd1, 0x04,
+	0xbc, 0x10, 0xa1, 0xf3, 0x1c, 0x4c, 0x07, 0xba, 0x84, 0x94, 0xf6, 0x60, 0xba, 0xb7, 0x60, 0xd9,
+	0x0c, 0xf8, 0x9e, 0xa6, 0x9f, 0x6f, 0xf9, 0xde, 0x3a, 0x2f, 0x66, 0xac, 0xa0, 0x55, 0x47, 0x0c,
+	0xcb, 0x05, 0xc2, 0x13, 0xc2, 0xf7, 0x55, 0x9e, 0xdf, 0x26, 0x5a, 0x6b, 0x42, 0x49, 0x2e, 0x06,
+	0xa2, 0x22, 0xbb, 0x07, 0x2e, 0xd1, 0x79, 0x51, 0x1a, 0x08, 0xed, 0x45, 0xd8, 0xcb, 0x22, 0x78,
+	0x47, 0xd3, 0x87, 0x5d, 0xa7, 0xe9, 0x30, 0xab, 0x86, 0x48, 0xbd, 0xed, 0xd4, 0xd9, 0x86, 0xe5,
+	0x10, 0xcb, 0x45, 0xc4, 0x18, 0x15, 0x43, 0xb2, 0x24, 0x8a, 0x47, 0xae, 0x31, 0x93, 0x28, 0x2c,
+	0x90, 0x45, 0x44, 0xb2, 0x82, 0xbf, 0x8c, 0x7d, 0xc6, 0xb0, 0xa8, 0x4c, 0x81, 0xb7, 0x35, 0x1d,
+	0x34, 0x1d, 0x62, 0x6d, 0x78, 0x4d, 0x6c, 0xd5, 0x1d, 0xba, 0x69, 0x35, 0x7c, 0x8c, 0x0d, 0x73,
+	0x4c, 0x1b, 0x3f, 0x35, 0xd5, 0x77, 0x25, 0xba, 0x59, 0xbb, 0xb2, 0xe2, 0xbc, 0x89, 0x67, 0x5e,
+	0xf9, 0x30, 0x34, 0x8f, 0xf0, 0x9d, 0xd8, 0x74, 0xc8, 0x6d, 0xaf, 0x89, 0xe7, 0x1c, 0xba, 0x39,
+	0xef, 0x63, 0x9c, 0xae, 0x8e, 0x82, 0x5c, 0xde, 0x07, 0x63, 0x97, 0x39, 0x91, 0x63, 0x93, 0x63,
+	0x97, 0x61, 0xb1, 0x39, 0xb8, 0xaf, 0xe9, 0x7d, 0xc9, 0x7a, 0x17, 0xc7, 0xce, 0x98, 0x38, 0x76,
+	0xfe, 0x2c, 0x52, 0x9e, 0x64, 0xd1, 0x46, 0x87, 0xcf, 0x29, 0x3f, 0xfb, 0xec, 0x86, 0xe6, 0x5c,
+	0x52, 0x71, 0x24, 0x32, 0xc5, 0x41, 0x14, 0xef, 0x00, 0x5a, 0x38, 0x53, 0x9a, 0x98, 0xa1, 0x2b,
+	0x5f, 0xa6, 0x1e, 0xe1, 0xb1, 0x3b, 0x67, 0x36, 0xff, 0x79, 0xb8, 0x57, 0x1d, 0x7f, 0x58, 0x53,
+	0x3c, 0x3f, 0x92, 0xf8, 0xc2, 0xcc, 0x8e, 0xef, 0x82, 0xd7, 0xf4, 0x01, 0xe4, 0xb6, 0x79, 0xf5,
+	0x15, 0xdd, 0x26, 0x10, 0xcc, 0xa8, 0xf1, 0xa4, 0xb8, 0xc4, 0xe3, 0x45, 0xef, 0x99, 0x08, 0x14,
+	0x55, 0xf9, 0x5d, 0xcc, 0xf8, 0xc2, 0x1f, 0x8a, 0x22, 0x4c, 0x4e, 0x5e, 0x81, 0x45, 0x45, 0xf0,
+	0x5f, 0x4d, 0x1f, 0xf7, 0xb6, 0xb0, 0xdf, 0xf6, 0x1d, 0xc6, 0x03, 0x47, 0xd3, 0x63, 0xd8, 0xaa,
+	0xe3, 0x2d, 0xc7, 0xc6, 0x16, 0x41, 0x4d, 0x4c, 0x79, 0x38, 0x8d, 0x0b, 0x21, 0xa3, 0x92, 0x5d,
+	0x2f, 0x8d, 0xdc, 0x4b, 0x1a, 0x41, 0xd1, 0x66, 0x0e, 0x6f, 0xdd, 0xe5, 0xea, 0x9d, 0xd0, 0xbc,
+	0xe4, 0x95, 0x20, 0xc7, 0xc6, 0x02, 0xbd, 0x47, 0x66, 0x23, 0x53, 0xdd, 0xd0, 0x7c, 0x49, 0x10,
+	0x7c, 0x08, 0xdd, 0xde, 0x8b, 0x92, 0x57, 0x71, 0x3d, 0x78, 0xc0, 0x87, 0x61, 0x01, 0xbe, 0xa6,
+	0x9f, 0xe3, 0x61, 0xcc, 0x72, 0x48, 0x1d, 0x6f, 0x5b, 0x7c, 0x25, 0xd7, 0x5c, 0xcf, 0xde, 0xa4,
+	0xc6, 0x25, 0xb1, 0xa5, 0xf9, 0xa2, 0x01, 0x5c, 0x61, 0x81, 0xe3, 0x4b, 0x0e, 0x99, 0x11, 0x68,
+	0x7a, 0x6b, 0x5b, 0x86, 0x94, 0x99, 0x72, 0x94, 0xff, 0x42, 0x85, 0x25, 0xf0, 0x4f, 0x9e, 0xee,
+	0x12, 0x64, 0x6f, 0xe2, 0xba, 0x45, 0x3c, 0xe6, 0x34, 0x1c, 0x1b, 0x45, 0xf7, 0x0f, 0x75, 0x6a,
+	0x54, 0xc5, 0xfc, 0xbe, 0xcb, 0x87, 0x7b, 0x78, 0x2d, 0x52, 0xba, 0x2b, 0xe9, 0x2c, 0xcc, 0xf1,
+	0xd1, 0x1e, 0x0e, 0x94, 0x48, 0x37, 0x34, 0x2f, 0x44, 0xa1, 0x5d, 0x05, 0x8b, 0xbb, 0x4a, 0x25,
+	0xd2, 0xdd, 0xab, 0xf6, 0xb0, 0xb8, 0xbb, 0x5f, 0xed, 0xc1, 0x02, 0x2a, 0x5b, 0xd4, 0x29, 0x80,
+	0xfa, 0x69, 0xe6, 0xa3, 0x46, 0xc3, 0xb1, 0x2d, 0xdb, 0x45, 0x94, 0x1a, 0x97, 0xc5, 0xb0, 0xbe,
+	0xc0, 0xeb, 0xe5, 0x18, 0x98, 0xe5, 0xf2, 0x6e, 0x68, 0x82, 0x68, 0x40, 0x25, 0x61, 0x7a, 0x51,
+	0x93, 0x53, 0x05, 0x6f, 0xe9, 0x83, 0xf1, 0x10, 0x5b, 0x0d, 0xcf, 0xad, 0x63, 0xdf, 0x6a, 0x21,
+	0xb6, 0x61, 0x3c, 0x25, 0x76, 0xfd, 0x9d, 0x83, 0xd0, 0xbc, 0x30, 0x87, 0x5b, 0x3e, 0xb6, 0x11,
+	0xc3, 0xf5, 0xb9, 0x48, 0x71, 0x5e, 0xe8, 0x2d, 0x23, 0xb6, 0xd1, 0x09, 0x4d, 0xed, 0x85, 0xb4,
+	0x3a, 0xaf, 0x17, 0xe1, 0xe7, 0xbd, 0xa6, 0xc3, 0x27, 0x89, 0xed, 0x54, 0x0c, 0x0d, 0x0e, 0x94,
+	0x70, 0xb0, 0xa9, 0x9f, 0xa5, 0x98, 0x59, 0xae, 0xd7, 0xb6, 0x5a, 0xbe, 0xe3, 0xf9, 0x0e, 0xdb,
+	0x31, 0x9e, 0x16, 0x9b, 0x62, 0xba, 0x13, 0x9a, 0xfd, 0x14, 0xb3, 0x45, 0xaf, 0xbd, 0x1c, 0x23,
+	0x69, 0x64, 0xcb, 0x8b, 0x7b, 0xa6, 0x18, 0x85, 0xe6, 0xe0, 0x3d, 0x4d, 0x1f, 0x6e, 0xa2, 0xed,
+	0xc4, 0x4d, 0xdb, 0x23, 0x76, 0xe0, 0xfb, 0x98, 0xd8, 0x3b, 0xc6, 0xb8, 0x18, 0x47, 0x2a, 0x2e,
+	0x5b, 0x50, 0x7b, 0x09, 0x6d, 0x47, 0x1c, 0x67, 0x33, 0x15, 0x7e, 0xe4, 0x37, 0x15, 0xf2, 0xf4,
+	0xc8, 0x57, 0x81, 0xc9, 0x90, 0x8b, 0xdb, 0x11, 0xb5, 0x5d, 0xa8, 0xb4, 0x0a, 0x3e, 0xd6, 0xf4,
+	0x41, 0xdb, 0x47, 0x74, 0xa3, 0x50, 0x03, 0x3c, 0x23, 0xa6, 0xe5, 0x7d, 0x51, 0x03, 0xcc, 0x26,
+	0x35, 0x80, 0x1d, 0xd7, 0x00, 0xf3, 0xd1, 0xd9, 0xcc, 0x9b, 0x65, 0xd9, 0xb8, 0x32, 0x0c, 0x0b,
+	0x9d, 0x72, 0x5e, 0x2f, 0xc4, 0x7c, 0x2d, 0x0f, 0x94, 0x8c, 0xf0, 0xea, 0xc0, 0x8e, 0xab, 0x83,
+	0xea, 0xc3, 0x98, 0xe1, 0xf5, 0xc1, 0x6c, 0x54, 0x1f, 0x14, 0x8c, 0xf9, 0x2e, 0xf8, 0x99, 0xa6,
+	0x8f, 0x14, 0xdd, 0x4b, 0xae, 0x65, 0x9e, 0x15, 0xf3, 0xef, 0x1c, 0x84, 0xe6, 0xc9, 0x59, 0x28,
+	0xbd, 0x28, 0xe4, 0xad, 0x14, 0x5f, 0x14, 0x94, 0x68, 0xaf, 0xa5, 0xb1, 0xbb, 0x5f, 0xcd, 0x6c,
+	0x43, 0xb5, 0x65, 0xf0, 0x0d, 0x4d, 0x1f, 0xa6, 0x2c, 0x20, 0x16, 0xcf, 0x9c, 0x90, 0xeb, 0x6c,
+	0x61, 0x2b, 0xca, 0x87, 0xa9, 0xf1, 0x5c, 0x9a, 0x8f, 0x0e, 0x72, 0x8d, 0x3b, 0x89, 0xc2, 0x0a,
+	0xc7, 0x57, 0xd2, 0x2c, 0x49, 0x81, 0xe5, 0x93, 0x79, 0x29, 0xa0, 0x1d, 0x9b, 0xbc, 0x39, 0x01,
+	0x55, 0xd6, 0x78, 0x8d, 0x5c, 0xa0, 0xc1, 0xe3, 0x2a, 0x35, 0x9e, 0x17, 0x24, 0x5e, 0xe5, 0x89,
+	0x5a, 0xae, 0xd9, 0x92, 0x43, 0xb2, 0x5a, 0xa2, 0x84, 0xc8, 0x39, 0x62, 0x2e, 0xa0, 0x4e, 0x4d,
+	0xc0, 0xb2, 0x1d, 0x9e, 0x95, 0xf7, 0x89, 0xde, 0x93, 0x87, 0xae, 0x17, 0x44, 0x0c, 0xad, 0x1f,
+	0x84, 0x66, 0x3f, 0x44, 0xed, 0x15, 0x16, 0x48, 0x4f, 0x5c, 0xa7, 0x68, 0xf6, 0x99, 0x5e, 0x46,
+	0x65, 0xb2, 0x07, 0x3e, 0xc3, 0x15, 0x2c, 0x42, 0xd9, 0x1e, 0xd8, 0xd2, 0xcf, 0xf0, 0xb2, 0xb3,
+	0x86, 0x28, 0xb6, 0xa2, 0x37, 0x47, 0xe3, 0xca, 0x98, 0x36, 0xde, 0x3f, 0xd5, 0x9f, 0xa4, 0x45,
+	0xab, 0x42, 0x2a, 0x6e, 0x0f, 0xfb, 0x13, 0xd5, 0x48, 0x96, 0x46, 0x8e, 0xbc, 0xb8, 0x32, 0x16,
+	0x17, 0x21, 0xf1, 0xf2, 0x78, 0x7b, 0xbf, 0xaa, 0xc1, 0x42, 0x53, 0xf0, 0xfd, 0xa3, 0xfa, 0x25,
+	0x1e, 0x35, 0xd2, 0x70, 0xc1, 0x8b, 0x58, 0xdb, 0x6b, 0xf2, 0x25, 0xeb, 0xe3, 0x37, 0x02, 0x4c,
+	0x99, 0xb5, 0xe9, 0xd4, 0x8c, 0xab, 0x62, 0x3a, 0xfe, 0xa6, 0xc5, 0x6f, 0x95, 0x4b, 0x68, 0x7b,
+	0x76, 0x01, 0x46, 0xf8, 0x1d, 0x67, 0xa6, 0x13, 0x9a, 0x66, 0x13, 0x6d, 0xa7, 0x5b, 0x9c, 0x2d,
+	0xc4, 0x36, 0x32, 0x95, 0xf4, 0x14, 0x7c, 0x80, 0x9e, 0x54, 0x00, 0x3e, 0xd0, 0xe4, 0x83, 0x55,
+	0xe2, 0xd7, 0xcf, 0x02, 0x5d, 0xf8, 0x80, 0x66, 0x35, 0xf0, 0xa9, 0xa6, 0x0f, 0xa7, 0x4f, 0x30,
+	0x2e, 0x92, 0x1f, 0x6d, 0x27, 0xc4, 0x06, 0xfe, 0x80, 0x8f, 0xc4, 0x50, 0xf2, 0x84, 0xb1, 0x38,
+	0x7d, 0x57, 0x7e, 0xb7, 0x1d, 0x42, 0x0a, 0x79, 0x9a, 0x48, 0xab, 0x40, 0xd5, 0xcb, 0x99, 0xd2,
+	0x48, 0x0f, 0xb9, 0xb4, 0xf5, 0x95, 0xa4, 0x60, 0xd6, 0x0a, 0x49, 0x8f, 0xbe, 0x5b, 0xfa, 0x79,
+	0xf1, 0xca, 0xd2, 0x08, 0x5c, 0x37, 0xce, 0x6a, 0x3c, 0x92, 0x94, 0xa8, 0xc6, 0xa4, 0xf0, 0xf4,
+	0x16, 0xcf, 0x1a, 0xb8, 0xd6, 0x7c, 0xe0, 0xba, 0x22, 0x1f, 0xb9, 0x47, 0xe2, 0xa2, 0xb2, 0x1b,
+	0x9a, 0x17, 0xe3, 0x23, 0x4b, 0x05, 0x57, 0x60, 0x8f, 0x76, 0xe0, 0x55, 0xfd, 0x74, 0x03, 0x23,
+	0x16, 0xf8, 0xd8, 0x6a, 0xb8, 0x68, 0x9d, 0x1a, 0x53, 0x62, 0xdf, 0x5d, 0xe6, 0x27, 0x7d, 0x0c,
+	0xcc, 0x73, 0x79, 0xfa, 0x22, 0x23, 0x09, 0x2b, 0x30, 0xa7, 0x02, 0xda, 0xfa, 0x88, 0xf4, 0x10,
+	0x13, 0xd5, 0x38, 0x98, 0x78, 0xc1, 0xfa, 0x86, 0x71, 0x4d, 0x2c, 0xda, 0x97, 0x45, 0x78, 0x4d,
+	0x55, 0x16, 0xb9, 0xc6, 0x2b, 0x42, 0x21, 0xcd, 0x7a, 0x94, 0x68, 0x9a, 0x51, 0xa8, 0x1b, 0x83,
+	0x4d, 0x7d, 0xa8, 0xd4, 0x71, 0x13, 0x6d, 0x1b, 0xd7, 0x45, 0xaf, 0x2f, 0xf1, 0x64, 0xb0, 0xd0,
+	0x70, 0x09, 0x6d, 0x77, 0x43, 0xd3, 0x50, 0x75, 0xb9, 0x84, 0xb6, 0xd3, 0xfe, 0x14, 0xcd, 0xc0,
+	0x57, 0xf4, 0xbe, 0xa0, 0x45, 0x5a, 0xe9, 0x31, 0xf2, 0xab, 0x79, 0x31, 0x39, 0x5f, 0x38, 0x08,
+	0xcd, 0x73, 0x59, 0x06, 0xb3, 0xb6, 0x4c, 0x96, 0xb3, 0x33, 0x45, 0xe4, 0x2e, 0x71, 0x5a, 0xd7,
+	0x22, 0xad, 0x18, 0x90, 0xb2, 0x96, 0xdd, 0xfd, 0xaa, 0xba, 0xb1, 0xa1, 0xc1, 0x53, 0x52, 0x13,
+	0xf0, 0x0b, 0x2d, 0xee, 0x3e, 0xb9, 0xb4, 0x7f, 0x6f, 0x5e, 0x38, 0xf9, 0xb6, 0xd8, 0x05, 0x79,
+	0x13, 0xe9, 0x05, 0xbe, 0xe8, 0x7e, 0x2c, 0xed, 0x5e, 0xbe, 0x78, 0x97, 0x38, 0x64, 0xdb, 0xfd,
+	0x7c, 0x6f, 0x2d, 0xbe, 0xac, 0x55, 0xbd, 0x18, 0x1a, 0xd4, 0xb3, 0x56, 0xe0, 0x77, 0x9a, 0xde,
+	0x2f, 0x68, 0x66, 0xd7, 0xf3, 0xbf, 0x8e, 0x88, 0x7e, 0x5b, 0x64, 0xc5, 0x79, 0x13, 0xd2, 0x55,
+	0xbd, 0xa0, 0x5a, 0x49, 0xa9, 0xe6, 0x2f, 0xd7, 0x95, 0x64, 0x2f, 0x7e, 0x96, 0x1e, 0xcf, 0x7d,
+	0xd5, 0x7d, 0x19, 0x1a, 0xec, 0x93, 0x5b, 0x66, 0x94, 0xb3, 0x4b, 0xf8, 0xf7, 0x7b, 0x53, 0x96,
+	0x2e, 0xe4, 0x0b, 0x94, 0xf3, 0x57, 0xe8, 0xbd, 0x29, 0xf7, 0xd2, 0x2b, 0x53, 0x4e, 0x34, 0x13,
+	0xca, 0xe9, 0x9d, 0x7b, 0x43, 0x8f, 0x1e, 0xfb, 0xd2, 0x43, 0xf3, 0x37, 0xf3, 0x62, 0xf7, 0x7e,
+	0x2e, 0xcf, 0x57, 0xbc, 0x97, 0x65, 0xa7, 0xa7, 0xb4, 0x18, 0xfd, 0x0c, 0xc9, 0xa7, 0xd0, 0x7d,
+	0x12, 0x42, 0xc5, 0x95, 0x45, 0xf9, 0xb6, 0xc0, 0x6a, 0xd9, 0xcc, 0xf8, 0x80, 0x0f, 0x91, 0x36,
+	0xb3, 0x74, 0x10, 0x9a, 0x17, 0xb3, 0x1e, 0x97, 0xf2, 0xb5, 0xfe, 0xb2, 0xcd, 0xf2, 0xe3, 0xd4,
+	0x2c, 0xe1, 0xf9, 0xee, 0x41, 0x59, 0x81, 0x67, 0x08, 0x43, 0x85, 0xf3, 0x91, 0xda, 0x88, 0x50,
+	0xe3, 0xb7, 0xd1, 0x2c, 0xad, 0x16, 0x28, 0xc8, 0xe7, 0xca, 0x0a, 0x57, 0x2c, 0x50, 0x28, 0xe1,
+	0xe5, 0xa9, 0x12, 0x4c, 0x4a, 0x7a, 0x33, 0x77, 0x3e, 0xfc, 0x64, 0xf4, 0xc8, 0xfe, 0x27, 0xa3,
+	0x47, 0x3e, 0x3c, 0x18, 0xd5, 0xf6, 0x0f, 0x46, 0xb5, 0xef, 0xde, 0x1f, 0x3d, 0xf2, 0xee, 0xfd,
+	0x51, 0x6d, 0xff, 0xfe, 0xe8, 0x91, 0x8f, 0xee, 0x8f, 0x1e, 0x79, 0xfd, 0x99, 0x75, 0x87, 0x6d,
+	0x04, 0xb5, 0x2b, 0xb6, 0xd7, 0xbc, 0x9a, 0x66, 0xad, 0xd2, 0xaf, 0xec, 0xdf, 0x4b, 0xb5, 0x13,
+	0xe2, 0xef, 0x4a, 0xd7, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, 0x2c, 0x30, 0xd1, 0x0d, 0x1a, 0x25,
+	0x00, 0x00,
 }
 
 func (m *OptionsConfiguration) Marshal() (dAtA []byte, err error) {
@@ -558,10 +559,10 @@ func (m *OptionsConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i--
 		dAtA[i] = 0xb8
 	}
-	if len(m.DefaultFolderPath) > 0 {
-		i -= len(m.DefaultFolderPath)
-		copy(dAtA[i:], m.DefaultFolderPath)
-		i = encodeVarintOptionsconfiguration(dAtA, i, uint64(len(m.DefaultFolderPath)))
+	if len(m.DeprecatedDefaultFolderPath) > 0 {
+		i -= len(m.DeprecatedDefaultFolderPath)
+		copy(dAtA[i:], m.DeprecatedDefaultFolderPath)
+		i = encodeVarintOptionsconfiguration(dAtA, i, uint64(len(m.DeprecatedDefaultFolderPath)))
 		i--
 		dAtA[i] = 0x2
 		i--
@@ -1018,7 +1019,7 @@ func (m *OptionsConfiguration) ProtoSize() (n int) {
 	if m.TrafficClass != 0 {
 		n += 2 + sovOptionsconfiguration(uint64(m.TrafficClass))
 	}
-	l = len(m.DefaultFolderPath)
+	l = len(m.DeprecatedDefaultFolderPath)
 	if l > 0 {
 		n += 2 + l + sovOptionsconfiguration(uint64(l))
 	}
@@ -1948,7 +1949,7 @@ func (m *OptionsConfiguration) Unmarshal(dAtA []byte) error {
 			}
 		case 38:
 			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field DefaultFolderPath", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedDefaultFolderPath", wireType)
 			}
 			var stringLen uint64
 			for shift := uint(0); ; shift += 7 {
@@ -1976,7 +1977,7 @@ func (m *OptionsConfiguration) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			m.DefaultFolderPath = string(dAtA[iNdEx:postIndex])
+			m.DeprecatedDefaultFolderPath = string(dAtA[iNdEx:postIndex])
 			iNdEx = postIndex
 		case 39:
 			if wireType != 0 {

+ 42 - 2
lib/config/testdata/overridenvalues.xml

@@ -1,4 +1,4 @@
-<configuration version="31">
+<configuration version="34">
     <options>
         <listenAddress>tcp://:23000</listenAddress>
         <allowDelete>false</allowDelete>
@@ -36,7 +36,6 @@
         <releasesURL>https://localhost/releases</releasesURL>
         <overwriteRemoteDeviceNamesOnConnect>true</overwriteRemoteDeviceNamesOnConnect>
         <tempIndexMinBlocks>100</tempIndexMinBlocks>
-        <defaultFolderPath>/media/syncthing</defaultFolderPath>
         <setLowPriority>false</setLowPriority>
         <crashReportingURL>https://localhost/newcrash</crashReportingURL>
         <crashReportingEnabled>false</crashReportingEnabled>
@@ -47,4 +46,45 @@
         <announceLANAddresses>false</announceLANAddresses>
         <featureFlag>feature</featureFlag>
     </options>
+    <defaults>
+        <folder id="" label="" path="/media/syncthing" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
+            <filesystemType>basic</filesystemType>
+            <minDiskFree unit="%">1</minDiskFree>
+            <versioning type="trashcan">
+                <param key="cleanoutDays" val="0"></param>
+                <cleanupIntervalS>3600</cleanupIntervalS>
+            </versioning>
+            <copiers>0</copiers>
+            <pullerMaxPendingKiB>0</pullerMaxPendingKiB>
+            <hashers>0</hashers>
+            <order>random</order>
+            <ignoreDelete>false</ignoreDelete>
+            <scanProgressIntervalS>0</scanProgressIntervalS>
+            <pullerPauseS>0</pullerPauseS>
+            <maxConflicts>10</maxConflicts>
+            <disableSparseFiles>false</disableSparseFiles>
+            <disableTempIndexes>false</disableTempIndexes>
+            <paused>false</paused>
+            <weakHashThresholdPct>25</weakHashThresholdPct>
+            <markerName>.stfolder</markerName>
+            <copyOwnershipFromParent>false</copyOwnershipFromParent>
+            <modTimeWindowS>0</modTimeWindowS>
+            <maxConcurrentWrites>2</maxConcurrentWrites>
+            <disableFsync>false</disableFsync>
+            <blockPullOrder>standard</blockPullOrder>
+            <copyRangeMethod>standard</copyRangeMethod>
+            <caseSensitiveFS>false</caseSensitiveFS>
+            <junctionsAsDirs>false</junctionsAsDirs>
+        </folder>
+        <device id="" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
+            <address>dynamic</address>
+            <paused>false</paused>
+            <autoAcceptFolders>false</autoAcceptFolders>
+            <maxSendKbps>0</maxSendKbps>
+            <maxRecvKbps>0</maxRecvKbps>
+            <maxRequestKiB>0</maxRequestKiB>
+            <untrusted>false</untrusted>
+            <remoteGUIPort>0</remoteGUIPort>
+        </device>
+    </defaults>
 </configuration>

+ 1 - 1
lib/config/versioningconfiguration.go

@@ -52,7 +52,7 @@ func (c *VersioningConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartEl
 	return nil
 }
 
-func (c *VersioningConfiguration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+func (c VersioningConfiguration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 	// Using EncodeElement instead of plain Encode ensures that we use the
 	// outer tag name from the VersioningConfiguration (i.e.,
 	// `<versioning>`) rather than whatever the internal representation

+ 14 - 0
lib/config/wrapper.go

@@ -96,10 +96,12 @@ type Wrapper interface {
 	Folders() map[string]FolderConfiguration
 	FolderList() []FolderConfiguration
 	FolderPasswords(device protocol.DeviceID) map[string]string
+	DefaultFolder() FolderConfiguration
 
 	Device(id protocol.DeviceID) (DeviceConfiguration, bool)
 	Devices() map[protocol.DeviceID]DeviceConfiguration
 	DeviceList() []DeviceConfiguration
+	DefaultDevice() DeviceConfiguration
 
 	IgnoredDevices() []ObservedDevice
 	IgnoredDevice(id protocol.DeviceID) bool
@@ -353,6 +355,12 @@ func (w *wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
 	})
 }
 
+func (w *wrapper) DefaultDevice() DeviceConfiguration {
+	w.mut.Lock()
+	defer w.mut.Unlock()
+	return w.cfg.Defaults.Device.Copy()
+}
+
 // Folders returns a map of folders.
 func (w *wrapper) Folders() map[string]FolderConfiguration {
 	w.mut.Lock()
@@ -388,6 +396,12 @@ func (w *wrapper) FolderPasswords(device protocol.DeviceID) map[string]string {
 	return w.cfg.FolderPasswords(device)
 }
 
+func (w *wrapper) DefaultFolder() FolderConfiguration {
+	w.mut.Lock()
+	defer w.mut.Unlock()
+	return w.cfg.Defaults.Folder.Copy()
+}
+
 // Options returns the current options configuration object.
 func (w *wrapper) Options() OptionsConfiguration {
 	w.mut.Lock()

+ 13 - 6
lib/connections/limiter_test.go

@@ -31,12 +31,19 @@ func init() {
 	device4, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2")
 }
 
+func newDeviceConfiguration(w config.Wrapper, id protocol.DeviceID, name string) config.DeviceConfiguration {
+	cfg := w.DefaultDevice()
+	cfg.DeviceID = id
+	cfg.Name = name
+	return cfg
+}
+
 func initConfig() (config.Wrapper, context.CancelFunc) {
 	wrapper := config.Wrap("/dev/null", config.New(device1), device1, events.NoopLogger)
-	dev1Conf = config.NewDeviceConfiguration(device1, "device1")
-	dev2Conf = config.NewDeviceConfiguration(device2, "device2")
-	dev3Conf = config.NewDeviceConfiguration(device3, "device3")
-	dev4Conf = config.NewDeviceConfiguration(device4, "device4")
+	dev1Conf = newDeviceConfiguration(wrapper, device1, "device1")
+	dev2Conf = newDeviceConfiguration(wrapper, device2, "device2")
+	dev3Conf = newDeviceConfiguration(wrapper, device3, "device3")
+	dev4Conf = newDeviceConfiguration(wrapper, device4, "device4")
 
 	var cancel context.CancelFunc = func() {}
 	if wrapperService, ok := wrapper.(suture.Service); ok {
@@ -149,7 +156,7 @@ func TestAddDevice(t *testing.T) {
 	lim := newLimiter(device1, wrapper)
 
 	addedDevice, _ := protocol.DeviceIDFromString("XZJ4UNS-ENI7QGJ-J45DT6G-QSGML2K-6I4XVOG-NAZ7BF5-2VAOWNT-TFDOMQU")
-	addDevConf := config.NewDeviceConfiguration(addedDevice, "addedDevice")
+	addDevConf := newDeviceConfiguration(wrapper, addedDevice, "addedDevice")
 	addDevConf.MaxRecvKbps = 120
 	addDevConf.MaxSendKbps = 240
 
@@ -183,7 +190,7 @@ func TestAddAndRemove(t *testing.T) {
 	lim := newLimiter(device1, wrapper)
 
 	addedDevice, _ := protocol.DeviceIDFromString("XZJ4UNS-ENI7QGJ-J45DT6G-QSGML2K-6I4XVOG-NAZ7BF5-2VAOWNT-TFDOMQU")
-	addDevConf := config.NewDeviceConfiguration(addedDevice, "addedDevice")
+	addDevConf := newDeviceConfiguration(wrapper, addedDevice, "addedDevice")
 	addDevConf.MaxRecvKbps = 120
 	addDevConf.MaxSendKbps = 240
 

+ 1 - 1
lib/db/structs.pb.go

@@ -8,10 +8,10 @@ import (
 	_ "github.com/gogo/protobuf/gogoproto"
 	proto "github.com/gogo/protobuf/proto"
 	github_com_gogo_protobuf_types "github.com/gogo/protobuf/types"
+	_ "github.com/golang/protobuf/ptypes/timestamp"
 	github_com_syncthing_syncthing_lib_protocol "github.com/syncthing/syncthing/lib/protocol"
 	protocol "github.com/syncthing/syncthing/lib/protocol"
 	_ "github.com/syncthing/syncthing/proto/ext"
-	_ "google.golang.org/protobuf/types/known/timestamppb"
 	io "io"
 	math "math"
 	math_bits "math/bits"

+ 1 - 2
lib/model/folder_sendrecv_test.go

@@ -21,7 +21,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/ignore"
@@ -806,7 +805,7 @@ func TestCopyOwner(t *testing.T) {
 
 	m, f, wcfgCancel := setupSendReceiveFolder(t)
 	defer cleanupSRFolder(f, m, wcfgCancel)
-	f.folder.FolderConfiguration = config.NewFolderConfiguration(m.id, f.ID, f.Label, fs.FilesystemTypeFake, "/TestCopyOwner")
+	f.folder.FolderConfiguration = newFolderConfiguration(m.cfg, f.ID, f.Label, fs.FilesystemTypeFake, "/TestCopyOwner")
 	f.folder.FolderConfiguration.CopyOwnershipFromParent = true
 
 	f.fset = newFileSet(t, f.ID, f.Filesystem(), m.db)

+ 27 - 10
lib/model/model.go

@@ -1218,7 +1218,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
 			haveFcfg := cfg.FolderMap()
 			for _, folder := range cm.Folders {
 				from, ok := haveFcfg[folder.ID]
-				if to, changed := m.handleAutoAccepts(deviceID, folder, ccDeviceInfos[folder.ID], from, ok, cfg.Options.DefaultFolderPath); changed {
+				if to, changed := m.handleAutoAccepts(deviceID, folder, ccDeviceInfos[folder.ID], from, ok, cfg.Defaults.Folder.Path); changed {
 					changedFcfg[folder.ID] = to
 				}
 			}
@@ -1638,7 +1638,7 @@ func (m *model) handleAutoAccepts(deviceID protocol.DeviceID, folder protocol.Fo
 				continue
 			}
 
-			fcfg := config.NewFolderConfiguration(m.id, folder.ID, folder.Label, fs.FilesystemTypeBasic, filepath.Join(defaultPath, path))
+			fcfg := newFolderConfiguration(m.cfg, folder.ID, folder.Label, fs.FilesystemTypeBasic, filepath.Join(defaultPath, path))
 			fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{
 				DeviceID: deviceID,
 			})
@@ -1678,6 +1678,15 @@ func (m *model) handleAutoAccepts(deviceID protocol.DeviceID, folder protocol.Fo
 	}
 }
 
+func (m *model) newFolderConfiguration(id, label string, fsType fs.FilesystemType, path string) config.FolderConfiguration {
+	fcfg := m.cfg.DefaultFolder()
+	fcfg.ID = id
+	fcfg.Label = label
+	fcfg.FilesystemType = fsType
+	fcfg.Path = path
+	return fcfg
+}
+
 func (m *model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) config.DeviceConfiguration {
 	addresses := []string{"dynamic"}
 	for _, addr := range device.Addresses {
@@ -1687,14 +1696,13 @@ func (m *model) introduceDevice(device protocol.Device, introducerCfg config.Dev
 	}
 
 	l.Infof("Adding device %v to config (vouched for by introducer %v)", device.ID, introducerCfg.DeviceID)
-	newDeviceCfg := config.DeviceConfiguration{
-		DeviceID:     device.ID,
-		Name:         device.Name,
-		Compression:  introducerCfg.Compression,
-		Addresses:    addresses,
-		CertName:     device.CertName,
-		IntroducedBy: introducerCfg.DeviceID,
-	}
+	newDeviceCfg := m.cfg.DefaultDevice()
+	newDeviceCfg.DeviceID = device.ID
+	newDeviceCfg.Name = device.Name
+	newDeviceCfg.Compression = introducerCfg.Compression
+	newDeviceCfg.Addresses = addresses
+	newDeviceCfg.CertName = device.CertName
+	newDeviceCfg.IntroducedBy = introducerCfg.DeviceID
 
 	// The introducers' introducers are also our introducers.
 	if device.Introducer {
@@ -3156,3 +3164,12 @@ func writeEncryptionToken(token []byte, cfg config.FolderConfiguration) error {
 		Token:    token,
 	})
 }
+
+func newFolderConfiguration(w config.Wrapper, id, label string, fsType fs.FilesystemType, path string) config.FolderConfiguration {
+	fcfg := w.DefaultFolder()
+	fcfg.ID = id
+	fcfg.Label = label
+	fcfg.FilesystemType = fsType
+	fcfg.Path = path
+	return fcfg
+}

+ 6 - 6
lib/model/model_test.go

@@ -1006,7 +1006,7 @@ func TestAutoAcceptNewFolderPremutationsNoPanic(t *testing.T) {
 				for _, dev2folder := range premutations {
 					cfg := defaultAutoAcceptCfg.Copy()
 					if localFolder.Label != "" {
-						fcfg := config.NewFolderConfiguration(myID, localFolder.ID, localFolder.Label, fs.FilesystemTypeBasic, localFolder.ID)
+						fcfg := newFolderConfiguration(defaultCfgWrapper, localFolder.ID, localFolder.Label, fs.FilesystemTypeBasic, localFolder.ID)
 						fcfg.Paused = localFolderPaused
 						cfg.Folders = append(cfg.Folders, fcfg)
 					}
@@ -1211,7 +1211,7 @@ func TestAutoAcceptPausedWhenFolderConfigChanged(t *testing.T) {
 	defer os.RemoveAll(idOther)
 
 	tcfg := defaultAutoAcceptCfg.Copy()
-	fcfg := config.NewFolderConfiguration(myID, id, "", fs.FilesystemTypeBasic, idOther)
+	fcfg := newFolderConfiguration(defaultCfgWrapper, id, "", fs.FilesystemTypeBasic, idOther)
 	fcfg.Paused = true
 	// The order of devices here is wrong (cfg.clean() sorts them), which will cause the folder to restart.
 	// Because of the restart, folder gets removed from m.deviceFolder, which means that generateClusterConfig will not panic.
@@ -1259,7 +1259,7 @@ func TestAutoAcceptPausedWhenFolderConfigNotChanged(t *testing.T) {
 	defer os.RemoveAll(idOther)
 
 	tcfg := defaultAutoAcceptCfg.Copy()
-	fcfg := config.NewFolderConfiguration(myID, id, "", fs.FilesystemTypeBasic, idOther)
+	fcfg := newFolderConfiguration(defaultCfgWrapper, id, "", fs.FilesystemTypeBasic, idOther)
 	fcfg.Paused = true
 	// The new folder is exactly the same as the one constructed by handleAutoAccept, which means
 	// the folder will not be restarted (even if it's paused), yet handleAutoAccept used to add the folder
@@ -2749,7 +2749,7 @@ func TestVersionRestore(t *testing.T) {
 	must(t, err)
 	defer os.RemoveAll(dir)
 
-	fcfg := config.NewFolderConfiguration(myID, "default", "default", fs.FilesystemTypeBasic, dir)
+	fcfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", fs.FilesystemTypeBasic, dir)
 	fcfg.Versioning.Type = "simple"
 	fcfg.FSWatcherEnabled = false
 	filesystem := fcfg.Filesystem()
@@ -3810,7 +3810,7 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec
 	m := setupModel(t, wcfg)
 	defer cleanupModel(m)
 
-	setDevice(t, wcfg, config.NewDeviceConfiguration(device2, "device2"))
+	setDevice(t, wcfg, newDeviceConfiguration(wcfg.DefaultDevice(), device2, "device2"))
 
 	if pre != nil {
 		pre(wcfg)
@@ -3877,7 +3877,7 @@ func TestIssue6961(t *testing.T) {
 	defer wcfgCancel()
 	tfs := fcfg.Filesystem()
 	waiter, err := wcfg.Modify(func(cfg *config.Configuration) {
-		cfg.SetDevice(config.NewDeviceConfiguration(device2, "device2"))
+		cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))
 		fcfg.Type = config.FolderTypeReceiveOnly
 		fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2})
 		cfg.SetFolder(fcfg)

+ 17 - 7
lib/model/testutils_test.go

@@ -40,12 +40,13 @@ func init() {
 	device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
 	device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
 
+	defaultCfgWrapper, defaultCfgWrapperCancel = createTmpWrapper(config.New(myID))
+
 	defaultFolderConfig = testFolderConfig("testdata")
 	defaultFs = defaultFolderConfig.Filesystem()
 
-	defaultCfgWrapper, defaultCfgWrapperCancel = createTmpWrapper(config.New(myID))
 	waiter, _ := defaultCfgWrapper.Modify(func(cfg *config.Configuration) {
-		cfg.SetDevice(config.NewDeviceConfiguration(device1, "device1"))
+		cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device1, "device1"))
 		cfg.SetFolder(defaultFolderConfig)
 		cfg.Options.KeepTemporariesH = 1
 	})
@@ -68,8 +69,10 @@ func init() {
 				AutoAcceptFolders: true,
 			},
 		},
-		Options: config.OptionsConfiguration{
-			DefaultFolderPath: ".",
+		Defaults: config.Defaults{
+			Folder: config.FolderConfiguration{
+				Path: ".",
+			},
 		},
 	}
 }
@@ -104,14 +107,14 @@ func testFolderConfigTmp() config.FolderConfiguration {
 }
 
 func testFolderConfig(path string) config.FolderConfiguration {
-	cfg := config.NewFolderConfiguration(myID, "default", "default", fs.FilesystemTypeBasic, path)
+	cfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", fs.FilesystemTypeBasic, path)
 	cfg.FSWatcherEnabled = false
 	cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{DeviceID: device1})
 	return cfg
 }
 
 func testFolderConfigFake() config.FolderConfiguration {
-	cfg := config.NewFolderConfiguration(myID, "default", "default", fs.FilesystemTypeFake, rand.String(32)+"?content=true")
+	cfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", fs.FilesystemTypeFake, rand.String(32)+"?content=true")
 	cfg.FSWatcherEnabled = false
 	cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{DeviceID: device1})
 	return cfg
@@ -326,6 +329,13 @@ func localIndexUpdate(m *testModel, folder string, fs []protocol.FileInfo) {
 	})
 }
 
+func newDeviceConfiguration(defaultCfg config.DeviceConfiguration, id protocol.DeviceID, name string) config.DeviceConfiguration {
+	cfg := defaultCfg.Copy()
+	cfg.DeviceID = id
+	cfg.Name = name
+	return cfg
+}
+
 func newFileSet(t testing.TB, folder string, fs fs.Filesystem, ldb *db.Lowlevel) *db.FileSet {
 	t.Helper()
 	fset, err := db.NewFileSet(folder, fs, ldb)
@@ -394,7 +404,7 @@ func setDevice(t testing.TB, w config.Wrapper, device config.DeviceConfiguration
 
 func addDevice2(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration) {
 	waiter, err := w.Modify(func(cfg *config.Configuration) {
-		cfg.SetDevice(config.NewDeviceConfiguration(device2, "device2"))
+		cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))
 		fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2})
 		cfg.SetFolder(fcfg)
 	})

+ 6 - 1
lib/syncthing/utils.go

@@ -52,7 +52,12 @@ func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger,
 		return config.Wrap(path, newCfg, myID, evLogger), nil
 	}
 
-	newCfg.Folders = append(newCfg.Folders, config.NewFolderConfiguration(myID, "default", "Default Folder", fs.FilesystemTypeBasic, locations.Get(locations.DefFolder)))
+	fcfg := newCfg.Defaults.Folder.Copy()
+	fcfg.ID = "default"
+	fcfg.Label = "Default Folder"
+	fcfg.FilesystemType = fs.FilesystemTypeBasic
+	fcfg.Path = locations.Get(locations.DefFolder)
+	newCfg.Folders = append(newCfg.Folders, fcfg)
 	l.Infoln("Default folder created and/or linked to new config")
 	return config.Wrap(path, newCfg, myID, evLogger), nil
 }

+ 2 - 1
lib/ur/usage_report.go

@@ -71,6 +71,7 @@ func (s *Service) ReportDataPreview(ctx context.Context, urVersion int) (*contra
 
 func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) (*contract.Report, error) {
 	opts := s.cfg.Options()
+	defaultFolder := s.cfg.DefaultFolder()
 
 	var totFiles, maxFiles int
 	var totBytes, maxBytes int64
@@ -212,7 +213,7 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) (
 		report.CacheIgnoredFiles = opts.CacheIgnoredFiles
 		report.OverwriteRemoteDeviceNames = opts.OverwriteRemoteDevNames
 		report.ProgressEmitterEnabled = opts.ProgressUpdateIntervalS > -1
-		report.CustomDefaultFolderPath = opts.DefaultFolderPath != "~"
+		report.CustomDefaultFolderPath = defaultFolder.Path != "~"
 		report.CustomTrafficClass = opts.TrafficClass != 0
 		report.CustomTempIndexMinBlocks = opts.TempIndexMinBlocks != 10
 		report.TemporariesDisabled = opts.KeepTemporariesH == 0

+ 1 - 0
proto/ext.proto

@@ -18,6 +18,7 @@ extend google.protobuf.FieldOptions {
     optional bool   device_id = 75009;
     optional string goname    = 75010;
     optional string gotype    = 75011;
+    optional bool   nodefault = 75012;
 }
 
 extend google.protobuf.EnumValueOptions {

+ 32 - 21
proto/ext/ext.pb.go

@@ -93,6 +93,15 @@ var E_Gotype = &proto.ExtensionDesc{
 	Filename:      "ext.proto",
 }
 
+var E_Nodefault = &proto.ExtensionDesc{
+	ExtendedType:  (*descriptor.FieldOptions)(nil),
+	ExtensionType: (*bool)(nil),
+	Field:         75012,
+	Name:          "ext.nodefault",
+	Tag:           "varint,75012,opt,name=nodefault",
+	Filename:      "ext.proto",
+}
+
 var E_Enumgoname = &proto.ExtensionDesc{
 	ExtendedType:  (*descriptor.EnumValueOptions)(nil),
 	ExtensionType: (*string)(nil),
@@ -111,31 +120,33 @@ func init() {
 	proto.RegisterExtension(E_DeviceId)
 	proto.RegisterExtension(E_Goname)
 	proto.RegisterExtension(E_Gotype)
+	proto.RegisterExtension(E_Nodefault)
 	proto.RegisterExtension(E_Enumgoname)
 }
 
 func init() { proto.RegisterFile("ext.proto", fileDescriptor_95fe6908ffcf64d3) }
 
 var fileDescriptor_95fe6908ffcf64d3 = []byte{
-	// 318 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0xd1, 0xcb, 0x4a, 0x33, 0x31,
-	0x14, 0xc0, 0x71, 0x86, 0x96, 0xaf, 0x6d, 0x96, 0x5d, 0x7d, 0x08, 0xd6, 0xba, 0xeb, 0x6a, 0x06,
-	0x11, 0x14, 0x43, 0x57, 0x8a, 0x82, 0x0b, 0x11, 0x8a, 0xb8, 0x70, 0x53, 0xd2, 0x99, 0xd3, 0x34,
-	0x92, 0xcb, 0x30, 0x39, 0x23, 0xd3, 0x9d, 0x97, 0x27, 0xf0, 0x95, 0x5c, 0x69, 0x57, 0xfa, 0x06,
-	0xd2, 0xa5, 0xef, 0xe0, 0x85, 0xe9, 0xa4, 0x5a, 0xe8, 0x22, 0xbb, 0x10, 0xfe, 0xbf, 0x93, 0x84,
-	0x90, 0x16, 0x14, 0x18, 0xa6, 0x99, 0x41, 0xd3, 0xae, 0x41, 0x81, 0x1b, 0x5d, 0x6e, 0x0c, 0x97,
-	0x10, 0x2d, 0xb6, 0x46, 0xf9, 0x38, 0x4a, 0xc0, 0xc6, 0x99, 0x48, 0xd1, 0x64, 0x55, 0x46, 0xfb,
-	0xa4, 0x59, 0x28, 0x39, 0x44, 0xc6, 0x6d, 0x7b, 0x2b, 0xac, 0xf2, 0x70, 0x99, 0x87, 0x67, 0x60,
-	0x2d, 0xe3, 0x70, 0x9e, 0xa2, 0x30, 0xda, 0xfe, 0x7f, 0x7c, 0xaa, 0x77, 0x83, 0x5e, 0x73, 0xd0,
-	0x28, 0x94, 0xbc, 0x60, 0xdc, 0xd2, 0x1d, 0x52, 0x2b, 0x94, 0x6c, 0x6f, 0xae, 0xc1, 0x13, 0x01,
-	0x32, 0x59, 0xb2, 0xcf, 0x97, 0x92, 0xb5, 0x06, 0x65, 0x4b, 0x77, 0x49, 0xfd, 0xda, 0x1a, 0xed,
-	0x33, 0x5f, 0xce, 0x2c, 0x62, 0x7a, 0x40, 0x1a, 0x09, 0x8c, 0x59, 0x2e, 0xd1, 0xe7, 0xbe, 0x9d,
-	0x5b, 0xf6, 0x25, 0xcd, 0xc0, 0x22, 0xcb, 0xbc, 0xf4, 0x76, 0xe6, 0x5e, 0xe7, 0x7a, 0xda, 0x27,
-	0xad, 0x04, 0x6e, 0x44, 0x0c, 0x43, 0x91, 0xf8, 0xf0, 0x9d, 0xc3, 0xcd, 0x4a, 0x9c, 0x26, 0x74,
-	0x9f, 0xfc, 0xe3, 0x46, 0x33, 0x05, 0x3e, 0x7a, 0x3f, 0xab, 0xae, 0xec, 0xf2, 0x0a, 0xe2, 0x34,
-	0xf5, 0xc2, 0x87, 0x3f, 0x58, 0xe6, 0xf4, 0x88, 0x10, 0xd0, 0xb9, 0x72, 0xa7, 0x6e, 0xaf, 0xe1,
-	0x63, 0x9d, 0xab, 0x4b, 0x26, 0xf3, 0xdf, 0xff, 0xfc, 0x78, 0xab, 0x06, 0xac, 0xb0, 0xc3, 0xbd,
-	0xe7, 0x79, 0x27, 0x78, 0x9d, 0x77, 0x82, 0xf7, 0x79, 0x27, 0xb8, 0xea, 0x71, 0x81, 0x93, 0x7c,
-	0x14, 0xc6, 0x46, 0x45, 0x76, 0xaa, 0x63, 0x9c, 0x08, 0xcd, 0x57, 0x56, 0x8b, 0xd9, 0x11, 0x14,
-	0xf8, 0x13, 0x00, 0x00, 0xff, 0xff, 0x5a, 0xa4, 0x49, 0x7c, 0x7b, 0x02, 0x00, 0x00,
+	// 332 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0xd2, 0x4d, 0x4b, 0xfb, 0x30,
+	0x1c, 0xc0, 0x71, 0xca, 0xc6, 0x7f, 0x6b, 0x8e, 0x3b, 0xfd, 0x11, 0x9c, 0xf3, 0xb6, 0x53, 0x8b,
+	0x08, 0x8a, 0x61, 0x5e, 0x14, 0x05, 0x0f, 0x22, 0x0c, 0xf1, 0xe0, 0x65, 0x64, 0xed, 0x6f, 0x59,
+	0x24, 0x0f, 0xa5, 0x49, 0xa5, 0xbb, 0xf9, 0xf4, 0x06, 0x7c, 0x4b, 0x9e, 0x74, 0x27, 0x7d, 0x07,
+	0xb2, 0xa3, 0xef, 0xc1, 0x07, 0xba, 0xa6, 0x6e, 0xb0, 0x43, 0xbc, 0x95, 0xf2, 0xfd, 0x24, 0xbf,
+	0x84, 0x20, 0x1f, 0x72, 0x13, 0x24, 0xa9, 0x32, 0xaa, 0x55, 0x83, 0xdc, 0xac, 0x75, 0xa8, 0x52,
+	0x94, 0x43, 0x38, 0xff, 0x35, 0xcc, 0x46, 0x61, 0x0c, 0x3a, 0x4a, 0x59, 0x62, 0x54, 0x5a, 0x66,
+	0xb8, 0x87, 0x9a, 0xb9, 0xe0, 0x03, 0x43, 0xa8, 0x6e, 0x6d, 0x04, 0x65, 0x1e, 0x54, 0x79, 0x70,
+	0x0a, 0x5a, 0x13, 0x0a, 0x67, 0x89, 0x61, 0x4a, 0xea, 0xff, 0x8f, 0x4f, 0xf5, 0x8e, 0xd7, 0x6d,
+	0xf6, 0x1b, 0xb9, 0xe0, 0xe7, 0x84, 0x6a, 0xbc, 0x85, 0x6a, 0xb9, 0xe0, 0xad, 0xf5, 0x15, 0x78,
+	0xcc, 0x80, 0xc7, 0x15, 0xfb, 0x7c, 0x29, 0x98, 0xdf, 0x2f, 0x5a, 0xbc, 0x8d, 0xea, 0x57, 0x5a,
+	0x49, 0x97, 0xf9, 0xb2, 0x66, 0x1e, 0xe3, 0x3d, 0xd4, 0x88, 0x61, 0x44, 0x32, 0x6e, 0x5c, 0xee,
+	0xdb, 0xba, 0xaa, 0x2f, 0x68, 0x0a, 0xda, 0x90, 0xd4, 0x49, 0x6f, 0xa6, 0xf6, 0x74, 0xb6, 0xc7,
+	0x3d, 0xe4, 0xc7, 0x70, 0xcd, 0x22, 0x18, 0xb0, 0xd8, 0x85, 0x6f, 0x2d, 0x6e, 0x96, 0xe2, 0x24,
+	0xc6, 0xbb, 0xe8, 0x1f, 0x55, 0x92, 0x08, 0x70, 0xd1, 0xbb, 0x69, 0x39, 0xb2, 0xcd, 0x4b, 0x68,
+	0x26, 0x89, 0x13, 0xde, 0x2f, 0x60, 0x91, 0xe3, 0x7d, 0xe4, 0x4b, 0xf5, 0xc7, 0x7b, 0x7a, 0xb0,
+	0xf3, 0x2e, 0x04, 0x3e, 0x44, 0x08, 0x64, 0x26, 0xec, 0xd0, 0x9b, 0x2b, 0xfe, 0x48, 0x66, 0xe2,
+	0x82, 0xf0, 0xec, 0xf7, 0x39, 0x7c, 0xbc, 0x95, 0xfb, 0x2f, 0xb1, 0x83, 0x9d, 0xe7, 0x59, 0xdb,
+	0x7b, 0x9d, 0xb5, 0xbd, 0xf7, 0x59, 0xdb, 0xbb, 0xec, 0x52, 0x66, 0xc6, 0xd9, 0x30, 0x88, 0x94,
+	0x08, 0xf5, 0x44, 0x46, 0x66, 0xcc, 0x24, 0x5d, 0xfa, 0x9a, 0xaf, 0x1d, 0x42, 0x6e, 0x7e, 0x02,
+	0x00, 0x00, 0xff, 0xff, 0x65, 0x95, 0xf9, 0xeb, 0xba, 0x02, 0x00, 0x00,
 }

+ 6 - 0
proto/lib/config/config.proto

@@ -20,4 +20,10 @@ message Configuration {
     OptionsConfiguration         options         = 6;
     repeated ObservedDevice      ignored_devices = 7 [(ext.json) = "remoteIgnoredDevices", (ext.xml) = "remoteIgnoredDevice"];
     repeated ObservedDevice      pending_devices = 8 [deprecated=true];
+    Defaults                     defaults        = 9;
+}
+
+message Defaults {
+    FolderConfiguration folder = 1;
+    DeviceConfiguration device = 2;
 }

+ 2 - 2
proto/lib/config/deviceconfiguration.proto

@@ -8,14 +8,14 @@ import "lib/config/observed.proto";
 import "ext.proto";
 
 message DeviceConfiguration {
-    bytes                   device_id                  = 1 [(ext.goname) = "DeviceID", (ext.xml) = "id,attr", (ext.json) = "deviceID", (ext.device_id) = true];
+    bytes                   device_id                  = 1 [(ext.goname) = "DeviceID", (ext.xml) = "id,attr", (ext.json) = "deviceID", (ext.device_id) = true, (ext.nodefault) = true];
     string                  name                       = 2 [(ext.xml) = "name,attr,omitempty"];
     repeated string         addresses                  = 3 [(ext.xml) = "address,omitempty", (ext.default) = "dynamic"];
     protocol.Compression    compression                = 4 [(ext.xml) = "compression,attr"];
     string                  cert_name                  = 5 [(ext.xml) = "certName,attr,omitempty"];
     bool                    introducer                 = 6 [(ext.xml) = "introducer,attr"];
     bool                    skip_introduction_removals = 7 [(ext.xml) = "skipIntroductionRemovals,attr"];
-    bytes                   introduced_by              = 8 [(ext.xml) = "introducedBy,attr", (ext.device_id) = true];
+    bytes                   introduced_by              = 8 [(ext.xml) = "introducedBy,attr", (ext.device_id) = true, (ext.nodefault) = true];
     bool                    paused                     = 9;
     repeated string         allowed_networks           = 10 [(ext.xml) = "allowedNetwork,omitempty"];
     bool                    auto_accept_folders        = 11;

+ 4 - 4
proto/lib/config/folderconfiguration.proto

@@ -20,10 +20,10 @@ message FolderDeviceConfiguration {
 }
 
 message FolderConfiguration {
-    string                             id                         = 1 [(ext.goname) = "ID", (ext.xml) = "id,attr"];
+    string                             id                         = 1 [(ext.goname) = "ID", (ext.xml) = "id,attr", (ext.nodefault) = true];
     string                             label                      = 2 [(ext.xml) = "label,attr", (ext.restart) = false];
     fs.FilesystemType                  filesystem_type            = 3;
-    string                             path                       = 4 [(ext.xml) = "path,attr"];
+    string                             path                       = 4 [(ext.xml) = "path,attr", (ext.default) = "~"];
     FolderType                         type                       = 5 [(ext.xml) = "type,attr"];
     repeated FolderDeviceConfiguration devices                    = 6;
     int32                              rescan_interval_s          = 7 [(ext.xml) = "rescanIntervalS,attr", (ext.default) = "3600"];
@@ -31,7 +31,7 @@ message FolderConfiguration {
     int32                              fs_watcher_delay_s         = 9 [(ext.goname) = "FSWatcherDelayS", (ext.xml) = "fsWatcherDelayS,attr", (ext.default) = "10"];
     bool                               ignore_perms               = 10 [(ext.xml) = "ignorePerms,attr"];
     bool                               auto_normalize             = 11 [(ext.xml) = "autoNormalize,attr", (ext.default) = "true"];
-    Size                               min_disk_free              = 12;
+    Size                               min_disk_free              = 12 [(ext.default) = "1 %"];
     VersioningConfiguration            versioning                 = 13;
     int32                              copiers                    = 14;
     int32                              puller_max_pending_kib     = 15 [(ext.goname) = "PullerMaxPendingKiB", (ext.xml) = "pullerMaxPendingKiB", (ext.json) = "pullerMaxPendingKiB"];
@@ -40,7 +40,7 @@ message FolderConfiguration {
     bool                               ignore_delete              = 18;
     int32                              scan_progress_interval_s   = 19;
     int32                              puller_pause_s             = 20;
-    int32                              max_conflicts              = 21 [(ext.default) = "-1"];
+    int32                              max_conflicts              = 21 [(ext.default) = "10"];
     bool                               disable_sparse_files       = 22;
     bool                               disable_temp_indexes       = 23;
     bool                               paused                     = 24;

+ 1 - 1
proto/lib/config/optionsconfiguration.proto

@@ -44,7 +44,7 @@ message OptionsConfiguration {
     int32           temp_index_min_blocks                    = 35 [(ext.default) = "10"];
     repeated string unacked_notification_ids                 = 36 [(ext.goname) = "UnackedNotificationIDs", (ext.xml) = "unackedNotificationID", (ext.json) = "unackedNotificationIDs"];
     int32           traffic_class                            = 37;
-    string          default_folder_path                      = 38 [(ext.default) = "~"];
+    string          default_folder_path                      = 38 [deprecated = true];
     bool            set_low_priority                         = 39 [(ext.default) = "true"];
     int32           max_folder_concurrency                   = 40 [(ext.goname) = "RawMaxFolderConcurrency"];
     string          crash_reporting_url                      = 41 [(ext.goname) = "CRURL", (ext.xml) = "crashReportingURL", (ext.json) = "crURL", (ext.default) = "https://crash.syncthing.net/newcrash"];

+ 7 - 0
proto/scripts/protoc_plugin.go

@@ -292,6 +292,13 @@ func HandleCustomExtensions(file *descriptor.FileDescriptorProto) func(msg *desc
 				current += fmt.Sprintf(`default:"%s"`, defaultValue)
 			}
 
+			if nodefaultValue, ok := GetFieldBooleanExtension(field, ext.E_Nodefault); ok {
+				if len(current) > 0 {
+					current += " "
+				}
+				current += fmt.Sprintf(`nodefault:"%t"`, nodefaultValue)
+			}
+
 			if restartValue, ok := GetFieldBooleanExtension(field, ext.E_Restart); ok {
 				if len(current) > 0 {
 					current += " "