-
Notifications
You must be signed in to change notification settings - Fork 2
/
settings.go
553 lines (471 loc) · 15.5 KB
/
settings.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
package drugdose
import (
"errors"
"fmt"
"os"
cp "github.com/otiai10/copy"
"github.com/pelletier/go-toml/v2"
)
type SourceConfig struct {
API_ADDRESS string
}
type MaxLogsPerUserSize int32
type Config struct {
MaxLogsPerUser MaxLogsPerUserSize
UseSource string
AutoFetch bool
AutoRemove bool
DBDriver string
VerbosePrinting bool
DBSettings map[string]DBSettings
Timezone string
ProxyURL string
Timeout string
CostCurrency string
}
type DBSettings struct {
Path string
Parameters string
}
const PsychonautwikiAddress string = "api.psychonautwiki.org"
const DefaultMaxLogsPerUser MaxLogsPerUserSize = 100
const DefaultSourceAddress string = PsychonautwikiAddress
const DefaultAutoFetch bool = true
const DefaultDBDir string = "GPD"
const DefaultDBName string = "gpd.db"
const DefaultAutoRemove bool = false
const DefaultDBDriver string = SqliteDriver
const DefaultMySQLAccess string = "user:password@tcp(127.0.0.1:3306)/database"
const DefaultVerbose bool = false
const DefaultTimezone string = "Local"
const DefaultProxyURL string = ""
const DefaultTimeout string = "5s"
const DefaultCostCurr string = ""
const DefaultUsername string = "defaultUser"
const DefaultSource string = "psychonautwiki"
const sourceSetFilename string = "gpd-sources.toml"
const settingsFilename string = "gpd-settings.toml"
func errorCantCreateConfig(filename string, err error, printN string) {
printName(printN, "errorCantCreateConfig(): Error, can't create config file:", filename, ";", err)
exitProgram(printN)
}
func errorCantCloseConfig(filename string, err error, printN string) {
printName(printN, "errorCantCloseConfig(): Error, can't close config file:", filename, ";", err)
exitProgram(printN)
}
func errorCantReadConfig(filename string, err error, printN string) {
printName(printN, "errorCantReadConfig(): Error, can't read config file:", filename, ";", err)
exitProgram(printN)
}
func errorCantChmodConfig(filename string, err error, printN string) {
printName(printN, "errorCantChmodConfig(): Error, can't change mode of config file:", filename, ";", err)
exitProgram(printN)
}
func otherError(filename string, err error, printN string) {
printName(printN, "otherError(): Other error for config file:", filename, ";", err)
exitProgram(printN)
}
// The name used when printing, to distinguish from other logs.
const moduleName string = "gopsydose"
// Format the name set by the caller.
func sprintPrefix(name string) string {
if name != "" {
return fmt.Sprint(moduleName + ": " + name + ": ")
}
return ""
}
// Print the name set by the caller,
// so that it's easier to track the origin of text output.
func printPrefix(name string) {
if name != "" {
fmt.Print(sprintPrefix(name))
}
}
// Print strings properly formatted for the module.
// This is so that when the module is imported, the user can better understand
// where a string is coming from.
// If you only need to add a newline, don't use this function!
func printName(name string, str ...any) {
printPrefix(name)
fmt.Println(str...)
}
// Variation of printName(), that doesn't output a newline at the end.
func printNameNoNewline(name string, str ...any) {
printPrefix(name)
fmt.Print(str...)
}
// Variation of printName(), that uses fmt.Printf() formatting.
func printNameF(name string, str string, variables ...any) {
printPrefix(name)
fmt.Printf(str, variables...)
}
// Same as printName(), but only for verbose output and is optional.
func printNameVerbose(verbose bool, name string, str ...any) {
if verbose == true {
printName(name, str...)
}
}
// Instead of printing, just return the formatted string without a newline.
func sprintName(name string, str ...any) string {
if name != "" {
return fmt.Sprintf("%s%s", sprintPrefix(name), fmt.Sprint(str...))
}
return ""
}
// Instead of printing, just return the formatted string with a newline.
func sprintfName(name string, str string, variables ...any) string {
if name != "" {
return fmt.Sprintf(sprintPrefix(name)+str, variables...)
}
return ""
}
// Instead of printing, just return the formatted string with a newline.
func sprintlnName(name string, str ...any) string {
if name != "" {
return fmt.Sprintf("%s\n", sprintName(name, str...))
}
return ""
}
// Initialise the Config struct using the default values.
//
// sourcecfg - The name of the implemented source to use.
// The meaning of "source" is for example an API server for which
// code is present in this repository.
func InitConfigStruct(sourcecfg string) Config {
cfg := Config{
MaxLogsPerUser: DefaultMaxLogsPerUser,
UseSource: sourcecfg,
AutoFetch: DefaultAutoFetch,
AutoRemove: DefaultAutoRemove,
DBDriver: DefaultDBDriver,
VerbosePrinting: DefaultVerbose,
DBSettings: nil,
Timezone: DefaultTimezone,
ProxyURL: DefaultProxyURL,
Timeout: DefaultTimeout,
CostCurrency: DefaultCostCurr,
}
return cfg
}
// InitSourceMap returns a map which for the given key (configured source)
// returns the address as it's value. The address could be an IP address,
// an URL and etc.
//
// apiAddress - the address to map to the source name from the Config struct
func (cfg *Config) InitSourceMap(apiAddress string) map[string]SourceConfig {
srcmap := map[string]SourceConfig{
cfg.UseSource: {
API_ADDRESS: apiAddress,
},
}
return srcmap
}
// InitSettingsDir creates the directory for the configuration files using the
// system path for configs and sets the proper mode to the new directory.
// It first checks if it already exists, skips the creation if true.
//
// Returns the full path to the directory as a string.
func InitSettingsDir() (error, string) {
const printN string = "InitSettingsDir()"
configdir, err := os.UserConfigDir()
if err != nil {
return fmt.Errorf("%s%w", sprintName(printN), err), ""
}
configdir = configdir + "/GPD"
_, err = os.Stat(configdir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
err = os.Mkdir(configdir, 0700)
if err != nil {
return fmt.Errorf("%s%w", sprintName(printN), err), ""
}
} else {
return fmt.Errorf("%s%w", sprintName(printN), err), ""
}
}
return nil, configdir
}
// InitSourceSettings creates the config file for the sources. This file
// contains the api name mapped to the api address. InitSourceMap() can be used
// to create the map, this function marshals it and writes it to the actual
// config file.
//
// newcfg - the source api name to api address map
//
// recreate - overwrite the current file if it exists with a new map
func (cfg *Config) InitSourceSettings(newcfg map[string]SourceConfig, recreate bool) error {
const printN string = "InitSourceSettings()"
err, setdir := InitSettingsDir()
if err != nil {
return fmt.Errorf("%s%w", sprintName(printN), err)
}
path := setdir + "/" + sourceSetFilename
_, err = os.Stat(path)
if err != nil || recreate {
if errors.Is(err, os.ErrNotExist) || recreate {
printName(printN, "Initialising config file:", path)
file, err := os.Create(path)
if err != nil {
errorCantCreateConfig(path, err, printN)
}
err = file.Chmod(0600)
if err != nil {
errorCantChmodConfig(path, err, printN)
}
mcfg, err := toml.Marshal(newcfg)
if err != nil {
return fmt.Errorf("%s%w", sprintName(printN), err)
}
_, err = file.WriteString(string(mcfg))
if err != nil {
errorCantCreateConfig(path, err, printN)
}
err = file.Close()
if err != nil {
errorCantCloseConfig(path, err, printN)
}
} else {
otherError(path, err, printN)
}
} else if err == nil {
printNameVerbose(cfg.VerbosePrinting, printN, "Config file: "+path+" ; already exists!")
return nil
}
return nil
}
// GetSourceData returns the map gotten from the source config file
// unmarshaled. The map returns for a given source name (key), an addres
// (value) for that source.
func GetSourceData() map[string]SourceConfig {
const printN string = "GetSourceData()"
err, setdir := InitSettingsDir()
if err != nil {
printName(printN, err)
exitProgram(printN)
}
path := setdir + "/" + sourceSetFilename
cfg := map[string]SourceConfig{}
file, err := os.ReadFile(path)
if err != nil {
errorCantReadConfig(path, err, printN)
}
err = toml.Unmarshal(file, &cfg)
if err != nil {
errorCantReadConfig(path, err, printN)
}
return cfg
}
// InitDBSettings returns a modified Config structure, which contains a properly
// formatted DBSettings map. Before using the modified struct, checkout if
// the returned error is not nil!
//
// dbdir - if this is set to the DefaultDBDir constant, it will try to use
// the system user directory as a path, if not the full path must be specified
//
// dbname - the name of sqlite db file
//
// mysqlaccess - the path for connecting to MySQL/MariaDB, example
// user:password@tcp(127.0.0.1:3306)/database
func (initcfg *Config) InitDBSettings(dbdir string, dbname string, mysqlaccess string) (error) {
const printN string = "InitDBSettings()"
if dbdir == DefaultDBDir {
home, err := os.UserHomeDir()
if err != nil {
err = fmt.Errorf("%s%w", sprintName(printN), err)
return err
}
path := home + "/.local/share"
_, err = os.Stat(path)
if err == nil {
home = path
} else if err != nil {
if !errors.Is(err, os.ErrNotExist) {
err = fmt.Errorf("%s%w", sprintName(printN), err)
return err
}
}
dbdir = home + "/" + dbdir
}
var dbSettings = map[string]DBSettings{
SqliteDriver: {
Path: dbdir + "/" + dbname,
Parameters: "?_pragma=busy_timeout=1000",
},
MysqlDriver: {
Path: mysqlaccess,
Parameters: "",
},
}
initcfg.DBSettings = dbSettings
return nil
}
// InitSettingsFile creates and fills the main global config file which
// is used for the Config struct. It sets the proper mode and stops the
// program on error. The data for writing to the file is taken from the passed
// Config structure.
//
// recreate - if true overwrites the currently existing config file with the
// currently passed Config struct data
//
// verbose - whether to print verbose information
func InitSettingsFile(recreate bool, verbose bool, sourcecfg string, dbDir string, dbName string, mysqlAccess string) {
const printN string = "InitSettingsFile()"
err, setdir := InitSettingsDir()
if err != nil {
printName(printN, err)
exitProgram(printN)
}
path := setdir + "/" + settingsFilename
_, err = os.Stat(path)
if err != nil || recreate {
if errors.Is(err, os.ErrNotExist) || recreate {
initconf := InitConfigStruct(sourcecfg)
err := initconf.InitDBSettings(dbDir, dbName, mysqlAccess)
if err != nil {
printName(printN, err)
exitProgram(printN)
}
mcfg, err := toml.Marshal(initconf)
if err != nil {
printName(printN, err)
exitProgram(printN)
}
printName(printN, "Initialising config file:", path)
file, err := os.Create(path)
if err != nil {
errorCantCreateConfig(path, err, printN)
}
err = file.Chmod(0600)
if err != nil {
errorCantChmodConfig(path, err, printN)
}
_, err = file.WriteString(string(mcfg))
if err != nil {
errorCantCreateConfig(path, err, printN)
}
err = file.Close()
if err != nil {
errorCantCloseConfig(path, err, printN)
}
} else {
otherError(path, err, printN)
}
} else {
printNameVerbose(verbose, printN, "Config file: "+path+" ; already exists!")
}
}
// InitNamesFiles copies to the OS config directory, the directory containing
// the toml files for configuring alternative names. If it doesn't exists in
// the config directory, the code checks if it's present in the current working
// directory. If it is, it's copied over to the OS config directory and used
// later to fill in the database.
func (cfg *Config) InitNamesFiles() error {
const printN string = "InitNamesFiles()"
err, setdir := InitSettingsDir()
if err != nil {
return fmt.Errorf("%s%w", sprintName(printN), err)
}
var CopyToPath string = setdir + "/" + allNamesConfigsDir
// Check if names directory exists in config directory.
// If it doen't, continue.
_, err = os.Stat(CopyToPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// Check if names directory exists in working directory.
// If it does, copy it to config directory.
_, err := os.Stat(allNamesConfigsDir)
if err == nil {
printName(printN, "Found the config directory in the working directory:",
allNamesConfigsDir, "; attempt at making a copy to:", CopyToPath)
// Sync (true) - flush everything to disk, to make sure everything is immediately copied
cpOpt := cp.Options{
Sync: true,
}
err = cp.Copy(allNamesConfigsDir, CopyToPath, cpOpt)
if err != nil {
return fmt.Errorf("%s%w", sprintName(printN), err)
} else if err == nil {
printName(printN, "Done copying to:", CopyToPath)
}
} else {
return fmt.Errorf("%s%w", sprintName(printN), err)
}
} else {
return fmt.Errorf("%s%w", sprintName(printN), err)
}
} else if err == nil {
printNameVerbose(cfg.VerbosePrinting, printN, "Name config already exists:", CopyToPath,
"; will not copy the config directory from the working directory:", allNamesConfigsDir)
}
return nil
}
// Get the Config structure data marshaled from the global config file.
// Returns the Config structure. Stops the program if an error is not nil.
func GetSettings() Config {
const printN string = "GetSettings()"
cfg := Config{}
err, setdir := InitSettingsDir()
if err != nil {
printName(printN, err)
exitProgram(printN)
}
path := setdir + "/" + settingsFilename
file, err := os.ReadFile(path)
if err != nil {
errorCantReadConfig(path, err, printN)
}
err = toml.Unmarshal(file, &cfg)
if err != nil {
errorCantReadConfig(path, err, printN)
}
return cfg
}
// InitAllSettings initializes the Config struct with default values,
// uses the struct to create the global config file, unmarshales the newly
// created config and stores the struct in a new variable, initializes the
// source map using the set source in the Config struct and the given API
// address, uses the map to create the source config file and returns the
// Config struct. It then copies the directory containing the toml files for
// filling alternative names to the database.
//
// Basically all you probably would want to do anyway, but in a single function.
//
// Checkout InitConfigStruct(), InitDBSettings(), InitSettingsFile()
// GetSettings(), InitSourceMap(), InitSourceSettings() for more info.
//
// sourcecfg - what source name to set by default for the Config struct
//
// dbDir - the path for using the sqlite database file
//
// dbName - the name of the sqlite db file
//
// mysqlAccess - the path for accessing an MySQL/MariaDB database
//
// recreateSettings - overwrite the settings file even if it exists
//
// recreateSources - overwrite the source settings file even if it exists
//
// verbose - if true print verbose information
//
// apiAddress - the address to use when initializing the source map
func InitAllSettings(sourcecfg string, dbDir string, dbName string, mysqlAccess string,
recreateSettings bool, recreateSources bool, verbose bool, apiAddress string) Config {
const printN string = "InitAllSettings()"
InitSettingsFile(recreateSettings, verbose, sourcecfg, dbDir, dbName, mysqlAccess)
gotsetcfg := GetSettings()
if verbose == true {
gotsetcfg.VerbosePrinting = true
}
gotsrc := gotsetcfg.InitSourceMap(apiAddress)
err := gotsetcfg.InitSourceSettings(gotsrc, recreateSources)
if err != nil {
printName(printN, "The sources file wasn't initialised: ", err)
exitProgram(printN)
}
err = gotsetcfg.InitNamesFiles()
if err != nil {
printName(printN, "The names files were not copied: ", err)
exitProgram(printN)
}
return gotsetcfg
}