[#6792] added filesystem.System.GetReuploadableFile method
This commit is contained in:
parent
7ffe9f63a5
commit
e80d64414b
|
@ -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
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue