From b1aae7001f5098cd4fb6eaedcc56511b272929f9 Mon Sep 17 00:00:00 2001
From: John Guo <john@johng.cn>
Date: Thu, 7 Nov 2024 11:25:36 +0800
Subject: [PATCH 1/4] fix(cmd/gf):remove dir after process done if given build
 file parameter is not a file but a dir name

---
 cmd/gf/internal/cmd/cmd_build.go              |  2 +-
 cmd/gf/internal/cmd/cmd_doc.go                |  6 +--
 cmd/gf/internal/cmd/cmd_run.go                |  8 +++-
 cmd/gf/internal/cmd/cmd_up.go                 |  2 +-
 cmd/gf/internal/cmd/genctrl/genctrl.go        |  2 +-
 .../genctrl/genctrl_generate_ctrl_clear.go    |  2 +-
 cmd/gf/internal/cmd/gendao/gendao_clear.go    |  2 +-
 cmd/gf/internal/cmd/genservice/genservice.go  |  4 +-
 contrib/registry/file/file_discovery.go       |  2 +-
 contrib/registry/file/file_registrar.go       |  2 +-
 os/gfile/gfile.go                             | 35 ++++++++++++++--
 os/gfile/gfile_z_unit_test.go                 | 42 ++++++++++++++++++-
 os/glog/glog_logger_rotate.go                 |  8 ++--
 os/gsession/gsession_storage_file.go          |  4 +-
 14 files changed, 97 insertions(+), 24 deletions(-)

diff --git a/cmd/gf/internal/cmd/cmd_build.go b/cmd/gf/internal/cmd/cmd_build.go
index edb7c382e96..182ee370b78 100644
--- a/cmd/gf/internal/cmd/cmd_build.go
+++ b/cmd/gf/internal/cmd/cmd_build.go
@@ -217,7 +217,7 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
 		if !gfile.Exists(in.PackDst) {
 			// Remove the go file that is automatically packed resource.
 			defer func() {
-				_ = gfile.Remove(in.PackDst)
+				_ = gfile.RemoveFile(in.PackDst)
 				mlog.Printf(`remove the automatically generated resource go file: %s`, in.PackDst)
 			}()
 		}
diff --git a/cmd/gf/internal/cmd/cmd_doc.go b/cmd/gf/internal/cmd/cmd_doc.go
index 9b1b855ed8a..7139a75d21c 100644
--- a/cmd/gf/internal/cmd/cmd_doc.go
+++ b/cmd/gf/internal/cmd/cmd_doc.go
@@ -106,10 +106,10 @@ func NewDocSetting(ctx context.Context, in cDocInput) *DocSetting {
 
 }
 
-// Clean clean the temporary directory
+// Clean cleans the temporary directory
 func (d *DocSetting) Clean() error {
 	if _, err := os.Stat(d.TempDir); err == nil {
-		err = gfile.Remove(d.TempDir)
+		err = gfile.RemoveAll(d.TempDir)
 		if err != nil {
 			mlog.Print("Failed to delete temporary directory:", err)
 			return err
@@ -168,7 +168,7 @@ func (d *DocSetting) DownloadDoc() error {
 	err := gcompress.UnZipFile(d.DocZipFile, d.TempDir)
 	if err != nil {
 		mlog.Print("Failed to unzip the file, please run again:", err)
-		gfile.Remove(d.DocZipFile)
+		_ = gfile.RemoveFile(d.DocZipFile)
 		return err
 	}
 
diff --git a/cmd/gf/internal/cmd/cmd_run.go b/cmd/gf/internal/cmd/cmd_run.go
index 8c30c4e3484..490a93a8b45 100644
--- a/cmd/gf/internal/cmd/cmd_run.go
+++ b/cmd/gf/internal/cmd/cmd_run.go
@@ -93,6 +93,12 @@ type (
 )
 
 func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err error) {
+	if !gfile.Exists(in.File) {
+		mlog.Fatalf(`given file "%s" not found`, in.File)
+	}
+	if !gfile.IsFile(in.File) {
+		mlog.Fatalf(`given "%s" is not a file`, in.File)
+	}
 	// Necessary check.
 	if gproc.SearchBinary("go") == "" {
 		mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
@@ -205,7 +211,7 @@ func (app *cRunApp) End(ctx context.Context, sig os.Signal, outputPath string) {
 			mlog.Debugf("kill process error: %s", err.Error())
 		}
 	}
-	if err := gfile.Remove(outputPath); err != nil {
+	if err := gfile.RemoveFile(outputPath); err != nil {
 		mlog.Printf("delete binary file error: %s", err.Error())
 	} else {
 		mlog.Printf("deleted binary file: %s", outputPath)
diff --git a/cmd/gf/internal/cmd/cmd_up.go b/cmd/gf/internal/cmd/cmd_up.go
index 8fb232ddd21..2bef38896f9 100644
--- a/cmd/gf/internal/cmd/cmd_up.go
+++ b/cmd/gf/internal/cmd/cmd_up.go
@@ -192,7 +192,7 @@ func (c cUp) doUpgradeCLI(ctx context.Context) (err error) {
 	defer func() {
 		mlog.Printf(`new version cli binary is successfully installed to "%s"`, gfile.SelfPath())
 		mlog.Printf(`remove temporary buffer file "%s"`, localSaveFilePath)
-		_ = gfile.Remove(localSaveFilePath)
+		_ = gfile.RemoveFile(localSaveFilePath)
 	}()
 
 	// It fails if file not exist or its size is less than 1MB.
diff --git a/cmd/gf/internal/cmd/genctrl/genctrl.go b/cmd/gf/internal/cmd/genctrl/genctrl.go
index d6788620aa2..87d87766ee2 100644
--- a/cmd/gf/internal/cmd/genctrl/genctrl.go
+++ b/cmd/gf/internal/cmd/genctrl/genctrl.go
@@ -128,7 +128,7 @@ func (c CGenCtrl) generateByWatchFile(watchFile, sdkPath string, sdkStdVersion,
 			return
 		}
 	}
-	defer gfile.Remove(flockFilePath)
+	defer gfile.RemoveFile(flockFilePath)
 	_ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
 
 	// check this updated file is an api file.
diff --git a/cmd/gf/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go b/cmd/gf/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go
index 73d562648b6..af328f8dd6e 100644
--- a/cmd/gf/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go
+++ b/cmd/gf/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go
@@ -49,7 +49,7 @@ func (c *controllerClearer) doClear(dstModuleFolderPath string, item apiItem) (e
 				`remove unimplemented and of no api definitions controller file: %s`,
 				methodFilePath,
 			)
-			err = gfile.Remove(methodFilePath)
+			err = gfile.RemoveFile(methodFilePath)
 		}
 	}
 	return
diff --git a/cmd/gf/internal/cmd/gendao/gendao_clear.go b/cmd/gf/internal/cmd/gendao/gendao_clear.go
index ad05918c37b..181804641b7 100644
--- a/cmd/gf/internal/cmd/gendao/gendao_clear.go
+++ b/cmd/gf/internal/cmd/gendao/gendao_clear.go
@@ -40,7 +40,7 @@ func doClearItem(item CGenDaoInternalGenItem, allGeneratedFilePaths []string) {
 	}
 	for _, filePath := range generatedFilePaths {
 		if !gstr.InArray(allGeneratedFilePaths, filePath) {
-			if err := gfile.Remove(filePath); err != nil {
+			if err := gfile.RemoveFile(filePath); err != nil {
 				mlog.Print(err)
 			}
 		}
diff --git a/cmd/gf/internal/cmd/genservice/genservice.go b/cmd/gf/internal/cmd/genservice/genservice.go
index 392cd4308e4..e02ed91ea4a 100644
--- a/cmd/gf/internal/cmd/genservice/genservice.go
+++ b/cmd/gf/internal/cmd/genservice/genservice.go
@@ -114,7 +114,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
 				return
 			}
 		}
-		defer gfile.Remove(flockFilePath)
+		defer gfile.RemoveFile(flockFilePath)
 		_ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
 
 		// It works only if given WatchFile is in SrcFolder.
@@ -253,7 +253,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
 				utils.IsFileDoNotEdit(relativeFilePath) {
 
 				mlog.Printf(`remove no longer used service file: %s`, relativeFilePath)
-				if err = gfile.Remove(file); err != nil {
+				if err = gfile.RemoveFile(file); err != nil {
 					return nil, err
 				}
 			}
diff --git a/contrib/registry/file/file_discovery.go b/contrib/registry/file/file_discovery.go
index 5fd21a7418e..b5c03d977b4 100644
--- a/contrib/registry/file/file_discovery.go
+++ b/contrib/registry/file/file_discovery.go
@@ -99,7 +99,7 @@ func (r *Registry) getServices(ctx context.Context) (services []gsvc.Service, er
 				`service "%s" is expired, update at: %s, current: %s, sub duration: %s`,
 				s.GetKey(), updateAt.String(), nowTime.String(), subDuration.String(),
 			)
-			_ = gfile.Remove(filePath)
+			_ = gfile.RemoveFile(filePath)
 			continue
 		}
 		services = append(services, s)
diff --git a/contrib/registry/file/file_registrar.go b/contrib/registry/file/file_registrar.go
index d98098779d3..d7bdfb8eedc 100644
--- a/contrib/registry/file/file_registrar.go
+++ b/contrib/registry/file/file_registrar.go
@@ -44,7 +44,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (register
 
 // Deregister off-lines and removes `service` from the Registry.
 func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error {
-	return gfile.Remove(r.getServiceFilePath(service))
+	return gfile.RemoveFile(r.getServiceFilePath(service))
 }
 
 func (r *Registry) getServiceFilePath(service gsvc.Service) string {
diff --git a/os/gfile/gfile.go b/os/gfile/gfile.go
index 7222ed900fb..26ab342d624 100644
--- a/os/gfile/gfile.go
+++ b/os/gfile/gfile.go
@@ -252,6 +252,8 @@ func Glob(pattern string, onlyNames ...bool) ([]string, error) {
 // If parameter `path` is directory, it deletes it recursively.
 //
 // It does nothing if given `path` does not exist or is empty.
+//
+// Deprecated, please use RemoveFile or RemoveAll instead.
 func Remove(path string) (err error) {
 	// It does nothing if `path` is empty.
 	if path == "" {
@@ -263,6 +265,25 @@ func Remove(path string) (err error) {
 	return
 }
 
+// RemoveFile removes the named file or (empty) directory.
+func RemoveFile(path string) (err error) {
+	if err = os.Remove(path); err != nil {
+		err = gerror.Wrapf(err, `os.Remove failed for path "%s"`, path)
+	}
+	return
+}
+
+// RemoveAll removes path and any children it contains.
+// It removes everything it can but returns the first error
+// it encounters. If the path does not exist, RemoveAll
+// returns nil (no error).
+func RemoveAll(path string) (err error) {
+	if err = os.RemoveAll(path); err != nil {
+		err = gerror.Wrapf(err, `os.RemoveAll failed for path "%s"`, path)
+	}
+	return
+}
+
 // IsReadable checks whether given `path` is readable.
 func IsReadable(path string) bool {
 	result := true
@@ -270,7 +291,9 @@ func IsReadable(path string) bool {
 	if err != nil {
 		result = false
 	}
-	file.Close()
+	if file != nil {
+		_ = file.Close()
+	}
 	return result
 }
 
@@ -294,7 +317,9 @@ func IsWritable(path string) bool {
 		if err != nil {
 			result = false
 		}
-		_ = file.Close()
+		if file != nil {
+			_ = file.Close()
+		}
 	}
 	return result
 }
@@ -406,15 +431,17 @@ func IsEmpty(path string) bool {
 		if err != nil {
 			return true
 		}
+		if file == nil {
+			return true
+		}
 		defer file.Close()
 		names, err := file.Readdirnames(-1)
 		if err != nil {
 			return true
 		}
 		return len(names) == 0
-	} else {
-		return stat.Size() == 0
 	}
+	return stat.Size() == 0
 }
 
 // Ext returns the file name extension used by path.
diff --git a/os/gfile/gfile_z_unit_test.go b/os/gfile/gfile_z_unit_test.go
index 4760ce1abff..40696d871de 100644
--- a/os/gfile/gfile_z_unit_test.go
+++ b/os/gfile/gfile_z_unit_test.go
@@ -16,6 +16,7 @@ import (
 	"github.com/gogf/gf/v2/os/gtime"
 	"github.com/gogf/gf/v2/test/gtest"
 	"github.com/gogf/gf/v2/util/gconv"
+	"github.com/gogf/gf/v2/util/guid"
 )
 
 func Test_IsDir(t *testing.T) {
@@ -213,7 +214,6 @@ func Test_OpenWithFlagPerm(t *testing.T) {
 }
 
 func Test_Exists(t *testing.T) {
-
 	gtest.C(t, func(t *gtest.T) {
 		var (
 			flag  bool
@@ -685,3 +685,43 @@ func Test_MTimestamp(t *testing.T) {
 		t.Assert(gfile.MTimestamp(gfile.Temp()) > 0, true)
 	})
 }
+
+func Test_RemoveFile_RemoveAll(t *testing.T) {
+	// safe deleting single file.
+	gtest.C(t, func(t *gtest.T) {
+		path := gfile.Temp(guid.S())
+		err := gfile.PutContents(path, "1")
+		t.AssertNil(err)
+		t.Assert(gfile.Exists(path), true)
+
+		err = gfile.RemoveFile(path)
+		t.AssertNil(err)
+		t.Assert(gfile.Exists(path), false)
+	})
+	// error deleting dir which is not empty.
+	gtest.C(t, func(t *gtest.T) {
+		var (
+			err       error
+			dirPath   = gfile.Temp(guid.S())
+			filePath1 = gfile.Join(dirPath, guid.S())
+			filePath2 = gfile.Join(dirPath, guid.S())
+		)
+		err = gfile.PutContents(filePath1, "1")
+		t.AssertNil(err)
+		t.Assert(gfile.Exists(filePath1), true)
+
+		err = gfile.PutContents(filePath2, "2")
+		t.AssertNil(err)
+		t.Assert(gfile.Exists(filePath2), true)
+
+		err = gfile.RemoveFile(dirPath)
+		t.AssertNE(err, nil)
+		t.Assert(gfile.Exists(filePath1), true)
+		t.Assert(gfile.Exists(filePath2), true)
+
+		err = gfile.RemoveAll(dirPath)
+		t.AssertNil(err)
+		t.Assert(gfile.Exists(filePath1), false)
+		t.Assert(gfile.Exists(filePath2), false)
+	})
+}
diff --git a/os/glog/glog_logger_rotate.go b/os/glog/glog_logger_rotate.go
index 4df261d1a53..7e840361663 100644
--- a/os/glog/glog_logger_rotate.go
+++ b/os/glog/glog_logger_rotate.go
@@ -55,7 +55,7 @@ func (l *Logger) doRotateFile(ctx context.Context, filePath string) error {
 
 	// No backups, it then just removes the current logging file.
 	if l.config.RotateBackupLimit == 0 {
-		if err := gfile.Remove(filePath); err != nil {
+		if err := gfile.RemoveFile(filePath); err != nil {
 			return err
 		}
 		intlog.Printf(
@@ -216,7 +216,7 @@ func (l *Logger) rotateChecksTimely(ctx context.Context) {
 				err := gcompress.GzipFile(path, path+".gz")
 				if err == nil {
 					intlog.Printf(ctx, `compressed done, remove original logging file: %s`, path)
-					if err = gfile.Remove(path); err != nil {
+					if err = gfile.RemoveFile(path); err != nil {
 						intlog.Print(ctx, err)
 					}
 				} else {
@@ -264,7 +264,7 @@ func (l *Logger) rotateChecksTimely(ctx context.Context) {
 		for i := 0; i < diff; i++ {
 			path, _ := backupFiles.PopLeft()
 			intlog.Printf(ctx, `remove exceeded backup limit file: %s`, path)
-			if err := gfile.Remove(path.(string)); err != nil {
+			if err = gfile.RemoveFile(path.(string)); err != nil {
 				intlog.Errorf(ctx, `%+v`, err)
 			}
 		}
@@ -284,7 +284,7 @@ func (l *Logger) rotateChecksTimely(ctx context.Context) {
 						`%v - %v = %v > %v, remove expired backup file: %s`,
 						now, mtime, subDuration, l.config.RotateBackupExpire, path,
 					)
-					if err := gfile.Remove(path); err != nil {
+					if err = gfile.RemoveFile(path); err != nil {
 						intlog.Errorf(ctx, `%+v`, err)
 					}
 					return true
diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go
index fdf5b54c3f3..94c9e1f1e58 100644
--- a/os/gsession/gsession_storage_file.go
+++ b/os/gsession/gsession_storage_file.go
@@ -128,7 +128,7 @@ func (s *StorageFile) sessionFilePath(sessionId string) string {
 
 // RemoveAll deletes all key-value pairs from storage.
 func (s *StorageFile) RemoveAll(ctx context.Context, sessionId string) error {
-	return gfile.Remove(s.sessionFilePath(sessionId))
+	return gfile.RemoveFile(s.sessionFilePath(sessionId))
 }
 
 // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage.
@@ -262,7 +262,7 @@ func (s *StorageFile) checkAndClearSessionFile(ctx context.Context, path string)
 				path, gtime.NewFromTimeStamp(fileTimestampMilli), s.ttl,
 			)
 		})
-		return gfile.Remove(path)
+		return gfile.RemoveFile(path)
 	}
 	return nil
 }

From 0975c0dd024d0eaa732ad2fac6bd32e4d4d42d89 Mon Sep 17 00:00:00 2001
From: John Guo <john@johng.cn>
Date: Thu, 7 Nov 2024 11:27:27 +0800
Subject: [PATCH 2/4] up

---
 os/gfile/gfile.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/os/gfile/gfile.go b/os/gfile/gfile.go
index 26ab342d624..51c45985208 100644
--- a/os/gfile/gfile.go
+++ b/os/gfile/gfile.go
@@ -253,7 +253,7 @@ func Glob(pattern string, onlyNames ...bool) ([]string, error) {
 //
 // It does nothing if given `path` does not exist or is empty.
 //
-// Deprecated, please use RemoveFile or RemoveAll instead.
+// Deprecated: please use RemoveFile or RemoveAll instead.
 func Remove(path string) (err error) {
 	// It does nothing if `path` is empty.
 	if path == "" {

From 68882b9689487a9be4eeb1a2a1d0bb9f9abb993f Mon Sep 17 00:00:00 2001
From: John Guo <john@johng.cn>
Date: Thu, 7 Nov 2024 11:30:06 +0800
Subject: [PATCH 3/4] up

---
 os/gfile/gfile.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/os/gfile/gfile.go b/os/gfile/gfile.go
index 51c45985208..ee1c902f150 100644
--- a/os/gfile/gfile.go
+++ b/os/gfile/gfile.go
@@ -253,7 +253,9 @@ func Glob(pattern string, onlyNames ...bool) ([]string, error) {
 //
 // It does nothing if given `path` does not exist or is empty.
 //
-// Deprecated: please use RemoveFile or RemoveAll instead.
+// Deprecated:
+// As the name Remove for files deleting is ambiguous,
+// please use RemoveFile or RemoveAll for explicit usage instead.
 func Remove(path string) (err error) {
 	// It does nothing if `path` is empty.
 	if path == "" {

From c50c9c840f21d74d5c08c685e2bcf3e75d6b98c7 Mon Sep 17 00:00:00 2001
From: John Guo <john@johng.cn>
Date: Mon, 11 Nov 2024 20:24:58 +0800
Subject: [PATCH 4/4] up

---
 os/gsession/gsession_storage_file.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go
index 94c9e1f1e58..27c83dcd662 100644
--- a/os/gsession/gsession_storage_file.go
+++ b/os/gsession/gsession_storage_file.go
@@ -128,7 +128,7 @@ func (s *StorageFile) sessionFilePath(sessionId string) string {
 
 // RemoveAll deletes all key-value pairs from storage.
 func (s *StorageFile) RemoveAll(ctx context.Context, sessionId string) error {
-	return gfile.RemoveFile(s.sessionFilePath(sessionId))
+	return gfile.RemoveAll(s.sessionFilePath(sessionId))
 }
 
 // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage.