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
import (
"crypto/sha256"
"fmt"
"net/http"
"strings"
"sync"
"time"
@ -27,10 +30,7 @@ func sendAllSockets(msgType string, msg interface{}) error {
defer socketSubscriptionsLock.RUnlock()
for _, hdl := range socketSubscriptions {
if err := hdl(map[string]interface{}{
"payload": msg,
"type": msgType,
}); err != nil {
if err := hdl(compileSocketMessage(msgType, msg)); err != nil {
return errors.Wrap(err, "submit message")
}
}
@ -52,6 +52,27 @@ func unsubscribeSocket(id string) {
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{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
@ -87,10 +108,7 @@ func handleUpdateSocket(w http.ResponseWriter, r *http.Request) {
}
}()
if err := conn.WriteJSON(map[string]interface{}{
"payload": store,
"type": msgTypeStore,
}); err != nil {
if err := conn.WriteJSON(compileSocketMessage(msgTypeStore, store)); err != nil {
log.WithError(err).Error("Unable to send initial state")
return
}

14
app.js
View file

@ -37,6 +37,7 @@ const app = new Vue({
store: {},
socket: null,
time: new Date(),
version: null,
},
el: '#app',
@ -68,9 +69,13 @@ const app = new Vue({
this.socket.onmessage = evt => {
const data = JSON.parse(evt.data)
if (data.version) {
this.version = data.version
}
switch (data.type) {
case 'store':
this.store = data
this.store = data.payload
break
default:
@ -92,5 +97,12 @@ const app = new Vue({
}
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
import (
"crypto/sha256"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"sync"
"time"
"github.com/gofrs/uuid"
"github.com/gorilla/mux"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/Luzifer/rconfig/v2"
@ -16,6 +21,7 @@ import (
var (
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"`
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"`
@ -30,9 +36,14 @@ var (
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
webhookSecret = uuid.Must(uuid.NewV4()).String()
version = "dev"
)
func init() {
@ -59,13 +70,20 @@ func main() {
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()
registerAPI(router)
router.HandleFunc("/{file:(?:app.js|overlay.html)}", 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"]))
})
router.HandleFunc(
fmt.Sprintf("/{file:(?:%s)}", strings.Join(assets, "|")),
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() {
if err := http.ListenAndServe(cfg.Listen, router); err != nil {
@ -78,6 +96,7 @@ func main() {
}
var (
timerAssetCheck = time.NewTicker(cfg.AssetCheckInterval)
timerForceSync = time.NewTicker(cfg.ForceSyncInterval)
timerUpdateFromAPI = time.NewTicker(cfg.UpdateFromAPIInterval)
timerWebhookRegister = time.NewTicker(cfg.WebHookTimeout)
@ -85,6 +104,11 @@ func main() {
for {
select {
case <-timerAssetCheck.C:
if err := updateAssetHashes(); err != nil {
log.WithError(err).Error("Unable to update asset hashes")
}
case <-timerForceSync.C:
if err := sendAllSockets(msgTypeStore, store); err != nil {
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
}