package storage import ( "encoding/json" "errors" "fmt" "io" "os" "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/models" "golang.org/x/text/cases" "golang.org/x/text/language" ) // readFromFile reads the servers from server.json. // It only reads servers that have the same version as the hardcoded servers version // to avoid JSON unmarshaling errors. func (s *Storage) readFromFile(filepath string, hardcoded models.AllServers) ( servers models.AllServers, err error) { file, err := os.Open(filepath) if os.IsNotExist(err) { return servers, nil } else if err != nil { return servers, err } b, err := io.ReadAll(file) if err != nil { return servers, err } if err := file.Close(); err != nil { return servers, err } return s.extractServersFromBytes(b, hardcoded) } func (s *Storage) extractServersFromBytes(b []byte, hardcoded models.AllServers) ( servers models.AllServers, err error) { rawMessages := make(map[string]json.RawMessage) if err := json.Unmarshal(b, &rawMessages); err != nil { return servers, fmt.Errorf("cannot decode servers: %w", err) } // Note schema version is at map key "version" as number allProviders := providers.All() servers.ProviderToServers = make(map[string]models.Servers, len(allProviders)) titleCaser := cases.Title(language.English) for _, provider := range allProviders { hardcoded, ok := hardcoded.ProviderToServers[provider] if !ok { panic(fmt.Sprintf("provider %s not found in hardcoded servers map", provider)) } rawMessage, ok := rawMessages[provider] if !ok { // If the provider is not found in the data bytes, just don't set it in // the providers map. That way the hardcoded servers will override them. // This is user provided and could come from different sources in the // future (e.g. a file or API request). continue } mergedServers, versionsMatch, err := s.readServers(provider, hardcoded, rawMessage, titleCaser) if err != nil { return models.AllServers{}, err } else if !versionsMatch { // mergedServers is the empty struct in this case, so don't set the key // in the providerToServers map. continue } servers.ProviderToServers[provider] = mergedServers } return servers, nil } var ( errDecodeProvider = errors.New("cannot decode servers for provider") ) func (s *Storage) readServers(provider string, hardcoded models.Servers, rawMessage json.RawMessage, titleCaser cases.Caser) (servers models.Servers, versionsMatch bool, err error) { provider = titleCaser.String(provider) var persistedServers models.Servers err = json.Unmarshal(rawMessage, &persistedServers) if err != nil { return servers, false, fmt.Errorf("%w: %s: %s", errDecodeProvider, provider, err) } versionsMatch = hardcoded.Version == persistedServers.Version if !versionsMatch { s.logVersionDiff(provider, hardcoded.Version, persistedServers.Version) return servers, versionsMatch, nil } return persistedServers, versionsMatch, nil }