wip: prepare to implement server-sent events for image download progress

This commit is contained in:
Tigor Hutasuhut 2024-06-04 00:15:07 +07:00
parent 0c784b4cc9
commit cb74b0d817
6 changed files with 165 additions and 0 deletions

18
api/events/events.go Normal file
View file

@ -0,0 +1,18 @@
package events
import (
"io"
"github.com/a-h/templ"
)
type Event interface {
templ.Component
// Event returns the event name
Event() string
// SerializeTo writes the event data to the writer.
//
// SerializeTo must not write multiple linebreaks (single linebreak is fine)
// in succession to the writer since it will mess up SSE events.
SerializeTo(w io.Writer) error
}

View file

@ -0,0 +1,56 @@
package events
import (
"context"
"encoding/json"
"io"
)
type ImageDownloadEvent string
const (
ImageDownloadStart ImageDownloadEvent = "image.download.start"
ImageDownloadEnd ImageDownloadEvent = "image.download.end"
ImageDownloadError ImageDownloadEvent = "image.download.error"
ImageDownloadProgress ImageDownloadEvent = "image.download.progress"
)
type ImageDownload struct {
EventKind ImageDownloadEvent `json:"event"`
ImageURL string `json:"image_url"`
ImageHeight int64 `json:"image_height"`
ImageWidth int64 `json:"image_width"`
ContentLength int64 `json:"content_length"`
Downloaded int64 `json:"downloaded"`
Subreddit string `json:"subreddit"`
PostURL string `json:"post_url"`
PostName string `json:"post_name"`
PostTitle string `json:"post_title"`
Error error `json:"error"`
}
// Render the template.
func (im ImageDownload) Render(ctx context.Context, w io.Writer) error {
panic("not implemented") // TODO: Implement
}
// Event returns the event name
func (im ImageDownload) Event() string {
return "image.download"
}
// SerializeTo writes the event data to the writer.
//
// SerializeTo must not write multiple linebreaks (single linebreak is fine)
// in succession to the writer since it will mess up SSE events.
func (im ImageDownload) SerializeTo(w io.Writer) error {
return json.NewEncoder(w).Encode(im)
}
type ImageDownloadSubreddit struct {
ImageDownload
}
func (im ImageDownloadSubreddit) Event() string {
return string(im.EventKind) + "." + im.Subreddit
}

View file

@ -0,0 +1,46 @@
package events
import (
"encoding/json"
"fmt"
"net/http"
"github.com/tigorlazuardi/redmage/pkg/log"
)
func (handler *Handler) SimpleDownloadEvent(rw http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "*Routes.EventsAPI")
defer span.End()
flush, ok := rw.(http.Flusher)
if !ok {
rw.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(rw).Encode(map[string]string{"error": "response writer does not support streaming"})
return
}
log.New(ctx).Info("new simple event stream connection", "user_agent", r.UserAgent())
rw.Header().Set("Content-Type", "text/event-stream")
rw.Header().Set("Cache-Control", "no-cache")
rw.Header().Set("Connection", "keep-alive")
rw.WriteHeader(200)
flush.Flush()
ev, close := handler.Subscribe()
defer close()
for {
select {
case <-r.Context().Done():
log.New(ctx).Info("simple event stream connection closed", "user_agent", r.UserAgent())
return
case event := <-ev:
msg := event.Event()
if _, err := fmt.Fprintf(rw, "event: %s\ndata: %s\n\n", msg, msg); err != nil {
return
}
flush.Flush()
}
}
}

View file

@ -0,0 +1,17 @@
package events
import (
"github.com/teivah/broadcast"
"github.com/tigorlazuardi/redmage/api/events"
"github.com/tigorlazuardi/redmage/config"
)
type Handler struct {
Config *config.Config
Broadcast *broadcast.Relay[events.Event]
}
func (handler *Handler) Subscribe() (<-chan events.Event, func()) {
listener := handler.Broadcast.Listener(10)
return listener.Ch(), listener.Close
}

View file

@ -0,0 +1,5 @@
package events
import "go.opentelemetry.io/otel"
var tracer = otel.Tracer("server/routes/events")

View file

@ -0,0 +1,23 @@
package progress
type ImageDownloadStartData struct {
ID string
Subreddit string
PostURL string
PostName string
PostTitle string
}
templ ImageDownloadStart(data ImageDownloadStartData) {
}
type ImageDownloadEndData struct {
ID string
Subreddit string
PostURL string
PostName string
PostTitle string
}
templ ImageDownloadEnd(data ImageDownloadEndData) {
}