Add auto-refresh logic for updates

This commit is contained in:
Knut Ahlers 2020-11-21 00:33:02 +01:00
parent 0c9a482716
commit cbdfb86389
Signed by: luzifer
GPG key ID: 0066F03ED215AD7D
3 changed files with 90 additions and 14 deletions

34
api.go
View file

@ -1,7 +1,10 @@
package main package main
import ( import (
"crypto/sha256"
"fmt"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
@ -27,10 +30,7 @@ func sendAllSockets(msgType string, msg interface{}) error {
defer socketSubscriptionsLock.RUnlock() defer socketSubscriptionsLock.RUnlock()
for _, hdl := range socketSubscriptions { for _, hdl := range socketSubscriptions {
if err := hdl(map[string]interface{}{ if err := hdl(compileSocketMessage(msgType, msg)); err != nil {
"payload": msg,
"type": msgType,
}); err != nil {
return errors.Wrap(err, "submit message") return errors.Wrap(err, "submit message")
} }
} }
@ -52,6 +52,27 @@ func unsubscribeSocket(id string) {
delete(socketSubscriptions, id) delete(socketSubscriptions, id)
} }
func compileSocketMessage(msgType string, msg interface{}) map[string]interface{} {
assetVersionsLock.RLock()
defer assetVersionsLock.RUnlock()
versionParts := []string{version}
for _, asset := range assets {
versionParts = append(versionParts, assetVersions[asset])
}
hash := sha256.New()
hash.Write([]byte(strings.Join(versionParts, "/")))
ver := fmt.Sprintf("%x", hash.Sum(nil))
return map[string]interface{}{
"payload": msg,
"type": msgType,
"version": ver,
}
}
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
ReadBufferSize: 1024, ReadBufferSize: 1024,
WriteBufferSize: 1024, WriteBufferSize: 1024,
@ -87,10 +108,7 @@ func handleUpdateSocket(w http.ResponseWriter, r *http.Request) {
} }
}() }()
if err := conn.WriteJSON(map[string]interface{}{ if err := conn.WriteJSON(compileSocketMessage(msgTypeStore, store)); err != nil {
"payload": store,
"type": msgTypeStore,
}); err != nil {
log.WithError(err).Error("Unable to send initial state") log.WithError(err).Error("Unable to send initial state")
return return
} }

14
app.js
View file

@ -37,6 +37,7 @@ const app = new Vue({
store: {}, store: {},
socket: null, socket: null,
time: new Date(), time: new Date(),
version: null,
}, },
el: '#app', el: '#app',
@ -68,9 +69,13 @@ const app = new Vue({
this.socket.onmessage = evt => { this.socket.onmessage = evt => {
const data = JSON.parse(evt.data) const data = JSON.parse(evt.data)
if (data.version) {
this.version = data.version
}
switch (data.type) { switch (data.type) {
case 'store': case 'store':
this.store = data this.store = data.payload
break break
default: default:
@ -92,5 +97,12 @@ const app = new Vue({
} }
this.showAlert('New Follower', `${to} just followed`) this.showAlert('New Follower', `${to} just followed`)
}, },
version(to, from) {
if (!from || !to || from === to) {
return
}
window.location.reload()
},
}, },
}) })

56
main.go
View file

@ -1,14 +1,19 @@
package main package main
import ( import (
"crypto/sha256"
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"path" "path"
"strings"
"sync"
"time" "time"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/Luzifer/rconfig/v2" "github.com/Luzifer/rconfig/v2"
@ -16,6 +21,7 @@ import (
var ( var (
cfg = struct { cfg = struct {
AssetCheckInterval time.Duration `flag:"asset-check-interval" default:"1m" description:"How often to check asset files for updates"`
AssetDir string `flag:"asset-dir" default:"." description:"Directory containing assets"` AssetDir string `flag:"asset-dir" default:"." description:"Directory containing assets"`
BaseURL string `flag:"base-url" default:"" description:"Base URL of this service" validate:"nonzero"` BaseURL string `flag:"base-url" default:"" description:"Base URL of this service" validate:"nonzero"`
ForceSyncInterval time.Duration `flag:"force-sync-interval" default:"1m" description:"How often to force a sync without updates"` ForceSyncInterval time.Duration `flag:"force-sync-interval" default:"1m" description:"How often to force a sync without updates"`
@ -30,9 +36,14 @@ var (
WebHookTimeout time.Duration `flag:"webhook-timeout" default:"15m" description:"When to re-register the webhooks"` WebHookTimeout time.Duration `flag:"webhook-timeout" default:"15m" description:"When to re-register the webhooks"`
}{} }{}
version = "dev" assets = []string{"app.js", "overlay.html"}
assetVersions = map[string]string{}
assetVersionsLock = new(sync.RWMutex)
store *storage store *storage
webhookSecret = uuid.Must(uuid.NewV4()).String() webhookSecret = uuid.Must(uuid.NewV4()).String()
version = "dev"
) )
func init() { func init() {
@ -59,13 +70,20 @@ func main() {
log.WithError(err).Fatal("Unable to load store") log.WithError(err).Fatal("Unable to load store")
} }
if err := updateAssetHashes(); err != nil {
log.WithError(err).Fatal("Unable to read asset hashes")
}
router := mux.NewRouter() router := mux.NewRouter()
registerAPI(router) registerAPI(router)
router.HandleFunc("/{file:(?:app.js|overlay.html)}", func(w http.ResponseWriter, r *http.Request) { router.HandleFunc(
w.Header().Set("Cache-Control", "no-cache") fmt.Sprintf("/{file:(?:%s)}", strings.Join(assets, "|")),
http.ServeFile(w, r, path.Join(cfg.AssetDir, mux.Vars(r)["file"])) func(w http.ResponseWriter, r *http.Request) {
}) w.Header().Set("Cache-Control", "no-cache")
http.ServeFile(w, r, path.Join(cfg.AssetDir, mux.Vars(r)["file"]))
},
)
go func() { go func() {
if err := http.ListenAndServe(cfg.Listen, router); err != nil { if err := http.ListenAndServe(cfg.Listen, router); err != nil {
@ -78,6 +96,7 @@ func main() {
} }
var ( var (
timerAssetCheck = time.NewTicker(cfg.AssetCheckInterval)
timerForceSync = time.NewTicker(cfg.ForceSyncInterval) timerForceSync = time.NewTicker(cfg.ForceSyncInterval)
timerUpdateFromAPI = time.NewTicker(cfg.UpdateFromAPIInterval) timerUpdateFromAPI = time.NewTicker(cfg.UpdateFromAPIInterval)
timerWebhookRegister = time.NewTicker(cfg.WebHookTimeout) timerWebhookRegister = time.NewTicker(cfg.WebHookTimeout)
@ -85,6 +104,11 @@ func main() {
for { for {
select { select {
case <-timerAssetCheck.C:
if err := updateAssetHashes(); err != nil {
log.WithError(err).Error("Unable to update asset hashes")
}
case <-timerForceSync.C: case <-timerForceSync.C:
if err := sendAllSockets(msgTypeStore, store); err != nil { if err := sendAllSockets(msgTypeStore, store); err != nil {
log.WithError(err).Error("Unable to send store to all sockets") log.WithError(err).Error("Unable to send store to all sockets")
@ -103,3 +127,25 @@ func main() {
} }
} }
} }
func updateAssetHashes() error {
assetVersionsLock.Lock()
defer assetVersionsLock.Unlock()
for _, asset := range assets {
hash := sha256.New()
f, err := os.Open(asset)
if err != nil {
return errors.Wrap(err, "open asset file")
}
defer f.Close()
if _, err = io.Copy(hash, f); err != nil {
return errors.Wrap(err, "read asset file")
}
assetVersions[asset] = fmt.Sprintf("%x", hash.Sum(nil))
}
return nil
}