Redmage/api/reddit/download_images.go

151 lines
4.4 KiB
Go
Raw Normal View History

2024-04-10 22:38:19 +07:00
package reddit
import (
"context"
2024-04-26 22:13:04 +07:00
"errors"
2024-04-10 22:38:19 +07:00
"io"
2024-04-11 16:04:13 +07:00
"net/http"
2024-04-10 22:38:19 +07:00
2024-04-11 16:04:13 +07:00
"github.com/alecthomas/units"
"github.com/teivah/broadcast"
2024-04-10 22:38:19 +07:00
"github.com/tigorlazuardi/redmage/api/bmessage"
"github.com/tigorlazuardi/redmage/api/events"
2024-04-11 16:04:13 +07:00
"github.com/tigorlazuardi/redmage/pkg/errs"
2024-04-10 22:38:19 +07:00
)
type DownloadStatusBroadcaster interface {
2024-04-11 16:04:13 +07:00
Broadcast(bmessage.ImageDownloadMessage)
2024-04-10 22:38:19 +07:00
}
type NullDownloadStatusBroadcaster struct{}
2024-04-11 16:04:13 +07:00
func (NullDownloadStatusBroadcaster) Broadcast(bmessage.ImageDownloadMessage) {}
2024-04-10 22:38:19 +07:00
type PostImage struct {
2024-04-14 00:32:55 +07:00
URL string
File io.ReadCloser
2024-04-10 22:38:19 +07:00
}
2024-04-14 00:32:55 +07:00
func (po *PostImage) Read(p []byte) (n int, err error) {
return po.File.Read(p)
}
func (po *PostImage) Close() error {
return po.File.Close()
}
// DownloadImage downloades the image.
//
// If downloading image or thumbnail fails
func (reddit *Reddit) DownloadImage(ctx context.Context, post Post, broadcaster *broadcast.Relay[events.Event]) (image PostImage, err error) {
2024-04-26 10:51:09 +07:00
ctx, span := tracer.Start(ctx, "*Reddit.DownloadImage")
defer span.End()
2024-04-14 00:32:55 +07:00
imageUrl := post.GetImageURL()
image.URL = imageUrl
2024-04-11 16:04:13 +07:00
2024-04-14 00:32:55 +07:00
image.File, err = reddit.downloadImage(ctx, post, bmessage.KindImage, broadcaster)
return image, err
}
2024-04-11 16:04:13 +07:00
func (reddit *Reddit) DownloadThumbnail(ctx context.Context, post Post, broadcaster *broadcast.Relay[events.Event]) (image PostImage, err error) {
2024-04-26 10:51:09 +07:00
ctx, span := tracer.Start(ctx, "*Reddit.DownloadThumbnail")
defer span.End()
2024-04-14 00:32:55 +07:00
imageUrl := post.GetThumbnailURL()
image.URL = imageUrl
image.File, err = reddit.downloadImage(ctx, post, bmessage.KindThumbnail, broadcaster)
2024-04-11 16:04:13 +07:00
return image, err
}
func (reddit *Reddit) downloadImage(ctx context.Context, post Post, kind bmessage.ImageKind, broadcaster *broadcast.Relay[events.Event]) (io.ReadCloser, error) {
2024-04-11 16:04:13 +07:00
var (
url string
2024-04-14 00:32:55 +07:00
height int64
width int64
2024-04-11 16:04:13 +07:00
)
if kind == bmessage.KindImage {
url = post.GetImageURL()
width, height = post.GetImageSize()
} else {
url = post.GetThumbnailURL()
width, height = post.GetThumbnailSize()
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, errs.Wrapw(err, "reddit: failed to create request", "url", url)
}
resp, err := reddit.Client.Do(req)
if err != nil {
return nil, errs.Wrapw(err, "reddit: failed to execute request", "url", url)
}
if resp.StatusCode >= 400 {
return nil, errs.Fail("unexpected status code when trying to download images",
"url", url,
"status_code", resp.StatusCode,
).Code(resp.StatusCode)
}
2024-04-11 16:04:13 +07:00
idleSpeedStr := reddit.Config.String("download.timeout.idlespeed")
metricSpeed, _ := units.ParseMetricBytes(idleSpeedStr)
if metricSpeed == 0 {
metricSpeed = 10 * units.KB
}
eventData := events.ImageDownload{
ImageURL: post.GetImageURL(),
ImageHeight: int32(height),
ImageWidth: int32(width),
Subreddit: post.GetSubreddit(),
PostURL: post.GetPostURL(),
PostName: post.GetPostName(),
PostTitle: post.GetPostTitle(),
PostCreated: post.GetPostCreated(),
PostAuthor: post.GetAuthor(),
PostAuthorURL: post.GetAuthorURL(),
ImageOriginalURL: post.GetImageURL(),
NSFW: int32(post.IsNSFWInt()),
2024-04-14 00:32:55 +07:00
}
2024-04-11 16:04:13 +07:00
idr := &ImageDownloadReader{
OnProgress: func(downloaded int64, contentLength int64, err error) {
var kind events.ImageDownloadEvent
2024-04-26 22:13:04 +07:00
if errors.Is(err, io.EOF) {
err = nil
}
2024-04-14 00:32:55 +07:00
if err != nil {
kind = events.ImageDownloadError
2024-04-14 00:32:55 +07:00
} else {
kind = events.ImageDownloadProgress
2024-04-14 00:32:55 +07:00
}
ev := eventData.Clone()
ev.EventKind = kind
ev.Downloaded = downloaded
ev.ImageSize = contentLength
ev.ContentLength = contentLength
events.PublishImageDownloadEvent(broadcaster, ev)
2024-04-11 16:04:13 +07:00
},
2024-04-14 00:32:55 +07:00
OnClose: func(downloaded, contentLength int64, closeErr error) {
ev := eventData.Clone()
ev.EventKind = events.ImageDownloadEnd
ev.Downloaded = downloaded
ev.ImageSize = contentLength
ev.ContentLength = contentLength
ev.Error = closeErr
events.PublishImageDownloadEvent(broadcaster, ev)
2024-04-14 00:32:55 +07:00
},
2024-04-11 16:04:13 +07:00
IdleTimeout: reddit.Config.Duration("download.timeout.idle"),
IdleSpeedThreshold: metricSpeed,
}
resp = idr.WrapHTTPResponse(resp)
reader, writer := io.Pipe()
go func() {
defer resp.Body.Close()
ev := eventData.Clone()
ev.EventKind = events.ImageDownloadStart
ev.ImageSize = resp.ContentLength
ev.ContentLength = resp.ContentLength
events.PublishImageDownloadEvent(broadcaster, ev)
2024-04-11 16:04:13 +07:00
_, err := io.Copy(writer, resp.Body)
_ = writer.CloseWithError(err)
}()
return reader, nil
2024-04-10 22:38:19 +07:00
}