2020-06-06 07:44:36 +00:00
|
|
|
package httputil
|
|
|
|
|
|
|
|
import (
|
2020-06-13 07:29:32 +00:00
|
|
|
"context"
|
2020-06-06 07:44:36 +00:00
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
|
|
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
|
|
|
"github.com/diamondburned/imgutil"
|
|
|
|
"github.com/gotk3/gotk3/gdk"
|
2020-06-13 07:29:32 +00:00
|
|
|
"github.com/gotk3/gotk3/glib"
|
2020-06-06 07:44:36 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2020-06-07 04:27:28 +00:00
|
|
|
type ImageContainer interface {
|
|
|
|
SetFromPixbuf(*gdk.Pixbuf)
|
|
|
|
SetFromAnimation(*gdk.PixbufAnimation)
|
2020-06-13 07:29:32 +00:00
|
|
|
Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error)
|
2020-06-07 04:27:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ImageContainerSizer interface {
|
|
|
|
ImageContainer
|
|
|
|
SetSizeRequest(w, h int)
|
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
// AsyncImage loads an image. This method uses the cache.
|
2020-06-07 04:27:28 +00:00
|
|
|
func AsyncImage(img ImageContainer, url string, procs ...imgutil.Processor) {
|
2020-06-13 07:29:32 +00:00
|
|
|
if url == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
connectDestroyer(img, cancel)
|
|
|
|
|
2020-06-17 07:06:34 +00:00
|
|
|
gif := strings.Contains(url, ".gif")
|
|
|
|
|
|
|
|
l, err := gdk.PixbufLoaderNew()
|
|
|
|
if err != nil {
|
|
|
|
log.Error(errors.Wrap(err, "Failed to make pixbuf loader"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
l.Connect("area-prepared", areaPreparedFn(ctx, img, gif))
|
|
|
|
|
|
|
|
go syncImage(ctx, l, url, procs, gif)
|
2020-06-07 04:27:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-28 01:35:26 +00:00
|
|
|
// AsyncImageSized resizes using GdkPixbuf. This method uses the cache.
|
2020-06-07 04:27:28 +00:00
|
|
|
func AsyncImageSized(img ImageContainerSizer, url string, w, h int, procs ...imgutil.Processor) {
|
2020-06-13 07:29:32 +00:00
|
|
|
if url == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a processor to resize.
|
2020-07-04 04:41:12 +00:00
|
|
|
procs = append(procs, imgutil.Resize(w, h))
|
2020-06-13 07:29:32 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
connectDestroyer(img, cancel)
|
|
|
|
|
2020-06-17 07:06:34 +00:00
|
|
|
gif := strings.Contains(url, ".gif")
|
2020-06-07 04:27:28 +00:00
|
|
|
|
2020-06-17 07:06:34 +00:00
|
|
|
l, err := gdk.PixbufLoaderNew()
|
|
|
|
if err != nil {
|
|
|
|
log.Error(errors.Wrap(err, "Failed to make pixbuf loader"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
l.Connect("size-prepared", func(l *gdk.PixbufLoader, imgW, imgH int) {
|
|
|
|
w, h = imgutil.MaxSize(imgW, imgH, w, h)
|
|
|
|
if w != imgW || h != imgH {
|
|
|
|
l.SetSize(w, h)
|
|
|
|
execIfCtx(ctx, func() { img.SetSizeRequest(w, h) })
|
|
|
|
}
|
2020-06-07 04:27:28 +00:00
|
|
|
})
|
2020-06-17 07:06:34 +00:00
|
|
|
|
|
|
|
l.Connect("area-prepared", areaPreparedFn(ctx, img, gif))
|
|
|
|
|
|
|
|
go syncImage(ctx, l, url, procs, gif)
|
2020-06-07 04:27:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
func connectDestroyer(img ImageContainer, cancel func()) {
|
|
|
|
img.Connect("destroy", func(img ImageContainer) {
|
|
|
|
cancel()
|
|
|
|
img.SetFromPixbuf(nil)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func areaPreparedFn(ctx context.Context, img ImageContainer, gif bool) func(l *gdk.PixbufLoader) {
|
2020-06-07 04:27:28 +00:00
|
|
|
return func(l *gdk.PixbufLoader) {
|
|
|
|
if !gif {
|
|
|
|
p, err := l.GetPixbuf()
|
|
|
|
if err != nil {
|
|
|
|
log.Error(errors.Wrap(err, "Failed to get pixbuf"))
|
|
|
|
return
|
|
|
|
}
|
2020-06-13 07:29:32 +00:00
|
|
|
execIfCtx(ctx, func() { img.SetFromPixbuf(p) })
|
2020-06-07 04:27:28 +00:00
|
|
|
} else {
|
|
|
|
p, err := l.GetAnimation()
|
|
|
|
if err != nil {
|
|
|
|
log.Error(errors.Wrap(err, "Failed to get animation"))
|
|
|
|
return
|
|
|
|
}
|
2020-06-13 07:29:32 +00:00
|
|
|
execIfCtx(ctx, func() { img.SetFromAnimation(p) })
|
2020-06-07 04:27:28 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-06 07:44:36 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
func execIfCtx(ctx context.Context, fn func()) {
|
|
|
|
gts.ExecAsync(func() {
|
|
|
|
if ctx.Err() == nil {
|
|
|
|
fn()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-17 07:06:34 +00:00
|
|
|
func syncImage(ctx context.Context, l io.WriteCloser, url string, p []imgutil.Processor, gif bool) {
|
|
|
|
// Close at the end when done.
|
|
|
|
defer l.Close()
|
2020-06-07 04:27:28 +00:00
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
r, err := get(ctx, url, true)
|
2020-06-06 07:44:36 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
// If we have processors, then write directly in there.
|
2020-06-17 07:06:34 +00:00
|
|
|
if len(p) > 0 {
|
2020-06-06 07:44:36 +00:00
|
|
|
if !gif {
|
2020-06-17 07:06:34 +00:00
|
|
|
err = imgutil.ProcessStream(l, r.Body, p)
|
2020-06-06 07:44:36 +00:00
|
|
|
} else {
|
2020-06-17 07:06:34 +00:00
|
|
|
err = imgutil.ProcessAnimationStream(l, r.Body, p)
|
2020-06-06 07:44:36 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Else, directly copy the body over.
|
|
|
|
_, err = io.Copy(l, r.Body)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Error(errors.Wrap(err, "Error processing image"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|