github.com/gogf/gf/v2@v2.7.4/os/gcfg/gcfg_adapter_file.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gcfg 8 9 import ( 10 "context" 11 12 "github.com/gogf/gf/v2/container/garray" 13 "github.com/gogf/gf/v2/container/gmap" 14 "github.com/gogf/gf/v2/container/gvar" 15 "github.com/gogf/gf/v2/encoding/gjson" 16 "github.com/gogf/gf/v2/errors/gerror" 17 "github.com/gogf/gf/v2/internal/command" 18 "github.com/gogf/gf/v2/internal/intlog" 19 "github.com/gogf/gf/v2/os/gfile" 20 "github.com/gogf/gf/v2/os/gfsnotify" 21 "github.com/gogf/gf/v2/os/gres" 22 "github.com/gogf/gf/v2/util/gmode" 23 "github.com/gogf/gf/v2/util/gutil" 24 ) 25 26 // AdapterFile implements interface Adapter using file. 27 type AdapterFile struct { 28 defaultFileNameOrPath string // Default configuration file name or file path. 29 searchPaths *garray.StrArray // Searching path array. 30 jsonMap *gmap.StrAnyMap // The pared JSON objects for configuration files. 31 violenceCheck bool // Whether it does violence check in value index searching. It affects the performance when set true(false in default). 32 } 33 34 const ( 35 commandEnvKeyForFile = "gf.gcfg.file" // commandEnvKeyForFile is the configuration key for command argument or environment configuring file name. 36 commandEnvKeyForPath = "gf.gcfg.path" // commandEnvKeyForPath is the configuration key for command argument or environment configuring directory path. 37 ) 38 39 var ( 40 supportedFileTypes = []string{"toml", "yaml", "yml", "json", "ini", "xml", "properties"} // All supported file types suffixes. 41 localInstances = gmap.NewStrAnyMap(true) // Instances map containing configuration instances. 42 customConfigContentMap = gmap.NewStrStrMap(true) // Customized configuration content. 43 44 // Prefix array for trying searching in resource manager. 45 resourceTryFolders = []string{ 46 "", "/", "config/", "config", "/config", "/config/", 47 "manifest/config/", "manifest/config", "/manifest/config", "/manifest/config/", 48 } 49 50 // Prefix array for trying searching in local system. 51 localSystemTryFolders = []string{"", "config/", "manifest/config"} 52 ) 53 54 // NewAdapterFile returns a new configuration management object. 55 // The parameter `file` specifies the default configuration file name for reading. 56 func NewAdapterFile(fileNameOrPath ...string) (*AdapterFile, error) { 57 var ( 58 err error 59 usedFileNameOrPath = DefaultConfigFileName 60 ) 61 if len(fileNameOrPath) > 0 { 62 usedFileNameOrPath = fileNameOrPath[0] 63 } else { 64 // Custom default configuration file name from command line or environment. 65 if customFile := command.GetOptWithEnv(commandEnvKeyForFile); customFile != "" { 66 usedFileNameOrPath = customFile 67 } 68 } 69 config := &AdapterFile{ 70 defaultFileNameOrPath: usedFileNameOrPath, 71 searchPaths: garray.NewStrArray(true), 72 jsonMap: gmap.NewStrAnyMap(true), 73 } 74 // Customized dir path from env/cmd. 75 if customPath := command.GetOptWithEnv(commandEnvKeyForPath); customPath != "" { 76 if gfile.Exists(customPath) { 77 if err = config.SetPath(customPath); err != nil { 78 return nil, err 79 } 80 } else { 81 return nil, gerror.Newf(`configuration directory path "%s" does not exist`, customPath) 82 } 83 } else { 84 // ================================================================================ 85 // Automatic searching directories. 86 // It does not affect adapter object cresting if these directories do not exist. 87 // ================================================================================ 88 89 // Dir path of working dir. 90 if err = config.AddPath(gfile.Pwd()); err != nil { 91 intlog.Errorf(context.TODO(), `%+v`, err) 92 } 93 94 // Dir path of main package. 95 if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { 96 if err = config.AddPath(mainPath); err != nil { 97 intlog.Errorf(context.TODO(), `%+v`, err) 98 } 99 } 100 101 // Dir path of binary. 102 if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { 103 if err = config.AddPath(selfPath); err != nil { 104 intlog.Errorf(context.TODO(), `%+v`, err) 105 } 106 } 107 } 108 return config, nil 109 } 110 111 // SetViolenceCheck sets whether to perform hierarchical conflict checking. 112 // This feature needs to be enabled when there is a level symbol in the key name. 113 // It is off in default. 114 // 115 // Note that, turning on this feature is quite expensive, and it is not recommended 116 // allowing separators in the key names. It is best to avoid this on the application side. 117 func (a *AdapterFile) SetViolenceCheck(check bool) { 118 a.violenceCheck = check 119 a.Clear() 120 } 121 122 // SetFileName sets the default configuration file name. 123 func (a *AdapterFile) SetFileName(fileNameOrPath string) { 124 a.defaultFileNameOrPath = fileNameOrPath 125 } 126 127 // GetFileName returns the default configuration file name. 128 func (a *AdapterFile) GetFileName() string { 129 return a.defaultFileNameOrPath 130 } 131 132 // Get retrieves and returns value by specified `pattern`. 133 // It returns all values of current Json object if `pattern` is given empty or string ".". 134 // It returns nil if no value found by `pattern`. 135 // 136 // We can also access slice item by its index number in `pattern` like: 137 // "list.10", "array.0.name", "array.0.1.id". 138 // 139 // It returns a default value specified by `def` if value for `pattern` is not found. 140 func (a *AdapterFile) Get(ctx context.Context, pattern string) (value interface{}, err error) { 141 j, err := a.getJson() 142 if err != nil { 143 return nil, err 144 } 145 if j != nil { 146 return j.Get(pattern).Val(), nil 147 } 148 return nil, nil 149 } 150 151 // Set sets value with specified `pattern`. 152 // It supports hierarchical data access by char separator, which is '.' in default. 153 // It is commonly used for updates certain configuration value in runtime. 154 // Note that, it is not recommended using `Set` configuration at runtime as the configuration would be 155 // automatically refreshed if underlying configuration file changed. 156 func (a *AdapterFile) Set(pattern string, value interface{}) error { 157 j, err := a.getJson() 158 if err != nil { 159 return err 160 } 161 if j != nil { 162 return j.Set(pattern, value) 163 } 164 return nil 165 } 166 167 // Data retrieves and returns all configuration data as map type. 168 func (a *AdapterFile) Data(ctx context.Context) (data map[string]interface{}, err error) { 169 j, err := a.getJson() 170 if err != nil { 171 return nil, err 172 } 173 if j != nil { 174 return j.Var().Map(), nil 175 } 176 return nil, nil 177 } 178 179 // MustGet acts as function Get, but it panics if error occurs. 180 func (a *AdapterFile) MustGet(ctx context.Context, pattern string) *gvar.Var { 181 v, err := a.Get(ctx, pattern) 182 if err != nil { 183 panic(err) 184 } 185 return gvar.New(v) 186 } 187 188 // Clear removes all parsed configuration files content cache, 189 // which will force reload configuration content from file. 190 func (a *AdapterFile) Clear() { 191 a.jsonMap.Clear() 192 } 193 194 // Dump prints current Json object with more manually readable. 195 func (a *AdapterFile) Dump() { 196 if j, _ := a.getJson(); j != nil { 197 j.Dump() 198 } 199 } 200 201 // Available checks and returns whether configuration of given `file` is available. 202 func (a *AdapterFile) Available(ctx context.Context, fileName ...string) bool { 203 checkFileName := gutil.GetOrDefaultStr(a.defaultFileNameOrPath, fileName...) 204 // Custom configuration content exists. 205 if a.GetContent(checkFileName) != "" { 206 return true 207 } 208 // Configuration file exists in system path. 209 if path, _ := a.GetFilePath(checkFileName); path != "" { 210 return true 211 } 212 return false 213 } 214 215 // autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main 216 // to the searching path list if it's currently in development environment. 217 func (a *AdapterFile) autoCheckAndAddMainPkgPathToSearchPaths() { 218 if gmode.IsDevelop() { 219 mainPkgPath := gfile.MainPkgPath() 220 if mainPkgPath != "" { 221 if !a.searchPaths.Contains(mainPkgPath) { 222 a.searchPaths.Append(mainPkgPath) 223 } 224 } 225 } 226 } 227 228 // getJson returns a *gjson.Json object for the specified `file` content. 229 // It would print error if file reading fails. It returns nil if any error occurs. 230 func (a *AdapterFile) getJson(fileNameOrPath ...string) (configJson *gjson.Json, err error) { 231 var ( 232 usedFileNameOrPath = a.defaultFileNameOrPath 233 ) 234 if len(fileNameOrPath) > 0 && fileNameOrPath[0] != "" { 235 usedFileNameOrPath = fileNameOrPath[0] 236 } else { 237 usedFileNameOrPath = a.defaultFileNameOrPath 238 } 239 // It uses json map to cache specified configuration file content. 240 result := a.jsonMap.GetOrSetFuncLock(usedFileNameOrPath, func() interface{} { 241 var ( 242 content string 243 filePath string 244 ) 245 // The configured content can be any kind of data type different from its file type. 246 isFromConfigContent := true 247 if content = a.GetContent(usedFileNameOrPath); content == "" { 248 isFromConfigContent = false 249 filePath, err = a.GetFilePath(usedFileNameOrPath) 250 if err != nil { 251 return nil 252 } 253 if filePath == "" { 254 return nil 255 } 256 if file := gres.Get(filePath); file != nil { 257 content = string(file.Content()) 258 } else { 259 content = gfile.GetContents(filePath) 260 } 261 } 262 // Note that the underlying configuration json object operations are concurrent safe. 263 dataType := gjson.ContentType(gfile.ExtName(filePath)) 264 if gjson.IsValidDataType(dataType) && !isFromConfigContent { 265 configJson, err = gjson.LoadContentType(dataType, content, true) 266 } else { 267 configJson, err = gjson.LoadContent(content, true) 268 } 269 if err != nil { 270 if filePath != "" { 271 err = gerror.Wrapf(err, `load config file "%s" failed`, filePath) 272 } else { 273 err = gerror.Wrap(err, `load configuration failed`) 274 } 275 return nil 276 } 277 configJson.SetViolenceCheck(a.violenceCheck) 278 // Add monitor for this configuration file, 279 // any changes of this file will refresh its cache in Config object. 280 if filePath != "" && !gres.Contains(filePath) { 281 _, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) { 282 a.jsonMap.Remove(usedFileNameOrPath) 283 }) 284 if err != nil { 285 return nil 286 } 287 } 288 return configJson 289 }) 290 if result != nil { 291 return result.(*gjson.Json), err 292 } 293 return 294 }