mirror of https://github.com/stashapp/stash.git
177 lines
4.5 KiB
Go
177 lines
4.5 KiB
Go
package fsutil
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// CopyFile copies the contents of the file at srcpath to a regular file at dstpath.
|
|
// It will copy the last modified timestamp
|
|
// If dstpath already exists the function will fail.
|
|
func CopyFile(srcpath, dstpath string) (err error) {
|
|
r, err := os.Open(srcpath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w, err := os.OpenFile(dstpath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0666)
|
|
if err != nil {
|
|
r.Close() // We need to close the input file as the defer below would not be called.
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
r.Close() // ok to ignore error: file was opened read-only.
|
|
e := w.Close()
|
|
// Report the error from w.Close, if any.
|
|
// But do so only if there isn't already an outgoing error.
|
|
if e != nil && err == nil {
|
|
err = e
|
|
}
|
|
// Copy modified time
|
|
if err == nil {
|
|
// io.Copy succeeded, we should fix the dstpath timestamp
|
|
srcFileInfo, e := os.Stat(srcpath)
|
|
if e != nil {
|
|
err = e
|
|
return
|
|
}
|
|
|
|
e = os.Chtimes(dstpath, srcFileInfo.ModTime(), srcFileInfo.ModTime())
|
|
if e != nil {
|
|
err = e
|
|
}
|
|
}
|
|
}()
|
|
|
|
_, err = io.Copy(w, r)
|
|
return err
|
|
}
|
|
|
|
// SafeMove attempts to move the file with path src to dest using os.Rename. If this fails, then it copies src to dest, then deletes src.
|
|
// If the copy fails, or the delete fails, the function will return an error.
|
|
func SafeMove(src, dst string) error {
|
|
err := os.Rename(src, dst)
|
|
|
|
if err != nil {
|
|
copyErr := CopyFile(src, dst)
|
|
if copyErr != nil {
|
|
return fmt.Errorf("copying file during SaveMove failed with: '%w'; renaming file failed previously with: '%v'", copyErr, err)
|
|
}
|
|
|
|
removeErr := os.Remove(src)
|
|
if removeErr != nil {
|
|
// if we can't remove the old file, remove the new one and fail
|
|
_ = os.Remove(dst)
|
|
return fmt.Errorf("removing old file during SafeMove failed with: '%w'; renaming file failed previously with: '%v'", removeErr, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MatchExtension returns true if the extension of the provided path
|
|
// matches any of the provided extensions.
|
|
func MatchExtension(path string, extensions []string) bool {
|
|
ext := filepath.Ext(path)
|
|
for _, e := range extensions {
|
|
if strings.EqualFold(ext, "."+e) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// FindInPaths returns the path to baseName in the first path where it exists from paths.
|
|
func FindInPaths(paths []string, baseName string) string {
|
|
for _, p := range paths {
|
|
filePath := filepath.Join(p, baseName)
|
|
if exists, _ := FileExists(filePath); exists {
|
|
return filePath
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// FileExists returns true if the given path exists and is a file.
|
|
// This function returns false and the error encountered if the call to os.Stat fails.
|
|
func FileExists(path string) (bool, error) {
|
|
info, err := os.Stat(path)
|
|
if err == nil {
|
|
return !info.IsDir(), nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
// WriteFile writes file to path creating parent directories if needed
|
|
func WriteFile(path string, file []byte) error {
|
|
pathErr := EnsureDirAll(filepath.Dir(path))
|
|
if pathErr != nil {
|
|
return fmt.Errorf("cannot ensure path exists: %w", pathErr)
|
|
}
|
|
|
|
return os.WriteFile(path, file, 0755)
|
|
}
|
|
|
|
// GetNameFromPath returns the name of a file from its path
|
|
// if stripExtension is true the extension is omitted from the name
|
|
func GetNameFromPath(path string, stripExtension bool) string {
|
|
fn := filepath.Base(path)
|
|
if stripExtension {
|
|
ext := filepath.Ext(fn)
|
|
fn = strings.TrimSuffix(fn, ext)
|
|
}
|
|
return fn
|
|
}
|
|
|
|
// Touch creates an empty file at the given path if it doesn't already exist
|
|
func Touch(path string) error {
|
|
var _, err = os.Stat(path)
|
|
if os.IsNotExist(err) {
|
|
var file, err = os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
replaceCharsRE = regexp.MustCompile(`[&=\\/:*"?_ ]`)
|
|
removeCharsRE = regexp.MustCompile(`[^[:alnum:]-.]`)
|
|
multiHyphenRE = regexp.MustCompile(`\-+`)
|
|
)
|
|
|
|
// SanitiseBasename returns a file basename removing any characters that are illegal or problematic to use in the filesystem.
|
|
func SanitiseBasename(v string) string {
|
|
v = strings.TrimSpace(v)
|
|
|
|
// replace illegal filename characters with -
|
|
v = replaceCharsRE.ReplaceAllString(v, "-")
|
|
|
|
// remove other characters
|
|
v = removeCharsRE.ReplaceAllString(v, "")
|
|
|
|
// remove multiple hyphens
|
|
v = multiHyphenRE.ReplaceAllString(v, "-")
|
|
|
|
return strings.TrimSpace(v)
|
|
}
|
|
|
|
// GetExeName returns the name of the given executable for the current platform.
|
|
// One windows it returns the name with the .exe extension.
|
|
func GetExeName(base string) string {
|
|
if runtime.GOOS == "windows" {
|
|
return base + ".exe"
|
|
}
|
|
return base
|
|
}
|