chore(all): memory and thread safe storage

- settings: get filter choices from storage for settings validation
- updater: update servers to the storage
- storage: minimal deep copying and data duplication
- storage: add merged servers mutex for thread safety
- connection: filter servers in storage
- formatter: format servers to Markdown in storage
- PIA: get server by name from storage directly
- Updater: get servers count from storage directly
- Updater: equality check done in storage, fix #882
This commit is contained in:
Quentin McGaw
2022-06-05 14:58:46 +00:00
parent 1e6b4ed5eb
commit 36b504609b
84 changed files with 1267 additions and 877 deletions

View File

@@ -6,7 +6,6 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
@@ -23,7 +22,7 @@ type Provider struct {
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
func (p *Provider) validate(vpnType string, storage Storage) (err error) {
// Validate Name
var validNames []string
if vpnType == vpn.OpenVPN {
@@ -42,7 +41,7 @@ func (p *Provider) validate(vpnType string, allServers models.AllServers) (err e
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
}
err = p.ServerSelection.validate(*p.Name, allServers)
err = p.ServerSelection.validate(*p.Name, storage)
if err != nil {
return fmt.Errorf("server selection: %w", err)
}

View File

@@ -68,21 +68,19 @@ var (
)
func (ss *ServerSelection) validate(vpnServiceProvider string,
allServers models.AllServers) (err error) {
storage Storage) (err error) {
switch ss.VPN {
case vpn.OpenVPN, vpn.Wireguard:
default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
}
countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, allServers)
filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage)
if err != nil {
return err // already wrapped error
}
err = validateServerFilters(*ss, countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices)
err = validateServerFilters(*ss, filterChoices)
if err != nil {
if errors.Is(err, helpers.ErrNoChoice) {
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
@@ -135,63 +133,48 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
return nil
}
func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
allServers models.AllServers) (
countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices []string,
func getLocationFilterChoices(vpnServiceProvider string,
ss *ServerSelection, storage Storage) (filterChoices models.FilterChoices,
err error) {
providerServers, ok := allServers.ProviderToServers[vpnServiceProvider]
if !ok && vpnServiceProvider != providers.Custom {
panic(fmt.Sprintf("VPN service provider unknown: %s", vpnServiceProvider))
}
servers := providerServers.Servers
countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.ExtractCities(servers)
ispChoices = validation.ExtractISPs(servers)
nameChoices = validation.ExtractServerNames(servers)
hostnameChoices = validation.ExtractHostnames(servers)
filterChoices = storage.GetFilterChoices(vpnServiceProvider)
if vpnServiceProvider == providers.Surfshark {
// // Retro compatibility
// TODO v4 remove
regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrRegionNotValid, err)
filterChoices.Regions = append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, filterChoices.Regions); err != nil {
return models.FilterChoices{}, fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
*ss = surfsharkRetroRegion(*ss)
}
return countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices, nil
return filterChoices, nil
}
// validateServerFilters validates filters against the choices given as arguments.
// Set an argument to nil to pass the check for a particular filter.
func validateServerFilters(settings ServerSelection,
countryChoices, regionChoices, cityChoices, ispChoices,
nameChoices, hostnameChoices []string) (err error) {
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) {
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil {
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil {
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
}
if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil {
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil {
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil {
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
}

View File

@@ -24,10 +24,14 @@ type Settings struct {
Pprof pprof.Settings
}
type Storage interface {
GetFilterChoices(provider string) models.FilterChoices
}
// Validate validates all the settings and returns an error
// if one of them is not valid.
// TODO v4 remove pointer for receiver (because of Surfshark).
func (s *Settings) Validate(allServers models.AllServers) (err error) {
func (s *Settings) Validate(storage Storage) (err error) {
nameToValidation := map[string]func() error{
"control server": s.ControlServer.validate,
"dns": s.DNS.validate,
@@ -42,7 +46,7 @@ func (s *Settings) Validate(allServers models.AllServers) (err error) {
"version": s.Version.validate,
// Pprof validation done in pprof constructor
"VPN": func() error {
return s.VPN.validate(allServers)
return s.VPN.validate(storage)
},
}
@@ -91,7 +95,7 @@ func (s *Settings) MergeWith(other Settings) {
}
func (s *Settings) OverrideWith(other Settings,
allServers models.AllServers) (err error) {
storage Storage) (err error) {
patchedSettings := s.copy()
patchedSettings.ControlServer.overrideWith(other.ControlServer)
patchedSettings.DNS.overrideWith(other.DNS)
@@ -106,7 +110,7 @@ func (s *Settings) OverrideWith(other Settings,
patchedSettings.Version.overrideWith(other.Version)
patchedSettings.VPN.overrideWith(other.VPN)
patchedSettings.Pprof.MergeWith(other.Pprof)
err = patchedSettings.Validate(allServers)
err = patchedSettings.Validate(storage)
if err != nil {
return err
}

View File

@@ -35,17 +35,18 @@ func (u Updater) Validate() (err error) {
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
}
for i, provider := range u.Providers {
validProviders := providers.All()
for _, provider := range u.Providers {
valid := false
for _, validProvider := range providers.All() {
for _, validProvider := range validProviders {
if provider == validProvider {
valid = true
break
}
}
if !valid {
return fmt.Errorf("%w: %s at index %d",
ErrVPNProviderNameNotValid, provider, i)
return fmt.Errorf("%w: %q can only be one of %s",
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders))
}
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
@@ -21,7 +20,7 @@ type VPN struct {
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) validate(allServers models.AllServers) (err error) {
func (v *VPN) validate(storage Storage) (err error) {
// Validate Type
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
@@ -29,7 +28,7 @@ func (v *VPN) validate(allServers models.AllServers) (err error) {
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
}
err = v.Provider.validate(v.Type, allServers)
err = v.Provider.validate(v.Type, storage)
if err != nil {
return fmt.Errorf("provider settings: %w", err)
}