This repository has been archived by the owner on Jan 10, 2020. It is now read-only.
/
file.go
380 lines (326 loc) · 10.4 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
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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
package utils
import (
"archive/zip"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/beyondblog/k8s-web-terminal/Godeps/_workspace/src/github.com/fsnotify/fsnotify"
"github.com/beyondblog/k8s-web-terminal/Godeps/_workspace/src/github.com/kataras/iris/logger"
)
const (
// ContentBINARY is the string of "application/octet-stream response headers
ContentBINARY = "application/octet-stream"
)
var (
// AssetsDirectory the path which iris saves some assets came from the internet ( used in iris control plugin (to download the html,css,js) and for iris command line tool to download the packages)
AssetsDirectory = ""
)
// init just sets the iris path for assets, used in iris control plugin and for iris command line tool(create command)
// the AssetsDirectory path should be like: C:/users/kataras/.iris (for windows) and for linux you can imagine
func init() {
homepath := ""
if runtime.GOOS == "windows" {
homepath = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
} else {
homepath = os.Getenv("HOME")
}
AssetsDirectory = homepath + PathSeparator + ".iris"
}
// DirectoryExists returns true if a directory(or file) exists, otherwise false
func DirectoryExists(dir string) bool {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return false
}
return true
}
// DownloadZip downloads a zip file returns the downloaded filename and an error.
//
// An indicator is always shown up to the terminal, so the user will know if (a plugin) try to download something
func DownloadZip(zipURL string, newDir string) (string, error) {
var err error
var size int64
finish := make(chan bool)
defer func() {
finish <- true
}()
go func() {
print("\n|")
print("_")
print("|")
for {
select {
case v := <-finish:
{
if v {
print("\010\010\010") //remove the loading chars
close(finish)
return
}
}
default:
print("\010\010-")
time.Sleep(time.Second / 2)
print("\010\\")
time.Sleep(time.Second / 2)
print("\010|")
time.Sleep(time.Second / 2)
print("\010/")
time.Sleep(time.Second / 2)
print("\010-")
time.Sleep(time.Second / 2)
print("|")
}
}
}()
os.MkdirAll(newDir, 0755) // os.ModeDir, 0755 fix for unix users by @cgyy
tokens := strings.Split(zipURL, "/")
fileName := newDir + tokens[len(tokens)-1]
if !strings.HasSuffix(fileName, ".zip") {
return "", ErrNoZip.Format(fileName)
}
output, err := os.Create(fileName)
if err != nil {
return "", ErrFileCreate.Format(err.Error())
}
defer output.Close()
response, err := http.Get(zipURL)
if err != nil {
return "", ErrFileDownload.Format(zipURL, err.Error())
}
defer response.Body.Close()
size, err = io.Copy(output, response.Body)
if err != nil {
return "", ErrFileCopy.Format(err.Error())
}
_ = size
//print("OK ", size, " bytes downloaded") //we keep that here so developer will always see in the terminal if a plugin downloads something or no ?
return fileName, nil
}
// Unzip extracts a zipped file to the target location
//
// it removes the zipped file after successfully completion
// returns a string with the path of the created folder (if any) and an error (if any)
func Unzip(archive string, target string) (string, error) {
reader, err := zip.OpenReader(archive)
if err != nil {
return "", err
}
if err := os.MkdirAll(target, 0755); err != nil {
return "", ErrDirCreate.Format(target, err.Error())
}
createdFolder := ""
for _, file := range reader.File {
path := filepath.Join(target, file.Name)
if file.FileInfo().IsDir() {
os.MkdirAll(path, file.Mode())
if createdFolder == "" {
// this is the new directory that zip has
createdFolder = path
}
continue
}
fileReader, err := file.Open()
if err != nil {
return "", ErrFileOpen.Format(err.Error())
}
defer fileReader.Close()
targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return "", ErrFileOpen.Format(err.Error())
}
defer targetFile.Close()
if _, err := io.Copy(targetFile, fileReader); err != nil {
return "", ErrFileCopy.Format(err.Error())
}
}
reader.Close()
return createdFolder, nil
}
// RemoveFile removes a file or directory and returns an error, if any
func RemoveFile(filePath string) error {
return ErrFileRemove.With(os.RemoveAll(filePath))
}
// Install is just the flow of: downloadZip -> unzip -> removeFile(zippedFile)
// accepts 2 parameters
//
// first parameter is the remote url file zip
// second parameter is the target directory
// returns a string(installedDirectory) and an error
//
// (string) installedDirectory is the directory which the zip file had, this is the real installation path, you don't need to know what it's because these things maybe change to the future let's keep it to return the correct path.
// the installedDirectory is not empty when the installation is succed, the targetDirectory is not already exists and no error happens
// the installedDirectory is empty when the installation is already done by previous time or an error happens
func Install(remoteFileZip string, targetDirectory string) (installedDirectory string, err error) {
var zipFile string
zipFile, err = DownloadZip(remoteFileZip, targetDirectory)
if err == nil {
installedDirectory, err = Unzip(zipFile, targetDirectory)
if err == nil {
installedDirectory += string(os.PathSeparator)
RemoveFile(zipFile)
}
}
return
}
// CopyFile copy a file, accepts full path of the source and full path of destination, if file exists it's overrides it
// this function doesn't checks for permissions and all that, it returns an error if didn't worked
func CopyFile(source string, destination string) error {
reader, err := os.Open(source)
if err != nil {
return ErrFileOpen.Format(err.Error())
}
defer reader.Close()
writer, err := os.Create(destination)
if err != nil {
return ErrFileCreate.Format(err.Error())
}
defer writer.Close()
_, err = io.Copy(writer, reader)
if err != nil {
return ErrFileCopy.Format(err.Error())
}
err = writer.Sync()
if err != nil {
return ErrFileCopy.Format(err.Error())
}
return nil
}
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist.
//
// Note: the CopyDir function was not written by me, but its working well
func CopyDir(source string, dest string) (err error) {
// get properties of source dir
fi, err := os.Stat(source)
if err != nil {
return err
}
if !fi.IsDir() {
return fmt.Errorf("Source is not a directory! Source path: %s", source)
}
/*_, err = os.Open(dest)
if !os.IsNotExist(err) {
return nil // Destination already exists
}*/
// create dest dir
err = os.MkdirAll(dest, fi.Mode())
if err != nil {
return err
}
entries, err := ioutil.ReadDir(source)
for _, entry := range entries {
sfp := source + "/" + entry.Name()
dfp := dest + "/" + entry.Name()
if entry.IsDir() {
err = CopyDir(sfp, dfp)
if err != nil {
return
}
} else {
// perform copy
err = CopyFile(sfp, dfp)
if err != nil {
return
}
}
}
return
}
// TypeByExtension returns the MIME type associated with the file extension ext.
// The extension ext should begin with a leading dot, as in ".html".
// When ext has no associated type, TypeByExtension returns "".
//
// Extensions are looked up first case-sensitively, then case-insensitively.
//
// The built-in table is small but on unix it is augmented by the local
// system's mime.types file(s) if available under one or more of these
// names:
//
// /etc/mime.types
// /etc/apache2/mime.types
// /etc/apache/mime.types
//
// On Windows, MIME types are extracted from the registry.
//
// Text types have the charset parameter set to "utf-8" by default.
func TypeByExtension(fullfilename string) (t string) {
ext := filepath.Ext(fullfilename)
//these should be found by the windows(registry) and unix(apache) but on windows some machines have problems on this part.
if t = mime.TypeByExtension(ext); t == "" {
// no use of map here because we will have to lock/unlock it, by hand is better, no problem:
if ext == ".json" {
t = "application/json"
} else if ext == ".zip" {
t = "application/zip"
} else if ext == ".3gp" {
t = "video/3gpp"
} else if ext == ".7z" {
t = "application/x-7z-compressed"
} else if ext == ".ace" {
t = "application/x-ace-compressed"
} else if ext == ".aac" {
t = "audio/x-aac"
} else if ext == ".ico" { // for any case
t = "image/x-icon"
} else {
t = ContentBINARY
}
}
return
}
// GetParentDir returns the parent directory(string) of the passed targetDirectory (string)
func GetParentDir(targetDirectory string) string {
lastSlashIndex := strings.LastIndexByte(targetDirectory, os.PathSeparator)
//check if the slash is at the end , if yes then re- check without the last slash, we don't want /path/to/ , we want /path/to in order to get the /path/ which is the parent directory of the /path/to
if lastSlashIndex == len(targetDirectory)-1 {
lastSlashIndex = strings.LastIndexByte(targetDirectory[0:lastSlashIndex], os.PathSeparator)
}
parentDirectory := targetDirectory[0:lastSlashIndex]
return parentDirectory
}
/*
// 3-BSD License for package fsnotify/fsnotify
// Copyright (c) 2012 The Go Authors. All rights reserved.
// Copyright (c) 2012 fsnotify Authors. All rights reserved.
*/
// WatchDirectoryChanges watches a directory and fires the callback with the changed name, receives a logger just to print with red letters any errors, no need for second callback.
func WatchDirectoryChanges(rootPath string, evt func(filename string), logger *logger.Logger) {
isWindows := runtime.GOOS == "windows"
watcher, werr := fsnotify.NewWatcher()
if werr != nil {
logger.Dangerf(werr.Error())
return
}
go func() {
var lastChange = time.Now()
var i = 0
for {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
//this is received two times, the last time is the real changed file, so
i++
if i%2 == 0 || !isWindows { // this 'hack' works for windows but I dont know if works for linux too, we can wait for issue reports here.
if time.Now().After(lastChange.Add(time.Duration(1) * time.Second)) {
lastChange = time.Now()
evt(event.Name)
}
}
}
case err := <-watcher.Errors:
logger.Dangerf(err.Error())
}
}
}()
werr = watcher.Add(rootPath)
if werr != nil {
logger.Dangerf(werr.Error())
}
}