1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-03-19 06:07:48 +02:00

added filesystem.NewFileFromUrl(ctx, url)

This commit is contained in:
Gani Georgiev 2023-12-06 20:42:30 +02:00
parent 64eefb44e8
commit f7df737c45
3 changed files with 116 additions and 2 deletions

View File

@ -67,6 +67,8 @@
- Added `onlyVerified` auth collection option to globally disallow authentication requests for unverified users.
- Added `filesystem.NewFileFromUrl(ctx, url)` helper method to construct a `*filesystem.BytesReader` file from the specified url.
## v0.20.0-rc3

View File

@ -2,11 +2,14 @@ package filesystem
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"strings"
@ -25,10 +28,10 @@ type FileReader interface {
//
// The file could be from a local path, multipipart/formdata header, etc.
type File struct {
Reader FileReader
Name string
OriginalName string
Size int64
Reader FileReader
}
// NewFileFromPath creates a new File instance from the provided local file path.
@ -65,7 +68,7 @@ func NewFileFromBytes(b []byte, name string) (*File, error) {
return f, nil
}
// NewFileFromMultipart creates a new File instace from the provided multipart header.
// NewFileFromMultipart creates a new File from the provided multipart header.
func NewFileFromMultipart(mh *multipart.FileHeader) (*File, error) {
f := &File{}
@ -77,6 +80,45 @@ func NewFileFromMultipart(mh *multipart.FileHeader) (*File, error) {
return f, nil
}
type UrlOptions struct {
Context context.Context
Url string
}
// NewFileFromUrl creates a new File from the provided url by
// downloading the resource and load it as BytesReader.
//
// Example
//
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
// defer cancel()
//
// file, err := filesystem.NewFileFromUrl(ctx, "https://example.com/image.png")
func NewFileFromUrl(ctx context.Context, url string) (*File, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode > 399 {
return nil, fmt.Errorf("failed to download url %s (%d)", url, res.StatusCode)
}
var buf bytes.Buffer
if _, err = io.Copy(&buf, res.Body); err != nil {
return nil, err
}
return NewFileFromBytes(buf.Bytes(), path.Base(url))
}
// -------------------------------------------------------------------
var _ FileReader = (*MultipartReader)(nil)

View File

@ -1,6 +1,9 @@
package filesystem_test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
@ -109,3 +112,70 @@ func TestNewFileFromMultipart(t *testing.T) {
t.Fatalf("Expected Reader to be MultipartReader, got %v", f.Reader)
}
}
func TestNewFileFromUrlTimeout(t *testing.T) {
// timeout
// invalid response
// valid response
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/error" {
w.WriteHeader(http.StatusInternalServerError)
}
fmt.Fprintf(w, "test")
}))
defer srv.Close()
// cancelled context
{
ctx, cancel := context.WithCancel(context.Background())
cancel()
f, err := filesystem.NewFileFromUrl(ctx, srv.URL+"/cancel")
if err == nil {
t.Fatal("[ctx_cancel] Expected error, got nil")
}
if f != nil {
t.Fatalf("[ctx_cancel] Expected file to be nil, got %v", f)
}
}
// error response
{
f, err := filesystem.NewFileFromUrl(context.Background(), srv.URL+"/error")
if err == nil {
t.Fatal("[error_status] Expected error, got nil")
}
if f != nil {
t.Fatalf("[error_status] Expected file to be nil, got %v", f)
}
}
// valid response
{
originalName := "image_! noext"
normalizedNamePattern := regexp.QuoteMeta("image_noext_") + `\w{10}` + regexp.QuoteMeta(".txt")
f, err := filesystem.NewFileFromUrl(context.Background(), srv.URL+"/"+originalName)
if err != nil {
t.Fatalf("[valid] Unexpected error %v", err)
}
if f == nil {
t.Fatal("[valid] Expected non-nil file")
}
// check the created file fields
if f.OriginalName != originalName {
t.Fatalf("Expected OriginalName %q, got %q", originalName, f.OriginalName)
}
if match, _ := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
t.Fatalf("Expected Name to match %v, got %q (%v)", normalizedNamePattern, f.Name, err)
}
if f.Size != 4 {
t.Fatalf("Expected Size %v, got %v", 4, f.Size)
}
if _, ok := f.Reader.(*filesystem.BytesReader); !ok {
t.Fatalf("Expected Reader to be BytesReader, got %v", f.Reader)
}
}
}