فهرست منبع

lib/config, lib/model: Handle shared with information in config (fixes #4870) (#4974)

Simon Frei 7 سال پیش
والد
کامیت
e2c44f519c
3فایلهای تغییر یافته به همراه142 افزوده شده و 187 حذف شده
  1. 9 0
      lib/config/folderconfiguration.go
  2. 98 150
      lib/model/model.go
  3. 35 37
      lib/model/model_test.go

+ 9 - 0
lib/config/folderconfiguration.go

@@ -261,6 +261,15 @@ func (f FolderConfiguration) RequiresRestartOnly() FolderConfiguration {
 	return copy
 }
 
+func (f *FolderConfiguration) SharedWith(device protocol.DeviceID) bool {
+	for _, dev := range f.Devices {
+		if dev.DeviceID == device {
+			return true
+		}
+	}
+	return false
+}
+
 type FolderDeviceConfigurationList []FolderDeviceConfiguration
 
 func (l FolderDeviceConfigurationList) Less(a, b int) bool {

+ 98 - 150
lib/model/model.go

@@ -95,8 +95,6 @@ type Model struct {
 
 	folderCfgs         map[string]config.FolderConfiguration                  // folder -> cfg
 	folderFiles        map[string]*db.FileSet                                 // folder -> files
-	folderDevices      folderDeviceSet                                        // folder -> deviceIDs
-	deviceFolders      map[protocol.DeviceID][]string                         // deviceID -> folders
 	deviceStatRefs     map[protocol.DeviceID]*stats.DeviceStatisticsReference // deviceID -> statsRef
 	folderIgnores      map[string]*ignore.Matcher                             // folder -> matcher object
 	folderRunners      map[string]service                                     // folder -> puller or scanner
@@ -150,8 +148,6 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, clientName, clientVersi
 		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),
@@ -326,11 +322,6 @@ func (m *Model) addFolderLocked(cfg config.FolderConfiguration) {
 	folderFs := cfg.Filesystem()
 	m.folderFiles[cfg.ID] = db.NewFileSet(cfg.ID, folderFs, m.db)
 
-	for _, device := range cfg.Devices {
-		m.folderDevices.set(device.DeviceID, cfg.ID)
-		m.deviceFolders[device.DeviceID] = append(m.deviceFolders[device.DeviceID], cfg.ID)
-	}
-
 	ignores := ignore.New(folderFs, ignore.WithCache(m.cacheIgnoredFiles))
 	if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
 		l.Warnln("Loading ignores:", err)
@@ -344,7 +335,7 @@ func (m *Model) RemoveFolder(cfg config.FolderConfiguration) {
 	// Delete syncthing specific files
 	cfg.Filesystem().RemoveAll(config.DefaultMarkerName)
 
-	m.tearDownFolderLocked(cfg.ID)
+	m.tearDownFolderLocked(cfg)
 	// Remove it from the database
 	db.DropFolder(m.db, cfg.ID)
 
@@ -352,47 +343,43 @@ func (m *Model) RemoveFolder(cfg config.FolderConfiguration) {
 	m.fmut.Unlock()
 }
 
-func (m *Model) tearDownFolderLocked(folder string) {
+func (m *Model) tearDownFolderLocked(cfg config.FolderConfiguration) {
 	// Stop the services running for this folder
-	for _, id := range m.folderRunnerTokens[folder] {
+	for _, id := range m.folderRunnerTokens[cfg.ID] {
 		m.Remove(id)
 	}
 
 	// Close connections to affected devices
-	for dev := range m.folderDevices[folder] {
-		if conn, ok := m.conn[dev]; ok {
+	for _, dev := range cfg.Devices {
+		if conn, ok := m.conn[dev.DeviceID]; ok {
 			closeRawConn(conn)
 		}
 	}
 
 	// Clean up our config maps
-	delete(m.folderCfgs, folder)
-	delete(m.folderFiles, folder)
-	delete(m.folderDevices, folder)
-	delete(m.folderIgnores, folder)
-	delete(m.folderRunners, folder)
-	delete(m.folderRunnerTokens, folder)
-	delete(m.folderStatRefs, folder)
-	for dev, folders := range m.deviceFolders {
-		m.deviceFolders[dev] = stringSliceWithout(folders, folder)
-	}
+	delete(m.folderCfgs, cfg.ID)
+	delete(m.folderFiles, cfg.ID)
+	delete(m.folderIgnores, cfg.ID)
+	delete(m.folderRunners, cfg.ID)
+	delete(m.folderRunnerTokens, cfg.ID)
+	delete(m.folderStatRefs, cfg.ID)
 }
 
-func (m *Model) RestartFolder(cfg config.FolderConfiguration) {
-	if len(cfg.ID) == 0 {
+func (m *Model) RestartFolder(from, to config.FolderConfiguration) {
+	if len(to.ID) == 0 {
 		panic("cannot add empty folder id")
 	}
 
 	m.fmut.Lock()
 	m.pmut.Lock()
 
-	m.tearDownFolderLocked(cfg.ID)
-	if cfg.Paused {
-		l.Infoln("Paused folder", cfg.Description())
+	m.tearDownFolderLocked(from)
+	if to.Paused {
+		l.Infoln("Paused folder", to.Description())
 	} else {
-		m.addFolderLocked(cfg)
-		folderType := m.startFolderLocked(cfg.ID)
-		l.Infoln("Restarted folder", cfg.Description(), fmt.Sprintf("(%s)", folderType))
+		m.addFolderLocked(to)
+		folderType := m.startFolderLocked(to.ID)
+		l.Infoln("Restarted folder", to.Description(), fmt.Sprintf("(%s)", folderType))
 	}
 
 	m.pmut.Unlock()
@@ -842,8 +829,11 @@ func (m *Model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
 
 	l.Debugf("%v (in): %s / %q: %d files", op, deviceID, folder, len(fs))
 
-	if !m.folderSharedWith(folder, deviceID) {
-		l.Debugf("%v for unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", op, folder, deviceID)
+	if cfg, ok := m.cfg.Folder(folder); !ok || !cfg.SharedWith(deviceID) {
+		l.Infof("%v for unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", op, folder, deviceID)
+		return
+	} else if cfg.Paused {
+		l.Debugf("%v for paused folder (ID %q) sent from device %q.", op, folder, deviceID)
 		return
 	}
 
@@ -881,22 +871,6 @@ func (m *Model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
 	})
 }
 
-func (m *Model) folderSharedWith(folder string, deviceID protocol.DeviceID) bool {
-	m.fmut.RLock()
-	shared := m.folderSharedWithLocked(folder, deviceID)
-	m.fmut.RUnlock()
-	return shared
-}
-
-func (m *Model) folderSharedWithLocked(folder string, deviceID protocol.DeviceID) bool {
-	for _, nfolder := range m.deviceFolders[deviceID] {
-		if nfolder == folder {
-			return true
-		}
-	}
-	return false
-}
-
 func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterConfig) {
 	// Check the peer device's announced folders against our own. Emits events
 	// for folders that we don't expect (unknown or not shared).
@@ -935,21 +909,12 @@ 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.cfg.IgnoredFolder(folder.ID) {
-			l.Infof("Ignoring folder %s from device %s since we are configured to", folder.Description(), deviceID)
-			continue
-		}
-
-		if !m.folderSharedWithLocked(folder.ID, deviceID) {
+		cfg, ok := m.cfg.Folder(folder.ID)
+		if !ok || !cfg.SharedWith(deviceID) {
+			if m.cfg.IgnoredFolder(folder.ID) {
+				l.Infof("Ignoring folder %s from device %s since we are configured to", folder.Description(), deviceID)
+				continue
+			}
 			events.Default.Log(events.FolderRejected, map[string]string{
 				"folder":      folder.ID,
 				"folderLabel": folder.Label,
@@ -958,6 +923,13 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
 			l.Infof("Unexpected folder %s sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.Description(), deviceID)
 			continue
 		}
+		if folder.Paused {
+			paused = append(paused, folder.ID)
+			continue
+		}
+		if cfg.Paused {
+			continue
+		}
 
 		if !folder.DisableTempIndexes {
 			tempIndexFolders = append(tempIndexFolders, folder.ID)
@@ -1094,7 +1066,6 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
 			continue
 		}
 
-	nextDevice:
 		for _, device := range folder.Devices {
 			// No need to share with self.
 			if device.ID == m.id {
@@ -1106,15 +1077,10 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
 			if _, ok := m.cfg.Devices()[device.ID]; !ok {
 				// The device is currently unknown. Add it to the config.
 				m.introduceDevice(device, introducerCfg)
-				changed = true
-			} else {
-				for _, dev := range fcfg.DeviceIDs() {
-					if dev == device.ID {
-						// We already share the folder with this device, so
-						// nothing to do.
-						continue nextDevice
-					}
-				}
+			} else if fcfg.SharedWith(device.ID) {
+				// We already share the folder with this device, so
+				// nothing to do.
+				continue
 			}
 
 			// We don't yet share this folder with this device. Add the device
@@ -1138,51 +1104,54 @@ func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm
 // handleDeintroductions handles removals of devices/shares that are removed by an introducer device
 func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig, foldersDevices folderDeviceSet) bool {
 	changed := false
-	foldersIntroducedByOthers := make(folderDeviceSet)
+	devicesNotIntroduced := make(map[protocol.DeviceID]struct{})
 
+	folders := m.cfg.FolderList()
 	// Check if we should unshare some folders, if the introducer has unshared them.
-	for _, folderCfg := range m.cfg.Folders() {
-		folderChanged := false
-		for i := 0; i < len(folderCfg.Devices); i++ {
-			if folderCfg.Devices[i].IntroducedBy == introducerCfg.DeviceID {
-				if !foldersDevices.has(folderCfg.Devices[i].DeviceID, folderCfg.ID) {
-					// We could not find that folder shared on the
-					// introducer with the device that was introduced to us.
-					// We should follow and unshare as well.
-					l.Infof("Unsharing folder %s with %v as introducer %v no longer shares the folder with that device", folderCfg.Description(), folderCfg.Devices[i].DeviceID, folderCfg.Devices[i].IntroducedBy)
-					folderCfg.Devices = append(folderCfg.Devices[:i], folderCfg.Devices[i+1:]...)
-					i--
-					folderChanged = true
-				}
-			} else {
-				foldersIntroducedByOthers.set(folderCfg.Devices[i].DeviceID, folderCfg.ID)
+	for i := range folders {
+		for k := 0; k < len(folders[i].Devices); k++ {
+			if folders[i].Devices[k].IntroducedBy != introducerCfg.DeviceID {
+				devicesNotIntroduced[folders[i].Devices[k].DeviceID] = struct{}{}
+				continue
+			}
+			if !foldersDevices.has(folders[i].Devices[k].DeviceID, folders[i].ID) {
+				// We could not find that folder shared on the
+				// introducer with the device that was introduced to us.
+				// We should follow and unshare as well.
+				l.Infof("Unsharing folder %s with %v as introducer %v no longer shares the folder with that device", folders[i].Description(), folders[i].Devices[k].DeviceID, folders[i].Devices[k].IntroducedBy)
+				folders[i].Devices = append(folders[i].Devices[:k], folders[i].Devices[k+1:]...)
+				k--
+				changed = true
 			}
-		}
-
-		// We've modified the folder, hence update it.
-		if folderChanged {
-			m.cfg.SetFolder(folderCfg)
-			changed = true
 		}
 	}
 
 	// Check if we should remove some devices, if the introducer no longer
 	// shares any folder with them. Yet do not remove if we share other
 	// folders that haven't been introduced by the introducer.
-	for _, device := range m.cfg.Devices() {
+	devMap := m.cfg.Devices()
+	devices := make([]config.DeviceConfiguration, 0, len(devMap))
+	for deviceID, device := range devMap {
 		if device.IntroducedBy == introducerCfg.DeviceID {
-			if !foldersDevices.hasDevice(device.DeviceID) {
-				if foldersIntroducedByOthers.hasDevice(device.DeviceID) {
-					l.Infof("Would have removed %v as %v no longer shares any folders, yet there are other folders that are shared with this device that haven't been introduced by this introducer.", device.DeviceID, device.IntroducedBy)
+			if !foldersDevices.hasDevice(deviceID) {
+				if _, ok := devicesNotIntroduced[deviceID]; !ok {
+					// The introducer no longer shares any folder with the
+					// device, remove the device.
+					l.Infof("Removing device %v as introducer %v no longer shares any folders with that device", deviceID, device.IntroducedBy)
+					changed = true
 					continue
 				}
-				// The introducer no longer shares any folder with the
-				// device, remove the device.
-				l.Infof("Removing device %v as introducer %v no longer shares any folders with that device", device.DeviceID, device.IntroducedBy)
-				m.cfg.RemoveDevice(device.DeviceID)
-				changed = true
+				l.Infof("Would have removed %v as %v no longer shares any folders, yet there are other folders that are shared with this device that haven't been introduced by this introducer.", deviceID, device.IntroducedBy)
 			}
 		}
+		devices = append(devices, device)
+	}
+
+	if changed {
+		cfg := m.cfg.RawCopy()
+		cfg.Folders = folders
+		cfg.Devices = devices
+		m.cfg.Replace(cfg)
 	}
 
 	return changed
@@ -1310,28 +1279,28 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
 		return protocol.ErrInvalid
 	}
 
-	// Make sure the path is valid and in canonical form
-	var err error
-	if name, err = fs.Canonicalize(name); err != nil {
-		l.Debugf("Request from %s in paused folder %q for invalid filename %s", deviceID, folder, name)
+	m.fmut.RLock()
+
+	if cfg, ok := m.cfg.Folder(folder); !ok || !cfg.SharedWith(deviceID) {
+		l.Warnf("Request from %s for file %s in unshared folder %q", deviceID, name, folder)
+		return protocol.ErrNoSuchFile
+	} else if cfg.Paused {
+		l.Debugf("Request from %s for file %s in paused folder %q", deviceID, name, folder)
 		return protocol.ErrInvalid
 	}
 
-	m.fmut.RLock()
 	folderCfg := m.folderCfgs[folder]
 	folderIgnores := m.folderIgnores[folder]
+
 	m.fmut.RUnlock()
 
-	if folderCfg.Paused {
-		l.Debugf("Request from %s for file %s in paused folder %q", deviceID, name, folder)
+	// Make sure the path is valid and in canonical form
+	var err error
+	if name, err = fs.Canonicalize(name); err != nil {
+		l.Debugf("Request from %s in folder %q for invalid filename %s", deviceID, folder, name)
 		return protocol.ErrInvalid
 	}
 
-	if !m.folderSharedWith(folder, deviceID) {
-		l.Warnf("Request from %s for file %s in unshared folder %q", deviceID, name, folder)
-		return protocol.ErrNoSuchFile
-	}
-
 	if deviceID != protocol.LocalDeviceID {
 		l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d t=%v", m, deviceID, folder, name, offset, len(buf), fromTemporary)
 	}
@@ -1639,15 +1608,11 @@ func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloR
 }
 
 func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
-	if !m.folderSharedWith(folder, device) {
-		return
-	}
-
 	m.fmut.RLock()
 	cfg, ok := m.folderCfgs[folder]
 	m.fmut.RUnlock()
 
-	if !ok || cfg.Type == config.FolderTypeSendOnly || cfg.DisableTempIndexes {
+	if !ok || cfg.Type == config.FolderTypeSendOnly || cfg.DisableTempIndexes || !cfg.SharedWith(device) {
 		return
 	}
 
@@ -2210,15 +2175,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
 	defer m.fmut.RUnlock()
 
 	for _, folderCfg := range m.cfg.FolderList() {
-		isShared := false
-		for _, sharedWith := range folderCfg.Devices {
-			if sharedWith.DeviceID == device {
-				isShared = true
-				break
-			}
-		}
-
-		if !isShared {
+		if !folderCfg.SharedWith(device) {
 			continue
 		}
 
@@ -2342,6 +2299,7 @@ func (m *Model) RemoteSequence(folder string) (int64, bool) {
 	defer m.fmut.RUnlock()
 
 	fs, ok := m.folderFiles[folder]
+	cfg := m.folderCfgs[folder]
 	if !ok {
 		// The folder might not exist, since this can be called with a user
 		// specified folder name from the REST interface.
@@ -2349,8 +2307,8 @@ func (m *Model) RemoteSequence(folder string) (int64, bool) {
 	}
 
 	var ver int64
-	for device := range m.folderDevices[folder] {
-		ver += fs.Sequence(device)
+	for _, device := range cfg.Devices {
+		ver += fs.Sequence(device.DeviceID)
 	}
 
 	return ver, true
@@ -2556,7 +2514,7 @@ func (m *Model) Availability(folder string, file protocol.FileInfo, block protoc
 	defer m.pmut.RUnlock()
 
 	fs, ok := m.folderFiles[folder]
-	devices := m.folderDevices[folder]
+	cfg := m.folderCfgs[folder]
 	m.fmut.RUnlock()
 
 	if !ok {
@@ -2577,9 +2535,9 @@ next:
 		}
 	}
 
-	for device := range devices {
-		if m.deviceDownloads[device].Has(folder, file.Name, file.Version, int32(block.Offset/int64(file.BlockSize()))) {
-			availabilities = append(availabilities, Availability{ID: device, FromTemporary: true})
+	for _, device := range cfg.Devices {
+		if m.deviceDownloads[device.DeviceID].Has(folder, file.Name, file.Version, int32(block.Offset/int64(file.BlockSize()))) {
+			availabilities = append(availabilities, Availability{ID: device.DeviceID, FromTemporary: true})
 		}
 	}
 
@@ -2641,7 +2599,7 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
 		// This folder exists on both sides. Settings might have changed.
 		// Check if anything differs that requires a restart.
 		if !reflect.DeepEqual(fromCfg.RequiresRestartOnly(), toCfg.RequiresRestartOnly()) {
-			m.RestartFolder(toCfg)
+			m.RestartFolder(fromCfg, toCfg)
 		}
 
 		// Emit the folder pause/resume event
@@ -2726,7 +2684,7 @@ func (m *Model) checkDeviceFolderConnectedLocked(device protocol.DeviceID, folde
 		return errors.New("device is not connected")
 	}
 
-	if !m.folderDevices.has(device, folder) {
+	if cfg, ok := m.cfg.Folder(folder); !ok || !cfg.SharedWith(device) {
 		return errors.New("folder is not shared with device")
 	}
 	return nil
@@ -2882,13 +2840,3 @@ func (s folderDeviceSet) hasDevice(dev protocol.DeviceID) bool {
 	}
 	return false
 }
-
-// sortedDevices returns the list of devices for a given folder, sorted
-func (s folderDeviceSet) sortedDevices(folder string) []protocol.DeviceID {
-	devs := make([]protocol.DeviceID, 0, len(s[folder]))
-	for dev := range s[folder] {
-		devs = append(devs, dev)
-	}
-	sort.Sort(protocol.DeviceIDs(devs))
-	return devs
-}

+ 35 - 37
lib/model/model_test.go

@@ -1084,7 +1084,7 @@ func TestAutoAcceptRejected(t *testing.T) {
 	for i := range tcfg.Devices {
 		tcfg.Devices[i].AutoAcceptFolders = false
 	}
-	wcfg, m := newState(tcfg)
+	_, m := newState(tcfg)
 	id := srand.String(8)
 	defer os.RemoveAll(filepath.Join("testdata", id))
 	m.ClusterConfig(device1, protocol.ClusterConfig{
@@ -1096,7 +1096,7 @@ func TestAutoAcceptRejected(t *testing.T) {
 		},
 	})
 
-	if _, ok := wcfg.Folder(id); ok || m.folderSharedWith(id, device1) {
+	if cfg, ok := m.cfg.Folder(id); ok && cfg.SharedWith(device1) {
 		t.Error("unexpected shared", id)
 	}
 }
@@ -1114,7 +1114,7 @@ func TestAutoAcceptNewFolder(t *testing.T) {
 			},
 		},
 	})
-	if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
+	if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
 		t.Error("expected shared", id)
 	}
 }
@@ -1138,10 +1138,10 @@ func TestAutoAcceptMultipleFolders(t *testing.T) {
 			},
 		},
 	})
-	if _, ok := wcfg.Folder(id1); !ok || !m.folderSharedWith(id1, device1) {
+	if fcfg, ok := wcfg.Folder(id1); !ok || !fcfg.SharedWith(device1) {
 		t.Error("expected shared", id1)
 	}
-	if _, ok := wcfg.Folder(id2); !ok || !m.folderSharedWith(id2, device1) {
+	if fcfg, ok := wcfg.Folder(id2); !ok || !fcfg.SharedWith(device1) {
 		t.Error("expected shared", id2)
 	}
 }
@@ -1161,7 +1161,7 @@ func TestAutoAcceptExistingFolder(t *testing.T) {
 		},
 	}
 	wcfg, m := newState(tcfg)
-	if _, ok := wcfg.Folder(id); !ok || m.folderSharedWith(id, device1) {
+	if fcfg, ok := wcfg.Folder(id); !ok || fcfg.SharedWith(device1) {
 		t.Error("missing folder, or shared", id)
 	}
 	m.ClusterConfig(device1, protocol.ClusterConfig{
@@ -1173,7 +1173,7 @@ func TestAutoAcceptExistingFolder(t *testing.T) {
 		},
 	})
 
-	if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || fcfg.Path != filepath.Join("testdata", idOther) {
+	if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) || fcfg.Path != filepath.Join("testdata", idOther) {
 		t.Error("missing folder, or unshared, or path changed", id)
 	}
 }
@@ -1193,7 +1193,7 @@ func TestAutoAcceptNewAndExistingFolder(t *testing.T) {
 		},
 	}
 	wcfg, m := newState(tcfg)
-	if _, ok := wcfg.Folder(id1); !ok || m.folderSharedWith(id1, device1) {
+	if fcfg, ok := wcfg.Folder(id1); !ok || fcfg.SharedWith(device1) {
 		t.Error("missing folder, or shared", id1)
 	}
 	m.ClusterConfig(device1, protocol.ClusterConfig{
@@ -1210,7 +1210,7 @@ func TestAutoAcceptNewAndExistingFolder(t *testing.T) {
 	})
 
 	for i, id := range []string{id1, id2} {
-		if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
+		if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
 			t.Error("missing folder, or unshared", i, id)
 		}
 	}
@@ -1233,7 +1233,7 @@ func TestAutoAcceptAlreadyShared(t *testing.T) {
 		},
 	}
 	wcfg, m := newState(tcfg)
-	if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
+	if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
 		t.Error("missing folder, or not shared", id)
 	}
 	m.ClusterConfig(device1, protocol.ClusterConfig{
@@ -1245,7 +1245,7 @@ func TestAutoAcceptAlreadyShared(t *testing.T) {
 		},
 	})
 
-	if _, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) {
+	if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
 		t.Error("missing folder, or not shared", id)
 	}
 }
@@ -1266,7 +1266,7 @@ func TestAutoAcceptNameConflict(t *testing.T) {
 			},
 		},
 	})
-	if _, ok := wcfg.Folder(id); ok || m.folderSharedWith(id, device1) {
+	if fcfg, ok := wcfg.Folder(id); ok && fcfg.SharedWith(device1) {
 		t.Error("unexpected folder", id)
 	}
 }
@@ -1286,7 +1286,7 @@ func TestAutoAcceptPrefersLabel(t *testing.T) {
 			},
 		},
 	})
-	if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || !strings.HasSuffix(fcfg.Path, label) {
+	if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) || !strings.HasSuffix(fcfg.Path, label) {
 		t.Error("expected shared, or wrong path", id, label, fcfg.Path)
 	}
 }
@@ -1307,7 +1307,7 @@ func TestAutoAcceptFallsBackToID(t *testing.T) {
 			},
 		},
 	})
-	if fcfg, ok := wcfg.Folder(id); !ok || !m.folderSharedWith(id, device1) || !strings.HasSuffix(fcfg.Path, id) {
+	if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) || !strings.HasSuffix(fcfg.Path, id) {
 		t.Error("expected shared, or wrong path", id, label, fcfg.Path)
 	}
 }
@@ -1330,8 +1330,8 @@ func TestAutoAcceptPausedWhenFolderConfigChanged(t *testing.T) {
 	})
 	tcfg.Folders = []config.FolderConfiguration{fcfg}
 	wcfg, m := newState(tcfg)
-	if _, ok := wcfg.Folder(id); !ok || m.folderSharedWith(id, device1) {
-		t.Error("missing folder, or shared", id)
+	if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
+		t.Error("missing folder, or not shared", id)
 	}
 	if _, ok := m.folderRunners[id]; ok {
 		t.Fatal("folder running?")
@@ -1360,7 +1360,7 @@ func TestAutoAcceptPausedWhenFolderConfigChanged(t *testing.T) {
 		t.Error("device missing")
 	}
 
-	if _, ok := m.folderDevices[id]; ok {
+	if _, ok := m.folderRunners[id]; ok {
 		t.Error("folder started")
 	}
 }
@@ -1386,8 +1386,8 @@ func TestAutoAcceptPausedWhenFolderConfigNotChanged(t *testing.T) {
 	}, fcfg.Devices...) // Need to ensure this device order to avoid folder restart.
 	tcfg.Folders = []config.FolderConfiguration{fcfg}
 	wcfg, m := newState(tcfg)
-	if _, ok := wcfg.Folder(id); !ok || m.folderSharedWith(id, device1) {
-		t.Error("missing folder, or shared", id)
+	if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
+		t.Error("missing folder, or not shared", id)
 	}
 	if _, ok := m.folderRunners[id]; ok {
 		t.Fatal("folder running?")
@@ -1416,7 +1416,7 @@ func TestAutoAcceptPausedWhenFolderConfigNotChanged(t *testing.T) {
 		t.Error("device missing")
 	}
 
-	if _, ok := m.folderDevices[id]; ok {
+	if _, ok := m.folderRunners[id]; ok {
 		t.Error("folder started")
 	}
 }
@@ -1536,7 +1536,7 @@ func TestIgnores(t *testing.T) {
 	pausedDefaultFolderConfig := defaultFolderConfig
 	pausedDefaultFolderConfig.Paused = true
 
-	m.RestartFolder(pausedDefaultFolderConfig)
+	m.RestartFolder(defaultFolderConfig, pausedDefaultFolderConfig)
 	// Here folder initialization is not an issue as a paused folder isn't
 	// added to the model and thus there is no initial scan happening.
 
@@ -2623,10 +2623,10 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
 		},
 	})
 
-	if !m.folderSharedWith("default", device1) {
+	if fcfg, ok := m.cfg.Folder("default"); !ok || !fcfg.SharedWith(device1) {
 		t.Error("not shared with device1")
 	}
-	if !m.folderSharedWith("default", device2) {
+	if fcfg, ok := m.cfg.Folder("default"); !ok || !fcfg.SharedWith(device2) {
 		t.Error("not shared with device2")
 	}
 
@@ -2643,12 +2643,21 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
 
 	time.Sleep(100 * time.Millisecond) // Committer notification happens in a separate routine
 
-	if !m.folderSharedWith("default", device1) {
+	fcfg, ok := m.cfg.Folder("default")
+	if !ok {
+		t.Fatal("default folder missing")
+	}
+	if !fcfg.SharedWith(device1) {
 		t.Error("not shared with device1")
 	}
-	if m.folderSharedWith("default", device2) { // checks m.deviceFolders
+	if fcfg.SharedWith(device2) {
 		t.Error("shared with device2")
 	}
+	for _, dev := range fcfg.Devices {
+		if dev.DeviceID == device2 {
+			t.Error("still there")
+		}
+	}
 
 	if !conn2.Closed() {
 		t.Error("connection not closed")
@@ -2658,17 +2667,6 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
 		t.Error("device still in config")
 	}
 
-	fdevs, ok := m.folderDevices["default"]
-	if !ok {
-		t.Error("folder missing?")
-	}
-
-	for id := range fdevs {
-		if id == device2 {
-			t.Error("still there")
-		}
-	}
-
 	if _, ok := m.conn[device2]; !ok {
 		t.Error("conn missing early")
 	}
@@ -3250,7 +3248,7 @@ func TestIssue4475(t *testing.T) {
 	conn := addFakeConn(m, device1)
 	conn.folder = "default"
 
-	if !m.folderSharedWith("default", device1) {
+	if fcfg, ok := m.cfg.Folder("default"); !ok || !fcfg.SharedWith(device1) {
 		t.Fatal("not shared with device1")
 	}