Add devices without restart (fixes #2083)
This commit is contained in:
@@ -82,8 +82,6 @@ type FolderConfiguration struct {
|
||||
IgnoreDelete bool `xml:"ignoreDelete" json:"ignoreDelete"`
|
||||
|
||||
Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved
|
||||
|
||||
deviceIDs []protocol.DeviceID
|
||||
}
|
||||
|
||||
func (f FolderConfiguration) Copy() FolderConfiguration {
|
||||
@@ -144,12 +142,11 @@ func (f *FolderConfiguration) HasMarker() bool {
|
||||
}
|
||||
|
||||
func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
|
||||
if f.deviceIDs == nil {
|
||||
for _, n := range f.Devices {
|
||||
f.deviceIDs = append(f.deviceIDs, n.DeviceID)
|
||||
}
|
||||
deviceIDs := make([]protocol.DeviceID, len(f.Devices))
|
||||
for i, n := range f.Devices {
|
||||
deviceIDs[i] = n.DeviceID
|
||||
}
|
||||
return f.deviceIDs
|
||||
return deviceIDs
|
||||
}
|
||||
|
||||
type VersioningConfiguration struct {
|
||||
|
||||
@@ -691,14 +691,7 @@ func (m *Model) Close(device protocol.DeviceID, err error) {
|
||||
|
||||
conn, ok := m.rawConn[device]
|
||||
if ok {
|
||||
if conn, ok := conn.(*tls.Conn); ok {
|
||||
// If the underlying connection is a *tls.Conn, Close() does more
|
||||
// than it says on the tin. Specifically, it sends a TLS alert
|
||||
// message, which might block forever if the connection is dead
|
||||
// and we don't have a deadline site.
|
||||
conn.SetWriteDeadline(time.Now().Add(250 * time.Millisecond))
|
||||
}
|
||||
conn.Close()
|
||||
closeRawConn(conn)
|
||||
}
|
||||
delete(m.protoConn, device)
|
||||
delete(m.rawConn, device)
|
||||
@@ -1732,30 +1725,132 @@ func (m *Model) VerifyConfiguration(from, to config.Configuration) error {
|
||||
func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
// TODO: This should not use reflect, and should take more care to try to handle stuff without restart.
|
||||
|
||||
// Adding, removing or changing folders requires restart
|
||||
if !reflect.DeepEqual(from.Folders, to.Folders) {
|
||||
return false
|
||||
// Go through the folder configs and figure out if we need to restart or not.
|
||||
|
||||
fromFolders := mapFolders(from.Folders)
|
||||
toFolders := mapFolders(to.Folders)
|
||||
for folderID := range toFolders {
|
||||
if _, ok := fromFolders[folderID]; !ok {
|
||||
// A folder was added. Requires restart.
|
||||
if debug {
|
||||
l.Debugln(m, "requires restart, adding folder", folderID)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for folderID, fromCfg := range fromFolders {
|
||||
toCfg, ok := toFolders[folderID]
|
||||
if !ok {
|
||||
// A folder was removed. Requires restart.
|
||||
if debug {
|
||||
l.Debugln(m, "requires restart, removing folder", folderID)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// This folder exists on both sides. Compare the device lists, as we
|
||||
// can handle adding a device (but not currently removing one).
|
||||
|
||||
fromDevs := mapDevices(fromCfg.DeviceIDs())
|
||||
toDevs := mapDevices(toCfg.DeviceIDs())
|
||||
for dev := range fromDevs {
|
||||
if _, ok := toDevs[dev]; !ok {
|
||||
// A device was removed. Requires restart.
|
||||
if debug {
|
||||
l.Debugln(m, "requires restart, removing device", dev, "from folder", folderID)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for dev := range toDevs {
|
||||
if _, ok := fromDevs[dev]; !ok {
|
||||
// A device was added. Handle it!
|
||||
|
||||
m.fmut.Lock()
|
||||
m.pmut.Lock()
|
||||
|
||||
m.folderCfgs[folderID] = toCfg
|
||||
m.folderDevices[folderID] = append(m.folderDevices[folderID], dev)
|
||||
m.deviceFolders[dev] = append(m.deviceFolders[dev], folderID)
|
||||
|
||||
// If we already have a connection to this device, we should
|
||||
// disconnect it so that we start sharing the folder with it.
|
||||
// We close the underlying connection and let the normal error
|
||||
// handling kick in to clean up and reconnect.
|
||||
if conn, ok := m.rawConn[dev]; ok {
|
||||
closeRawConn(conn)
|
||||
}
|
||||
|
||||
m.pmut.Unlock()
|
||||
m.fmut.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Check if anything else differs, apart from the device list.
|
||||
fromCfg.Devices = nil
|
||||
toCfg.Devices = nil
|
||||
if !reflect.DeepEqual(fromCfg, toCfg) {
|
||||
if debug {
|
||||
l.Debugln(m, "requires restart, folder", folderID, "configuration differs")
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Removing a device requres restart
|
||||
toDevs := make(map[protocol.DeviceID]bool, len(from.Devices))
|
||||
for _, dev := range to.Devices {
|
||||
toDevs[dev.DeviceID] = true
|
||||
}
|
||||
toDevs := mapDeviceCfgs(from.Devices)
|
||||
for _, dev := range from.Devices {
|
||||
if _, ok := toDevs[dev.DeviceID]; !ok {
|
||||
if debug {
|
||||
l.Debugln(m, "requires restart, device", dev.DeviceID, "was removed")
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// All of the generic options require restart
|
||||
if !reflect.DeepEqual(from.Options, to.Options) {
|
||||
if debug {
|
||||
l.Debugln(m, "requires restart, options differ")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// mapFolders returns a map of folder ID to folder configuration for the given
|
||||
// slice of folder configurations.
|
||||
func mapFolders(folders []config.FolderConfiguration) map[string]config.FolderConfiguration {
|
||||
m := make(map[string]config.FolderConfiguration, len(folders))
|
||||
for _, cfg := range folders {
|
||||
m[cfg.ID] = cfg
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// mapDevices returns a map of device ID to nothing for the given slice of
|
||||
// device IDs.
|
||||
func mapDevices(devices []protocol.DeviceID) map[protocol.DeviceID]struct{} {
|
||||
m := make(map[protocol.DeviceID]struct{}, len(devices))
|
||||
for _, dev := range devices {
|
||||
m[dev] = struct{}{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// mapDeviceCfgs returns a map of device ID to nothing for the given slice of
|
||||
// device configurations.
|
||||
func mapDeviceCfgs(devices []config.DeviceConfiguration) map[protocol.DeviceID]struct{} {
|
||||
m := make(map[protocol.DeviceID]struct{}, len(devices))
|
||||
for _, dev := range devices {
|
||||
m[dev.DeviceID] = struct{}{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func filterIndex(folder string, fs []protocol.FileInfo, dropDeletes bool) []protocol.FileInfo {
|
||||
for i := 0; i < len(fs); {
|
||||
if fs[i].Flags&^protocol.FlagsAll != 0 {
|
||||
@@ -1816,3 +1911,14 @@ func getChunk(data []string, skip, get int) ([]string, int, int) {
|
||||
}
|
||||
return data[skip : skip+get], 0, 0
|
||||
}
|
||||
|
||||
func closeRawConn(conn io.Closer) error {
|
||||
if conn, ok := conn.(*tls.Conn); ok {
|
||||
// If the underlying connection is a *tls.Conn, Close() does more
|
||||
// than it says on the tin. Specifically, it sends a TLS alert
|
||||
// message, which might block forever if the connection is dead
|
||||
// and we don't have a deadline set.
|
||||
conn.SetWriteDeadline(time.Now().Add(250 * time.Millisecond))
|
||||
}
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ func NewProcess(addr string) *Process {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Process) ID() protocol.DeviceID {
|
||||
return p.id
|
||||
}
|
||||
|
||||
// LogTo creates the specified log file and ensures that stdout and stderr
|
||||
// from the Start()ed process is redirected there. Must be called before
|
||||
// Start().
|
||||
@@ -229,6 +233,34 @@ func (p *Process) RescanDelay(folder string, delaySeconds int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Process) ConfigInSync() (bool, error) {
|
||||
bs, err := p.Get("/rest/system/config/insync")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return bytes.Contains(bs, []byte("true")), nil
|
||||
}
|
||||
|
||||
func (p *Process) GetConfig() (config.Configuration, error) {
|
||||
var cfg config.Configuration
|
||||
bs, err := p.Get("/rest/system/config")
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bs, &cfg)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func (p *Process) PostConfig(cfg config.Configuration) error {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buf).Encode(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := p.Post("/rest/system/config", buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func InSync(folder string, ps ...*Process) bool {
|
||||
for _, p := range ps {
|
||||
p.eventMut.Lock()
|
||||
|
||||
Reference in New Issue
Block a user