replace the old clumsy overlay mechanism with a new reflection-based overlay mechanism

This commit is contained in:
2026-03-03 22:16:44 -07:00
parent ac31333c59
commit dee77d6f71
+52 -117
View File
@@ -15,6 +15,7 @@ import (
"errors"
"fmt"
"os"
"reflect"
"regexp"
"strconv"
@@ -149,123 +150,57 @@ func init() {
}
}
/* overlayString is a helper that takes a loaded or defaulted string and returns it.
* Parameters:
* loaded - The string loaded from a configuration file.
* defaulted - The default value of this string.
* Returns:
* loaded if it's not empty, otherwise defaulted.
*/
func overlayString(loaded string, defaulted string) string {
if loaded == "" {
return defaulted
// overlayStructValue overlays the "loaded" and "defaults" structure onto the "dest" structure. All parameters are AmConfig structures.
func overlayStructValue(dest, loaded, defaults reflect.Value) {
typ := dest.Type()
for i := 0; i < dest.NumField(); i++ {
fldDest := dest.Field(i)
fldLoaded := loaded.Field(i)
fldDefaults := defaults.Field(i)
if fldDest.Kind() == reflect.Struct {
// nested struct - call recursively
overlayStructValue(fldDest, fldLoaded, fldDefaults)
} else if fldDest.Kind() == reflect.String {
// string field handling
s := fldLoaded.Interface().(string)
if s == "" {
fldDest.Set(fldDefaults)
} else {
fldDest.Set(fldLoaded)
}
} else if fldDest.Kind() == reflect.Array || fldDest.Kind() == reflect.Slice {
// array of strings - merge the two arrays
m := make(map[string]bool)
for i := 0; i < fldDefaults.Len(); i++ {
m[fldDefaults.Index(i).String()] = true
}
for i := 0; i < fldLoaded.Len(); i++ {
m[fldLoaded.Index(i).String()] = true
}
rc := make([]string, 0, len(m))
for s := range m {
rc = append(rc, s)
}
fldDest.Set(reflect.ValueOf(rc))
} else if fldDest.Kind() == reflect.Bool {
// just "or" the boolean values together
b1 := fldDefaults.Bool()
b2 := fldLoaded.Bool()
fldDest.SetBool(b1 || b2)
} else if fldDest.CanInt() {
// int field handling
n := fldLoaded.Int()
if n == 0 {
fldDest.Set(fldDefaults)
} else {
fldDest.Set(fldLoaded)
}
} else {
// if we see this message, this function needs more work
errField := typ.Field(i)
log.Errorf("*** unable to deal with field %s of type %s", errField.Name, typ.Name())
}
}
return loaded
}
/* overlayString is a helper that takes a loaded or defaulted string array and returns it. (It merges the two
* if two different arrays are specified.)
* Parameters:
* loaded - The array loaded from a configuration file.
* defaulted - The default value of this array.
* Returns:
* Merged version of the two arrays.
*/
func overlayStringArray(loaded, defaulted []string) []string {
m := make(map[string]bool)
for _, s := range defaulted {
m[s] = true
}
for _, s := range loaded {
m[s] = true
}
rc := make([]string, 0, len(m))
for s := range m {
rc = append(rc, s)
}
return rc
}
/* overlayInt is a helper that takes a loaded or defaulted integer and returns it.
* Parameters:
* loaded - The integer loaded from a configuration file.
* defaulted - The default value of this integer.
* Returns:
* loaded if it's not empty, otherwise defaulted.
*/
func overlayInt(loaded int, defaulted int) int {
if loaded != 0 {
return loaded
}
return defaulted
}
/* overlayOptionFlag is a helper that takes a loaded or defaulted option flag and returns it.
* Parameters:
* loaded - The option flag loaded from a configuration file.
* defaulted - The default value of this option flag.
* Returns:
* Combined value.
*/
func overlayOptionFlag(loaded, defaulted bool) bool {
return loaded || defaulted
}
/* overlayConfig takes two configuration structures and overlays them to create the third.
* Parameters:
* dest - Points to the destination configuration structure.
* loaded - Points to the loaded configuration structure.
* defaults - Points to the default configuration structure.
*/
func overlayConfig(dest *AmConfig, loaded *AmConfig, defaults *AmConfig) {
dest.Site.BaseURL = overlayString(loaded.Site.BaseURL, defaults.Site.BaseURL)
dest.Site.Title = overlayString(loaded.Site.Title, defaults.Site.Title)
dest.Site.TopRefresh = overlayInt(loaded.Site.TopRefresh, defaults.Site.TopRefresh)
dest.Site.LoginCookieName = overlayString(loaded.Site.LoginCookieName, defaults.Site.LoginCookieName)
dest.Site.LoginCookieAge = overlayInt(loaded.Site.LoginCookieAge, defaults.Site.LoginCookieAge)
dest.Site.SessionExpire = overlayString(loaded.Site.SessionExpire, defaults.Site.SessionExpire)
dest.Site.UserAgreement.Title = overlayString(loaded.Site.UserAgreement.Title, defaults.Site.UserAgreement.Title)
dest.Site.UserAgreement.Text = overlayString(loaded.Site.UserAgreement.Text, defaults.Site.UserAgreement.Text)
dest.Site.ExternalPath = overlayString(loaded.Site.ExternalPath, defaults.Site.ExternalPath)
dest.Database.Driver = overlayString(loaded.Database.Driver, defaults.Database.Driver)
dest.Database.Dsn = overlayString(loaded.Database.Dsn, defaults.Database.Dsn)
dest.Defaults.Language = overlayString(loaded.Defaults.Language, defaults.Defaults.Language)
dest.Defaults.TimeZone = overlayString(loaded.Defaults.TimeZone, defaults.Defaults.TimeZone)
dest.Email.Host = overlayString(loaded.Email.Host, defaults.Email.Host)
dest.Email.Port = overlayInt(loaded.Email.Port, defaults.Email.Port)
dest.Email.Tls = overlayString(loaded.Email.Tls, defaults.Email.Tls)
dest.Email.AuthType = overlayString(loaded.Email.AuthType, defaults.Email.AuthType)
dest.Email.User = overlayString(loaded.Email.User, defaults.Email.User)
dest.Email.Password = overlayString(loaded.Email.Password, defaults.Email.Password)
dest.Email.MailFromAddr = overlayString(loaded.Email.MailFromAddr, defaults.Email.MailFromAddr)
dest.Email.MailFromName = overlayString(loaded.Email.MailFromName, defaults.Email.MailFromName)
dest.Email.Signature = overlayString(loaded.Email.Signature, defaults.Email.Signature)
dest.Email.Disclaimer = overlayString(loaded.Email.Disclaimer, defaults.Email.Disclaimer)
dest.Rendering.TemplateDir = overlayString(loaded.Rendering.TemplateDir, defaults.Rendering.TemplateDir)
dest.Rendering.CookieKey = overlayString(loaded.Rendering.CookieKey, defaults.Rendering.CookieKey)
dest.Rendering.CountryList.Prioritize = overlayString(loaded.Rendering.CountryList.Prioritize, defaults.Rendering.CountryList.Prioritize)
dest.Rendering.VeniceCompatibleImageURLs = overlayOptionFlag(loaded.Rendering.VeniceCompatibleImageURLs, defaults.Rendering.VeniceCompatibleImageURLs)
dest.Posting.ExternalDictionary = overlayString(loaded.Posting.ExternalDictionary, defaults.Posting.ExternalDictionary)
dest.Posting.Uploads.MaxSize = overlayString(loaded.Posting.Uploads.MaxSize, defaults.Posting.Uploads.MaxSize)
dest.Posting.Uploads.NoCompressTypes = overlayStringArray(loaded.Posting.Uploads.NoCompressTypes, defaults.Posting.Uploads.NoCompressTypes)
dest.Tuning.WorkerTasks = overlayInt(loaded.Tuning.WorkerTasks, defaults.Tuning.WorkerTasks)
dest.Tuning.Queues.AuditWrites = overlayInt(loaded.Tuning.Queues.AuditWrites, defaults.Tuning.Queues.AuditWrites)
dest.Tuning.Queues.ContextRecycle = overlayInt(loaded.Tuning.Queues.ContextRecycle, defaults.Tuning.Queues.ContextRecycle)
dest.Tuning.Queues.EmailRecycle = overlayInt(loaded.Tuning.Queues.EmailRecycle, defaults.Tuning.Queues.EmailRecycle)
dest.Tuning.Queues.EmailSend = overlayInt(loaded.Tuning.Queues.EmailSend, defaults.Tuning.Queues.EmailSend)
dest.Tuning.Queues.IPBans = overlayInt(loaded.Tuning.Queues.IPBans, defaults.Tuning.Queues.IPBans)
dest.Tuning.Queues.WorkerTasks = overlayInt(loaded.Tuning.Queues.WorkerTasks, defaults.Tuning.Queues.WorkerTasks)
dest.Tuning.Caches.Ads = overlayInt(loaded.Tuning.Caches.Ads, defaults.Tuning.Caches.Ads)
dest.Tuning.Caches.Communities = overlayInt(loaded.Tuning.Caches.Communities, defaults.Tuning.Caches.Communities)
dest.Tuning.Caches.CommunityProps = overlayInt(loaded.Tuning.Caches.CommunityProps, defaults.Tuning.Caches.CommunityProps)
dest.Tuning.Caches.Conferences = overlayInt(loaded.Tuning.Caches.Conferences, defaults.Tuning.Caches.Conferences)
dest.Tuning.Caches.ConferenceProps = overlayInt(loaded.Tuning.Caches.ConferenceProps, defaults.Tuning.Caches.ConferenceProps)
dest.Tuning.Caches.ContactInfo = overlayInt(loaded.Tuning.Caches.ContactInfo, defaults.Tuning.Caches.ContactInfo)
dest.Tuning.Caches.Members = overlayInt(loaded.Tuning.Caches.Members, defaults.Tuning.Caches.Members)
dest.Tuning.Caches.Menus = overlayInt(loaded.Tuning.Caches.Menus, defaults.Tuning.Caches.Menus)
dest.Tuning.Caches.Services = overlayInt(loaded.Tuning.Caches.Services, defaults.Tuning.Caches.Services)
dest.Tuning.Caches.Users = overlayInt(loaded.Tuning.Caches.Users, defaults.Tuning.Caches.Users)
dest.Tuning.Caches.UserProps = overlayInt(loaded.Tuning.Caches.UserProps, defaults.Tuning.Caches.UserProps)
}
// parseDataSize converts the data size in bytes, kilobytes, megabytes, or gigabytes to a number value.
@@ -311,7 +246,7 @@ func SetupConfig() {
if err = yaml.Unmarshal(data, &loadedConfig); err != nil {
panic(fmt.Sprintf("unable to load configuration file %s: %v", CommandLine.ConfigFile, err))
}
overlayConfig(&GlobalConfig, &loadedConfig, &defaultConfig)
overlayStructValue(reflect.ValueOf(&GlobalConfig).Elem(), reflect.ValueOf(&loadedConfig).Elem(), reflect.ValueOf(&defaultConfig).Elem())
} else {
GlobalConfig = defaultConfig // just copy over the defaults
}