summary refs log blame commit diff stats
path: root/svc/conf.go
blob: 8abb4a697c121a1a81370b7139ab2d887dbedf33 (plain) (tree)
1
2
3
4
5
6
7
8


                                                      



                       
              





                                      

                                                  







                                                          

                                                                 


                                       

                                     








                                                  





















































                                                                                          
                   
                                                 

                         

                           





                                                    
                           





















                                                    
                    



                         

 

                                              









                                                                                                       
                                                        







                                                                  
 
                                                              
                                                                             
 







                                      

                                      








                                                      
 






                                                                                                          

                                    
                        












                                                           




                                                    
 
 


                                               
                   




                                                    

                                                                         


                                            

                                                        



                                                                             

                                                              















                                                                             
                                    






                                                                       
 
package svc // import "github.com/getwtxt/getwtxt/svc"

import (
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

// Configuration values are held in an instance of
// this struct.
type Configuration struct {
	Mu            sync.RWMutex
	Port          int           `yaml:"ListenPort"`
	LogFile       string        `yaml:"LogFile"`
	DBType        string        `yaml:"DatabaseType"`
	DBPath        string        `yaml:"DatabasePath"`
	AssetsDir     string        `yaml:"-"`
	StdoutLogging bool          `yaml:"StdoutLogging"`
	CacheInterval time.Duration `yaml:"StatusFetchInterval"`
	DBInterval    time.Duration `yaml:"DatabasePushInterval"`
	Instance      `yaml:"Instance"`
}

// Instance refers to meta data about
// this specific instance of getwtxt
type Instance struct {
	Vers  string `yaml:"-"`
	Name  string `yaml:"Instance.SiteName"`
	URL   string `yaml:"Instance.URL"`
	Owner string `yaml:"Instance.OwnerName"`
	Mail  string `yaml:"Instance.Email"`
	Desc  string `yaml:"Instance.Description"`
}

// This is a wrapper for a *time.Ticker
// that adds another channel. It's used
// to signal to the ticker goroutines
// that they should stop the tickers
// and exit.
type tick struct {
	isDB bool
	t    *time.Ticker
	exit chan bool
}

// Creates a new instance of a tick
func initTicker(db bool, interval time.Duration) *tick {
	return &tick{
		isDB: db,
		t:    time.NewTicker(interval),
		exit: make(chan bool, 1),
	}
}

// Sends the signal to stop the tickers
// and for their respective goroutines
// to exit.
func killTickers() {
	ct := <-cTickC
	dt := <-dbTickC
	ct.exit <- true
	dt.exit <- true
}

// Waits for a signal from the database
// *tick. Either stops the ticker and
// kills the goroutine or it will
// update cache / push the DB to disk
func dataTimer(tkr *tick) {
	for {
		select {
		case signal := <-tkr.t.C:
			if tkr.isDB {
				errLog("", pushDB())
				log.Printf("Database push took: %v\n", time.Since(signal))
				continue
			}
			cacheUpdate()
			log.Printf("Cache update took: %v\n", time.Since(signal))
		case <-tkr.exit:
			tkr.t.Stop()
			return
		}
	}
}

// Called on start-up. Initializes everything
// related to configuration values.
func initConfig() {
	log.Printf("Loading configuration ...\n")

	parseConfigFlag()
	setConfigDefaults()

	if err := viper.ReadInConfig(); err != nil {
		log.Printf("%v\n", err.Error())
		log.Printf("Using defaults ...\n")
		bindConfig()
		return
	}
	viper.WatchConfig()
	viper.OnConfigChange(reInit)

	bindConfig()
}

// Called when a change is detected in the
// configuration file. Closes log file,
// closes database connection, stops all
// tickers, then binds new configuration
// values, opens new log file, connects to
// new database, and starts new cache and
// database tickers.
func reInit(e fsnotify.Event) {
	log.Printf("%v. Reloading...\n", e.String())

	if !confObj.StdoutLogging {
		closeLog <- true
	}

	killTickers()
	killDB()

	bindConfig()

	initLogging()
	initDatabase()
	initPersistence()
}

// Registers either stdout or a specified file
// to the default logger.
func initLogging() {

	confObj.Mu.RLock()

	if confObj.StdoutLogging {
		log.SetOutput(os.Stdout)

	} else {

		logfile, err := os.OpenFile(confObj.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
		errLog("Could not open log file: ", err)

		// Listen for the signal to close the log file
		// in a separate thread. Passing it as an argument
		// to prevent race conditions when the config is
		// reloaded.
		go func(logfile *os.File) {

			<-closeLog

			log.Printf("Closing log file ...\n\n")
			errLog("Could not close log file: ", logfile.Close())

		}(logfile)

		log.SetOutput(logfile)
	}

	confObj.Mu.RUnlock()
}

// Default values should a config file
// not be available.
func setConfigDefaults() {
	viper.SetDefault("ListenPort", 9001)
	viper.SetDefault("LogFile", "getwtxt.log")
	viper.SetDefault("DatabasePath", "getwtxt.db")
	viper.SetDefault("AssetsDirectory", "assets")
	viper.SetDefault("DatabaseType", "leveldb")
	viper.SetDefault("StdoutLogging", false)
	viper.SetDefault("ReCacheInterval", "1h")
	viper.SetDefault("DatabasePushInterval", "5m")

	viper.SetDefault("Instance.SiteName", "getwtxt")
	viper.SetDefault("Instance.OwnerName", "Anonymous Microblogger")
	viper.SetDefault("Instance.Email", "nobody@knows")
	viper.SetDefault("Instance.URL", "https://twtxt.example.com")
	viper.SetDefault("Instance.Description", "A fast, resilient twtxt registry server written in Go!")
}

// Reads data from the configuration
// flag and acts accordingly.
func parseConfigFlag() {
	if *flagConfFile == "" {
		viper.SetConfigName("getwtxt")
		viper.SetConfigType("yml")
		viper.AddConfigPath(".")
		viper.AddConfigPath("/usr/local/getwtxt")
		viper.AddConfigPath("/etc")
		viper.AddConfigPath("/usr/local/etc")

	} else {
		path, file := filepath.Split(*flagConfFile)
		if path == "" {
			path = "."
		}
		filename := strings.Split(file, ".")
		viper.SetConfigName(filename[0])
		viper.SetConfigType(filename[1])
		viper.AddConfigPath(path)
	}
}

// Simply goes down the list of fields
// in the confObj instance of &Configuration{},
// assigning values from the config file.
func bindConfig() {
	confObj.Mu.Lock()

	confObj.Port = viper.GetInt("ListenPort")
	confObj.LogFile = viper.GetString("LogFile")

	confObj.DBType = strings.ToLower(viper.GetString("DatabaseType"))
	if *flagDBType != "" {
		confObj.DBType = *flagDBType
	}

	confObj.DBPath = viper.GetString("DatabasePath")
	if *flagDBPath != "" {
		confObj.DBPath = *flagDBPath
	}
	log.Printf("Using %v database: %v\n", confObj.DBType, confObj.DBPath)

	confObj.AssetsDir = viper.GetString("AssetsDirectory")
	if *flagAssets != "" {
		confObj.AssetsDir = *flagAssets
	}

	confObj.StdoutLogging = viper.GetBool("StdoutLogging")
	if confObj.StdoutLogging {
		log.Printf("Logging to stdout\n")
	} else {
		log.Printf("Logging to %v\n", confObj.LogFile)
	}

	confObj.CacheInterval = viper.GetDuration("StatusFetchInterval")
	log.Printf("User status fetch interval: %v\n", confObj.CacheInterval)

	confObj.DBInterval = viper.GetDuration("DatabasePushInterval")
	log.Printf("Database push interval: %v\n", confObj.DBInterval)

	confObj.Instance.Vers = Vers
	confObj.Instance.Name = viper.GetString("Instance.SiteName")
	confObj.Instance.URL = viper.GetString("Instance.URL")
	confObj.Instance.Owner = viper.GetString("Instance.OwnerName")
	confObj.Instance.Mail = viper.GetString("Instance.Email")
	confObj.Instance.Desc = viper.GetString("Instance.Description")

	confObj.Mu.Unlock()
}