Quellcode durchsuchen

all: Add folder pause, make pauses permanent (fixes #3407, fixes #215, fixes #3001)

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3520
Audrius Butkevicius vor 8 Jahren
Ursprung
Commit
bab7c8ebbf

+ 33 - 37
cmd/syncthing/gui.go

@@ -82,8 +82,6 @@ type modelIntf interface {
 	Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []model.Availability
 	GetIgnores(folder string) ([]string, []string, error)
 	SetIgnores(folder string, content []string) error
-	PauseDevice(device protocol.DeviceID)
-	ResumeDevice(device protocol.DeviceID)
 	DelayScan(folder string, next time.Duration)
 	ScanFolder(folder string) error
 	ScanFolders() map[string]error
@@ -105,6 +103,7 @@ type configIntf interface {
 	Subscribe(c config.Committer)
 	Folders() map[string]config.FolderConfiguration
 	Devices() map[protocol.DeviceID]config.DeviceConfiguration
+	SetDevice(config.DeviceConfiguration) error
 	Save() error
 	ListenAddresses() []string
 	RequiresRestart() bool
@@ -258,21 +257,21 @@ func (s *apiService) Serve() {
 
 	// The POST handlers
 	postRestMux := http.NewServeMux()
-	postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio)                      // folder file [perpage] [page]
-	postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores)                // folder
-	postRestMux.HandleFunc("/rest/db/override", s.postDBOverride)              // folder
-	postRestMux.HandleFunc("/rest/db/scan", s.postDBScan)                      // folder [sub...] [delay]
-	postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig)          // <body>
-	postRestMux.HandleFunc("/rest/system/error", s.postSystemError)            // <body>
-	postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
-	postRestMux.HandleFunc("/rest/system/ping", s.restPing)                    // -
-	postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset)            // [folder]
-	postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart)        // -
-	postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown)      // -
-	postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade)        // -
-	postRestMux.HandleFunc("/rest/system/pause", s.postSystemPause)            // device
-	postRestMux.HandleFunc("/rest/system/resume", s.postSystemResume)          // device
-	postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug)            // [enable] [disable]
+	postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio)                          // folder file [perpage] [page]
+	postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores)                    // folder
+	postRestMux.HandleFunc("/rest/db/override", s.postDBOverride)                  // folder
+	postRestMux.HandleFunc("/rest/db/scan", s.postDBScan)                          // folder [sub...] [delay]
+	postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig)              // <body>
+	postRestMux.HandleFunc("/rest/system/error", s.postSystemError)                // <body>
+	postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear)     // -
+	postRestMux.HandleFunc("/rest/system/ping", s.restPing)                        // -
+	postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset)                // [folder]
+	postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart)            // -
+	postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown)          // -
+	postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade)            // -
+	postRestMux.HandleFunc("/rest/system/pause", s.makeDevicePauseHandler(true))   // device
+	postRestMux.HandleFunc("/rest/system/resume", s.makeDevicePauseHandler(false)) // device
+	postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug)                // [enable] [disable]
 
 	// Debug endpoints, not for general use
 	debugMux := http.NewServeMux()
@@ -1103,30 +1102,27 @@ func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func (s *apiService) postSystemPause(w http.ResponseWriter, r *http.Request) {
-	var qs = r.URL.Query()
-	var deviceStr = qs.Get("device")
-
-	device, err := protocol.DeviceIDFromString(deviceStr)
-	if err != nil {
-		http.Error(w, err.Error(), 500)
-		return
-	}
+func (s *apiService) makeDevicePauseHandler(paused bool) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var qs = r.URL.Query()
+		var deviceStr = qs.Get("device")
 
-	s.model.PauseDevice(device)
-}
+		device, err := protocol.DeviceIDFromString(deviceStr)
+		if err != nil {
+			http.Error(w, err.Error(), 500)
+			return
+		}
 
-func (s *apiService) postSystemResume(w http.ResponseWriter, r *http.Request) {
-	var qs = r.URL.Query()
-	var deviceStr = qs.Get("device")
+		cfg, ok := s.cfg.Devices()[device]
+		if !ok {
+			http.Error(w, "not found", http.StatusNotFound)
+		}
 
-	device, err := protocol.DeviceIDFromString(deviceStr)
-	if err != nil {
-		http.Error(w, err.Error(), 500)
-		return
+		cfg.Paused = paused
+		if err := s.cfg.SetDevice(cfg); err != nil {
+			http.Error(w, err.Error(), 500)
+		}
 	}
-
-	s.model.ResumeDevice(device)
 }
 
 func (s *apiService) postDBScan(w http.ResponseWriter, r *http.Request) {

+ 23 - 5
cmd/syncthing/main.go

@@ -212,6 +212,7 @@ type RuntimeOptions struct {
 	auditEnabled   bool
 	verbose        bool
 	paused         bool
+	unpaused       bool
 	guiAddress     string
 	guiAPIKey      string
 	generateDir    string
@@ -267,7 +268,8 @@ func parseCommandLineOptions() RuntimeOptions {
 	flag.StringVar(&options.upgradeTo, "upgrade-to", options.upgradeTo, "Force upgrade directly from specified URL")
 	flag.BoolVar(&options.auditEnabled, "audit", false, "Write events to audit file")
 	flag.BoolVar(&options.verbose, "verbose", false, "Print verbose log output")
-	flag.BoolVar(&options.paused, "paused", false, "Start with all devices paused")
+	flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused")
+	flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
 	flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (use \"-\" for stdout)")
 	if runtime.GOOS == "windows" {
 		// Allow user to hide the console window
@@ -701,14 +703,17 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 		m.StartDeadlockDetector(20 * time.Minute)
 	}
 
-	if runtimeOptions.paused {
-		for device := range cfg.Devices() {
-			m.PauseDevice(device)
-		}
+	if runtimeOptions.unpaused {
+		setPauseState(cfg, false)
+	} else if runtimeOptions.paused {
+		setPauseState(cfg, true)
 	}
 
 	// Add and start folders
 	for _, folderCfg := range cfg.Folders() {
+		if folderCfg.Paused {
+			continue
+		}
 		m.AddFolder(folderCfg)
 		m.StartFolder(folderCfg.ID)
 	}
@@ -1203,3 +1208,16 @@ func showPaths() {
 	fmt.Printf("GUI override directory:\n\t%s\n\n", locations[locGUIAssets])
 	fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations[locDefFolder])
 }
+
+func setPauseState(cfg *config.Wrapper, paused bool) {
+	raw := cfg.RawCopy()
+	for i := range raw.Devices {
+		raw.Devices[i].Paused = paused
+	}
+	for i := range raw.Folders {
+		raw.Folders[i].Paused = paused
+	}
+	if err := cfg.Replace(raw); err != nil {
+		l.Fatalln("Cannot adjust paused state:", err)
+	}
+}

+ 4 - 0
cmd/syncthing/mocked_config_test.go

@@ -45,6 +45,10 @@ func (c *mockedConfig) Devices() map[protocol.DeviceID]config.DeviceConfiguratio
 	return nil
 }
 
+func (c *mockedConfig) SetDevice(config.DeviceConfiguration) error {
+	return nil
+}
+
 func (c *mockedConfig) Save() error {
 	return nil
 }

+ 12 - 0
cmd/syncthing/verboseservice.go

@@ -166,6 +166,18 @@ func (s *verboseService) formatEvent(ev events.Event) string {
 		device := data["device"]
 		return fmt.Sprintf("Device %v was resumed", device)
 
+	case events.FolderPaused:
+		data := ev.Data.(map[string]string)
+		id := data["id"]
+		label := data["label"]
+		return fmt.Sprintf("Folder %v (%v) was paused", id, label)
+
+	case events.FolderResumed:
+		data := ev.Data.(map[string]string)
+		id := data["id"]
+		label := data["label"]
+		return fmt.Sprintf("Folder %v (%v) was resumed", id, label)
+
 	case events.ListenAddressesChanged:
 		data := ev.Data.(map[string]interface{})
 		address := data["address"]

+ 12 - 5
gui/default/index.html

@@ -271,6 +271,7 @@
                   <span class="fa fa-fw" ng-class="[folder.type == 'readonly' ? 'fa-lock' : 'fa-folder']"></span>
                 </div>
                 <div class="panel-status pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
+                  <span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs">&#9724;</span></span>
                   <span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">&#9724;</span></span>
                   <span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span>
                   <span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs">&#9724;</span></span>
@@ -307,11 +308,11 @@
                         <span tooltip data-original-title="{{folder.path}}">{{folder.path}}</span>
                       </td>
                     </tr>
-                    <tr ng-if="model[folder.id].invalid || model[folder.id].error">
+                    <tr ng-if="!folder.paused && (model[folder.id].invalid || model[folder.id].error)">
                       <th><span class="fa fa-fw fa-exclamation-triangle"></span>&nbsp;<span translate>Error</span></th>
                       <td class="text-right">{{model[folder.id].invalid || model[folder.id].error}}</td>
                     </tr>
-                    <tr>
+                    <tr ng-if="!folder.paused">
                       <th><span class="fa fa-fw fa-globe"></span>&nbsp;<span translate>Global State</span></th>
                       <td class="text-right">
                         <span tooltip data-original-title="{{model[folder.id].globalFiles | alwaysNumber}} {{'files' | translate}}, {{model[folder.id].globalDirectories | alwaysNumber}} {{'directories' | translate}}, ~{{model[folder.id].globalBytes | binary}}B">
@@ -321,7 +322,7 @@
                         </span>
                       </td>
                     </tr>
-                    <tr>
+                    <tr ng-if="!folder.paused">
                       <th><span class="fa fa-fw fa-home"></span>&nbsp;<span translate>Local State</span></th>
                       <td class="text-right">
                         <span tooltip data-original-title="{{model[folder.id].localFiles | alwaysNumber}} {{'files' | translate}}, {{model[folder.id].localDirectories | alwaysNumber}} {{'directories' | translate}}, ~{{model[folder.id].localBytes | binary}}B">
@@ -417,6 +418,12 @@
                   <span class="fa fa-arrow-circle-up"></span>&nbsp;<span translate>Override Changes</span>
                 </button>
                 <span class="pull-right">
+                  <button ng-if="!folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, true)">
+                    <span class="fa fa-pause"></span>&nbsp;<span translate>Pause</span>
+                  </button>
+                  <button ng-if="folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, false)">
+                    <span class="fa fa-play"></span>&nbsp;<span translate>Resume</span>
+                  </button>
                   <button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-show="['idle', 'stopped', 'unshared'].indexOf(folderStatus(folder)) > -1">
                     <span class="fa fa-refresh"></span>&nbsp;<span translate>Rescan</span>
                   </button>
@@ -601,10 +608,10 @@
               </div>
               <div class="panel-footer">
                 <span class="pull-right">
-                  <button ng-if="!connections[deviceCfg.deviceID].paused" type="button" class="btn btn-sm btn-default" ng-click="pauseDevice(deviceCfg.deviceID)">
+                  <button ng-if="!deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, true)">
                     <span class="fa fa-pause"></span>&nbsp;<span translate>Pause</span>
                   </button>
-                  <button ng-if="connections[deviceCfg.deviceID].paused" type="button" class="btn btn-sm btn-default" ng-click="resumeDevice(deviceCfg.deviceID)">
+                  <button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
                     <span class="fa fa-play"></span>&nbsp;<span translate>Resume</span>
                   </button>
                   <button type="button" class="btn btn-sm btn-default" ng-click="editDevice(deviceCfg)">

+ 2 - 0
gui/default/syncthing/core/eventService.js

@@ -77,6 +77,8 @@ angular.module('syncthing.core')
             STATE_CHANGED:        'StateChanged',   // Emitted when a folder changes state
             FOLDER_ERRORS:        'FolderErrors',   // Emitted when a folder has errors preventing a full sync
             FOLDER_SCAN_PROGRESS: 'FolderScanProgress',   // Emitted every ScanProgressIntervalS seconds, indicating how far into the scan it is at.
+            FOLDER_PAUSED:        'FolderPaused',   // Emitted when a folder is paused
+            FOLDER_RESUMED:       'FolderResumed',   // Emitted when a folder is resumed
 
             start: function() {
                 $http.get(urlbase + '/events?limit=1')

+ 24 - 14
gui/default/syncthing/core/syncthingController.js

@@ -235,14 +235,6 @@ angular.module('syncthing.core')
             $scope.deviceRejections[arg.data.device] = arg;
         });
 
-        $scope.$on(Events.DEVICE_PAUSED, function (event, arg) {
-            $scope.connections[arg.data.device].paused = true;
-        });
-
-        $scope.$on(Events.DEVICE_RESUMED, function (event, arg) {
-            $scope.connections[arg.data.device].paused = false;
-        });
-
         $scope.$on(Events.FOLDER_REJECTED, function (event, arg) {
             $scope.folderRejections[arg.data.folder + "-" + arg.data.device] = arg;
         });
@@ -652,6 +644,10 @@ angular.module('syncthing.core')
                 return 'unknown';
             }
 
+            if (folderCfg.paused) {
+                return 'paused';
+            }
+
             // after restart syncthing process state may be empty
             if (!$scope.model[folderCfg.id].state) {
                 return 'unknown';
@@ -685,6 +681,9 @@ angular.module('syncthing.core')
             if (status === 'idle') {
                 return 'success';
             }
+            if (status == 'paused') {
+                return 'default';
+            }
             if (status === 'syncing' || status === 'scanning') {
                 return 'primary';
             }
@@ -801,7 +800,7 @@ angular.module('syncthing.core')
                 return 'unknown';
             }
 
-            if ($scope.connections[deviceCfg.deviceID].paused) {
+            if (deviceCfg.paused) {
                 return 'paused';
             }
 
@@ -827,7 +826,7 @@ angular.module('syncthing.core')
                 return 'info';
             }
 
-            if ($scope.connections[deviceCfg.deviceID].paused) {
+            if (deviceCfg.paused) {
                 return 'default';
             }
 
@@ -964,12 +963,23 @@ angular.module('syncthing.core')
             return device.deviceID.substr(0, 6);
         };
 
-        $scope.pauseDevice = function (device) {
-            $http.post(urlbase + "/system/pause?device=" + device);
+        $scope.setDevicePause = function (device, pause) {
+            $scope.devices.forEach(function (cfg) {
+                if (cfg.deviceID == device) {
+                    cfg.paused = pause;
+                }
+            });
+            $scope.config.devices = $scope.devices;
+            $scope.saveConfig();
         };
 
-        $scope.resumeDevice = function (device) {
-            $http.post(urlbase + "/system/resume?device=" + device);
+        $scope.setFolderPause = function (folder, pause) {
+            var cfg = $scope.folders[folder];
+            if (cfg) {
+                cfg.paused = pause;
+                $scope.config.folders = folderList($scope.folders);
+                $scope.saveConfig();
+            }
         };
 
         $scope.editSettings = function () {

+ 1 - 0
lib/config/deviceconfiguration.go

@@ -17,6 +17,7 @@ type DeviceConfiguration struct {
 	Introducer               bool                 `xml:"introducer,attr" json:"introducer"`
 	SkipIntroductionRemovals bool                 `xml:"skipIntroductionRemovals,attr" json:"skipIntroductionRemovals"`
 	IntroducedBy             protocol.DeviceID    `xml:"introducedBy,attr" json:"introducedBy"`
+	Paused                   bool                 `xml:"paused" json:"paused"`
 }
 
 func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfiguration {

+ 1 - 0
lib/config/folderconfiguration.go

@@ -41,6 +41,7 @@ type FolderConfiguration struct {
 	DisableTempIndexes    bool                        `xml:"disableTempIndexes" json:"disableTempIndexes"`
 	Fsync                 bool                        `xml:"fsync" json:"fsync"`
 	DisableWeakHash       bool                        `xml:"disableWeakHash" json:"disableWeakHash"`
+	Paused                bool                        `xml:"paused" json:"paused"`
 
 	cachedPath string
 

+ 12 - 0
lib/config/wrapper.go

@@ -323,6 +323,18 @@ func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
 	return DeviceConfiguration{}, false
 }
 
+// Folder returns the configuration for the given folder and an "ok" bool.
+func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
+	w.mut.Lock()
+	defer w.mut.Unlock()
+	for _, folder := range w.cfg.Folders {
+		if folder.ID == id {
+			return folder, true
+		}
+	}
+	return FolderConfiguration{}, false
+}
+
 // Save writes the configuration to disk, and generates a ConfigSaved event.
 func (w *Wrapper) Save() error {
 	fd, err := osutil.CreateAtomic(w.path)

+ 1 - 2
lib/connections/service.go

@@ -343,8 +343,7 @@ func (s *Service) connect() {
 				continue
 			}
 
-			paused := s.model.IsPaused(deviceID)
-			if paused {
+			if deviceCfg.Paused {
 				continue
 			}
 

+ 0 - 1
lib/connections/structs.go

@@ -116,7 +116,6 @@ type Model interface {
 	protocol.Model
 	AddConnection(conn Connection, hello protocol.HelloResult)
 	ConnectedTo(remoteID protocol.DeviceID) bool
-	IsPaused(remoteID protocol.DeviceID) bool
 	OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error
 	GetHello(protocol.DeviceID) protocol.HelloIntf
 }

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

@@ -982,33 +982,33 @@ var (
 )
 
 var fileDescriptorStructs = []byte{
-	// 446 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0xd1, 0x8a, 0xd3, 0x40,
-	0x14, 0xed, 0x6c, 0xb3, 0xdb, 0xf6, 0xc6, 0xae, 0xee, 0x20, 0xcb, 0x50, 0x30, 0x0d, 0x05, 0x21,
-	0x08, 0xa6, 0x5a, 0xf1, 0xc5, 0xc7, 0x7d, 0x58, 0x10, 0x44, 0x24, 0x2e, 0xeb, 0x63, 0x69, 0x32,
-	0xb7, 0xd9, 0xc1, 0x64, 0x26, 0x66, 0x26, 0x85, 0xfa, 0x25, 0xbe, 0xb9, 0x9f, 0xd3, 0x47, 0xbf,
-	0x40, 0xb4, 0xfe, 0x88, 0x74, 0x92, 0xc6, 0x3c, 0xfa, 0x76, 0xce, 0xbd, 0xe7, 0xdc, 0x7b, 0x66,
-	0x2e, 0x8c, 0xb5, 0x29, 0xab, 0xc4, 0xe8, 0xb0, 0x28, 0x95, 0x51, 0xf4, 0x84, 0xc7, 0x93, 0xe7,
-	0xa9, 0x30, 0x77, 0x55, 0x1c, 0x26, 0x2a, 0x9f, 0xa7, 0x2a, 0x55, 0x73, 0xdb, 0x8a, 0xab, 0xb5,
-	0x65, 0x96, 0x58, 0x54, 0x5b, 0x26, 0xaf, 0x3b, 0x72, 0xbd, 0x95, 0x89, 0xb9, 0x13, 0x32, 0xed,
-	0xa0, 0x4c, 0xc4, 0xf5, 0x84, 0x44, 0x65, 0xf3, 0x18, 0x8b, 0xda, 0x36, 0xfb, 0x04, 0xee, 0xb5,
-	0xc8, 0xf0, 0x16, 0x4b, 0x2d, 0x94, 0xa4, 0x2f, 0x60, 0xb0, 0xa9, 0x21, 0x23, 0x3e, 0x09, 0xdc,
-	0xc5, 0xa3, 0xf0, 0x68, 0x0a, 0x6f, 0x31, 0x31, 0xaa, 0xbc, 0x72, 0x76, 0x3f, 0xa7, 0xbd, 0xe8,
-	0x28, 0xa3, 0x97, 0x70, 0xc6, 0x71, 0x23, 0x12, 0x64, 0x27, 0x3e, 0x09, 0x1e, 0x44, 0x0d, 0x9b,
-	0x5d, 0x83, 0xdb, 0x0c, 0x7d, 0x27, 0xb4, 0xa1, 0x2f, 0x61, 0xd8, 0x38, 0x34, 0x23, 0x7e, 0x3f,
-	0x70, 0x17, 0x0f, 0x43, 0x1e, 0x87, 0x9d, 0xdd, 0xcd, 0xe0, 0x56, 0xf6, 0xc6, 0xf9, 0x76, 0x3f,
-	0xed, 0xcd, 0xbe, 0xf7, 0xe1, 0xe2, 0xa0, 0x7a, 0x2b, 0xd7, 0xea, 0xa6, 0xac, 0x64, 0xb2, 0x32,
-	0xc8, 0x29, 0x05, 0x47, 0xae, 0x72, 0xb4, 0x21, 0x47, 0x91, 0xc5, 0xf4, 0x19, 0x38, 0x66, 0x5b,
-	0xd4, 0x39, 0xce, 0x17, 0x97, 0xff, 0x82, 0xb7, 0xf6, 0x6d, 0x81, 0x91, 0xd5, 0x1c, 0xfc, 0x5a,
-	0x7c, 0x45, 0xd6, 0xf7, 0x49, 0xd0, 0x8f, 0x2c, 0xa6, 0x3e, 0xb8, 0x05, 0x96, 0xb9, 0xd0, 0x75,
-	0x4a, 0xc7, 0x27, 0xc1, 0x38, 0xea, 0x96, 0xe8, 0x13, 0x80, 0x5c, 0x71, 0xb1, 0x16, 0xc8, 0x97,
-	0x9a, 0x9d, 0x5a, 0xef, 0xe8, 0x58, 0xf9, 0x48, 0x19, 0x0c, 0x38, 0x66, 0x68, 0x90, 0xb3, 0x33,
-	0x9f, 0x04, 0xc3, 0xe8, 0x48, 0x0f, 0x1d, 0x21, 0x37, 0xab, 0x4c, 0x70, 0x36, 0xa8, 0x3b, 0x0d,
-	0xa5, 0x4f, 0xe1, 0x5c, 0xaa, 0x65, 0x77, 0xef, 0xd0, 0x0a, 0xc6, 0x52, 0x7d, 0xe8, 0x6c, 0xee,
-	0xdc, 0x65, 0xf4, 0x7f, 0x77, 0x99, 0xc0, 0x50, 0xe3, 0x97, 0x0a, 0x65, 0x82, 0x0c, 0x6c, 0xd2,
-	0x96, 0xd3, 0x29, 0xb8, 0xed, 0x3b, 0xa4, 0x66, 0xae, 0x4f, 0x82, 0xd3, 0xa8, 0x7d, 0xda, 0x7b,
-	0x7d, 0x48, 0xa5, 0xb7, 0x79, 0x26, 0xe4, 0xe7, 0xa5, 0x59, 0x95, 0x29, 0x1a, 0x76, 0x61, 0x3f,
-	0x7a, 0xdc, 0x54, 0x6f, 0x6c, 0xb1, 0xbe, 0xd0, 0xd5, 0xe3, 0xdd, 0x6f, 0xaf, 0xb7, 0xdb, 0x7b,
-	0xe4, 0xc7, 0xde, 0x23, 0xbf, 0xf6, 0x5e, 0xef, 0xfe, 0x8f, 0x47, 0xe2, 0x33, 0x9b, 0xef, 0xd5,
-	0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1, 0x2f, 0x12, 0xb6, 0xda, 0x02, 0x00, 0x00,
+	// 442 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0x5d, 0x8b, 0xd3, 0x40,
+	0x14, 0x6d, 0xb7, 0xd9, 0x7e, 0xdc, 0xd8, 0xd5, 0x1d, 0x64, 0x19, 0x0a, 0xb6, 0x4b, 0x41, 0x10,
+	0xc1, 0x54, 0x57, 0x7c, 0xf1, 0x71, 0x1f, 0x16, 0x04, 0x11, 0x19, 0x97, 0xf5, 0xb1, 0x34, 0x99,
+	0xdb, 0xec, 0x60, 0x32, 0x53, 0x33, 0x93, 0x42, 0xfd, 0x25, 0xbe, 0xb9, 0x3f, 0xa7, 0x8f, 0xfe,
+	0x02, 0xd1, 0xfa, 0x47, 0x9c, 0xce, 0xa4, 0x31, 0x8f, 0xfb, 0x10, 0xb8, 0xe7, 0x9e, 0x73, 0xee,
+	0x3d, 0x93, 0x0b, 0x43, 0x6d, 0x8a, 0x32, 0x31, 0x3a, 0x5a, 0x15, 0xca, 0x28, 0x72, 0xc4, 0xe3,
+	0xd1, 0x8b, 0x54, 0x98, 0xdb, 0x32, 0x8e, 0x12, 0x95, 0xcf, 0x52, 0x95, 0xaa, 0x99, 0xa3, 0xe2,
+	0x72, 0xe9, 0x90, 0x03, 0xae, 0xf2, 0x96, 0xd1, 0x9b, 0x86, 0x5c, 0x6f, 0x64, 0x62, 0x6e, 0x85,
+	0x4c, 0x1b, 0x55, 0x26, 0x62, 0x3f, 0x21, 0x51, 0xd9, 0x2c, 0xc6, 0x95, 0xb7, 0x4d, 0x3f, 0x43,
+	0x78, 0x25, 0x32, 0xbc, 0xc1, 0x42, 0x0b, 0x25, 0xc9, 0x4b, 0xe8, 0xad, 0x7d, 0x49, 0xdb, 0xe7,
+	0xed, 0x67, 0xe1, 0xc5, 0xa3, 0xe8, 0x60, 0x8a, 0x6e, 0x30, 0x31, 0xaa, 0xb8, 0x0c, 0xb6, 0xbf,
+	0x26, 0x2d, 0x76, 0x90, 0x91, 0x33, 0xe8, 0x72, 0x5c, 0x8b, 0x04, 0xe9, 0x91, 0x35, 0x3c, 0x60,
+	0x15, 0x9a, 0x5e, 0x41, 0x58, 0x0d, 0x7d, 0x2f, 0xb4, 0x21, 0xaf, 0xa0, 0x5f, 0x39, 0xb4, 0x9d,
+	0xdc, 0xb1, 0x93, 0x1f, 0x46, 0x3c, 0x8e, 0x1a, 0xbb, 0xab, 0xc1, 0xb5, 0xec, 0x6d, 0xf0, 0xfd,
+	0x6e, 0xd2, 0x9a, 0xfe, 0xe8, 0xc0, 0xe9, 0x5e, 0xf5, 0x4e, 0x2e, 0xd5, 0x75, 0x51, 0xca, 0x64,
+	0x61, 0x90, 0x13, 0x02, 0x81, 0x5c, 0xe4, 0xe8, 0x42, 0x0e, 0x98, 0xab, 0xc9, 0x73, 0x08, 0xcc,
+	0x66, 0xe5, 0x73, 0x9c, 0x5c, 0x9c, 0xfd, 0x0f, 0x5e, 0xdb, 0x2d, 0xcb, 0x9c, 0x66, 0xef, 0xd7,
+	0xe2, 0x1b, 0xd2, 0x8e, 0xd5, 0x76, 0x98, 0xab, 0xc9, 0x39, 0x84, 0x2b, 0x2c, 0x72, 0xa1, 0x7d,
+	0xca, 0xc0, 0x52, 0x43, 0xd6, 0x6c, 0x91, 0x27, 0x00, 0xb9, 0xe2, 0x62, 0x29, 0x90, 0xcf, 0x35,
+	0x3d, 0x76, 0xde, 0xc1, 0xa1, 0xf3, 0x89, 0x50, 0xe8, 0x71, 0xcc, 0xd0, 0xe6, 0xa3, 0x5d, 0xcb,
+	0xf5, 0xd9, 0x01, 0xee, 0x19, 0x21, 0xd7, 0x8b, 0x4c, 0x70, 0xda, 0xf3, 0x4c, 0x05, 0xc9, 0x53,
+	0x38, 0x91, 0x6a, 0xde, 0xdc, 0xdb, 0x77, 0x82, 0xa1, 0x54, 0x1f, 0x1b, 0x9b, 0x1b, 0x77, 0x19,
+	0xdc, 0xef, 0x2e, 0x23, 0xe8, 0x6b, 0xfc, 0x5a, 0xa2, 0xb4, 0x97, 0x01, 0x97, 0xb4, 0xc6, 0x64,
+	0x02, 0x61, 0xfd, 0x0e, 0xbb, 0x31, 0xb4, 0xf4, 0x31, 0xab, 0x9f, 0xf6, 0x41, 0xef, 0x53, 0xe9,
+	0x4d, 0x9e, 0x09, 0xf9, 0x65, 0x6e, 0x16, 0x45, 0x8a, 0x86, 0x9e, 0xba, 0x1f, 0x3d, 0xac, 0xba,
+	0xd7, 0xae, 0xe9, 0x2f, 0x74, 0xf9, 0x78, 0xfb, 0x67, 0xdc, 0xda, 0xee, 0xc6, 0xed, 0x9f, 0xf6,
+	0xfb, 0xbd, 0x1b, 0xb7, 0xee, 0xfe, 0x8e, 0xdb, 0x71, 0xd7, 0xe5, 0x7b, 0xfd, 0x2f, 0x00, 0x00,
+	0xff, 0xff, 0xb1, 0x2f, 0x12, 0xb6, 0xda, 0x02, 0x00, 0x00,
 }

+ 16 - 17
lib/discover/local.pb.go

@@ -382,21 +382,20 @@ var (
 )
 
 var fileDescriptorLocal = []byte{
-	// 241 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8e, 0x4f, 0x4e, 0x84, 0x30,
-	0x14, 0xc6, 0x29, 0x24, 0x66, 0xa6, 0x63, 0x5c, 0x10, 0x17, 0xc4, 0x98, 0x42, 0x5c, 0xb1, 0x11,
-	0x16, 0x7a, 0x01, 0x09, 0x9b, 0x6e, 0xb9, 0x80, 0x81, 0xb6, 0x32, 0x2f, 0xc1, 0x3e, 0x43, 0x61,
-	0x12, 0x6f, 0xe3, 0x05, 0xbc, 0x07, 0x4b, 0xd7, 0x2e, 0x1a, 0xad, 0x17, 0x31, 0xe9, 0x68, 0x86,
-	0xdd, 0xf7, 0xfd, 0xf2, 0x7b, 0x7f, 0xe8, 0x6e, 0x40, 0xd1, 0x0e, 0xc5, 0xcb, 0x88, 0x13, 0xc6,
-	0x1b, 0x09, 0x46, 0xe0, 0x41, 0x8d, 0x57, 0xb7, 0x3d, 0x4c, 0xfb, 0xb9, 0x2b, 0x04, 0x3e, 0x97,
-	0x3d, 0xf6, 0x58, 0x7a, 0xa1, 0x9b, 0x9f, 0x7c, 0xf3, 0xc5, 0xa7, 0xe3, 0xe0, 0xcd, 0x3b, 0xa1,
-	0x9b, 0x07, 0xad, 0x71, 0xd6, 0x42, 0xc5, 0x0d, 0x0d, 0x41, 0x26, 0x24, 0x23, 0xf9, 0x79, 0x55,
-	0x2d, 0x36, 0x0d, 0x3e, 0x6d, 0x7a, 0xbf, 0xda, 0x67, 0x5e, 0xb5, 0x98, 0xf6, 0xa0, 0xfb, 0x55,
-	0x1a, 0xa0, 0x3b, 0x9e, 0x10, 0x38, 0x14, 0xb5, 0x3a, 0x80, 0x50, 0xbc, 0x76, 0x36, 0x0d, 0x79,
-	0xdd, 0x84, 0x20, 0xe3, 0x6b, 0xba, 0x6d, 0xa5, 0x1c, 0x95, 0x31, 0xca, 0x24, 0x61, 0x16, 0xe5,
-	0xdb, 0xe6, 0x04, 0xe2, 0x92, 0xee, 0x40, 0x9b, 0xa9, 0xd5, 0x42, 0x3d, 0x82, 0x4c, 0xa2, 0x8c,
-	0xe4, 0x51, 0x75, 0xe1, 0x6c, 0x4a, 0xf9, 0x1f, 0xe6, 0x75, 0x43, 0xff, 0x15, 0x2e, 0xab, 0xcb,
-	0xe5, 0x9b, 0x05, 0x8b, 0x63, 0xe4, 0xc3, 0x31, 0xf2, 0xe5, 0x58, 0xf0, 0xf6, 0xc3, 0x48, 0x77,
-	0xe6, 0x3f, 0xb8, 0xfb, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x46, 0x4f, 0x13, 0x14, 0x01, 0x00,
-	0x00,
+	// 235 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0xc9, 0x4f, 0x4e,
+	0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x48, 0xc9, 0x2c, 0x4e, 0xce, 0x2f, 0x4b,
+	0x2d, 0x92, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf,
+	0x4f, 0xcf, 0xd7, 0x07, 0x2b, 0x48, 0x2a, 0x4d, 0x03, 0xf3, 0xc0, 0x1c, 0x30, 0x0b, 0xa2, 0x51,
+	0x69, 0x2d, 0x23, 0x17, 0x87, 0x63, 0x5e, 0x5e, 0x7e, 0x69, 0x5e, 0x72, 0xaa, 0x50, 0x10, 0x17,
+	0x53, 0x66, 0x8a, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x93, 0xd3, 0x89, 0x7b, 0xf2, 0x0c, 0xb7,
+	0xee, 0xc9, 0x9b, 0x20, 0x99, 0x57, 0x5c, 0x99, 0x97, 0x5c, 0x92, 0x91, 0x99, 0x97, 0x8e, 0xc4,
+	0xca, 0xc9, 0x4c, 0x82, 0x58, 0x91, 0x9c, 0x9f, 0xa3, 0xe7, 0x92, 0x5a, 0x96, 0x99, 0x9c, 0xea,
+	0xe9, 0xf2, 0xe8, 0x9e, 0x3c, 0x93, 0xa7, 0x4b, 0x10, 0xd0, 0x34, 0x21, 0x19, 0x2e, 0xce, 0xc4,
+	0x94, 0x94, 0xa2, 0xd4, 0xe2, 0xe2, 0xd4, 0x62, 0x09, 0x26, 0x05, 0x66, 0x0d, 0xce, 0x20, 0x84,
+	0x80, 0x90, 0x3e, 0x17, 0x77, 0x66, 0x5e, 0x71, 0x49, 0x22, 0xd0, 0xf6, 0x78, 0xa0, 0xd5, 0xcc,
+	0x40, 0xab, 0x99, 0x9d, 0xf8, 0x80, 0xda, 0xb9, 0x3c, 0xa1, 0xc2, 0x40, 0x63, 0xb8, 0x60, 0x4a,
+	0x3c, 0x53, 0x9c, 0x44, 0x4e, 0x3c, 0x94, 0x63, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0x02, 0x10, 0x3f,
+	0x78, 0x24, 0xc7, 0xb0, 0xe0, 0xb1, 0x1c, 0x63, 0x12, 0x1b, 0xd8, 0x05, 0xc6, 0x80, 0x00, 0x00,
+	0x00, 0xff, 0xff, 0xa4, 0x46, 0x4f, 0x13, 0x14, 0x01, 0x00, 0x00,
 }

+ 6 - 0
lib/events/events.go

@@ -43,6 +43,8 @@ const (
 	FolderCompletion
 	FolderErrors
 	FolderScanProgress
+	FolderPaused
+	FolderResumed
 	ListenAddressesChanged
 	LoginAttempt
 
@@ -101,6 +103,10 @@ func (t EventType) String() string {
 		return "DeviceResumed"
 	case FolderScanProgress:
 		return "FolderScanProgress"
+	case FolderPaused:
+		return "FolderPaused"
+	case FolderResumed:
+		return "FolderResumed"
 	case ListenAddressesChanged:
 		return "ListenAddressesChanged"
 	case LoginAttempt:

+ 130 - 79
lib/model/model.go

@@ -93,12 +93,12 @@ type Model struct {
 	folderStatRefs     map[string]*stats.FolderStatisticsReference            // folder -> statsRef
 	fmut               sync.RWMutex                                           // protects the above
 
-	conn            map[protocol.DeviceID]connections.Connection
-	closed          map[protocol.DeviceID]chan struct{}
-	helloMessages   map[protocol.DeviceID]protocol.HelloResult
-	devicePaused    map[protocol.DeviceID]bool
-	deviceDownloads map[protocol.DeviceID]*deviceDownloadState
-	pmut            sync.RWMutex // protects the above
+	conn                map[protocol.DeviceID]connections.Connection
+	closed              map[protocol.DeviceID]chan struct{}
+	helloMessages       map[protocol.DeviceID]protocol.HelloResult
+	deviceDownloads     map[protocol.DeviceID]*deviceDownloadState
+	remotePausedFolders map[protocol.DeviceID][]string // deviceID -> folders
+	pmut                sync.RWMutex                   // protects the above
 }
 
 type folderFactory func(*Model, config.FolderConfiguration, versioner.Versioner, *fs.MtimeFS) service
@@ -134,33 +134,33 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
 				l.Debugln(line)
 			},
 		}),
-		cfg:                cfg,
-		db:                 ldb,
-		finder:             db.NewBlockFinder(ldb),
-		progressEmitter:    NewProgressEmitter(cfg),
-		id:                 id,
-		shortID:            id.Short(),
-		cacheIgnoredFiles:  cfg.Options().CacheIgnoredFiles,
-		protectedFiles:     protectedFiles,
-		deviceName:         deviceName,
-		clientName:         clientName,
-		clientVersion:      clientVersion,
-		folderCfgs:         make(map[string]config.FolderConfiguration),
-		folderFiles:        make(map[string]*db.FileSet),
-		folderDevices:      make(folderDeviceSet),
-		deviceFolders:      make(map[protocol.DeviceID][]string),
-		deviceStatRefs:     make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
-		folderIgnores:      make(map[string]*ignore.Matcher),
-		folderRunners:      make(map[string]service),
-		folderRunnerTokens: make(map[string][]suture.ServiceToken),
-		folderStatRefs:     make(map[string]*stats.FolderStatisticsReference),
-		conn:               make(map[protocol.DeviceID]connections.Connection),
-		closed:             make(map[protocol.DeviceID]chan struct{}),
-		helloMessages:      make(map[protocol.DeviceID]protocol.HelloResult),
-		devicePaused:       make(map[protocol.DeviceID]bool),
-		deviceDownloads:    make(map[protocol.DeviceID]*deviceDownloadState),
-		fmut:               sync.NewRWMutex(),
-		pmut:               sync.NewRWMutex(),
+		cfg:                 cfg,
+		db:                  ldb,
+		finder:              db.NewBlockFinder(ldb),
+		progressEmitter:     NewProgressEmitter(cfg),
+		id:                  id,
+		shortID:             id.Short(),
+		cacheIgnoredFiles:   cfg.Options().CacheIgnoredFiles,
+		protectedFiles:      protectedFiles,
+		deviceName:          deviceName,
+		clientName:          clientName,
+		clientVersion:       clientVersion,
+		folderCfgs:          make(map[string]config.FolderConfiguration),
+		folderFiles:         make(map[string]*db.FileSet),
+		folderDevices:       make(folderDeviceSet),
+		deviceFolders:       make(map[protocol.DeviceID][]string),
+		deviceStatRefs:      make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
+		folderIgnores:       make(map[string]*ignore.Matcher),
+		folderRunners:       make(map[string]service),
+		folderRunnerTokens:  make(map[string][]suture.ServiceToken),
+		folderStatRefs:      make(map[string]*stats.FolderStatisticsReference),
+		conn:                make(map[protocol.DeviceID]connections.Connection),
+		closed:              make(map[protocol.DeviceID]chan struct{}),
+		helloMessages:       make(map[protocol.DeviceID]protocol.HelloResult),
+		deviceDownloads:     make(map[protocol.DeviceID]*deviceDownloadState),
+		remotePausedFolders: make(map[protocol.DeviceID][]string),
+		fmut:                sync.NewRWMutex(),
+		pmut:                sync.NewRWMutex(),
 	}
 	if cfg.Options().ProgressUpdateIntervalS > -1 {
 		go m.progressEmitter.Serve()
@@ -183,8 +183,10 @@ func (m *Model) StartDeadlockDetector(timeout time.Duration) {
 // StartFolder constructs the folder service and starts it.
 func (m *Model) StartFolder(folder string) {
 	m.fmut.Lock()
+	m.pmut.Lock()
 	folderType := m.startFolderLocked(folder)
 	folderCfg := m.folderCfgs[folder]
+	m.pmut.Unlock()
 	m.fmut.Unlock()
 
 	l.Infof("Ready to synchronize %s (%s)", folderCfg.Description(), folderType)
@@ -218,6 +220,11 @@ func (m *Model) startFolderLocked(folder string) config.FolderType {
 		}
 	}
 
+	// Close connections to affected devices
+	for _, id := range cfg.DeviceIDs() {
+		m.closeLocked(id)
+	}
+
 	v, ok := fs.Sequence(protocol.LocalDeviceID), true
 	indexHasFiles := ok && v > 0
 	if !indexHasFiles {
@@ -366,13 +373,16 @@ func (m *Model) RestartFolder(cfg config.FolderConfiguration) {
 	m.pmut.Lock()
 
 	m.tearDownFolderLocked(cfg.ID)
-	m.addFolderLocked(cfg)
-	folderType := m.startFolderLocked(cfg.ID)
+	if !cfg.Paused {
+		m.addFolderLocked(cfg)
+		folderType := m.startFolderLocked(cfg.ID)
+		l.Infoln("Restarted folder", cfg.Description(), fmt.Sprintf("(%s)", folderType))
+	} else {
+		l.Infoln("Paused folder", cfg.Description())
+	}
 
 	m.pmut.Unlock()
 	m.fmut.Unlock()
-
-	l.Infoln("Restarted folder", cfg.ID, fmt.Sprintf("(%s)", folderType))
 }
 
 type ConnectionInfo struct {
@@ -405,7 +415,7 @@ func (m *Model) ConnectionStats() map[string]interface{} {
 	res := make(map[string]interface{})
 	devs := m.cfg.Devices()
 	conns := make(map[string]ConnectionInfo, len(devs))
-	for device := range devs {
+	for device, deviceCfg := range devs {
 		hello := m.helloMessages[device]
 		versionString := hello.ClientVersion
 		if hello.ClientName != "syncthing" {
@@ -413,7 +423,7 @@ func (m *Model) ConnectionStats() map[string]interface{} {
 		}
 		ci := ConnectionInfo{
 			ClientVersion: strings.TrimSpace(versionString),
-			Paused:        m.devicePaused[device],
+			Paused:        deviceCfg.Paused,
 		}
 		if conn, ok := m.conn[device]; ok {
 			ci.Type = conn.Type()
@@ -783,7 +793,17 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
 	}
 
 	m.fmut.Lock()
+	var paused []string
 	for _, folder := range cm.Folders {
+		if folder.Paused {
+			paused = append(paused, folder.ID)
+			continue
+		}
+
+		if cfg, ok := m.cfg.Folder(folder.ID); ok && cfg.Paused {
+			continue
+		}
+
 		if !m.folderSharedWithLocked(folder.ID, deviceID) {
 			events.Default.Log(events.FolderRejected, map[string]string{
 				"folder":      folder.ID,
@@ -871,6 +891,10 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
 		go sendIndexes(conn, folder.ID, fs, m.folderIgnores[folder.ID], startSequence, dbLocation, dropSymlinks)
 	}
 
+	m.pmut.Lock()
+	m.remotePausedFolders[deviceID] = paused
+	m.pmut.Unlock()
+
 	// This breaks if we send multiple CM messages during the same connection.
 	if len(tempIndexFolders) > 0 {
 		m.pmut.RLock()
@@ -1058,6 +1082,7 @@ func (m *Model) Closed(conn protocol.Connection, err error) {
 	delete(m.conn, device)
 	delete(m.helloMessages, device)
 	delete(m.deviceDownloads, device)
+	delete(m.remotePausedFolders, device)
 	closed := m.closed[device]
 	delete(m.closed, device)
 	m.pmut.Unlock()
@@ -1070,6 +1095,24 @@ func (m *Model) Closed(conn protocol.Connection, err error) {
 	close(closed)
 }
 
+// close will close the underlying connection for a given device
+func (m *Model) close(device protocol.DeviceID) {
+	m.pmut.Lock()
+	m.closeLocked(device)
+	m.pmut.Unlock()
+}
+
+// closeLocked will close the underlying connection for a given device
+func (m *Model) closeLocked(device protocol.DeviceID) {
+	conn, ok := m.conn[device]
+	if !ok {
+		// There is no connection to close
+		return
+	}
+
+	closeRawConn(conn)
+}
+
 // Request returns the specified data segment by reading it from local disk.
 // Implements the protocol.Model interface.
 func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset int64, hash []byte, fromTemporary bool, buf []byte) error {
@@ -1257,16 +1300,15 @@ func (m *Model) SetIgnores(folder string, content []string) error {
 // This allows us to extract some information from the Hello message
 // and add it to a list of known devices ahead of any checks.
 func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloResult) error {
-	if m.IsPaused(remoteID) {
-		return errDevicePaused
-	}
-
 	if m.cfg.IgnoredDevice(remoteID) {
 		return errDeviceIgnored
 	}
 
-	if _, ok := m.cfg.Device(remoteID); ok {
+	if cfg, ok := m.cfg.Device(remoteID); ok {
 		// The device exists
+		if cfg.Paused {
+			return errDevicePaused
+		}
 		return nil
 	}
 
@@ -1349,17 +1391,6 @@ func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloR
 	m.deviceWasSeen(deviceID)
 }
 
-func (m *Model) PauseDevice(device protocol.DeviceID) {
-	m.pmut.Lock()
-	m.devicePaused[device] = true
-	conn, ok := m.conn[device]
-	m.pmut.Unlock()
-	if ok {
-		closeRawConn(conn)
-	}
-	events.Default.Log(events.DevicePaused, map[string]string{"device": device.String()})
-}
-
 func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
 	if !m.folderSharedWith(folder, device) {
 		return
@@ -1385,20 +1416,6 @@ func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, update
 	})
 }
 
-func (m *Model) ResumeDevice(device protocol.DeviceID) {
-	m.pmut.Lock()
-	m.devicePaused[device] = false
-	m.pmut.Unlock()
-	events.Default.Log(events.DeviceResumed, map[string]string{"device": device.String()})
-}
-
-func (m *Model) IsPaused(device protocol.DeviceID) bool {
-	m.pmut.Lock()
-	paused := m.devicePaused[device]
-	m.pmut.Unlock()
-	return paused
-}
-
 func (m *Model) deviceStatRef(deviceID protocol.DeviceID) *stats.DeviceStatisticsReference {
 	m.fmut.Lock()
 	defer m.fmut.Unlock()
@@ -1983,6 +2000,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
 			IgnorePermissions:  folderCfg.IgnorePerms,
 			IgnoreDelete:       folderCfg.IgnoreDelete,
 			DisableTempIndexes: folderCfg.DisableTempIndexes,
+			Paused:             folderCfg.Paused,
 		}
 
 		// Devices are sorted, so we always get the same order.
@@ -2192,7 +2210,13 @@ func (m *Model) Availability(folder, file string, version protocol.Vector, block
 	}
 
 	var availabilities []Availability
+next:
 	for _, device := range fs.Availability(file) {
+		for _, pausedFolder := range m.remotePausedFolders[device] {
+			if pausedFolder == folder {
+				continue next
+			}
+		}
 		_, ok := m.conn[device]
 		if ok {
 			availabilities = append(availabilities, Availability{ID: device, FromTemporary: false})
@@ -2350,16 +2374,6 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
 			l.Debugln(m, "adding folder", folderID)
 			m.AddFolder(cfg)
 			m.StartFolder(folderID)
-
-			// Drop connections to all devices that can now share the new
-			// folder.
-			m.pmut.Lock()
-			for _, dev := range cfg.DeviceIDs() {
-				if conn, ok := m.conn[dev]; ok {
-					closeRawConn(conn)
-				}
-			}
-			m.pmut.Unlock()
 		}
 	}
 
@@ -2381,6 +2395,15 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
 		if !reflect.DeepEqual(fromCfgCopy, toCfgCopy) {
 			m.RestartFolder(toCfg)
 		}
+
+		// Emit the folder pause/resume event
+		if fromCfg.Paused != toCfg.Paused {
+			eventType := events.FolderResumed
+			if toCfg.Paused {
+				eventType = events.FolderPaused
+			}
+			events.Default.Log(eventType, map[string]string{"id": toCfg.ID, "label": toCfg.Label})
+		}
 	}
 
 	// Removing a device. We actually don't need to do anything.
@@ -2390,6 +2413,24 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
 	// At some point model.Close() will get called for that device which will
 	// clean residue device state that is not part of any folder.
 
+	// Pausing a device, unpausing is handled by the connection service.
+	fromDevices := mapDeviceConfigs(from.Devices)
+	toDevices := mapDeviceConfigs(to.Devices)
+	for deviceID, toCfg := range toDevices {
+		fromCfg, ok := fromDevices[deviceID]
+		if !ok || fromCfg.Paused == toCfg.Paused {
+			continue
+		}
+
+		if toCfg.Paused {
+			l.Infoln("Pausing", deviceID)
+			m.close(deviceID)
+			events.Default.Log(events.DevicePaused, map[string]string{"device": deviceID.String()})
+		} else {
+			events.Default.Log(events.DeviceResumed, map[string]string{"device": deviceID.String()})
+		}
+	}
+
 	// Some options don't require restart as those components handle it fine
 	// by themselves.
 	from.Options.URAccepted = to.Options.URAccepted
@@ -2431,6 +2472,16 @@ func mapDevices(devices []protocol.DeviceID) map[protocol.DeviceID]struct{} {
 	return m
 }
 
+// mapDeviceConfigs returns a map of device ID to device configuration for the given
+// slice of folder configurations.
+func mapDeviceConfigs(devices []config.DeviceConfiguration) map[protocol.DeviceID]config.DeviceConfiguration {
+	m := make(map[protocol.DeviceID]config.DeviceConfiguration, len(devices))
+	for _, dev := range devices {
+		m[dev.DeviceID] = dev
+	}
+	return m
+}
+
 func symlinkInvalid(folder string, fi db.FileIntf) bool {
 	if !symlinks.Supported && fi.IsSymlink() && !fi.IsInvalid() && !fi.IsDeleted() {
 		symlinkWarning.Do(func() {

+ 91 - 0
lib/model/model_test.go

@@ -2198,6 +2198,97 @@ func TestIssue3829(t *testing.T) {
 	}
 }
 
+func TestNoRequestsFromPausedDevices(t *testing.T) {
+	dbi := db.OpenMemory()
+
+	fcfg := config.NewFolderConfiguration("default", "testdata")
+	fcfg.Devices = []config.FolderDeviceConfiguration{
+		{DeviceID: device1},
+		{DeviceID: device2},
+	}
+	cfg := config.Configuration{
+		Folders: []config.FolderConfiguration{fcfg},
+		Devices: []config.DeviceConfiguration{
+			config.NewDeviceConfiguration(device1, "device1"),
+			config.NewDeviceConfiguration(device2, "device2"),
+		},
+		Options: config.OptionsConfiguration{
+			// Don't remove temporaries directly on startup
+			KeepTemporariesH: 1,
+		},
+	}
+
+	wcfg := config.Wrap("/tmp/test", cfg)
+
+	m := NewModel(wcfg, protocol.LocalDeviceID, "device", "syncthing", "dev", dbi, nil)
+	m.AddFolder(fcfg)
+	m.StartFolder(fcfg.ID)
+	m.ServeBackground()
+
+	file := testDataExpected["foo"]
+	files := m.folderFiles["default"]
+	files.Update(device1, []protocol.FileInfo{file})
+	files.Update(device2, []protocol.FileInfo{file})
+
+	avail := m.Availability("default", file.Name, file.Version, file.Blocks[0])
+	if len(avail) != 0 {
+		t.Errorf("should not be available, no connections")
+	}
+
+	addFakeConn(m, device1)
+	addFakeConn(m, device2)
+
+	// !!! This is not what I'd expect to happen, as we don't even know if the peer has the original index !!!
+
+	avail = m.Availability("default", file.Name, file.Version, file.Blocks[0])
+	if len(avail) != 2 {
+		t.Errorf("should have two available")
+	}
+
+	cc := protocol.ClusterConfig{
+		Folders: []protocol.Folder{
+			{
+				ID: "default",
+				Devices: []protocol.Device{
+					{ID: device1},
+					{ID: device2},
+				},
+			},
+		},
+	}
+
+	m.ClusterConfig(device1, cc)
+	m.ClusterConfig(device2, cc)
+
+	avail = m.Availability("default", file.Name, file.Version, file.Blocks[0])
+	if len(avail) != 2 {
+		t.Errorf("should have two available")
+	}
+
+	m.Closed(&fakeConnection{id: device1}, errDeviceUnknown)
+	m.Closed(&fakeConnection{id: device2}, errDeviceUnknown)
+
+	avail = m.Availability("default", file.Name, file.Version, file.Blocks[0])
+	if len(avail) != 0 {
+		t.Errorf("should have no available")
+	}
+
+	// Test that remote paused folders are not used.
+
+	addFakeConn(m, device1)
+	addFakeConn(m, device2)
+
+	m.ClusterConfig(device1, cc)
+	ccp := cc
+	ccp.Folders[0].Paused = true
+	m.ClusterConfig(device1, ccp)
+
+	avail = m.Availability("default", file.Name, file.Version, file.Blocks[0])
+	if len(avail) != 1 {
+		t.Errorf("should have one available")
+	}
+}
+
 func TestRootedJoinedPath(t *testing.T) {
 	type testcase struct {
 		root   string

+ 140 - 108
lib/protocol/bep.pb.go

@@ -246,6 +246,7 @@ type Folder struct {
 	IgnorePermissions  bool     `protobuf:"varint,4,opt,name=ignore_permissions,json=ignorePermissions,proto3" json:"ignore_permissions,omitempty"`
 	IgnoreDelete       bool     `protobuf:"varint,5,opt,name=ignore_delete,json=ignoreDelete,proto3" json:"ignore_delete,omitempty"`
 	DisableTempIndexes bool     `protobuf:"varint,6,opt,name=disable_temp_indexes,json=disableTempIndexes,proto3" json:"disable_temp_indexes,omitempty"`
+	Paused             bool     `protobuf:"varint,7,opt,name=paused,proto3" json:"paused,omitempty"`
 	Devices            []Device `protobuf:"bytes,16,rep,name=devices" json:"devices"`
 }
 
@@ -593,6 +594,16 @@ func (m *Folder) MarshalTo(data []byte) (int, error) {
 		}
 		i++
 	}
+	if m.Paused {
+		data[i] = 0x38
+		i++
+		if m.Paused {
+			data[i] = 1
+		} else {
+			data[i] = 0
+		}
+		i++
+	}
 	if len(m.Devices) > 0 {
 		for _, msg := range m.Devices {
 			data[i] = 0x82
@@ -1306,6 +1317,9 @@ func (m *Folder) ProtoSize() (n int) {
 	if m.DisableTempIndexes {
 		n += 2
 	}
+	if m.Paused {
+		n += 2
+	}
 	if len(m.Devices) > 0 {
 		for _, e := range m.Devices {
 			l = e.ProtoSize()
@@ -2065,6 +2079,26 @@ func (m *Folder) Unmarshal(data []byte) error {
 				}
 			}
 			m.DisableTempIndexes = bool(v != 0)
+		case 7:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Paused", wireType)
+			}
+			var v int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowBep
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				v |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			m.Paused = bool(v != 0)
 		case 16:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType)
@@ -4140,112 +4174,110 @@ var (
 )
 
 var fileDescriptorBep = []byte{
-	// 1700 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x73, 0xdb, 0xc6,
-	0x15, 0x17, 0x48, 0xf0, 0xdf, 0x23, 0xa5, 0x40, 0x6b, 0x59, 0x41, 0x61, 0x85, 0x42, 0xe0, 0xb8,
-	0x55, 0x34, 0x8d, 0xe2, 0xc6, 0x69, 0x33, 0xd3, 0x69, 0x3b, 0x43, 0x91, 0x90, 0xcc, 0x09, 0x0d,
-	0xb2, 0x4b, 0xca, 0xae, 0x73, 0x28, 0x06, 0x24, 0x96, 0x14, 0x46, 0x20, 0x96, 0x05, 0x40, 0xd9,
-	0xea, 0x47, 0x60, 0xbf, 0x40, 0x2f, 0x9c, 0xc9, 0xb5, 0xf7, 0x7e, 0x08, 0xf7, 0x96, 0xc9, 0xb1,
-	0x07, 0x4f, 0xa3, 0x5e, 0x7a, 0xec, 0xa5, 0xf7, 0x0e, 0x76, 0x01, 0x10, 0x94, 0xe4, 0x8e, 0x0f,
-	0x3d, 0x71, 0xf7, 0xbd, 0xdf, 0xbe, 0xdd, 0xf7, 0x7b, 0xef, 0xf7, 0x08, 0xa8, 0x0c, 0xc9, 0xec,
-	0x68, 0xe6, 0xd3, 0x90, 0xa2, 0x32, 0xfb, 0x19, 0x51, 0x57, 0xf9, 0x6c, 0xe2, 0x84, 0xe7, 0xf3,
-	0xe1, 0xd1, 0x88, 0x4e, 0x3f, 0x9f, 0xd0, 0x09, 0xfd, 0x9c, 0x79, 0x86, 0xf3, 0x31, 0xdb, 0xb1,
-	0x0d, 0x5b, 0xf1, 0x83, 0xda, 0x0c, 0x0a, 0x4f, 0x89, 0xeb, 0x52, 0xb4, 0x0f, 0x55, 0x9b, 0x5c,
-	0x3a, 0x23, 0x62, 0x7a, 0xd6, 0x94, 0xc8, 0x82, 0x2a, 0x1c, 0x54, 0x30, 0x70, 0x93, 0x61, 0x4d,
-	0x49, 0x04, 0x18, 0xb9, 0x0e, 0xf1, 0x42, 0x0e, 0xc8, 0x71, 0x00, 0x37, 0x31, 0xc0, 0x23, 0xd8,
-	0x8a, 0x01, 0x97, 0xc4, 0x0f, 0x1c, 0xea, 0xc9, 0x79, 0x86, 0xd9, 0xe4, 0xd6, 0xe7, 0xdc, 0xa8,
-	0x05, 0x50, 0x7c, 0x4a, 0x2c, 0x9b, 0xf8, 0xe8, 0x53, 0x10, 0xc3, 0xab, 0x19, 0xbf, 0x6b, 0xeb,
-	0x8b, 0xfb, 0x47, 0x49, 0x0e, 0x47, 0xcf, 0x48, 0x10, 0x58, 0x13, 0x32, 0xb8, 0x9a, 0x11, 0xcc,
-	0x20, 0xe8, 0x37, 0x50, 0x1d, 0xd1, 0xe9, 0xcc, 0x27, 0x01, 0x0b, 0x9c, 0x63, 0x27, 0xf6, 0x6e,
-	0x9d, 0x68, 0xae, 0x30, 0x38, 0x7b, 0x40, 0x6b, 0xc0, 0x66, 0xd3, 0x9d, 0x07, 0x21, 0xf1, 0x9b,
-	0xd4, 0x1b, 0x3b, 0x13, 0xf4, 0x18, 0x4a, 0x63, 0xea, 0xda, 0xc4, 0x0f, 0x64, 0x41, 0xcd, 0x1f,
-	0x54, 0xbf, 0x90, 0x56, 0xc1, 0x4e, 0x98, 0xe3, 0x58, 0x7c, 0xf3, 0x76, 0x7f, 0x03, 0x27, 0x30,
-	0xed, 0x4f, 0x39, 0x28, 0x72, 0x0f, 0xda, 0x85, 0x9c, 0x63, 0x73, 0x8a, 0x8e, 0x8b, 0xd7, 0x6f,
-	0xf7, 0x73, 0xed, 0x16, 0xce, 0x39, 0x36, 0xda, 0x81, 0x82, 0x6b, 0x0d, 0x89, 0x1b, 0x93, 0xc3,
-	0x37, 0xe8, 0x01, 0x54, 0x7c, 0x62, 0xd9, 0x26, 0xf5, 0xdc, 0x2b, 0x46, 0x49, 0x19, 0x97, 0x23,
-	0x43, 0xd7, 0x73, 0xaf, 0xd0, 0x67, 0x80, 0x9c, 0x89, 0x47, 0x7d, 0x62, 0xce, 0x88, 0x3f, 0x75,
-	0xd8, 0x6b, 0x03, 0x59, 0x64, 0xa8, 0x6d, 0xee, 0xe9, 0xad, 0x1c, 0xe8, 0x21, 0x6c, 0xc6, 0x70,
-	0x9b, 0xb8, 0x24, 0x24, 0x72, 0x81, 0x21, 0x6b, 0xdc, 0xd8, 0x62, 0x36, 0xf4, 0x18, 0x76, 0x6c,
-	0x27, 0xb0, 0x86, 0x2e, 0x31, 0x43, 0x32, 0x9d, 0x99, 0x8e, 0x67, 0x93, 0xd7, 0x24, 0x90, 0x8b,
-	0x0c, 0x8b, 0x62, 0xdf, 0x80, 0x4c, 0x67, 0x6d, 0xee, 0x89, 0xd8, 0xe0, 0x95, 0x0e, 0x64, 0xe9,
-	0x26, 0x1b, 0x2d, 0xe6, 0x48, 0xd8, 0x88, 0x61, 0xda, 0xbf, 0x73, 0x50, 0xe4, 0x1e, 0xf4, 0xe3,
-	0x94, 0x8d, 0xda, 0xf1, 0x6e, 0x84, 0xfa, 0xfb, 0xdb, 0xfd, 0x32, 0xf7, 0xb5, 0x5b, 0x19, 0x76,
-	0x10, 0x88, 0x99, 0xce, 0x61, 0x6b, 0xb4, 0x07, 0x15, 0xcb, 0xb6, 0xa3, 0x2a, 0x91, 0x40, 0xce,
-	0xab, 0xf9, 0x83, 0x0a, 0x5e, 0x19, 0xd0, 0x57, 0xeb, 0x55, 0x17, 0x6f, 0xf6, 0xc9, 0xbb, 0xca,
-	0x1d, 0x51, 0x3e, 0x22, 0x7e, 0xdc, 0xa9, 0x05, 0x76, 0x5f, 0x39, 0x32, 0xb0, 0x3e, 0xfd, 0x18,
-	0x6a, 0x53, 0xeb, 0xb5, 0x19, 0x90, 0x3f, 0xcc, 0x89, 0x37, 0x22, 0x8c, 0x96, 0x3c, 0xae, 0x4e,
-	0xad, 0xd7, 0xfd, 0xd8, 0x84, 0xea, 0x00, 0x8e, 0x17, 0xfa, 0xd4, 0x9e, 0x8f, 0x88, 0x2f, 0x97,
-	0x18, 0x6f, 0x19, 0x0b, 0xfa, 0x39, 0x94, 0x19, 0xa9, 0xa6, 0x63, 0xcb, 0x65, 0x55, 0x38, 0x10,
-	0x8f, 0x95, 0x38, 0xf1, 0x12, 0xa3, 0x94, 0xe5, 0x9d, 0x2c, 0x71, 0x89, 0x61, 0xdb, 0x36, 0xfa,
-	0x15, 0x28, 0xc1, 0x85, 0x13, 0x15, 0x84, 0x47, 0x0a, 0x1d, 0xea, 0x99, 0x3e, 0x99, 0xd2, 0x4b,
-	0xcb, 0x0d, 0xe4, 0x0a, 0xbb, 0x46, 0x8e, 0x10, 0xed, 0x0c, 0x00, 0xc7, 0x7e, 0xad, 0x0b, 0x05,
-	0x16, 0x11, 0xed, 0x42, 0x91, 0x37, 0x65, 0xac, 0xd2, 0x78, 0x87, 0x8e, 0xa0, 0x30, 0x76, 0x5c,
-	0x12, 0xc8, 0x39, 0x56, 0x43, 0x94, 0xe9, 0x68, 0xc7, 0x25, 0x6d, 0x6f, 0x4c, 0xe3, 0x2a, 0x72,
-	0x98, 0x76, 0x06, 0x55, 0x16, 0xf0, 0x6c, 0x66, 0x5b, 0x21, 0xf9, 0xbf, 0x85, 0xfd, 0x5b, 0x1e,
-	0xca, 0x89, 0x27, 0x2d, 0xba, 0x90, 0x29, 0xfa, 0x61, 0xac, 0x7b, 0xae, 0xe2, 0xdd, 0xdb, 0xf1,
-	0x32, 0xc2, 0x47, 0x20, 0x06, 0xce, 0x1f, 0x09, 0xd3, 0x4d, 0x1e, 0xb3, 0x35, 0x52, 0xa1, 0x7a,
-	0x53, 0x2c, 0x9b, 0x38, 0x6b, 0x42, 0x1f, 0x01, 0x4c, 0xa9, 0xed, 0x8c, 0x1d, 0x62, 0x9b, 0x01,
-	0x6b, 0x80, 0x3c, 0xae, 0x24, 0x96, 0x3e, 0x92, 0xa3, 0x76, 0x8f, 0xa4, 0x62, 0xc7, 0x9a, 0x48,
-	0xb6, 0x91, 0xc7, 0xf1, 0x2e, 0x2d, 0xd7, 0xb1, 0xe3, 0xaa, 0x27, 0xdb, 0x68, 0xba, 0x79, 0x74,
-	0x4d, 0xa4, 0x65, 0x06, 0xd8, 0xf4, 0x68, 0x56, 0xa0, 0x8f, 0xa1, 0x94, 0x4c, 0xbf, 0xa8, 0x9e,
-	0x6b, 0x4a, 0x7a, 0x4e, 0x46, 0x21, 0x4d, 0xe7, 0x4a, 0x0c, 0x43, 0x0a, 0x94, 0xd3, 0x56, 0x04,
-	0xf6, 0xd2, 0x74, 0x1f, 0xcd, 0xdc, 0x34, 0x0f, 0x2f, 0x90, 0xab, 0xaa, 0x70, 0x50, 0xc0, 0x69,
-	0x6a, 0x46, 0x80, 0x7e, 0x06, 0xc5, 0x63, 0x97, 0x8e, 0x2e, 0x12, 0xdd, 0xde, 0x5b, 0xdd, 0xc6,
-	0xec, 0x99, 0xea, 0x14, 0x87, 0x0c, 0x18, 0x25, 0x12, 0x5c, 0x4d, 0x5d, 0xc7, 0xbb, 0x30, 0x43,
-	0xcb, 0x9f, 0x90, 0x50, 0xde, 0xe6, 0x63, 0x3a, 0xb6, 0x0e, 0x98, 0xf1, 0x97, 0xe2, 0x9f, 0xbf,
-	0xdd, 0xdf, 0xd0, 0x3c, 0xa8, 0xa4, 0x71, 0xa2, 0x06, 0xa1, 0xe3, 0x71, 0x40, 0x42, 0x56, 0xcd,
-	0x3c, 0x8e, 0x77, 0x69, 0x8d, 0x72, 0xec, 0x79, 0xbc, 0x46, 0x08, 0xc4, 0x73, 0x2b, 0x38, 0x67,
-	0x75, 0xab, 0x61, 0xb6, 0x8e, 0x54, 0xf9, 0x8a, 0x58, 0x17, 0x26, 0x73, 0xf0, 0xaa, 0x95, 0x23,
-	0xc3, 0x53, 0x2b, 0x38, 0x8f, 0xef, 0xfb, 0x35, 0x14, 0x39, 0x4b, 0xe8, 0x09, 0x94, 0x47, 0x74,
-	0xee, 0x85, 0xab, 0x09, 0xbd, 0x9d, 0x15, 0x3e, 0xf3, 0xc4, 0x99, 0xa5, 0x40, 0xed, 0x04, 0x4a,
-	0xb1, 0x0b, 0x3d, 0x4a, 0xa7, 0x92, 0x78, 0x7c, 0x3f, 0x11, 0x67, 0xff, 0x9c, 0xfa, 0xe1, 0xda,
-	0x50, 0xda, 0x81, 0xc2, 0xa5, 0xe5, 0xce, 0xf9, 0xe3, 0x45, 0xcc, 0x37, 0xda, 0x5f, 0x05, 0x28,
-	0xe1, 0xa8, 0x08, 0x41, 0x98, 0x19, 0xf6, 0x85, 0xb5, 0x61, 0xbf, 0x92, 0x4b, 0x6e, 0x4d, 0x2e,
-	0x49, 0xc7, 0xe7, 0x33, 0x1d, 0xbf, 0x62, 0x4e, 0xbc, 0x93, 0xb9, 0xc2, 0x1d, 0xcc, 0x15, 0x33,
-	0xcc, 0x3d, 0x82, 0xad, 0xb1, 0x4f, 0xa7, 0x6c, 0x9c, 0x53, 0xdf, 0xf2, 0xaf, 0xe2, 0xee, 0xdc,
-	0x8c, 0xac, 0x83, 0xc4, 0xa8, 0x99, 0x50, 0xc6, 0x24, 0x98, 0x51, 0x2f, 0x20, 0xef, 0x7c, 0x36,
-	0x02, 0xd1, 0xb6, 0x42, 0x8b, 0x3d, 0xba, 0x86, 0xd9, 0x1a, 0xfd, 0x04, 0xc4, 0x11, 0xb5, 0xf9,
-	0x93, 0xb7, 0xb2, 0x3d, 0xa4, 0xfb, 0x3e, 0xf5, 0x9b, 0xd4, 0x26, 0x98, 0x01, 0xb4, 0x19, 0x48,
-	0x2d, 0xfa, 0xca, 0x73, 0xa9, 0x65, 0xf7, 0x7c, 0x3a, 0x89, 0xc6, 0xed, 0x3b, 0xc7, 0x46, 0x0b,
-	0x4a, 0x73, 0x36, 0x58, 0x92, 0xc1, 0xf1, 0xc9, 0xba, 0xd0, 0x6f, 0x06, 0xe2, 0x53, 0x28, 0x51,
-	0x47, 0x7c, 0x54, 0xfb, 0x5e, 0x00, 0xe5, 0xdd, 0x68, 0xd4, 0x86, 0x2a, 0x47, 0x9a, 0x99, 0x2f,
-	0x89, 0x83, 0xf7, 0xb9, 0x88, 0xcd, 0x18, 0x98, 0xa7, 0xeb, 0x3b, 0xff, 0x9e, 0x32, 0x6a, 0xce,
-	0xbf, 0x9f, 0x9a, 0x1f, 0xc2, 0x26, 0xd3, 0x59, 0xfa, 0xa7, 0x2b, 0xaa, 0xf9, 0x83, 0x02, 0xae,
-	0x0d, 0xb9, 0x8a, 0x98, 0x4d, 0x2b, 0x82, 0xd8, 0x73, 0xbc, 0x89, 0xb6, 0x0f, 0x85, 0xa6, 0x4b,
-	0x59, 0xb1, 0x8a, 0x3e, 0xb1, 0x02, 0xea, 0x25, 0x1c, 0xf2, 0xdd, 0xe1, 0xf7, 0x39, 0xa8, 0x66,
-	0x3e, 0x86, 0xd0, 0x63, 0xd8, 0x6a, 0x76, 0xce, 0xfa, 0x03, 0x1d, 0x9b, 0xcd, 0xae, 0x71, 0xd2,
-	0x3e, 0x95, 0x36, 0x94, 0xbd, 0xc5, 0x52, 0x95, 0xa7, 0x2b, 0xd0, 0xfa, 0x77, 0xce, 0x3e, 0x14,
-	0xda, 0x46, 0x4b, 0xff, 0x9d, 0x24, 0x28, 0x3b, 0x8b, 0xa5, 0x2a, 0x65, 0x80, 0xfc, 0xcf, 0xe4,
-	0xa7, 0x50, 0x63, 0x00, 0xf3, 0xac, 0xd7, 0x6a, 0x0c, 0x74, 0x29, 0xa7, 0x28, 0x8b, 0xa5, 0xba,
-	0x7b, 0x13, 0x17, 0xf3, 0xfd, 0x10, 0x4a, 0x58, 0xff, 0xed, 0x99, 0xde, 0x1f, 0x48, 0x79, 0x65,
-	0x77, 0xb1, 0x54, 0x51, 0x06, 0x98, 0x28, 0xe6, 0x11, 0x94, 0xb1, 0xde, 0xef, 0x75, 0x8d, 0xbe,
-	0x2e, 0x89, 0xca, 0x87, 0x8b, 0xa5, 0x7a, 0x6f, 0x0d, 0x15, 0x77, 0xe8, 0x2f, 0x60, 0xbb, 0xd5,
-	0x7d, 0x61, 0x74, 0xba, 0x8d, 0x96, 0xd9, 0xc3, 0xdd, 0x53, 0xac, 0xf7, 0xfb, 0x52, 0x41, 0xd9,
-	0x5f, 0x2c, 0xd5, 0x07, 0x19, 0xfc, 0xad, 0x86, 0xfb, 0x08, 0xc4, 0x5e, 0xdb, 0x38, 0x95, 0x8a,
-	0xca, 0xbd, 0xc5, 0x52, 0xfd, 0x20, 0x03, 0x8d, 0x48, 0x8d, 0x32, 0x6e, 0x76, 0xba, 0x7d, 0x5d,
-	0x2a, 0xdd, 0xca, 0x98, 0x91, 0x7d, 0xf8, 0x7b, 0x40, 0xb7, 0x3f, 0x17, 0xd1, 0x27, 0x20, 0x1a,
-	0x5d, 0x43, 0x97, 0x36, 0x78, 0xfe, 0xb7, 0x11, 0x06, 0xf5, 0x08, 0xd2, 0x20, 0xdf, 0xf9, 0xe6,
-	0x4b, 0x49, 0x50, 0x7e, 0xb4, 0x58, 0xaa, 0xf7, 0x6f, 0x83, 0x3a, 0xdf, 0x7c, 0x79, 0x48, 0xa1,
-	0x9a, 0x0d, 0xac, 0x41, 0xf9, 0x99, 0x3e, 0x68, 0xb4, 0x1a, 0x83, 0x86, 0xb4, 0xc1, 0x9f, 0x94,
-	0xb8, 0x9f, 0x91, 0xd0, 0x62, 0x02, 0xdc, 0x83, 0x82, 0xa1, 0x3f, 0xd7, 0xb1, 0x24, 0x28, 0xdb,
-	0x8b, 0xa5, 0xba, 0x99, 0x00, 0x0c, 0x72, 0x49, 0x7c, 0x54, 0x87, 0x62, 0xa3, 0xf3, 0xa2, 0xf1,
-	0xb2, 0x2f, 0xe5, 0x14, 0xb4, 0x58, 0xaa, 0x5b, 0x89, 0xbb, 0xe1, 0xbe, 0xb2, 0xae, 0x82, 0xc3,
-	0xff, 0x08, 0x50, 0xcb, 0xfe, 0x75, 0xa2, 0x3a, 0x88, 0x27, 0xed, 0x8e, 0x9e, 0x5c, 0x97, 0xf5,
-	0x45, 0x6b, 0x74, 0x00, 0x95, 0x56, 0x1b, 0xeb, 0xcd, 0x41, 0x17, 0xbf, 0x4c, 0x72, 0xc9, 0x82,
-	0x5a, 0x8e, 0xcf, 0x9a, 0x3b, 0xfa, 0x3c, 0xad, 0xf5, 0x5f, 0x3e, 0xeb, 0xb4, 0x8d, 0xaf, 0x4d,
-	0x16, 0x31, 0xa7, 0x3c, 0x58, 0x2c, 0xd5, 0x0f, 0xb3, 0xe0, 0x3e, 0xff, 0xdb, 0x60, 0x81, 0xbf,
-	0x82, 0xed, 0x04, 0xbe, 0xba, 0x20, 0xaf, 0xa8, 0x8b, 0xa5, 0xba, 0x77, 0xc7, 0x99, 0xd5, 0x3d,
-	0x4f, 0xe0, 0x83, 0xe4, 0xe0, 0x99, 0xf1, 0xb5, 0xd1, 0x7d, 0x61, 0x48, 0xa2, 0x52, 0x5f, 0x2c,
-	0x55, 0xe5, 0x8e, 0x63, 0x67, 0xde, 0x85, 0x47, 0x5f, 0x79, 0x87, 0x7f, 0x11, 0xa0, 0x92, 0x4e,
-	0xa8, 0x88, 0x67, 0xa3, 0x6b, 0xea, 0x18, 0x77, 0x71, 0x92, 0x78, 0xea, 0x34, 0x28, 0x5b, 0xa2,
-	0x8f, 0xa1, 0x74, 0xaa, 0x1b, 0x3a, 0x6e, 0x37, 0x13, 0x3d, 0xa4, 0x90, 0x53, 0xe2, 0x11, 0xdf,
-	0x19, 0xa1, 0x4f, 0xa1, 0x66, 0x74, 0xcd, 0xfe, 0x59, 0xf3, 0x69, 0x92, 0x31, 0x6b, 0xe0, 0x4c,
-	0xa8, 0xfe, 0x7c, 0x74, 0xce, 0xb2, 0x3d, 0x8c, 0xa4, 0xf3, 0xbc, 0xd1, 0x69, 0xb7, 0x38, 0x34,
-	0xaf, 0xc8, 0x8b, 0xa5, 0xba, 0x93, 0x42, 0xdb, 0xfc, 0xd3, 0x21, 0xc2, 0x1e, 0xda, 0x50, 0xff,
-	0xdf, 0xb3, 0x08, 0xa9, 0x50, 0x6c, 0xf4, 0x7a, 0xba, 0xd1, 0x4a, 0x5e, 0xbf, 0xf2, 0x35, 0x66,
-	0x33, 0xe2, 0xd9, 0x11, 0xe2, 0xa4, 0x8b, 0x4f, 0xf5, 0x41, 0xf2, 0xf8, 0x15, 0xe2, 0x84, 0x46,
-	0x7f, 0xda, 0xc7, 0x7b, 0x6f, 0x7e, 0xa8, 0x6f, 0x7c, 0xf7, 0x43, 0x7d, 0xe3, 0xcd, 0x75, 0x5d,
-	0xf8, 0xee, 0xba, 0x2e, 0xfc, 0xe3, 0xba, 0xbe, 0xf1, 0xaf, 0xeb, 0xba, 0xf0, 0xed, 0x3f, 0xeb,
-	0xc2, 0xb0, 0xc8, 0x66, 0xd7, 0x93, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xee, 0xe2, 0x0f, 0x00,
-	0x38, 0x0e, 0x00, 0x00,
+	// 1670 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x57, 0x4f, 0x73, 0xdb, 0x4c,
+	0x19, 0x8f, 0x6d, 0xf9, 0xdf, 0xda, 0xc9, 0xeb, 0x6c, 0xd3, 0xbc, 0x46, 0xcd, 0x9b, 0x04, 0xbd,
+	0x6f, 0x21, 0x78, 0x68, 0x0a, 0x2d, 0xd0, 0x19, 0x06, 0x98, 0x71, 0x6c, 0x25, 0xd1, 0xd4, 0x91,
+	0x8d, 0x6c, 0xa7, 0x94, 0x03, 0x1a, 0xd9, 0x5a, 0x3b, 0x9a, 0xc8, 0x5a, 0x23, 0xc9, 0x6d, 0xc3,
+	0x47, 0xe0, 0x13, 0x70, 0x61, 0xa6, 0x33, 0x9c, 0xb8, 0xf3, 0x21, 0xca, 0xad, 0xd3, 0x23, 0x87,
+	0x0e, 0x94, 0x0b, 0x47, 0x2e, 0xdc, 0x79, 0x76, 0x57, 0x92, 0xe5, 0xfc, 0x61, 0x7a, 0xe0, 0x90,
+	0xb1, 0xf6, 0x79, 0x7e, 0xfb, 0x3c, 0xfb, 0xfc, 0xf9, 0x3d, 0xbb, 0x41, 0xe5, 0x11, 0x99, 0x1f,
+	0xce, 0x7d, 0x1a, 0x52, 0x5c, 0xe2, 0x3f, 0x63, 0xea, 0xca, 0x8f, 0xa6, 0x4e, 0x78, 0xb1, 0x18,
+	0x1d, 0x8e, 0xe9, 0xec, 0xf1, 0x94, 0x4e, 0xe9, 0x63, 0xae, 0x19, 0x2d, 0x26, 0x7c, 0xc5, 0x17,
+	0xfc, 0x4b, 0x6c, 0x54, 0xe6, 0x28, 0x7f, 0x4a, 0x5c, 0x97, 0xe2, 0x3d, 0x54, 0xb1, 0xc9, 0x2b,
+	0x67, 0x4c, 0x4c, 0xcf, 0x9a, 0x91, 0x7a, 0x66, 0x3f, 0x73, 0x50, 0x36, 0x90, 0x10, 0xe9, 0x20,
+	0x61, 0x80, 0xb1, 0xeb, 0x10, 0x2f, 0x14, 0x80, 0xac, 0x00, 0x08, 0x11, 0x07, 0x3c, 0x44, 0x1b,
+	0x11, 0xe0, 0x15, 0xf1, 0x03, 0x87, 0x7a, 0xf5, 0x1c, 0xc7, 0xac, 0x0b, 0xe9, 0xb9, 0x10, 0x2a,
+	0x01, 0x2a, 0x9c, 0x12, 0xcb, 0x26, 0x3e, 0xfe, 0x1e, 0x92, 0xc2, 0xab, 0xb9, 0xf0, 0xb5, 0xf1,
+	0xe4, 0xfe, 0x61, 0x1c, 0xc3, 0xe1, 0x19, 0x09, 0x02, 0x6b, 0x4a, 0x06, 0xa0, 0x34, 0x38, 0x04,
+	0xff, 0x02, 0x9c, 0xd3, 0xd9, 0xdc, 0x07, 0x05, 0x33, 0x9c, 0xe5, 0x3b, 0x76, 0x6e, 0xec, 0x68,
+	0x2d, 0x31, 0x46, 0x7a, 0x83, 0xd2, 0x44, 0xeb, 0x2d, 0x77, 0x11, 0x84, 0xc4, 0x6f, 0x51, 0x6f,
+	0xe2, 0x4c, 0xf1, 0x0f, 0x50, 0x71, 0x42, 0x5d, 0x38, 0x45, 0x00, 0xee, 0x73, 0x07, 0x95, 0x27,
+	0xb5, 0xa5, 0xb1, 0x63, 0xae, 0x38, 0x92, 0xde, 0x7d, 0xdc, 0x5b, 0x33, 0x62, 0x98, 0xf2, 0xa7,
+	0x2c, 0x2a, 0x08, 0x0d, 0xde, 0x46, 0x59, 0xc7, 0x16, 0x29, 0x3a, 0x2a, 0x7c, 0xfa, 0xb8, 0x97,
+	0xd5, 0xda, 0x06, 0x48, 0xf0, 0x16, 0xca, 0xbb, 0xd6, 0x88, 0xb8, 0x51, 0x72, 0xc4, 0x02, 0x3f,
+	0x40, 0x65, 0x1f, 0x02, 0x36, 0xa9, 0xe7, 0x5e, 0xf1, 0x94, 0x94, 0x8c, 0x12, 0x13, 0x74, 0x61,
+	0x8d, 0x1f, 0x21, 0xec, 0x4c, 0x3d, 0xea, 0x13, 0x73, 0x4e, 0xfc, 0x99, 0xc3, 0x4f, 0x1b, 0xd4,
+	0x25, 0x8e, 0xda, 0x14, 0x9a, 0xde, 0x52, 0x81, 0xbf, 0x46, 0xeb, 0x11, 0xdc, 0x26, 0x2e, 0x09,
+	0x49, 0x3d, 0xcf, 0x91, 0x55, 0x21, 0x6c, 0x73, 0x19, 0xc4, 0xb6, 0x65, 0x3b, 0x81, 0x35, 0x72,
+	0x89, 0x19, 0x92, 0xd9, 0xdc, 0x74, 0x3c, 0x9b, 0xbc, 0x21, 0x41, 0xbd, 0xc0, 0xb1, 0x38, 0xd2,
+	0x0d, 0x40, 0xa5, 0x09, 0x0d, 0x04, 0x54, 0x98, 0x5b, 0x8b, 0x80, 0xd8, 0xf5, 0x22, 0xc7, 0x44,
+	0x2b, 0x96, 0x25, 0xd1, 0x01, 0x41, 0xbd, 0x76, 0x3d, 0x4b, 0x6d, 0xae, 0x88, 0xb3, 0x14, 0xc1,
+	0x94, 0x7f, 0x43, 0x96, 0x84, 0x06, 0x7f, 0x27, 0xc9, 0x52, 0xf5, 0x68, 0x9b, 0xa1, 0xfe, 0xf6,
+	0x71, 0xaf, 0x24, 0x74, 0x5a, 0x3b, 0x95, 0x35, 0x8c, 0xa4, 0x54, 0x47, 0xf1, 0x6f, 0xbc, 0x83,
+	0xca, 0x96, 0x6d, 0xb3, 0xea, 0x81, 0xeb, 0x1c, 0xb8, 0x2e, 0x1b, 0x4b, 0x01, 0x7e, 0xb6, 0xda,
+	0x0d, 0xd2, 0xf5, 0xfe, 0xb9, 0xab, 0x0d, 0x58, 0x29, 0xc6, 0xc4, 0x8f, 0x3a, 0x38, 0xcf, 0xfd,
+	0x95, 0x98, 0x80, 0xf7, 0xef, 0xb7, 0x51, 0x75, 0x66, 0xbd, 0x31, 0x03, 0xf2, 0xdb, 0x05, 0xf1,
+	0xc6, 0x84, 0xa7, 0x2b, 0x67, 0x54, 0x40, 0xd6, 0x8f, 0x44, 0x78, 0x17, 0x21, 0xc7, 0x0b, 0x7d,
+	0x6a, 0x2f, 0x60, 0x57, 0x94, 0xab, 0x94, 0x04, 0xff, 0x18, 0x95, 0x78, 0xb2, 0x4d, 0x08, 0xbc,
+	0x04, 0x5a, 0xe9, 0x48, 0x8e, 0x02, 0x2f, 0xf2, 0x54, 0xf3, 0xb8, 0xe3, 0x4f, 0xa3, 0xc8, 0xb1,
+	0x9a, 0x8d, 0x7f, 0x86, 0xe4, 0xe0, 0xd2, 0x61, 0x85, 0x12, 0x96, 0x42, 0x38, 0xab, 0xe9, 0x93,
+	0x19, 0x7d, 0x65, 0xb9, 0x41, 0xbd, 0xcc, 0xdd, 0xd4, 0x19, 0x42, 0x4b, 0x01, 0x8c, 0x48, 0xaf,
+	0x74, 0x51, 0x9e, 0x5b, 0x64, 0x55, 0x14, 0xcd, 0x1a, 0xb1, 0x37, 0x5a, 0xe1, 0x43, 0x94, 0x9f,
+	0x38, 0x2e, 0x24, 0x32, 0xcb, 0x6b, 0x88, 0x53, 0x9d, 0x0e, 0x62, 0xcd, 0x9b, 0xd0, 0xa8, 0x8a,
+	0x02, 0xa6, 0x0c, 0x51, 0x85, 0x1b, 0x1c, 0xce, 0x6d, 0x0b, 0xda, 0xe9, 0xff, 0x65, 0xf6, 0xaf,
+	0x39, 0x54, 0x8a, 0x35, 0x49, 0xd1, 0x33, 0xa9, 0xa2, 0x37, 0xa2, 0x79, 0x20, 0xd8, 0xbd, 0x7d,
+	0xd3, 0x5e, 0x6a, 0x20, 0xc0, 0xfe, 0xc0, 0xf9, 0x1d, 0xe1, 0x7c, 0xca, 0x19, 0xfc, 0x1b, 0xef,
+	0xa3, 0xca, 0x75, 0x12, 0xad, 0x1b, 0x69, 0x11, 0xfe, 0x0a, 0xa1, 0x19, 0xb5, 0x9d, 0x89, 0x43,
+	0x6c, 0x33, 0xe0, 0x0d, 0x90, 0x33, 0xca, 0xb1, 0xa4, 0x8f, 0xeb, 0xac, 0xdd, 0x19, 0x85, 0xec,
+	0x88, 0x2b, 0xf1, 0x92, 0x69, 0x1c, 0x0f, 0xb2, 0xed, 0xc4, 0x0c, 0x89, 0x97, 0x6c, 0xea, 0x79,
+	0x74, 0x85, 0xbc, 0x25, 0x0e, 0x58, 0xf7, 0x68, 0x9a, 0xb8, 0xc0, 0xa4, 0x78, 0x2a, 0xb2, 0x7a,
+	0xae, 0x30, 0xe9, 0x9c, 0x8c, 0x43, 0x9a, 0xcc, 0x9b, 0x08, 0x86, 0x65, 0x54, 0x4a, 0x5a, 0x11,
+	0xf1, 0x93, 0x26, 0x6b, 0x36, 0x8b, 0x93, 0x38, 0xc0, 0x63, 0x05, 0xd4, 0x79, 0x23, 0x09, 0x4d,
+	0x0f, 0xf0, 0x0f, 0x51, 0xe1, 0xc8, 0xa5, 0xe3, 0xcb, 0x98, 0xb7, 0xf7, 0x96, 0xde, 0xb8, 0x3c,
+	0x55, 0x9d, 0xc2, 0x88, 0x03, 0x59, 0x20, 0xc1, 0xd5, 0xcc, 0x75, 0xbc, 0x4b, 0x33, 0xb4, 0xfc,
+	0x29, 0x09, 0xeb, 0x9b, 0x62, 0x7c, 0x47, 0xd2, 0x01, 0x17, 0xfe, 0x54, 0xfa, 0xc3, 0xdb, 0xbd,
+	0x35, 0xc5, 0x43, 0xe5, 0xc4, 0x0e, 0x6b, 0x10, 0x3a, 0x99, 0x04, 0xb0, 0x23, 0xc3, 0xcf, 0x19,
+	0xad, 0x92, 0x1a, 0x65, 0xf9, 0xf1, 0x44, 0x8d, 0x40, 0x76, 0x61, 0x05, 0x17, 0xbc, 0x6e, 0x55,
+	0x83, 0x7f, 0x33, 0x56, 0xbe, 0x26, 0xd6, 0xa5, 0xc9, 0x15, 0xa2, 0x6a, 0x25, 0x26, 0x38, 0x85,
+	0x75, 0xe4, 0xef, 0xe7, 0xa8, 0x20, 0xb2, 0x84, 0x9f, 0xa2, 0xd2, 0x98, 0x2e, 0xbc, 0x70, 0x39,
+	0xb9, 0x37, 0xd3, 0xc4, 0xe7, 0x9a, 0x28, 0xb2, 0x04, 0xa8, 0x1c, 0xa3, 0x62, 0xa4, 0x82, 0x30,
+	0xe3, 0xa9, 0x24, 0x1d, 0xdd, 0x8f, 0xc9, 0xd9, 0xbf, 0xa0, 0x7e, 0xb8, 0x32, 0x94, 0x60, 0x94,
+	0x43, 0x7d, 0x17, 0xe2, 0xf0, 0x92, 0x21, 0x16, 0xca, 0x5f, 0x32, 0xa8, 0x68, 0xb0, 0x22, 0x04,
+	0x61, 0xea, 0x12, 0xc8, 0xaf, 0x5c, 0x02, 0x4b, 0xba, 0x64, 0x57, 0xe8, 0x12, 0x77, 0x7c, 0x2e,
+	0xd5, 0xf1, 0xcb, 0xcc, 0x49, 0xb7, 0x66, 0x2e, 0x7f, 0x4b, 0xe6, 0x0a, 0xa9, 0xcc, 0x41, 0xcd,
+	0x26, 0x3e, 0x9d, 0xf1, 0x31, 0x4f, 0x7d, 0xcb, 0xbf, 0x8a, 0xba, 0x73, 0x9d, 0x49, 0x07, 0xb1,
+	0x50, 0x31, 0x51, 0xc9, 0x20, 0xc1, 0x1c, 0xfa, 0x90, 0xdc, 0x79, 0x6c, 0x30, 0x0f, 0x6c, 0xb7,
+	0xf8, 0xa1, 0xc1, 0x3c, 0xfb, 0xc6, 0xdf, 0x45, 0xd2, 0x98, 0xda, 0xe2, 0xc8, 0x1b, 0xe9, 0x1e,
+	0x52, 0x7d, 0x9f, 0xc2, 0x4d, 0x6a, 0x03, 0x1b, 0x19, 0x00, 0x5e, 0x11, 0xb5, 0x36, 0x7d, 0xed,
+	0xb9, 0xd4, 0xb2, 0x7b, 0x3e, 0x9d, 0xb2, 0x71, 0x7b, 0xe7, 0xd8, 0x68, 0xa3, 0xe2, 0x82, 0x0f,
+	0x96, 0x78, 0x70, 0x7c, 0xb3, 0x4a, 0xf4, 0xeb, 0x86, 0xc4, 0x14, 0x8a, 0xd9, 0x11, 0x6d, 0x55,
+	0x3e, 0x64, 0x90, 0x7c, 0x37, 0x1a, 0x6b, 0xa8, 0x22, 0x90, 0x66, 0xea, 0x85, 0x71, 0xf0, 0x39,
+	0x8e, 0xf8, 0x8c, 0x41, 0x8b, 0xe4, 0xfb, 0xd6, 0xeb, 0x29, 0xc5, 0xe6, 0xdc, 0xe7, 0xb1, 0x19,
+	0x2e, 0x6e, 0xce, 0xb3, 0xe4, 0x32, 0x96, 0x20, 0xf6, 0xbc, 0x51, 0x1d, 0x09, 0x16, 0x71, 0x99,
+	0x52, 0x40, 0x52, 0xcf, 0xf1, 0xa6, 0xca, 0x1e, 0xca, 0xb7, 0x5c, 0xca, 0x8b, 0x55, 0x80, 0x97,
+	0x42, 0x00, 0x6e, 0xa2, 0x1c, 0x8a, 0x55, 0xe3, 0x43, 0x16, 0x55, 0x52, 0x8f, 0x24, 0x38, 0xcf,
+	0x46, 0xab, 0x33, 0xec, 0x0f, 0x54, 0xc3, 0x6c, 0x75, 0xf5, 0x63, 0xed, 0xa4, 0xb6, 0x26, 0xef,
+	0xfc, 0xfe, 0x8f, 0xfb, 0xf5, 0xd9, 0x12, 0xb4, 0xfa, 0xfe, 0x01, 0x17, 0x9a, 0xde, 0x56, 0x7f,
+	0x55, 0xcb, 0xc8, 0x5b, 0x00, 0xac, 0xa5, 0x80, 0xe2, 0x32, 0xf9, 0x3e, 0xaa, 0x72, 0x80, 0x39,
+	0xec, 0xb5, 0x9b, 0x03, 0xb5, 0x96, 0x95, 0x65, 0xc0, 0x6d, 0x5f, 0xc7, 0x45, 0xf9, 0xfe, 0x1a,
+	0x78, 0xa1, 0xfe, 0x72, 0xa8, 0xf6, 0x07, 0xb5, 0x9c, 0xbc, 0x0d, 0x40, 0x9c, 0x02, 0xc6, 0x8c,
+	0x79, 0x08, 0x6d, 0xa8, 0xf6, 0x7b, 0x5d, 0xbd, 0xaf, 0xd6, 0x24, 0xf9, 0x4b, 0x40, 0xdd, 0x5b,
+	0x41, 0x45, 0x1d, 0xfa, 0x13, 0xb4, 0xd9, 0xee, 0xbe, 0xd0, 0x3b, 0xdd, 0x66, 0xdb, 0xec, 0x19,
+	0xdd, 0x13, 0xd8, 0xd3, 0xaf, 0xe5, 0xe5, 0x3d, 0xc0, 0x3f, 0x48, 0xe1, 0x6f, 0x34, 0xdc, 0x57,
+	0x90, 0x3d, 0x4d, 0x3f, 0xa9, 0x15, 0xe4, 0x7b, 0x00, 0xfd, 0x22, 0x05, 0x65, 0x49, 0x65, 0x11,
+	0xb7, 0x3a, 0x5d, 0x70, 0x5d, 0xbc, 0x11, 0x31, 0x4f, 0x76, 0xe3, 0x37, 0x08, 0xdf, 0x7c, 0x46,
+	0xe2, 0x6f, 0x90, 0xa4, 0x77, 0x75, 0x15, 0x12, 0xca, 0xe3, 0xbf, 0x89, 0xd0, 0xa9, 0x47, 0xb0,
+	0x82, 0x72, 0x9d, 0x5f, 0xff, 0x08, 0x92, 0xf9, 0x2d, 0x00, 0xdd, 0xbf, 0x09, 0x02, 0x65, 0x83,
+	0xa2, 0x4a, 0xda, 0xb0, 0x82, 0x4a, 0x67, 0xea, 0xa0, 0x09, 0xc9, 0x6d, 0x82, 0x71, 0x7e, 0xa4,
+	0x58, 0x7d, 0x46, 0x42, 0x8b, 0x13, 0x70, 0x07, 0xe5, 0x75, 0xf5, 0x5c, 0x35, 0xc0, 0xf0, 0x26,
+	0x00, 0xd6, 0x63, 0x80, 0x4e, 0xa0, 0xaf, 0xe0, 0x35, 0x52, 0x68, 0x76, 0x5e, 0x34, 0x5f, 0xf6,
+	0xa1, 0x38, 0x18, 0xd4, 0x1b, 0xb1, 0xba, 0xe9, 0xbe, 0xb6, 0xae, 0x82, 0xc6, 0x7f, 0x32, 0xa8,
+	0x9a, 0xbe, 0x3a, 0x61, 0x83, 0x74, 0xac, 0x75, 0xd4, 0xd8, 0x5d, 0x5a, 0xc7, 0xbe, 0xf1, 0x01,
+	0x2a, 0xb7, 0x35, 0x43, 0x6d, 0x0d, 0xba, 0xc6, 0xcb, 0x38, 0x96, 0x34, 0xa8, 0xed, 0xf8, 0xbc,
+	0xb9, 0xd9, 0xb3, 0xb5, 0xda, 0x7f, 0x79, 0xd6, 0xd1, 0xf4, 0xe7, 0x26, 0xb7, 0x98, 0x95, 0x1f,
+	0x00, 0xf8, 0xcb, 0x34, 0xb8, 0x2f, 0xae, 0x0d, 0x6e, 0xf8, 0x19, 0xda, 0x8c, 0xe1, 0x4b, 0x07,
+	0x39, 0x79, 0x1f, 0xf6, 0xec, 0xdc, 0xb2, 0x67, 0xe9, 0xe7, 0x29, 0xfa, 0x22, 0xde, 0x38, 0xd4,
+	0x9f, 0xeb, 0xd0, 0x16, 0xd0, 0x39, 0xbb, 0xb0, 0x4d, 0xbe, 0x65, 0xdb, 0xd0, 0xbb, 0xf4, 0xa0,
+	0x29, 0x1a, 0x7f, 0xce, 0xa0, 0x72, 0x32, 0xa1, 0x58, 0x9e, 0xf5, 0xae, 0xa9, 0x1a, 0x46, 0xd7,
+	0x88, 0x03, 0x4f, 0x94, 0x3a, 0xe5, 0x9f, 0xf0, 0xf4, 0x2b, 0x9e, 0xa8, 0xba, 0x6a, 0x68, 0xad,
+	0x98, 0x0f, 0x09, 0xe4, 0x84, 0x78, 0xc4, 0x77, 0xc6, 0xf0, 0xcf, 0x4a, 0x15, 0xcc, 0xf4, 0x87,
+	0xad, 0xd3, 0x38, 0x62, 0xde, 0xc0, 0x29, 0x53, 0xfd, 0xc5, 0xf8, 0x82, 0x47, 0xdb, 0x60, 0xd4,
+	0x39, 0x6f, 0x76, 0xb4, 0xb6, 0x80, 0xe6, 0xe4, 0x3a, 0x40, 0xb7, 0x12, 0xa8, 0x26, 0x9e, 0x0e,
+	0x0c, 0xdb, 0xb0, 0xd1, 0xee, 0xff, 0x9e, 0x45, 0xf0, 0xaa, 0x29, 0x34, 0x7b, 0x3d, 0x55, 0x6f,
+	0xc7, 0xa7, 0x5f, 0xea, 0x9a, 0xf3, 0x39, 0xf1, 0x6c, 0x86, 0x38, 0xee, 0x1a, 0x27, 0xea, 0x20,
+	0x3e, 0xfc, 0x12, 0x71, 0x4c, 0xd9, 0xa5, 0x7d, 0xb4, 0xf3, 0xee, 0x1f, 0xbb, 0x6b, 0xef, 0xe1,
+	0xef, 0xdd, 0xa7, 0xdd, 0xcc, 0x7b, 0xf8, 0xfb, 0xfb, 0xa7, 0xdd, 0xb5, 0x7f, 0xc1, 0xef, 0xdb,
+	0x7f, 0xee, 0x66, 0x46, 0x05, 0x3e, 0xbb, 0x9e, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0x93, 0x35,
+	0x80, 0x49, 0x50, 0x0e, 0x00, 0x00,
 }

+ 1 - 0
lib/protocol/bep.proto

@@ -58,6 +58,7 @@ message Folder {
     bool   ignore_permissions   = 4;
     bool   ignore_delete        = 5;
     bool   disable_temp_indexes = 6;
+    bool   paused               = 7;
 
     repeated Device devices = 16 [(gogoproto.nullable) = false];
 }

+ 8 - 8
lib/protocol/deviceid_test.pb.go

@@ -427,16 +427,16 @@ var (
 )
 
 var fileDescriptorDeviceidTest = []byte{
-	// 176 bytes of a gzipped FileDescriptorProto
+	// 171 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x49, 0x2d, 0xcb,
 	0x4c, 0x4e, 0xcd, 0x4c, 0x89, 0x2f, 0x49, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
 	0xe2, 0x00, 0x53, 0xc9, 0xf9, 0x39, 0x52, 0xba, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9,
 	0xf9, 0xb9, 0xfa, 0xe9, 0xf9, 0xe9, 0xf9, 0xfa, 0x60, 0x99, 0xa4, 0xd2, 0x34, 0x30, 0x0f, 0xcc,
-	0x01, 0xb3, 0x20, 0x1a, 0x95, 0x54, 0xb9, 0xf8, 0x43, 0x52, 0x8b, 0x4b, 0xfc, 0x73, 0x52, 0x5c,
-	0xc0, 0xc6, 0x7a, 0xba, 0x08, 0x09, 0x71, 0xb1, 0x80, 0x4c, 0x96, 0x60, 0x54, 0x60, 0xd4, 0xe0,
-	0x09, 0x02, 0xb3, 0x95, 0xcc, 0x21, 0xca, 0xfc, 0x52, 0xcb, 0xe1, 0xca, 0x54, 0x90, 0x95, 0x39,
-	0x09, 0x9c, 0xb8, 0x27, 0xcf, 0x70, 0xeb, 0x9e, 0x3c, 0x07, 0x4c, 0x1e, 0xa2, 0xd1, 0x49, 0xe6,
-	0xc4, 0x43, 0x39, 0x86, 0x0b, 0x0f, 0xe5, 0x18, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e,
-	0xf1, 0xc1, 0x23, 0x39, 0x86, 0x17, 0x8f, 0xe4, 0x18, 0x16, 0x3c, 0x96, 0x63, 0x4c, 0x62, 0x03,
-	0x3b, 0xc2, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x35, 0x9c, 0x00, 0x78, 0xd4, 0x00, 0x00, 0x00,
+	0x01, 0xb3, 0x20, 0x1a, 0x95, 0x54, 0xb9, 0xf8, 0x43, 0x80, 0xc6, 0xf8, 0xe7, 0xa4, 0xb8, 0x80,
+	0x8d, 0xf5, 0x74, 0x11, 0x12, 0xe2, 0x62, 0x01, 0x99, 0x2c, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x13,
+	0x04, 0x66, 0x2b, 0x99, 0x43, 0x94, 0xf9, 0xa5, 0x96, 0xc3, 0x95, 0xa9, 0x20, 0x2b, 0x73, 0x12,
+	0x38, 0x71, 0x4f, 0x9e, 0xe1, 0xd6, 0x3d, 0x79, 0x0e, 0x98, 0x3c, 0x44, 0xa3, 0x93, 0xcc, 0x89,
+	0x87, 0x72, 0x0c, 0x17, 0x80, 0xf8, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x40, 0xfc, 0xe0, 0x91, 0x1c,
+	0xc3, 0x0b, 0x20, 0x5e, 0xf0, 0x58, 0x8e, 0x31, 0x89, 0x0d, 0xec, 0x08, 0x63, 0x40, 0x00, 0x00,
+	0x00, 0xff, 0xff, 0x35, 0x9c, 0x00, 0x78, 0xd4, 0x00, 0x00, 0x00,
 }