Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,18 @@ thumbPath, err := info.DownloadThumbnail(ctx, "thumbnails", 150, false)
if err != nil {
return err
}

imgBytes, err := info.ImageBytes(ctx)
if err != nil {
return err
}
fmt.Println(len(imgBytes))
```

### Public API

- `Fetch(ctx, rawURL, userAgent)` extracts metadata from a page.
- `(*Webinfo).ImageBytes(ctx)` downloads `Webinfo.ImageURL` into memory.
- `(*Webinfo).DownloadImage(ctx, destDir, temporary)` downloads `Webinfo.ImageURL`.
- `(*Webinfo).DownloadThumbnail(ctx, destDir, width, temporary)` creates a resized thumbnail.

Expand All @@ -96,6 +103,7 @@ if err != nil {
3. sniff first 512 bytes (`http.DetectContentType`)
4. fallback `.img`
- `DownloadThumbnail` uses width `150` when `width <= 0`.
- `ImageBytes` reads the full response body into memory; very large images can increase memory usage.

## Error handling

Expand Down
45 changes: 45 additions & 0 deletions webinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,51 @@ type Webinfo struct {
UserAgent string `json:"user_agent,omitempty"` // User-Agent used to fetch the page
}

// ImageBytes downloads w.ImageURL and returns its contents in memory.
//
// Risk: this method reads the entire response body into memory, so very large
// images can increase memory usage.
//
// Returned errors are wrapped with context and include response close failures.
func (w *Webinfo) ImageBytes(ctx context.Context) (data []byte, err error) {
if w == nil {
err = errs.Wrap(ErrNullPointer)
return
}
if w.ImageURL == "" {
err = errs.Wrap(ErrNoImageURL)
return
}

parsed, uerr := fetch.URL(strings.TrimSpace(w.ImageURL))
if uerr != nil {
err = errs.Wrap(uerr, errs.WithContext("url", w.ImageURL))
return
}

resp, ferr := fetch.New(fetch.WithHTTPClient(newHTTPClient())).GetWithContext(
ctx,
parsed,
fetch.WithRequestHeaderSet("User-Agent", getUserAgent(w.UserAgent)),
)
if ferr != nil {
err = errs.Wrap(ferr, errs.WithContext("url", parsed.String()))
return
}
defer func() {
if cerr := resp.Close(); cerr != nil && cerr != os.ErrClosed {
err = errs.Join(cerr, err)
}
}()

data, err = io.ReadAll(resp.Body())
if err != nil {
err = errs.Wrap(err, errs.WithContext("url", parsed.String()))
return
}
return
}

// DownloadImage downloads w.ImageURL and writes it under destDir.
//
// If temporary is true, or if the URL path has no filename, a temporary file is created.
Expand Down
35 changes: 35 additions & 0 deletions webinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,41 @@ func TestDownloadImage_NilReceiver(t *testing.T) {
}
}

func TestImageBytes_NilReceiver(t *testing.T) {
var w *Webinfo
_, err := w.ImageBytes(context.Background())
if err == nil {
t.Fatalf("expected error for nil receiver")
}
}

func TestImageBytes_EmptyURL(t *testing.T) {
w := &Webinfo{}
_, err := w.ImageBytes(context.Background())
if err == nil {
t.Fatalf("expected error for empty image URL")
}
}

func TestImageBytes_Success(t *testing.T) {
body := makeImageBytes(10, 10, "png", 0xaa, 0xbb, 0xcc)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
_, _ = w.Write(body)
})
srv := httptest.NewServer(handler)
defer srv.Close()

w := &Webinfo{ImageURL: srv.URL + "/img.png"}
got, err := w.ImageBytes(context.Background())
if err != nil {
t.Fatalf("ImageBytes failed: %v", err)
}
if !bytes.Equal(got, body) {
t.Fatalf("ImageBytes content mismatch")
}
}

func TestDownloadImage_SaveWithFilename(t *testing.T) {
// serve a PNG at /images/pic.png
handler := func(wr http.ResponseWriter, r *http.Request) {
Expand Down
Loading