-
Notifications
You must be signed in to change notification settings - Fork 0
/
fileutil.go
282 lines (243 loc) · 6.99 KB
/
fileutil.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
// Copyright 2021 dairongpeng <dairongpeng@foxmail.com>. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fileutil
import (
"archive/zip"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/user"
"path/filepath"
"regexp"
"github.com/h2non/filetype"
"github.com/h2non/filetype/types"
)
// FileType uses the filetype package to determine the given file path's type.
func FileType(filePath string) (types.Type, error) {
file, _ := os.Open(filePath)
// We only have to pass the file header = first 261 bytes
head := make([]byte, 261)
_, _ = file.Read(head)
return filetype.Match(head)
}
// FileExists returns true if the given path exists.
func FileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
return false, err
}
// DirExists returns true if the given path exists and is a directory.
func DirExists(path string) (bool, error) {
exists, _ := FileExists(path)
fileInfo, _ := os.Stat(path)
if !exists || !fileInfo.IsDir() {
return false, fmt.Errorf("path either doesn't exist, or is not a directory <%s>", path)
}
return true, nil
}
// 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
}
// EnsureDir will create a directory at the given path if it doesn't already exist.
func EnsureDir(path string) error {
exists, err := FileExists(path)
if !exists {
err = os.Mkdir(path, 0755)
return err
}
return err
}
// EnsureDirAll will create a directory at the given path along with any necessary parents if they don't already exist.
func EnsureDirAll(path string) error {
return os.MkdirAll(path, 0755)
}
// RemoveDir removes the given dir (if it exists) along with all of its contents.
func RemoveDir(path string) error {
return os.RemoveAll(path)
}
// EmptyDir will recursively remove the contents of a directory at the given path.
func EmptyDir(path string) error {
d, err := os.Open(path)
if err != nil {
return err
}
defer d.Close()
names, err := d.Readdirnames(-1)
if err != nil {
return err
}
for _, name := range names {
err = os.RemoveAll(filepath.Join(path, name))
if err != nil {
return err
}
}
return nil
}
// ListDir will return the contents of a given directory path as a string slice.
func ListDir(path string) []string {
files, err := ioutil.ReadDir(path)
if err != nil {
path = filepath.Dir(path)
files, _ = ioutil.ReadDir(path)
}
//nolint: prealloc
var dirPaths []string
for _, file := range files {
if !file.IsDir() {
continue
}
dirPaths = append(dirPaths, filepath.Join(path, file.Name()))
}
return dirPaths
}
// GetHomeDirectory returns the path of the user's home directory. ~ on Unix and C:\Users\UserName on Windows.
func GetHomeDirectory() string {
currentUser, err := user.Current()
if err != nil {
panic(err)
}
return currentUser.HomeDir
}
// SafeMove move src to dst in safe mode.
func SafeMove(src, dst string) error {
err := os.Rename(src, dst)
//nolint: nestif
if err != nil {
fmt.Printf("[fileutil] unable to rename: \"%s\" due to %s. Falling back to copying.", src, err.Error())
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
err = out.Close()
if err != nil {
return err
}
err = os.Remove(src)
if err != nil {
return err
}
}
return nil
}
// IsZipFileUncompressed returns true if zip file in path is using 0 compression level.
func IsZipFileUncompressed(path string) (bool, error) {
r, err := zip.OpenReader(path)
if err != nil {
fmt.Printf("Error reading zip file %s: %s\n", path, err)
return false, err
}
defer r.Close()
for _, f := range r.File {
if f.FileInfo().IsDir() { // skip dirs, they always get store level compression
continue
}
return f.Method == 0, nil // check compression level of first actual file
}
return false, nil
}
// 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 %s", pathErr)
}
err := ioutil.WriteFile(path, file, 0600)
if err != nil {
return fmt.Errorf("write error for thumbnail %s: %s ", path, err)
}
return nil
}
// GetIntraDir returns a string that can be added to filepath.Join to implement directory depth, "" on error
// eg given a pattern of 0af63ce3c99162e9df23a997f62621c5 and a depth of 2 length of 3
// returns 0af/63c or 0af\63c ( dependin on os) that can be later used like this filepath.Join(directory, intradir,
// basename).
func GetIntraDir(pattern string, depth, length int) string {
if depth < 1 || length < 1 || (depth*length > len(pattern)) {
return ""
}
intraDir := pattern[0:length] // depth 1 , get length number of characters from pattern
for i := 1; i < depth; i++ { // for every extra depth: move to the right of the pattern length positions, get length number of chars
intraDir = filepath.Join(
intraDir,
pattern[length*i:length*(i+1)],
) // adding each time to intradir the extra characters with a filepath join
}
return intraDir
}
// GetParent returns the parent directory of the given path.
func GetParent(path string) *string {
isRoot := path[len(path)-1:] == "/"
if isRoot {
return nil
}
parentPath := filepath.Clean(path + "/..")
return &parentPath
}
// ServeFileNoCache serves the provided file, ensuring that the response
// contains headers to prevent caching.
func ServeFileNoCache(w http.ResponseWriter, r *http.Request, filepath string) {
w.Header().Add("Cache-Control", "no-cache")
http.ServeFile(w, r, filepath)
}
// MatchEntries returns a string slice of the entries in directory dir which
// match the regexp pattern. On error an empty slice is returned
// MatchEntries isn't recursive, only the specific 'dir' is searched
// without being expanded.
func MatchEntries(dir, pattern string) ([]string, error) {
var res []string
var err error
re, err := regexp.Compile(pattern)
if err != nil {
return nil, err
}
f, err := os.Open(dir)
if err != nil {
return nil, err
}
defer f.Close()
files, err := f.Readdirnames(-1)
if err != nil {
return nil, err
}
for _, file := range files {
if re.Match([]byte(file)) {
res = append(res, filepath.Join(dir, file))
}
}
return res, err
}