diff --git a/README.md b/README.md index 0c8de42e0b02e7e454badc5b36eed5e3bff734b4..8a99fdaf2935791d272f35843f3c192c6c99dafb 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,11 @@ Usage of gqgmcd: --listen-address string Address for HTTP requests (default ":8080") --model string Model of Geiger Counter (default "gqgmc") --sleep-cycle int Seconds to sleep per cycle. (default 5) - --static-dir string Static files directory (default "static") - --template-dir string Template directory (default "templates") ``` The config file uses the same variables (not `config` obviously) -but with any dashes replaced with underscores. So -`static_dir = /var/lib/gqgmcd/static` for instance. +but with dashes replaced with underscores. So `sleep-cycle = 10` +for instance. ## Prometheus variables diff --git a/cmd/gqgmcd/main.go b/cmd/gqgmcd/main.go index fba7492013d7043239365c19a31ea57b964f4a1e..7cd474bc2947323695a9956b4b0afdb92ab36e37 100644 --- a/cmd/gqgmcd/main.go +++ b/cmd/gqgmcd/main.go @@ -19,13 +19,11 @@ import ( ) var ( - addr = flag.String("listen-address", ":8080", "Address for HTTP requests") - device = flag.String("device", "/dev/gqgmc", "Device for Geiger Counter") - model = flag.String("model", "gqgmc", "Model of Geiger Counter") - templateDir = flag.String("template-dir", "templates", "Template directory") - staticDir = flag.String("static-dir", "static", "Static files directory") - sleepCycle = flag.Int64("sleep-cycle", 5, "Seconds to sleep per cycle.") - cfg = flag.String("config", "gqgmc.conf", "Config file") + addr = flag.String("listen-address", ":8080", "Address for HTTP requests") + device = flag.String("device", "/dev/gqgmc", "Device for Geiger Counter") + model = flag.String("model", "gqgmc", "Model of Geiger Counter") + sleepCycle = flag.Int64("sleep-cycle", 5, "Seconds to sleep per cycle.") + cfg = flag.String("config", "", "Config file") ) func main() { @@ -36,12 +34,17 @@ func main() { return } - gc, _ := geiger.New(geiger.Config{Model: c.Model, Device: c.Device}) + gc, gcErr := geiger.New(geiger.Config{Model: c.Model, Device: c.Device}) + if gcErr != nil { + log.Printf("Error: %s\n", gcErr) + } - p := pages.New(gc, c.StaticDir, c.TemplateDir) + p := pages.New(gc, gcErr) p.Register() - m := metrics.Register(gc) - go m.Gather(c.SleepCycle) + if gcErr == nil { + m := metrics.Register(gc) + go m.Gather(c.SleepCycle) + } log.Fatal(http.ListenAndServe(c.ListenAddress, nil)) } diff --git a/config/config.go b/config/config.go index ee482474a7e66640b2ae0a941838dd9c947764b9..0683b15d18c2ce92e1896dc0727068a59c71b1f2 100644 --- a/config/config.go +++ b/config/config.go @@ -17,8 +17,6 @@ type Config struct { ListenAddress string `mapstructure:"listen_address"` Device string `mapstructure:"device"` Model string `mapstructure:"model"` - TemplateDir string `mapstructure:"template_dir"` - StaticDir string `mapstructure:"static_dir"` SleepCycle int64 `mapstructure:"sleep_cycle"` } @@ -26,18 +24,18 @@ func setDefaults() { viper.BindPFlag("listen_address", pflag.Lookup("listen-address")) viper.BindPFlag("device", pflag.Lookup("device")) viper.BindPFlag("model", pflag.Lookup("model")) - viper.BindPFlag("template_dir", pflag.Lookup("template-dir")) - viper.BindPFlag("static_dir", pflag.Lookup("static-dir")) viper.BindPFlag("sleep_cycle", pflag.Lookup("sleep-cycle")) } // ReadConfig reads the client configuration from a file into a Config struct. func ReadConfig(cfg string) (*Config, error) { setDefaults() - viper.SetConfigFile(cfg) - viper.SetConfigType("hcl") - if err := viper.ReadInConfig(); err != nil { - return nil, err + if cfg != "" { + viper.SetConfigFile(cfg) + viper.SetConfigType("hcl") + if err := viper.ReadInConfig(); err != nil { + return nil, err + } } c := &Config{} if err := viper.Unmarshal(c); err != nil { diff --git a/run_tests.sh b/run_tests.sh index 10667104b5d32357fed8c380725bb2161ff8540a..4fc4b27173945e27b1fc25028a707b6b0581be74 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -6,6 +6,7 @@ set -xue +go generate ./... go install -v ./cmd/* go list ./... |grep -v vendor/ |xargs go test gofmt -d $(find * -type f -name '*.go' -not -path 'vendor/*') diff --git a/server/pages/pages.go b/server/pages/pages.go index ef62b6b37c0b207ba20e717978053b2ca8f4ab52..f002edb35aea5b5f45a1f6bbd4947b3272664d22 100644 --- a/server/pages/pages.go +++ b/server/pages/pages.go @@ -5,22 +5,23 @@ // Distributed under terms of the MIT license. // +//go:generate go get -u github.com/mjibson/esc +//go:generate esc -o static.go -pkg pages static templates + package pages import ( "html/template" "log" "net/http" - "path" "gitlab.com/lyda/gqgmc/devices/geiger" ) // Pages where data for pages goes type Pages struct { - gc geiger.Counter - staticDir, - templateDir string + gc geiger.Counter + gcErr error } type indexPage struct { @@ -32,36 +33,40 @@ type indexPage struct { } // New create new Pages. -func New(gc geiger.Counter, staticDir, templateDir string) Pages { - return Pages{gc: gc, staticDir: staticDir, templateDir: templateDir} +func New(gc geiger.Counter, gcErr error) Pages { + return Pages{gc: gc, gcErr: gcErr} } // Register pages. func (p Pages) Register() { http.HandleFunc("/", p.indexHandler) - http.HandleFunc("/favicon.ico", p.staticHandler) - http.HandleFunc("/robots.txt", p.staticHandler) - http.HandleFunc("/humans.txt", p.staticHandler) - http.Handle("/static", http.StripPrefix("/static/", http.FileServer(http.Dir(p.staticDir)))) + http.Handle("/favicon.ico", http.FileServer(Dir(false, "/static/"))) + http.Handle("/robots.txt", http.FileServer(Dir(false, "/static/"))) + http.Handle("/humans.txt", http.FileServer(Dir(false, "/static/"))) } func (p Pages) indexHandler(w http.ResponseWriter, r *http.Request) { var indexPg indexPage - indexPg.CPM, _ = p.gc.GetCPM() - indexPg.Volts, _ = p.gc.Volts() - indexPg.Model = p.gc.Model() - indexPg.Version = p.gc.Version() - indexPg.Serial = p.gc.Serial() + if p.gcErr == nil { + indexPg.CPM, _ = p.gc.GetCPM() + indexPg.Volts, _ = p.gc.Volts() + indexPg.Model = p.gc.Model() + indexPg.Version = p.gc.Version() + indexPg.Serial = p.gc.Serial() + } else { + indexPg.CPM = 0 + indexPg.Volts = 0 + indexPg.Model = "ERROR" + indexPg.Version = "ERROR" + indexPg.Serial = "ERROR" + } - t, err := template.ParseFiles(path.Join(p.templateDir, "index.html")) + index, _ := FSString(false, "index.html") + t, err := template.New("index.html").Parse(index) if err != nil { log.Printf("Template error: %s\n", err) + return // TODO: 404 } t.Execute(w, &indexPg) } - -func (p Pages) staticHandler(w http.ResponseWriter, r *http.Request) { - staticFile := path.Join(p.staticDir, path.Base(r.URL.Path)) - http.ServeFile(w, r, staticFile) -} diff --git a/server/pages/static.go b/server/pages/static.go new file mode 100644 index 0000000000000000000000000000000000000000..ebb7613adb01ee332465fc1ecdfd15a7fbb778df --- /dev/null +++ b/server/pages/static.go @@ -0,0 +1,256 @@ +package pages + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "io/ioutil" + "net/http" + "os" + "path" + "sync" + "time" +) + +type _escLocalFS struct{} + +var _escLocal _escLocalFS + +type _escStaticFS struct{} + +var _escStatic _escStaticFS + +type _escDirectory struct { + fs http.FileSystem + name string +} + +type _escFile struct { + compressed string + size int64 + modtime int64 + local string + isDir bool + + once sync.Once + data []byte + name string +} + +func (_escLocalFS) Open(name string) (http.File, error) { + f, present := _escData[path.Clean(name)] + if !present { + return nil, os.ErrNotExist + } + return os.Open(f.local) +} + +func (_escStaticFS) prepare(name string) (*_escFile, error) { + f, present := _escData[path.Clean(name)] + if !present { + return nil, os.ErrNotExist + } + var err error + f.once.Do(func() { + f.name = path.Base(name) + if f.size == 0 { + return + } + var gr *gzip.Reader + b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed)) + gr, err = gzip.NewReader(b64) + if err != nil { + return + } + f.data, err = ioutil.ReadAll(gr) + }) + if err != nil { + return nil, err + } + return f, nil +} + +func (fs _escStaticFS) Open(name string) (http.File, error) { + f, err := fs.prepare(name) + if err != nil { + return nil, err + } + return f.File() +} + +func (dir _escDirectory) Open(name string) (http.File, error) { + return dir.fs.Open(dir.name + name) +} + +func (f *_escFile) File() (http.File, error) { + type httpFile struct { + *bytes.Reader + *_escFile + } + return &httpFile{ + Reader: bytes.NewReader(f.data), + _escFile: f, + }, nil +} + +func (f *_escFile) Close() error { + return nil +} + +func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) { + return nil, nil +} + +func (f *_escFile) Stat() (os.FileInfo, error) { + return f, nil +} + +func (f *_escFile) Name() string { + return f.name +} + +func (f *_escFile) Size() int64 { + return f.size +} + +func (f *_escFile) Mode() os.FileMode { + return 0 +} + +func (f *_escFile) ModTime() time.Time { + return time.Unix(f.modtime, 0) +} + +func (f *_escFile) IsDir() bool { + return f.isDir +} + +func (f *_escFile) Sys() interface{} { + return f +} + +// FS returns a http.Filesystem for the embedded assets. If useLocal is true, +// the filesystem's contents are instead used. +func FS(useLocal bool) http.FileSystem { + if useLocal { + return _escLocal + } + return _escStatic +} + +// Dir returns a http.Filesystem for the embedded assets on a given prefix dir. +// If useLocal is true, the filesystem's contents are instead used. +func Dir(useLocal bool, name string) http.FileSystem { + if useLocal { + return _escDirectory{fs: _escLocal, name: name} + } + return _escDirectory{fs: _escStatic, name: name} +} + +// FSByte returns the named file from the embedded assets. If useLocal is +// true, the filesystem's contents are instead used. +func FSByte(useLocal bool, name string) ([]byte, error) { + if useLocal { + f, err := _escLocal.Open(name) + if err != nil { + return nil, err + } + b, err := ioutil.ReadAll(f) + f.Close() + return b, err + } + f, err := _escStatic.prepare(name) + if err != nil { + return nil, err + } + return f.data, nil +} + +// FSMustByte is the same as FSByte, but panics if name is not present. +func FSMustByte(useLocal bool, name string) []byte { + b, err := FSByte(useLocal, name) + if err != nil { + panic(err) + } + return b +} + +// FSString is the string version of FSByte. +func FSString(useLocal bool, name string) (string, error) { + b, err := FSByte(useLocal, name) + return string(b), err +} + +// FSMustString is the string version of FSMustByte. +func FSMustString(useLocal bool, name string) string { + return string(FSMustByte(useLocal, name)) +} + +var _escData = map[string]*_escFile{ + + "/static/favicon.ico": { + local: "static/favicon.ico", + size: 1150, + modtime: 1485950788, + compressed: ` +H4sIAAAJbogA/6RSTevpYRCdf/13d6O7uGvL+yl8BFufgG5SspaFl0hJFt7Jy0okC6F8BLHAjqxkIcqS +EHM7o3lC1+qemp7zm3PmaZ6ZH9EXfZHFgtNKf76JfhHRbyKyEJGVHnnBN9HPH49Q8BPu97vht9vtn/zZ +847z+cyj0Yi3262p01rkoMHzDvU0m02u1+ucTqe51+sZHRw5aPB86imfz/PxeBReqVS43+9LgAPQ4Pn0 +1mKxyPv93mher1dC/dAKhcJLDc7r9Sq80+nwdDoVPpvNpGcEOAANHgA1esflcuHVasWpVIrdbjc3Gg22 +2+2cyWQkwJGDBg+8qNE7k8kkt1otns/nopdKJR4Oh+Z94OVyWTR44EUNakOh0MueMGuXyyXcZrNJAE6n +0+xE/eFwmKPRKK/XazMzzFtn5vF4JHSW3W7XzBI1qN1sNtIvZj8YDDgSici+sGsFOHLBYFA88KIGtdo3 +dlur1TgQCMh7HQ6H3I8AR87v93O1WuXT6WTegV50lu12m5fLpfDdbmf2Bw4sFguZne5M36wnZnw4HMy3 +z+eTUA809P7+/ynP5XLSG+5OJBI8Ho8lwJGDls1mP/6/2E8sFuN4PM6TycTo4MhBe94hQP+JvwEAAP// +uq6JNX4EAAA= +`, + }, + + "/static/humans.txt": { + local: "static/humans.txt", + size: 303, + modtime: 1485951109, + compressed: ` +H4sIAAAJbogA/0SOsWrzQBCE+3uK6X4wQuqvM7L5nUSOA6cm5SGt7cW6XXPas+O3D3IC6WY+mOFrVui3 +6z1WjXP9YXNwn1oyjG0ij2eWmKh2gY08KEWeKkwsF5giYlCxOBiOmlMFsqF2/Z3NKHs8lvVvQ5kp/zx1 +OkRjFY+W7VGh1SKWH7Vzi8tu/f4W/mwWFl767ZMEizLGPM4eHRkS4SJ6Bx9hZ5bTjJhJ/hluceIRu37f +NW0IzWtwraarConNHv+1wkfWRHamMrugR7vHTB4butGkVxpRZpYTOpbyVeHGCVFGnNhq9x0AAP//oOY3 +by8BAAA= +`, + }, + + "/static/robots.txt": { + local: "static/robots.txt", + size: 44, + modtime: 1485950875, + compressed: ` +H4sIAAAJbogA/wotTi3SdUxPzSuxUtDicsksTszJyS+3UkBi6uemlhRlJhdzAQIAAP//2UBAzSwAAAA= +`, + }, + + "/templates/index.html": { + local: "templates/index.html", + size: 358, + modtime: 1485942389, + compressed: ` +H4sIAAAJbogA/3SQwUrEMBBAz8lXjL1rriJjLlE8FQuK4DHa0QbSBtLJYSn992UzhV0ozSUv4fFgBu9e +3t3nd/cKA4/RapRL4UC+txq2gyOxh9/B55n4uSn8d//YgLFaKeTAkewbhX/K4FKZmDIa+dUKjZQU/qT+ +dC0CYIm3TwCMwbapp/gEy/JQaV3RxLDXvijPIU1V3PhI/aAcvCQFD5sp8izFCx1prmur5Lp2r6ApdXtG +hkUj6zwHAAD//4sAPXxmAQAA +`, + }, + + "/": { + isDir: true, + local: "", + }, + + "/static": { + isDir: true, + local: "static", + }, + + "/templates": { + isDir: true, + local: "templates", + }, +} diff --git a/static/favicon.ico b/server/pages/static/favicon.ico similarity index 100% rename from static/favicon.ico rename to server/pages/static/favicon.ico diff --git a/static/humans.txt b/server/pages/static/humans.txt similarity index 100% rename from static/humans.txt rename to server/pages/static/humans.txt diff --git a/static/robots.txt b/server/pages/static/robots.txt similarity index 100% rename from static/robots.txt rename to server/pages/static/robots.txt diff --git a/templates/index.html b/server/pages/templates/index.html similarity index 100% rename from templates/index.html rename to server/pages/templates/index.html