diff --git a/pkg/service/datastore/datastore.go b/pkg/service/datastore/datastore.go index 8f8376d..0bd9774 100644 --- a/pkg/service/datastore/datastore.go +++ b/pkg/service/datastore/datastore.go @@ -1330,6 +1330,25 @@ func (ds *DataStore) SaveConfiguredSources(account, device string, sources []mod } `xml:"sourceKey"` } + // Deduplicate by ID before saving; first occurrence wins to preserve established data + seen := make(map[string]bool) + + deduped := make([]models.ConfiguredSource, 0, len(sources)) + for i := range sources { + s := &sources[i] + if s.ID != "" { + if seen[s.ID] { + continue + } + + seen[s.ID] = true + } + + deduped = append(deduped, *s) + } + + sources = deduped + // Ensure SourceKey is populated from legacy fields if necessary before saving // and map to persistentSource to avoid custom MarshalXML for disk storage persistSources := make([]persistentSource, len(sources)) @@ -1591,6 +1610,14 @@ func (ds *DataStore) GetETagForPresets(account, device string) int64 { return info.ModTime().UnixNano() / int64(time.Millisecond) } +// HasConfiguredSources reports whether a Sources.xml file exists for the given account and device. +func (ds *DataStore) HasConfiguredSources(account, device string) bool { + path := filepath.Join(ds.AccountDeviceDir(account, device), constants.SourcesFile) + _, err := os.Stat(path) + + return err == nil +} + // GetETagForSources returns the ETag (modification time) for the sources file for a specific device. func (ds *DataStore) GetETagForSources(account, device string) int64 { path := filepath.Join(ds.AccountDeviceDir(account, device), constants.SourcesFile) diff --git a/pkg/service/handlers/server.go b/pkg/service/handlers/server.go index 5fcf6a7..d7328c7 100644 --- a/pkg/service/handlers/server.go +++ b/pkg/service/handlers/server.go @@ -569,12 +569,14 @@ func (s *Server) handleDiscoveredDevice(d models.DiscoveredDevice) { return } - // 8. Ensure default sources exist if missing - if sources, err := s.ds.GetConfiguredSources(accountID, deviceID); err == nil { - log.Printf("Creating default Sources.xml for device %s", deviceID) + // 8. Create default Sources.xml only when no sources file exists yet + if !s.ds.HasConfiguredSources(accountID, deviceID) { + if sources, err := s.ds.GetConfiguredSources(accountID, deviceID); err == nil { + log.Printf("Creating default Sources.xml for device %s", deviceID) - if err := s.ds.SaveConfiguredSources(accountID, deviceID, sources); err != nil { - log.Printf("Failed to save default sources for %s: %v", deviceID, err) + if err := s.ds.SaveConfiguredSources(accountID, deviceID, sources); err != nil { + log.Printf("Failed to save default sources for %s: %v", deviceID, err) + } } } @@ -620,12 +622,14 @@ func (s *Server) handleDiscoveredDeviceFallback(d models.DiscoveredDevice) { return } - // Ensure default sources exist if missing - if sources, err := s.ds.GetConfiguredSources(accountID, deviceID); err == nil { - log.Printf("Creating default Sources.xml for device %s (fallback)", deviceID) + // Create default Sources.xml only when no sources file exists yet + if !s.ds.HasConfiguredSources(accountID, deviceID) { + if sources, err := s.ds.GetConfiguredSources(accountID, deviceID); err == nil { + log.Printf("Creating default Sources.xml for device %s (fallback)", deviceID) - if err := s.ds.SaveConfiguredSources(accountID, deviceID, sources); err != nil { - log.Printf("Failed to save default sources for %s: %v", deviceID, err) + if err := s.ds.SaveConfiguredSources(accountID, deviceID, sources); err != nil { + log.Printf("Failed to save default sources for %s: %v", deviceID, err) + } } }