[#6792] added filesystem.System.GetReuploadableFile method

This commit is contained in:
Gani Georgiev 2025-05-03 18:36:16 +03:00
parent 7ffe9f63a5
commit e80d64414b
5 changed files with 3658 additions and 3498 deletions

View File

@ -5,12 +5,15 @@
- Updated `app.DB()` to automatically routes raw write SQL statements to the nonconcurrent db pool ([#6689](https://github.com/pocketbase/pocketbase/discussions/6689)).
_For the rare cases when it is needed users still have the option to explicitly target the specific pool they want using `app.ConcurrentDB()`/`app.NonconcurrentDB()`._
- ⚠️ Soft-deprecated and replaced `fsys.GetFile(fileKey)` with `fsys.GetReader(fileKey)` to avoid the confusion with `filesystem.File`.
_The old method will still continue to work for at least until v0.29.0 but you'll get a console warning to replace it with `GetReader`._
- ⚠️ Changed the default `json` field max size to 1MB.
_Users still have the option to adjust the default limit from the collection field options but keep in mind that storing large strings/blobs in the database is known to cause performance issues and should be avoided when possible._
- ⚠️ Soft-deprecated and replaced `filesystem.System.GetFile(fileKey)` with `filesystem.System.GetReader(fileKey)` to avoid the confusion with `filesystem.File`.
_The old method will still continue to work for at least until v0.29.0 but you'll get a console warning to replace it with `GetReader`._
- Added new `filesystem.System.GetReuploadableFile(fileKey, preserveName)` method to return an existing blob as a `*filesystem.File` value ([#6792](https://github.com/pocketbase/pocketbase/discussions/6792)).
_This method could be useful in case you want to clone an existing Record file and assign it to a new Record (e.g. in a Record duplicate action)._
## v0.27.2

File diff suppressed because it is too large Load Diff

View File

@ -176,6 +176,18 @@ func (r *bytesReadSeekCloser) Close() error {
// -------------------------------------------------------------------
var _ FileReader = (openFuncAsReader)(nil)
// openFuncAsReader defines a FileReader from a bare Open function.
type openFuncAsReader func() (io.ReadSeekCloser, error)
// Open implements the [filesystem.FileReader] interface.
func (r openFuncAsReader) Open() (io.ReadSeekCloser, error) {
return r()
}
// -------------------------------------------------------------------
var extInvalidCharsRegex = regexp.MustCompile(`[^\w\.\*\-\+\=\#]+`)
const randomAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789"

View File

@ -8,6 +8,7 @@ import (
"mime/multipart"
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"sort"
@ -30,6 +31,8 @@ import (
// note: the same as blob.ErrNotFound for backward compatibility with earlier versions
var ErrNotFound = blob.ErrNotFound
const metadataOriginalName = "original-filename"
type System struct {
ctx context.Context
bucket *blob.Bucket
@ -123,6 +126,45 @@ func (s *System) GetFile(fileKey string) (*blob.Reader, error) {
return s.GetReader(fileKey)
}
// GetReaderAsFile constructs a new reuploadable File value from the
// associated fileKey blob.Reader.
//
// If preserveName is false then the returned File.Name will have
// a new randomly generated suffix, otherwise it will reuse the original one.
//
// This method could be useful in case you want to clone an existing
// Record file and assign it to a new Record (e.g. in a Record duplicate action).
//
// If you simply want to copy an existing file to a new location you
// could check the Copy(srcKey, dstKey) method.
func (s *System) GetReuploadableFile(fileKey string, preserveName bool) (*File, error) {
attrs, err := s.Attributes(fileKey)
if err != nil {
return nil, err
}
name := path.Base(fileKey)
originalName := attrs.Metadata[metadataOriginalName]
if originalName == "" {
originalName = name
}
file := &File{}
file.Size = attrs.Size
file.OriginalName = originalName
file.Reader = openFuncAsReader(func() (io.ReadSeekCloser, error) {
return s.GetReader(fileKey)
})
if preserveName {
file.Name = name
} else {
file.Name = normalizeName(file.Reader, originalName)
}
return file, nil
}
// Copy copies the file stored at srcKey to dstKey.
//
// If srcKey file doesn't exist, it returns ErrNotFound.
@ -197,7 +239,7 @@ func (s *System) UploadFile(file *File, fileKey string) error {
opts := &blob.WriterOptions{
ContentType: mt.String(),
Metadata: map[string]string{
"original-filename": originalName,
metadataOriginalName: originalName,
},
}
@ -239,7 +281,7 @@ func (s *System) UploadMultipart(fh *multipart.FileHeader, fileKey string) error
opts := &blob.WriterOptions{
ContentType: mt.String(),
Metadata: map[string]string{
"original-filename": originalName,
metadataOriginalName: originalName,
},
}

View File

@ -578,6 +578,83 @@ func TestFileSystemGetReader(t *testing.T) {
}
}
func TestFileSystemGetReuploadableFile(t *testing.T) {
dir := createTestDir(t)
defer os.RemoveAll(dir)
fsys, err := filesystem.NewLocal(dir)
if err != nil {
t.Fatal(err)
}
defer fsys.Close()
t.Run("missing.txt", func(t *testing.T) {
_, err := fsys.GetReuploadableFile("missing.txt", false)
if err == nil {
t.Fatal("Expected error, got nil")
}
})
testReader := func(t *testing.T, f *filesystem.File, expectedContent string) {
r, err := f.Reader.Open()
if err != nil {
t.Fatal(err)
}
raw, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
rawStr := string(raw)
if rawStr != expectedContent {
t.Fatalf("Expected content %q, got %q", expectedContent, rawStr)
}
}
t.Run("existing (preserve name)", func(t *testing.T) {
file, err := fsys.GetReuploadableFile("test/sub1.txt", true)
if err != nil {
t.Fatal(err)
}
if v := file.OriginalName; v != "sub1.txt" {
t.Fatalf("Expected originalName %q, got %q", "sub1.txt", v)
}
if v := file.Size; v != 4 {
t.Fatalf("Expected size %d, got %d", 4, v)
}
if v := file.Name; v != "sub1.txt" {
t.Fatalf("Expected name to be preserved, got %q", v)
}
testReader(t, file, "sub1")
})
t.Run("existing (new random suffix name)", func(t *testing.T) {
file, err := fsys.GetReuploadableFile("test/sub1.txt", false)
if err != nil {
t.Fatal(err)
}
if v := file.OriginalName; v != "sub1.txt" {
t.Fatalf("Expected originalName %q, got %q", "sub1.txt", v)
}
if v := file.Size; v != 4 {
t.Fatalf("Expected size %d, got %d", 4, v)
}
if v := file.Name; v == "sub1.txt" || len(v) <= len("sub1.txt.png") {
t.Fatalf("Expected name to have new random suffix, got %q", v)
}
testReader(t, file, "sub1")
})
}
func TestFileSystemCopy(t *testing.T) {
dir := createTestDir(t)
defer os.RemoveAll(dir)