From 889aef184657bd3d72a8f6aee552d5fcd029bb98 Mon Sep 17 00:00:00 2001 From: Wenbo Han Date: Wed, 24 May 2023 15:51:43 +0800 Subject: [PATCH] Feat: [#133] Add some methods to contracts/filesystem::File (#119) * Add some methods to contracts/filesystem::File * Optimize unit tests --- contracts/filesystem/storage.go | 9 ++-- filesystem/file.go | 49 ++++++++++------- filesystem/file_test.go | 2 +- filesystem/local.go | 34 ++---------- http/gin_request.go | 2 +- route/gin_test.go | 74 +++++++++++++++++++++++++ support/file/file.go | 96 +++++++++++++++++++++++---------- support/file/file_test.go | 38 ++++++++++++- 8 files changed, 220 insertions(+), 84 deletions(-) diff --git a/contracts/filesystem/storage.go b/contracts/filesystem/storage.go index 2e16ae925..33a10bf6d 100644 --- a/contracts/filesystem/storage.go +++ b/contracts/filesystem/storage.go @@ -40,11 +40,14 @@ type Driver interface { //go:generate mockery --name=File type File interface { Disk(disk string) File + Extension() (string, error) File() string - Store(path string) (string, error) - StoreAs(path string, name string) (string, error) GetClientOriginalName() string GetClientOriginalExtension() string HashName(path ...string) string - Extension() (string, error) + LastModified() (time.Time, error) + MimeType() (string, error) + Size() (int64, error) + Store(path string) (string, error) + StoreAs(path string, name string) (string, error) } diff --git a/filesystem/file.go b/filesystem/file.go index c9eb4880f..fe5e7923d 100644 --- a/filesystem/file.go +++ b/filesystem/file.go @@ -9,6 +9,7 @@ import ( "os" "path" "strings" + "time" "github.com/goravel/framework/contracts/filesystem" "github.com/goravel/framework/facades" @@ -17,9 +18,9 @@ import ( ) type File struct { - disk string - file string - filename string + disk string + path string + name string } func NewFile(file string) (*File, error) { @@ -29,7 +30,7 @@ func NewFile(file string) (*File, error) { disk := facades.Config.GetString("filesystems.default") - return &File{disk: disk, file: file, filename: path.Base(file)}, nil + return &File{disk: disk, path: file, name: path.Base(file)}, nil } func NewFileFromRequest(fileHeader *multipart.FileHeader) (*File, error) { @@ -53,7 +54,7 @@ func NewFileFromRequest(fileHeader *multipart.FileHeader) (*File, error) { disk := facades.Config.GetString("filesystems.default") - return &File{disk: disk, file: tempFile.Name(), filename: fileHeader.Filename}, nil + return &File{disk: disk, path: tempFile.Name(), name: fileHeader.Filename}, nil } func (f *File) Disk(disk string) filesystem.File { @@ -62,24 +63,20 @@ func (f *File) Disk(disk string) filesystem.File { return f } -func (f *File) File() string { - return f.file -} - -func (f *File) Store(path string) (string, error) { - return facades.Storage.Disk(f.disk).PutFile(path, f) +func (f *File) Extension() (string, error) { + return supportfile.Extension(f.path) } -func (f *File) StoreAs(path string, name string) (string, error) { - return facades.Storage.Disk(f.disk).PutFileAs(path, f, name) +func (f *File) File() string { + return f.path } func (f *File) GetClientOriginalName() string { - return f.filename + return f.name } func (f *File) GetClientOriginalExtension() string { - return supportfile.ClientOriginalExtension(f.filename) + return supportfile.ClientOriginalExtension(f.name) } func (f *File) HashName(path ...string) string { @@ -88,7 +85,7 @@ func (f *File) HashName(path ...string) string { realPath = strings.TrimRight(path[0], "/") + "/" } - extension, _ := supportfile.Extension(f.file, true) + extension, _ := supportfile.Extension(f.path, true) if extension == "" { return realPath + str.Random(40) } @@ -96,6 +93,22 @@ func (f *File) HashName(path ...string) string { return realPath + str.Random(40) + "." + extension } -func (f *File) Extension() (string, error) { - return supportfile.Extension(f.file) +func (f *File) LastModified() (time.Time, error) { + return supportfile.LastModified(f.path, facades.Config.GetString("app.timezone")) +} + +func (f *File) MimeType() (string, error) { + return supportfile.MimeType(f.path) +} + +func (f *File) Size() (int64, error) { + return supportfile.Size(f.path) +} + +func (f *File) Store(path string) (string, error) { + return facades.Storage.Disk(f.disk).PutFile(path, f) +} + +func (f *File) StoreAs(path string, name string) (string, error) { + return facades.Storage.Disk(f.disk).PutFileAs(path, f, name) } diff --git a/filesystem/file_test.go b/filesystem/file_test.go index 7ff7f8e48..3bf13c8ac 100644 --- a/filesystem/file_test.go +++ b/filesystem/file_test.go @@ -83,7 +83,7 @@ func TestNewFileFromRequest(t *testing.T) { assert.Nil(t, err) file, err := NewFileFromRequest(f) assert.Nil(t, err) - assert.Equal(t, ".txt", path.Ext(file.file)) + assert.Equal(t, ".txt", path.Ext(file.path)) mockConfig.AssertExpectations(t) } diff --git a/filesystem/local.go b/filesystem/local.go index 28cac6aea..6a684ce3c 100644 --- a/filesystem/local.go +++ b/filesystem/local.go @@ -9,10 +9,9 @@ import ( "strings" "time" - "github.com/gabriel-vasile/mimetype" - "github.com/goravel/framework/contracts/filesystem" "github.com/goravel/framework/facades" + supportfile "github.com/goravel/framework/support/file" "github.com/goravel/framework/support/str" ) @@ -143,17 +142,7 @@ func (r *Local) Get(file string) (string, error) { } func (r *Local) LastModified(file string) (time.Time, error) { - fileInfo, err := os.Stat(r.fullPath(file)) - if err != nil { - return time.Time{}, err - } - - l, err := time.LoadLocation(facades.Config.GetString("app.timezone")) - if err != nil { - return time.Time{}, err - } - - return fileInfo.ModTime().In(l), nil + return supportfile.LastModified(r.fullPath(file), facades.Config.GetString("app.timezone")) } func (r *Local) MakeDirectory(directory string) error { @@ -161,12 +150,7 @@ func (r *Local) MakeDirectory(directory string) error { } func (r *Local) MimeType(file string) (string, error) { - mtype, err := mimetype.DetectFile(r.fullPath(file)) - if err != nil { - return "", err - } - - return mtype.String(), nil + return supportfile.MimeType(r.fullPath(file)) } func (r *Local) Missing(file string) bool { @@ -232,17 +216,7 @@ func (r *Local) PutFileAs(filePath string, source filesystem.File, name string) } func (r *Local) Size(file string) (int64, error) { - fileInfo, err := os.Open(r.fullPath(file)) - if err != nil { - return 0, err - } - - fi, err := fileInfo.Stat() - if err != nil { - return 0, err - } - - return fi.Size(), nil + return supportfile.Size(r.fullPath(file)) } func (r *Local) TemporaryUrl(file string, time time.Time) (string, error) { diff --git a/http/gin_request.go b/http/gin_request.go index 90919ac25..89ba2ae3b 100644 --- a/http/gin_request.go +++ b/http/gin_request.go @@ -67,7 +67,7 @@ func (r *GinRequest) All() map[string]any { } } r.instance.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) - } else if contentType == "multipart/form-data" { + } else if contentType == "multipart/form-data" && r.instance.Request.ContentLength > 0 { if r.instance.Request.PostForm == nil { if err := r.instance.Request.ParseMultipartForm(defaultMemory); err != nil { facades.Log.Errorf("when calling request all method, parse multipart form error: %v", err) diff --git a/route/gin_test.go b/route/gin_test.go index 7eb435eeb..a846e80e7 100644 --- a/route/gin_test.go +++ b/route/gin_test.go @@ -462,6 +462,27 @@ func TestGinRequest(t *testing.T) { expectCode: http.StatusOK, expectBody: "{\"all\":{\"a\":\"1,2\",\"b\":\"4\",\"e\":\"e\",\"file\":{\"Filename\":\"logo.png\",\"Header\":{\"Content-Disposition\":[\"form-data; name=\\\"file\\\"; filename=\\\"logo.png\\\"\"],\"Content-Type\":[\"application/octet-stream\"]},\"Size\":16438}}}", }, + { + name: "All with empty form when Post", + method: "POST", + url: "/all?a=1&a=2&b=3", + setup: func(method, url string) error { + mock.Log() + + gin.Post("/all", func(ctx contractshttp.Context) { + ctx.Response().Success().Json(contractshttp.Json{ + "all": ctx.Request().All(), + }) + }) + + req, _ = http.NewRequest(method, url, nil) + req.Header.Set("Content-Type", "multipart/form-data;boundary=0") + + return nil + }, + expectCode: http.StatusOK, + expectBody: "{\"all\":{\"a\":\"1,2\",\"b\":\"3\"}}", + }, { name: "All with json when Post", method: "POST", @@ -495,6 +516,59 @@ func TestGinRequest(t *testing.T) { expectCode: http.StatusOK, expectBody: "{\"age\":1,\"all\":{\"Age\":1,\"Name\":\"goravel\",\"a\":\"1,2\",\"name\":\"3\"},\"name\":\"goravel\"}", }, + { + name: "All with error json when Post", + method: "POST", + url: "/all?a=1&a=2&name=3", + setup: func(method, url string) error { + mock.Log() + gin.Post("/all", func(ctx contractshttp.Context) { + all := ctx.Request().All() + type Test struct { + Name string + Age int + } + var test Test + _ = ctx.Request().Bind(&test) + + ctx.Response().Success().Json(contractshttp.Json{ + "all": all, + "name": test.Name, + "age": test.Age, + }) + }) + + payload := strings.NewReader(`{ + "Name": "goravel", + "Age": 1, + }`) + req, _ = http.NewRequest(method, url, payload) + req.Header.Set("Content-Type", "application/json") + + return nil + }, + expectCode: http.StatusOK, + expectBody: "{\"age\":0,\"all\":null,\"name\":\"\"}", + }, + { + name: "All with empty json when Post", + method: "POST", + url: "/all?a=1&a=2&name=3", + setup: func(method, url string) error { + gin.Post("/all", func(ctx contractshttp.Context) { + ctx.Response().Success().Json(contractshttp.Json{ + "all": ctx.Request().All(), + }) + }) + + req, _ = http.NewRequest(method, url, nil) + req.Header.Set("Content-Type", "application/json") + + return nil + }, + expectCode: http.StatusOK, + expectBody: "{\"all\":{\"a\":\"1,2\",\"name\":\"3\"}}", + }, { name: "All with json when Put", method: "PUT", diff --git a/support/file/file.go b/support/file/file.go index d95c1b9eb..6589cbf88 100644 --- a/support/file/file.go +++ b/support/file/file.go @@ -6,10 +6,27 @@ import ( "os" "path" "strings" + "time" "github.com/gabriel-vasile/mimetype" ) +func ClientOriginalExtension(file string) string { + return strings.ReplaceAll(path.Ext(file), ".", "") +} + +func Contain(file string, search string) bool { + if Exists(file) { + data, err := ioutil.ReadFile(file) + if err != nil { + return false + } + return strings.Contains(string(data), search) + } + + return false +} + func Create(file string, content string) { err := os.MkdirAll(path.Dir(file), os.ModePerm) if err != nil { @@ -39,6 +56,49 @@ func Exists(file string) bool { return true } +//Extension Supported types: https://github.com/gabriel-vasile/mimetype/blob/master/supported_mimes.md +func Extension(file string, originalWhenUnknown ...bool) (string, error) { + mtype, err := mimetype.DetectFile(file) + if err != nil { + return "", err + } + + if mtype.String() == "" { + if len(originalWhenUnknown) > 0 { + if originalWhenUnknown[0] { + return ClientOriginalExtension(file), nil + } + } + + return "", errors.New("unknown file extension") + } + + return strings.TrimPrefix(mtype.Extension(), "."), nil +} + +func LastModified(file, timezone string) (time.Time, error) { + fileInfo, err := os.Stat(file) + if err != nil { + return time.Time{}, err + } + + l, err := time.LoadLocation(timezone) + if err != nil { + return time.Time{}, err + } + + return fileInfo.ModTime().In(l), nil +} + +func MimeType(file string) (string, error) { + mtype, err := mimetype.DetectFile(file) + if err != nil { + return "", err + } + + return mtype.String(), nil +} + func Remove(file string) bool { fi, err := os.Stat(file) if err != nil { @@ -65,38 +125,16 @@ func Remove(file string) bool { return err == nil } -func Contain(file string, search string) bool { - if Exists(file) { - data, err := ioutil.ReadFile(file) - if err != nil { - return false - } - return strings.Contains(string(data), search) - } - - return false -} - -//Extension Supported types: https://github.com/gabriel-vasile/mimetype/blob/master/supported_mimes.md -func Extension(file string, originalWhenUnknown ...bool) (string, error) { - mtype, err := mimetype.DetectFile(file) +func Size(file string) (int64, error) { + fileInfo, err := os.Open(file) if err != nil { - return "", err + return 0, err } - if mtype.String() == "" { - if len(originalWhenUnknown) > 0 { - if originalWhenUnknown[0] { - return ClientOriginalExtension(file), nil - } - } - - return "", errors.New("unknown file extension") + fi, err := fileInfo.Stat() + if err != nil { + return 0, err } - return strings.TrimPrefix(mtype.Extension(), "."), nil -} - -func ClientOriginalExtension(file string) string { - return strings.ReplaceAll(path.Ext(file), ".", "") + return fi.Size(), nil } diff --git a/support/file/file_test.go b/support/file/file_test.go index df999f36d..defc09008 100644 --- a/support/file/file_test.go +++ b/support/file/file_test.go @@ -9,6 +9,14 @@ import ( "github.com/stretchr/testify/assert" ) +func TestClientOriginalExtension(t *testing.T) { + assert.Equal(t, ClientOriginalExtension("logo.png"), "png") +} + +func TestContain(t *testing.T) { + assert.True(t, Contain("../constant.go", "const Version")) +} + func TestCreate(t *testing.T) { pwd, _ := os.Getwd() path := pwd + "/goravel/goravel.txt" @@ -19,12 +27,38 @@ func TestCreate(t *testing.T) { assert.True(t, Remove(pwd+"/goravel")) } +func TestExists(t *testing.T) { + assert.True(t, Exists("file.go")) +} + func TestExtension(t *testing.T) { extension, err := Extension("file.go") assert.Nil(t, err) assert.Equal(t, "txt", extension) } -func TestClientOriginalExtension(t *testing.T) { - assert.Equal(t, ClientOriginalExtension("logo.png"), "png") +func TestLastModified(t *testing.T) { + ti, err := LastModified("../../logo.png", "UTC") + assert.Nil(t, err) + assert.NotNil(t, ti) +} + +func TestMimeType(t *testing.T) { + mimeType, err := MimeType("../../logo.png") + assert.Nil(t, err) + assert.Equal(t, "image/png", mimeType) +} + +func TestRemove(t *testing.T) { + pwd, _ := os.Getwd() + path := pwd + "/goravel/goravel.txt" + Create(path, `goravel`) + + assert.True(t, Remove(path)) +} + +func TestSize(t *testing.T) { + size, err := Size("../../logo.png") + assert.Nil(t, err) + assert.Equal(t, int64(16438), size) }