diff --git a/VERSION b/VERSION
index 085135e..49d5957 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v0.1
+0.1
diff --git a/main.go b/main.go
index 6d5ebeb..0a00c93 100644
--- a/main.go
+++ b/main.go
@@ -18,6 +18,192 @@ import (
"time"
)
+var (
+ version string
+ listen string
+)
+
+func main() {
+ flag.StringVar(&listen, "l", ":7878", "listen addr")
+ flag.Parse()
+
+ ic := &ImageCrawler{
+ url: "https://uvi.bfs.de/Tagesgrafiken",
+ c: &http.Client{},
+ m: make(map[string]*Image),
+ }
+ http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "POST" {
+ r.ParseForm()
+ loc := r.PostForm.Get("loc")
+ if _, ok := locations[loc]; ok {
+ c := &http.Cookie{
+ Name: "loc",
+ Path: "/",
+ Value: loc,
+ Secure: true,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ }
+ http.SetCookie(w, c)
+ http.Redirect(w, r, "/", http.StatusMovedPermanently)
+ return
+ }
+ }
+ w.Write([]byte(locPage))
+ })
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ cc := r.CookiesNamed("loc")
+ var c *http.Cookie
+ if len(cc) == 0 {
+ c = &http.Cookie{
+ Name: "loc",
+ Path: "/",
+ Value: DefaultCity,
+ Secure: true,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ }
+ http.SetCookie(w, c)
+ } else {
+ c = cc[0]
+ }
+ loc := c.Value
+ img, mod, err := ic.getImage(loc)
+ mod = mod.Local()
+ location := selectListReverse[loc]
+ if err != nil {
+ fmt.Fprintf(w, indexPage,
+ "000000",
+ "Err",
+ mod.Format(time.RFC1123),
+ time.Now().Format(time.RFC1123),
+ location,
+ version,
+ )
+ return
+ }
+ vals := getUV(img)
+ if len(vals) == 0 {
+ fmt.Fprintf(w, indexPage,
+ "000000",
+ "Err",
+ mod.Format(time.RFC1123),
+ time.Now().Format(time.RFC1123),
+ location,
+ version,
+ )
+ return
+ }
+ last := vals[len(vals)-1]
+ fmt.Fprintf(w, indexPage,
+ getColor(last),
+ fmt.Sprintf("%.2f", last),
+ mod.Format(time.RFC1123),
+ time.Now().Format(time.RFC1123),
+ location,
+ version,
+ )
+ })
+ err := http.ListenAndServe(listen, nil)
+ if err != nil {
+ fatal(err)
+ }
+}
+
+type ImageCrawler struct {
+ mu sync.RWMutex
+ url string
+ c *http.Client
+ m map[string]*Image
+}
+
+type Image struct {
+ mu sync.RWMutex
+ ic *ImageCrawler
+ file string
+ img image.Image
+ etag string
+ modified time.Time
+ lastGet time.Time
+}
+
+func (ic *ImageCrawler) getImage(loc string) (img image.Image, mod time.Time, err error) {
+ file, ok := locations[loc]
+ if !ok {
+ return nil, time.Time{}, errors.New("invalid location")
+ }
+ ic.mu.RLock()
+ if i, ok := ic.m[loc]; ok {
+ ic.mu.RUnlock()
+ return i.getImage()
+ }
+ ic.mu.RUnlock()
+ ic.mu.Lock()
+ i := &Image{
+ ic: ic,
+ file: file,
+ }
+ ic.m[loc] = i
+ ic.mu.Unlock()
+ return i.getImage()
+}
+
+func (i *Image) getImage() (img image.Image, mod time.Time, err error) {
+ i.mu.RLock()
+ if !i.lastGet.IsZero() &&
+ time.Now().Sub(i.lastGet) < time.Minute*3 {
+ defer i.mu.RUnlock()
+ return i.img, i.modified, nil
+ }
+ i.mu.RUnlock()
+ req, err := http.NewRequest("GET", i.ic.url+"/"+i.file, nil)
+ if err != nil {
+ return
+ }
+ req.Header.Set("user-agent", "uvgo/"+version)
+ i.mu.RLock()
+ if !i.modified.IsZero() {
+ req.Header.Set("if-modified-since", i.modified.Format(time.RFC1123))
+ }
+ if i.etag != "" {
+ req.Header.Set("if-none-match", i.etag)
+ }
+ i.mu.RUnlock()
+ resp, err := i.ic.c.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusNotModified {
+ i.mu.RLock()
+ defer i.mu.RUnlock()
+ return i.img, i.modified, nil
+ }
+ if resp.StatusCode == http.StatusOK {
+ img, err = png.Decode(resp.Body)
+ if err != nil {
+ return
+ }
+ i.mu.Lock()
+ i.img = img
+ modTime, err := time.Parse(time.RFC1123, resp.Header.Get("last-modified"))
+ if err == nil {
+ i.modified = modTime
+ }
+ i.etag = resp.Header.Get("etag")
+ i.lastGet = time.Now()
+ defer i.mu.Unlock()
+ return i.img, i.modified, nil
+ }
+ i.mu.RLock()
+ defer i.mu.RUnlock()
+ if i.img != nil {
+ return i.img, i.modified, nil
+ }
+ return nil, time.Time{}, errors.New("error getting image")
+}
+
const (
X = 82
maxX = 574
@@ -98,33 +284,6 @@ func getUV(img image.Image) (vals []float64) {
return
}
-var tmp = `
-
-
-UV Index
-
-
-
-
-
-UV Index
-%s
-Updated: %s
-Server: %s
-
-
-
-
-`
-
func getColor(v float64) string {
if v < 1.0 {
return "006300"
@@ -152,195 +311,6 @@ func getColor(v float64) string {
return "000000"
}
-var (
- locPage string
- version string
- listen string
-)
-
-func main() {
- flag.StringVar(&listen, "l", ":7878", "listen addr")
- flag.Parse()
-
- url := "https://uvi.bfs.de/Tagesgrafiken"
- generateLocPage()
-
- ic := &ImageCrawler{
- url: url,
- c: &http.Client{},
- m: make(map[string]*Image),
- }
- http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
- if r.Method == "POST" {
- r.ParseForm()
- loc := r.PostForm.Get("loc")
- if _, ok := locations[loc]; ok {
- c := &http.Cookie{
- Name: "loc",
- Path: "/",
- Value: loc,
- Secure: true,
- HttpOnly: true,
- SameSite: http.SameSiteStrictMode,
- }
- http.SetCookie(w, c)
- http.Redirect(w, r, "/", http.StatusMovedPermanently)
- return
- }
- }
- w.Write([]byte(locPage))
- })
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- cc := r.CookiesNamed("loc")
- var c *http.Cookie
- if len(cc) == 0 {
- c = &http.Cookie{
- Name: "loc",
- Path: "/",
- Value: "24",
- Secure: true,
- HttpOnly: true,
- SameSite: http.SameSiteStrictMode,
- }
- http.SetCookie(w, c)
- } else {
- c = cc[0]
- }
- loc := c.Value
- img, mod, err := ic.getImage(loc)
- mod = mod.Local()
- location := selectListReverse[loc]
- if err != nil {
- fmt.Fprintf(w, tmp,
- "000000",
- "Err",
- mod.Format(time.RFC1123),
- time.Now().Format(time.RFC1123),
- location,
- version,
- )
- return
- }
- vals := getUV(img)
- if len(vals) == 0 {
- fmt.Fprintf(w, tmp,
- "000000",
- "Err",
- mod.Format(time.RFC1123),
- time.Now().Format(time.RFC1123),
- location,
- version,
- )
- return
- }
- last := vals[len(vals)-1]
- fmt.Fprintf(w, tmp,
- getColor(last),
- fmt.Sprintf("%.2f", last),
- mod.Format(time.RFC1123),
- time.Now().Format(time.RFC1123),
- location,
- version,
- )
- })
- err := http.ListenAndServe(listen, nil)
- if err != nil {
- fatal(err)
- }
-}
-
-type ImageCrawler struct {
- mu sync.RWMutex
- url string
- c *http.Client
- m map[string]*Image
-}
-
-type Image struct {
- mu sync.RWMutex
- ic *ImageCrawler
- file string
- img image.Image
- etag string
- modified time.Time
- lastGet time.Time
-}
-
-func (ic *ImageCrawler) getImage(loc string) (img image.Image, mod time.Time, err error) {
- file, ok := locations[loc]
- if !ok {
- return nil, time.Time{}, errors.New("invalid location")
- }
- ic.mu.RLock()
- if i, ok := ic.m[loc]; ok {
- ic.mu.RUnlock()
- return i.getImage()
- }
- ic.mu.RUnlock()
- ic.mu.Lock()
- i := &Image{
- ic: ic,
- file: file,
- }
- ic.m[loc] = i
- ic.mu.Unlock()
- return i.getImage()
-}
-
-func (i *Image) getImage() (img image.Image, mod time.Time, err error) {
- i.mu.RLock()
- if !i.lastGet.IsZero() &&
- time.Now().Sub(i.lastGet) < time.Minute*3 {
- defer i.mu.RUnlock()
- return i.img, i.modified, nil
- }
- i.mu.RUnlock()
- req, err := http.NewRequest("GET", i.ic.url+"/"+i.file, nil)
- if err != nil {
- return
- }
- i.mu.RLock()
- if !i.modified.IsZero() {
- req.Header.Set("if-modified-since", i.modified.Format(time.RFC1123))
- }
- if i.etag != "" {
- req.Header.Set("if-none-match", i.etag)
- }
- i.mu.RUnlock()
- resp, err := i.ic.c.Do(req)
- if err != nil {
- return
- }
- defer resp.Body.Close()
- if resp.StatusCode == http.StatusNotModified {
- i.mu.RLock()
- defer i.mu.RUnlock()
- return i.img, i.modified, nil
- }
- if resp.StatusCode == http.StatusOK {
- img, err = png.Decode(resp.Body)
- if err != nil {
- return
- }
- i.mu.Lock()
- i.img = img
- modTime, err := time.Parse(time.RFC1123, resp.Header.Get("last-modified"))
- if err == nil {
- i.modified = modTime
- }
- i.etag = resp.Header.Get("etag")
- i.lastGet = time.Now()
- defer i.mu.Unlock()
- return i.img, i.modified, nil
- }
- i.mu.RLock()
- defer i.mu.RUnlock()
- if i.img != nil {
- return i.img, i.modified, nil
- }
- return nil, time.Time{}, errors.New("error getting image")
-}
-
func isWhite(col color.Color) bool {
col = color.RGBAModel.Convert(col)
c, ok := col.(color.RGBA)
@@ -419,23 +389,7 @@ func fatal(err error) {
}
}
-func generateLocPage() {
- loc := `
-
-UV Index Change Location
-
-
-
-
-Change Location
-`
- locPage = loc
-}
+const DefaultCity = "24"
var locations = map[string]string{
"0": "EEr_Andernach_today.png",
@@ -556,3 +510,50 @@ func sortedMap[K cmp.Ordered, V any](m map[K]V) iter.Seq2[K, V] {
}
}
}
+
+func generateLocPage() string {
+ loc := `
+
+UV Index Change Location
+
+
+
+
+Change Location
+`
+ return loc
+}
+
+var locPage = generateLocPage()
+
+var indexPage = `
+
+
+UV Index
+
+
+
+
+
+UV Index
+%s
+Updated: %s
+Server: %s
+
+
+
+
+`