From f867bcfa215c28e03c863bd651d00dbb94e01057 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Sun, 5 Mar 2023 22:38:29 +0100 Subject: [PATCH] add updates serve command to serve a json or html version of the changelog.json, with contents based on ?from= query string --- main.go | 1 + updates.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/main.go b/main.go index 5450e6d..06c2019 100644 --- a/main.go +++ b/main.go @@ -145,6 +145,7 @@ var commands = []struct { {"updates addsigned", cmdUpdatesAddSigned}, {"updates genkey", cmdUpdatesGenkey}, {"updates pubkey", cmdUpdatesPubkey}, + {"updates serve", cmdUpdatesServe}, {"updates verify", cmdUpdatesVerify}, } diff --git a/updates.go b/updates.go index 3c3b39e..9c223e1 100644 --- a/updates.go +++ b/updates.go @@ -7,10 +7,14 @@ import ( "encoding/base64" "encoding/json" "fmt" + htmltemplate "html/template" "io" "log" + "net/http" "os" + "strings" + "github.com/mjl-/mox/moxio" "github.com/mjl-/mox/updates" ) @@ -132,3 +136,142 @@ func cmdUpdatesPubkey(c *cmd) { err = enc.Close() xcheckf(err, "writing public key") } + +var updatesTemplate = htmltemplate.Must(htmltemplate.New("changelog").Parse(` + + + + + mox changelog + + + +

Changes{{ if .FromVersion }} since {{ .FromVersion }}{{ end }}

+ {{ if not .Changes }} +
No changes
+ {{ end }} + {{ range .Changes }} +
{{ .Text }}
+
+ {{ end }} + + +`)) + +func cmdUpdatesServe(c *cmd) { + c.unlisted = true + c.help = "Serve changelog.json with updates." + var address, changelog string + c.flag.StringVar(&address, "address", "127.0.0.1:8596", "address to serve /changelog on") + c.flag.StringVar(&changelog, "changelog", "changelog.json", "changelog file to serve") + args := c.Parse() + if len(args) != 0 { + c.Usage() + } + + parseFile := func() (*updates.Changelog, error) { + f, err := os.Open(changelog) + if err != nil { + return nil, err + } + defer f.Close() + var cl updates.Changelog + if err := json.NewDecoder(f).Decode(&cl); err != nil { + return nil, err + } + return &cl, nil + } + + _, err := parseFile() + if err != nil { + log.Fatalf("parsing %s: %v", changelog, err) + } + + srv := http.NewServeMux() + srv.HandleFunc("/changelog", func(w http.ResponseWriter, r *http.Request) { + cl, err := parseFile() + if err != nil { + log.Printf("parsing %s: %v", changelog, err) + http.Error(w, "500 - internal server error", http.StatusInternalServerError) + return + } + from := r.URL.Query().Get("from") + var fromVersion *updates.Version + if from != "" { + v, err := updates.ParseVersion(from) + if err == nil { + fromVersion = &v + } + } + if fromVersion != nil { + nextchange: + for i, c := range cl.Changes { + for _, line := range strings.Split(strings.Split(c.Text, "\n\n")[0], "\n") { + if strings.HasPrefix(line, "version:") { + v, err := updates.ParseVersion(strings.TrimSpace(strings.TrimPrefix(line, "version:"))) + if err == nil && !v.After(*fromVersion) { + cl.Changes = cl.Changes[:i] + break nextchange + } + } + } + } + } + + // Check if client accepts html. If so, we'll provide a human-readable version. + accept := r.Header.Get("Accept") + var html bool + accept: + for _, ac := range strings.Split(accept, ",") { + var ok bool + for i, kv := range strings.Split(strings.TrimSpace(ac), ";") { + if i == 0 { + ct := strings.TrimSpace(kv) + if strings.EqualFold(ct, "text/html") || strings.EqualFold(ct, "text/*") { + ok = true + continue + } + continue accept + } + t := strings.SplitN(strings.TrimSpace(kv), "=", 2) + if !strings.EqualFold(t[0], "q") || len(t) != 2 { + continue + } + switch t[1] { + case "0", "0.", "0.0", "0.00", "0.000": + ok = false + continue accept + } + break + } + if ok { + html = true + break + } + } + + if html { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + err := updatesTemplate.Execute(w, map[string]any{ + "FromVersion": fromVersion, + "Changes": cl.Changes, + }) + if err != nil && !moxio.IsClosed(err) { + log.Printf("writing changelog html: %v", err) + } + } else { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + if err := json.NewEncoder(w).Encode(cl); err != nil && !moxio.IsClosed(err) { + log.Printf("writing changelog json: %v", err) + } + } + }) + log.Printf("listening on %s", address) + log.Fatalln(http.ListenAndServe(address, srv)) +}