[#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
+12
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"
+44 -2
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,
},
}
+77
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)