-
-
Notifications
You must be signed in to change notification settings - Fork 760
/
file.go
165 lines (139 loc) · 4.08 KB
/
file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package fsutil
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/stashapp/stash/pkg/logger"
)
// 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.
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)
}
err = os.Remove(src)
if err != nil {
logger.Errorf("error removing old file %s during SafeMove: %v", src, 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)
}