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"
|
2024-06-11 15:13:56 +07:00
|
|
|
"github.com/teivah/broadcast"
|
2024-04-10 22:38:19 +07:00
|
|
|
"github.com/tigorlazuardi/redmage/api/bmessage"
|
2024-06-11 15:13:56 +07:00
|
|
|
"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
|
2024-06-11 15:13:56 +07:00
|
|
|
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
|
|
|
|
2024-06-11 15:13:56 +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
|
|
|
|
}
|
|
|
|
|
2024-06-11 15:13:56 +07:00
|
|
|
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)
|
|
|
|
}
|
2024-04-27 15:16:14 +07:00
|
|
|
if resp.StatusCode >= 400 {
|
|
|
|
return nil, errs.Fail("unexpected status code when trying to download images",
|
|
|
|
"url", url,
|
|
|
|
"status_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
|
|
|
|
}
|
2024-06-11 15:13:56 +07:00
|
|
|
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) {
|
2024-06-11 15:13:56 +07:00
|
|
|
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 {
|
2024-06-11 15:13:56 +07:00
|
|
|
kind = events.ImageDownloadError
|
2024-04-14 00:32:55 +07:00
|
|
|
} else {
|
2024-06-11 15:13:56 +07:00
|
|
|
kind = events.ImageDownloadProgress
|
2024-04-14 00:32:55 +07:00
|
|
|
}
|
2024-06-11 15:13:56 +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) {
|
2024-06-11 15:13:56 +07:00
|
|
|
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()
|
2024-06-11 15:13:56 +07:00
|
|
|
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
|
|
|
}
|