oss/lib/util.go (614 lines of code) (raw):
package lib
import (
"bytes"
"fmt"
"hash"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
"golang.org/x/crypto/ssh/terminal"
)
var sys_name string
var sys_release string
var sys_machine string
func init() {
sys_name = runtime.GOOS
sys_release = "-"
sys_machine = runtime.GOARCH
if !strings.EqualFold(sys_name, "windows") {
if out, err := exec.Command("uname", "-s").CombinedOutput(); err == nil {
sys_name = string(bytes.TrimSpace(out))
}
if out, err := exec.Command("uname", "-r").CombinedOutput(); err == nil {
sys_release = string(bytes.TrimSpace(out))
}
if out, err := exec.Command("uname", "-m").CombinedOutput(); err == nil {
sys_machine = string(bytes.TrimSpace(out))
}
}
}
// Output print input string to stdout and add '\n'
func Output(str string) {
fmt.Println(str)
}
// FindPos find the elem position in a string array
func FindPos(elem string, elemArray []string) int {
for p, v := range elemArray {
if v == elem {
return p
}
}
return -1
}
// FindPosCaseInsen find the elem position in a string array, ignore case
func FindPosCaseInsen(elem string, elemArray []string) int {
for p, v := range elemArray {
if strings.EqualFold(v, elem) {
return p
}
}
return -1
}
func getBinaryPath() (string, string) {
filePath, _ := exec.LookPath(os.Args[0])
if path, err := os.Readlink(filePath); err == nil {
filePath = path
}
fileName := filepath.Base(filePath)
renameFilePath := ".temp_" + fileName
return filePath, renameFilePath
}
type sysInfo struct {
name string // 操作系统名称windows/Linux
release string // 操作系统版本 2.6.32-220.23.2.ali1089.el5.x86_64等
machine string // 机器类型amd64/x86_64
}
// Get system info
// 获取操作系统信息、机器类型
func getSysInfo() sysInfo {
return sysInfo{name: sys_name, release: sys_release, machine: sys_machine}
}
func getUserAgent(ua string) string {
sys := getSysInfo()
if ua == "" {
return fmt.Sprintf("aliyun-sdk-go/%s (%s/%s/%s;%s)/%s-%s", oss.Version, sys.name, sys.release, sys.machine, runtime.Version(), Package, Version)
}
return fmt.Sprintf("aliyun-sdk-go/%s (%s/%s/%s;%s)/%s-%s/%s", oss.Version, sys.name, sys.release, sys.machine, runtime.Version(), Package, Version, ua)
}
func utcToLocalTime(utc time.Time) time.Time {
return utc.In(time.Local)
}
func max(a, b int64) int64 {
if a >= b {
return a
}
return b
}
func getSizeString(size int64) string {
prefix := ""
str := fmt.Sprintf("%d", size)
if size < 0 {
prefix = "-"
str = str[1:]
}
len := len(str)
strList := []string{}
i := len % 3
if i != 0 {
strList = append(strList, str[0:i])
}
for ; i < len; i += 3 {
strList = append(strList, str[i:i+3])
}
return fmt.Sprintf("%s%s", prefix, strings.Join(strList, ","))
}
// Returns a new slice containing all strings in the
// slice that satisfy the predicate `f`.
func filter(vs []string, f func(string) bool) []string {
vsf := make([]string, 0)
for _, v := range vs {
if f(v) {
vsf = append(vsf, v)
}
}
return vsf
}
// predicate `f` has 2 parameters
func filter2(vs []string, s string, f func(_, _ string) bool) []string {
vsf := make([]string, 0)
for _, v := range vs {
if f(v, s) {
vsf = append(vsf, v)
}
}
return vsf
}
func filterStr(v string, s string, f func(string, string) bool) bool {
return f(v, s)
}
func filterStrWithPattern(v, p string) bool {
_, name := filepath.Split(v)
res, _ := filepath.Match(p, name)
return res
}
func filterStrsWithPattern(vs []string, p string) []string {
vsf := make([]string, 0)
for _, v := range vs {
_, name := filepath.Split(v)
res, _ := filepath.Match(p, name)
if res {
vsf = append(vsf, v)
}
}
return vsf
}
func filterObjectsFromListResultWithPattern(lor oss.ListObjectsResult, pattern string) []string {
objs := make([]string, 0)
for _, obj := range lor.Objects {
objs = append(objs, obj.Key)
}
return filterStrsWithPattern(objs, pattern)
}
func filterObjectsFromChanWithPattern(srcCh <-chan string, pattern string, dstCh chan<- string) {
for obj := range srcCh {
if filterStrWithPattern(obj, pattern) {
dstCh <- obj
}
}
defer close(dstCh)
}
// Following for strings
func getFilter(cmdline []string) (bool, []filterOptionType) {
filters := make([]filterOptionType, 0)
for i, item := range cmdline {
var strTag = ""
if strings.Index(item, IncludePrompt) == 0 {
strTag = IncludePrompt
} else if strings.Index(item, ExcludePrompt) == 0 {
strTag = ExcludePrompt
}
if strTag != "" {
var filter filterOptionType
var strArg string
filter.name = strTag
if item == strTag {
strArg = cmdline[i+1]
} else if item[len(strTag)] == '=' {
strArg = item[len(strTag)+1:]
}
if strArg == "" {
continue
}
// To support standard glob
filter.pattern = strings.Replace(strArg, "[!", "[^", -1)
dir, _ := filepath.Split(filter.pattern)
if dir != "" {
return false, filters
}
filters = append(filters, filter)
}
}
return true, filters
}
func containsInStrsSlice(vs []string, t string) bool {
if len(vs) == 0 {
return false
}
for _, v := range vs {
if v == t {
return true
}
}
return false
}
func filterSingleStr(v, p string, include bool) bool {
_, name := filepath.Split(v)
res, _ := filepath.Match(p, name)
if include {
return res
} else {
return !res
}
}
func filterStrsWithInclude(vs []string, p string) []string {
vsf := make([]string, 0)
for _, v := range vs {
_, name := filepath.Split(v)
res, _ := filepath.Match(p, name)
if res {
vsf = append(vsf, v)
}
}
return vsf
}
func filterStrsWithExclude(vs []string, p string) []string {
vsf := make([]string, 0)
for _, v := range vs {
_, name := filepath.Split(v)
res, _ := filepath.Match(p, name)
if !res {
vsf = append(vsf, v)
}
}
return vsf
}
func matchFiltersForStr(str string, filters []filterOptionType) bool {
if len(filters) == 0 {
return true
}
var res bool
if filters[0].name == IncludePrompt {
res = filterSingleStr(str, filters[0].pattern, true)
} else {
res = filterSingleStr(str, filters[0].pattern, false)
}
for _, filter := range filters[1:] {
if filter.name == IncludePrompt {
res = res || filterSingleStr(str, filter.pattern, true)
} else {
res = res && filterSingleStr(str, filter.pattern, false)
}
}
return res
}
func matchFiltersForStrs(strs []string, filters []filterOptionType) []string {
if len(filters) == 0 {
return strs
}
vsf := make([]string, 0)
for _, str := range strs {
if matchFiltersForStr(str, filters) {
vsf = append(vsf, str)
}
}
return vsf
}
func matchFiltersForStrsInArray(strs []string, filters []filterOptionType) []string {
if len(filters) == 0 {
return strs
}
vsf := make([]string, 0)
if filters[0].name == IncludePrompt {
vsf = append(vsf, filterStrsWithInclude(strs, filters[0].pattern)...)
} else {
vsf = append(vsf, filterStrsWithExclude(strs, filters[0].pattern)...)
}
for _, filter := range filters[1:] {
if filter.name == IncludePrompt {
vsf = append(vsf, filterStrsWithInclude(strs, filter.pattern)...)
} else {
vsf = filterStrsWithExclude(vsf, filter.pattern)
}
}
return vsf
}
// Following for files
func doesSingleFileMatchPatterns(filename string, filters []filterOptionType) bool {
if len(filters) == 0 {
return true
}
files := []string{filename}
vsf := matchFiltersForStrs(files, filters)
if len(vsf) > 0 {
return true
}
return false
}
func containsInFileSlice(vs []fileInfoType, t fileInfoType) bool {
if len(vs) == 0 {
return false
}
for _, v := range vs {
if v == t {
return true
}
}
return false
}
func filterFilesWithInclude(vs []fileInfoType, p string) []fileInfoType {
vsf := make([]fileInfoType, 0)
for _, v := range vs {
_, filename := filepath.Split(v.filePath)
res, _ := filepath.Match(p, filename)
if res {
vsf = append(vsf, v)
}
}
return vsf
}
func filterFilesWithExclude(vs []fileInfoType, p string) []fileInfoType {
vsf := make([]fileInfoType, 0)
for _, v := range vs {
_, filename := filepath.Split(v.filePath)
res, _ := filepath.Match(p, filename)
if !res {
vsf = append(vsf, v)
}
}
return vsf
}
// Following for objects
func doesSingleObjectMatchPatterns(object string, filters []filterOptionType) bool {
if len(filters) == 0 {
return true
}
return doesSingleFileMatchPatterns(object, filters)
}
func getObjectsFromChanToArray(chObjects <-chan objectInfoType) []objectInfoType {
objects := make([]objectInfoType, 0)
for f := range chObjects {
objects = append(objects, f)
}
return objects
}
func filterObjectsWithInclude(vs []objectInfoType, p string) []objectInfoType {
vsf := make([]objectInfoType, 0)
for _, v := range vs {
_, key := filepath.Split(v.relativeKey)
//_, key := filepath.Split(v.key)
res, _ := filepath.Match(p, key)
if res {
vsf = append(vsf, v)
}
}
return vsf
}
func filterObjectsWithExclude(vs []objectInfoType, p string) []objectInfoType {
vsf := make([]objectInfoType, 0)
for _, v := range vs {
_, key := filepath.Split(v.relativeKey)
//_, key := filepath.Split(v.key)
res, _ := filepath.Match(p, key)
if !res {
vsf = append(vsf, v)
}
}
return vsf
}
func matchFiltersForObjects(objects []objectInfoType, filters []filterOptionType) []objectInfoType {
if len(filters) == 0 {
return objects
}
vsf := make([]objectInfoType, 0)
for i, filter := range filters {
if filter.name == IncludePrompt {
vsf = append(vsf, filterObjectsWithInclude(objects, filter.pattern)...)
} else {
if i == 0 {
vsf = append(vsf, filterObjectsWithExclude(objects, filter.pattern)...)
} else {
vsf = filterObjectsWithExclude(vsf, filter.pattern)
}
}
}
return vsf
}
func makeObjectChanFromArray(objects []objectInfoType, chObjs chan<- objectInfoType) {
for _, f := range objects {
chObjs <- f
}
}
func filterObjectsFromChanWithPatterns(chObjects <-chan objectInfoType, filters []filterOptionType, dstObjs chan<- objectInfoType) {
objects := getObjectsFromChanToArray(chObjects)
vsf := matchFiltersForObjects(objects, filters)
makeObjectChanFromArray(vsf, dstObjs)
defer close(dstObjs)
}
func GetCloudUrl(strlUrl, encodingType string) (*CloudURL, error) {
bucketUrL, err := StorageURLFromString(strlUrl, encodingType)
if err != nil {
return nil, err
}
if !bucketUrL.IsCloudURL() {
return nil, fmt.Errorf("parameter is not a cloud url,url is %s", bucketUrL.ToString())
}
cloudUrl := bucketUrL.(CloudURL)
if cloudUrl.bucket == "" {
return nil, fmt.Errorf("bucket name is empty,url is %s", bucketUrL.ToString())
}
return &cloudUrl, nil
}
func matchHash(fnvIns hash.Hash64, key string, modeValue int, countValue int) bool {
fnvIns.Reset()
fnvIns.Write([]byte(key))
if fnvIns.Sum64()%uint64(countValue) == uint64(modeValue) {
return true
}
return false
}
var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyz")
func randStr(n int) string {
b := make([]rune, n)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := range b {
b[i] = letters[r.Intn(len(letters))]
}
return string(b)
}
func currentHomeDir() string {
homeDir := ""
homeDrive := os.Getenv("HOMEDRIVE")
homePath := os.Getenv("HOMEPATH")
if runtime.GOOS == "windows" && homeDrive != "" && homePath != "" {
homeDir = homeDrive + string(os.PathSeparator) + homePath
}
if homeDir != "" {
return homeDir
}
usr, _ := user.Current()
if usr != nil {
homeDir = usr.HomeDir
} else {
homeDir = os.Getenv("HOME")
}
return homeDir
}
func getCurrentDirFileListCommon(dpath string, chFiles chan<- fileInfoType, filters []filterOptionType) error {
if !strings.HasSuffix(dpath, string(os.PathSeparator)) {
dpath += string(os.PathSeparator)
}
fileList, err := ioutil.ReadDir(dpath)
if err != nil {
return err
}
for _, fileInfo := range fileList {
if !fileInfo.IsDir() {
realInfo, errF := os.Stat(dpath + fileInfo.Name())
if errF == nil && realInfo.IsDir() {
// for symlink
continue
}
if doesSingleFileMatchPatterns(fileInfo.Name(), filters) {
chFiles <- fileInfoType{fileInfo.Name(), dpath}
}
}
}
return nil
}
func getFileListCommon(dpath string, chFiles chan<- fileInfoType, onlyCurrentDir bool, disableAllSymlink bool,
enableSymlinkDir bool, filters []filterOptionType) error {
defer close(chFiles)
if onlyCurrentDir {
return getCurrentDirFileListCommon(dpath, chFiles, filters)
}
name := dpath
symlinkDiretorys := []string{dpath}
walkFunc := func(fpath string, f os.FileInfo, err error) error {
if f == nil {
return err
}
dpath = filepath.Clean(dpath)
fpath = filepath.Clean(fpath)
fileName, err := filepath.Rel(dpath, fpath)
if err != nil {
return fmt.Errorf("list file error: %s, info: %s", fpath, err.Error())
}
if f.IsDir() {
if fpath != dpath {
if strings.HasSuffix(fileName, "\\") || strings.HasSuffix(fileName, "/") {
chFiles <- fileInfoType{fileName, name}
} else {
chFiles <- fileInfoType{fileName + string(os.PathSeparator), name}
}
}
return nil
}
if disableAllSymlink && (f.Mode()&os.ModeSymlink) != 0 {
return nil
}
if enableSymlinkDir && (f.Mode()&os.ModeSymlink) != 0 {
// there is difference between os.Stat and os.Lstat in filepath.Walk
realInfo, err := os.Stat(fpath)
if err != nil {
return err
}
if realInfo.IsDir() {
// it's symlink dir
// if linkDir has suffix os.PathSeparator,os.Lstat determine it is a dir
if !strings.HasSuffix(name, string(os.PathSeparator)) {
name += string(os.PathSeparator)
}
linkDir := name + fileName + string(os.PathSeparator)
symlinkDiretorys = append(symlinkDiretorys, linkDir)
return nil
}
}
if doesSingleFileMatchPatterns(fileName, filters) {
chFiles <- fileInfoType{fileName, name}
}
return nil
}
var err error
for {
symlinks := symlinkDiretorys
symlinkDiretorys = []string{}
for _, v := range symlinks {
err = filepath.Walk(v, walkFunc)
if err != nil {
return err
}
}
if len(symlinkDiretorys) == 0 {
break
}
}
return err
}
func getObjectListCommon(bucket *oss.Bucket, cloudURL CloudURL, chObjects chan<- objectInfoType,
onlyCurrentDir bool, filters []filterOptionType, payerOptions []oss.Option) error {
defer close(chObjects)
pre := oss.Prefix(cloudURL.object)
marker := oss.Marker("")
//while the src object is end with "/", use object key as marker, exclude the object itself
if strings.HasSuffix(cloudURL.object, "/") {
marker = oss.Marker(cloudURL.object)
}
del := oss.Delimiter("")
if onlyCurrentDir {
del = oss.Delimiter("/")
}
listOptions := append(payerOptions, pre, marker, del, oss.MaxKeys(1000))
for {
lor, err := bucket.ListObjects(listOptions...)
if err != nil {
return err
}
for _, object := range lor.Objects {
prefix := ""
relativeKey := object.Key
index := strings.LastIndex(cloudURL.object, "/")
if index > 0 {
prefix = object.Key[:index+1]
relativeKey = object.Key[index+1:]
}
if doesSingleObjectMatchPatterns(object.Key, filters) {
if strings.ToLower(object.Type) == "symlink" {
props, err := bucket.GetObjectDetailedMeta(object.Key, payerOptions...)
if err != nil {
LogError("ossGetObjectStatRetry error info:%s\n", err.Error())
return err
}
size, err := strconv.ParseInt(props.Get(oss.HTTPHeaderContentLength), 10, 64)
if err != nil {
LogError("strconv.ParseInt error info:%s\n", err.Error())
return err
}
object.Size = size
}
chObjects <- objectInfoType{prefix, relativeKey, int64(object.Size), object.LastModified}
}
}
pre = oss.Prefix(lor.Prefix)
marker = oss.Marker(lor.NextMarker)
listOptions = append(payerOptions, pre, marker, oss.MaxKeys(1000))
if !lor.IsTruncated {
break
}
}
return nil
}
func GetPassword(prompt string) ([]byte, error) {
fd := int(os.Stdin.Fd())
if terminal.IsTerminal(fd) {
state, err := terminal.MakeRaw(fd)
if err != nil {
f := "getpass: cannot disable terminal echoing — %v"
return nil, fmt.Errorf(f, err)
}
defer terminal.Restore(fd, state)
defer fmt.Println()
}
if prompt == "" {
prompt = "enter password: "
}
fmt.Fprint(os.Stderr, prompt)
return terminal.ReadPassword(fd)
}
// AddStringsToOption add strings option to oss option
func AddStringsToOption(params []string, options []oss.Option) ([]oss.Option, error) {
if params == nil {
return options, nil
}
paramsMap := map[string]string{}
for _, s := range params {
pair := strings.SplitN(s, ":", 2)
name := pair[0]
value := ""
if len(pair) > 1 {
value = pair[1]
}
paramsMap[name] = value
}
for key, value := range paramsMap {
options = append(options, oss.AddParam(key, value))
}
return options, nil
}