1package common
2
3import (
4	"errors"
5	"fmt"
6	"os"
7	"path"
8	"strings"
9)
10
11// IsDir returns true if given path is a directory,
12// or returns false when it's a file or does not exist.
13func IsDir(dir string) bool {
14	f, e := os.Stat(dir)
15	if e != nil {
16		return false
17	}
18	return f.IsDir()
19}
20
21func statDir(dirPath, recPath string, includeDir, isDirOnly bool) ([]string, error) {
22	dir, err := os.Open(dirPath)
23	if err != nil {
24		return nil, err
25	}
26	defer dir.Close()
27
28	fis, err := dir.Readdir(0)
29	if err != nil {
30		return nil, err
31	}
32
33	var statList []string
34
35	for _, fi := range fis {
36		if strings.Contains(fi.Name(), ".DS_Store") {
37			continue
38		}
39
40		relPath := path.Join(recPath, fi.Name())
41		curPath := path.Join(dirPath, fi.Name())
42		if fi.IsDir() {
43			if includeDir {
44				statList = append(statList, relPath+"/")
45			}
46			s, err := statDir(curPath, relPath, includeDir, isDirOnly)
47			if err != nil {
48				return nil, err
49			}
50			statList = append(statList, s...)
51		} else if !isDirOnly {
52			statList = append(statList, relPath)
53		}
54	}
55	return statList, nil
56}
57
58// StatDir gathers information of given directory by depth-first.
59// It returns slice of file list and includes subdirectories if enabled;
60// it returns error and nil slice when error occurs in underlying functions,
61// or given path is not a directory or does not exist.
62//
63// Slice does not include given path itself.
64// If subdirectories is enabled, they will have suffix '/'.
65func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
66	if !IsDir(rootPath) {
67		return nil, errors.New("not a directory or does not exist: " + rootPath)
68	}
69
70	isIncludeDir := false
71	if len(includeDir) >= 1 {
72		isIncludeDir = includeDir[0]
73	}
74	return statDir(rootPath, "", isIncludeDir, false)
75}
76
77// GetAllSubDirs returns all subdirectories of given root path.
78// Slice does not include given path itself.
79func GetAllSubDirs(rootPath string) ([]string, error) {
80	if !IsDir(rootPath) {
81		return nil, errors.New("not a directory or does not exist: " + rootPath)
82	}
83	return statDir(rootPath, "", true, true)
84}
85
86// GetFileListBySuffix returns an ordered list of file paths.
87// It recognize if given path is a file, and don't do recursive find.
88func GetFileListBySuffix(dirPath, suffix string) ([]string, error) {
89	if !IsExist(dirPath) {
90		return nil, fmt.Errorf("given path does not exist: %s", dirPath)
91	} else if IsFile(dirPath) {
92		return []string{dirPath}, nil
93	}
94
95	// Given path is a directory.
96	dir, err := os.Open(dirPath)
97	if err != nil {
98		return nil, err
99	}
100
101	fis, err := dir.Readdir(0)
102	if err != nil {
103		return nil, err
104	}
105
106	files := make([]string, 0, len(fis))
107	for _, fi := range fis {
108		if strings.HasSuffix(fi.Name(), suffix) {
109			files = append(files, path.Join(dirPath, fi.Name()))
110		}
111	}
112
113	return files, nil
114}
115
116// CopyDir copy files recursively from source to target directory.
117//
118// The filter accepts a function that process the path info.
119// and should return true for need to filter.
120//
121// It returns error when error occurs in underlying functions.
122func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error {
123	// Check if target directory exists.
124	if IsExist(destPath) {
125		return errors.New("file or directory alreay exists: " + destPath)
126	}
127
128	err := os.MkdirAll(destPath, os.ModePerm)
129	if err != nil {
130		return err
131	}
132
133	// Gather directory info.
134	infos, err := StatDir(srcPath, true)
135	if err != nil {
136		return err
137	}
138
139	var filter func(filePath string) bool
140	if len(filters) > 0 {
141		filter = filters[0]
142	}
143
144	for _, info := range infos {
145		if filter != nil && filter(info) {
146			continue
147		}
148
149		curPath := path.Join(destPath, info)
150		if strings.HasSuffix(info, "/") {
151			err = os.MkdirAll(curPath, os.ModePerm)
152		} else {
153			err = Copy(path.Join(srcPath, info), curPath)
154		}
155		if err != nil {
156			return err
157		}
158	}
159	return nil
160}
161