github.com/viant/toolbox@v0.34.5/storage/scp/fileinfo_parser.go (about)

     1  package scp
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/lunixbochs/vtclean"
     6  	"github.com/viant/toolbox"
     7  	"github.com/viant/toolbox/storage"
     8  	"net/url"
     9  	"strings"
    10  	"time"
    11  	"unicode"
    12  )
    13  
    14  const (
    15  	fileInfoPermission = iota
    16  	_
    17  	fileInfoOwner
    18  	fileInfoGroup
    19  	fileInfoSize
    20  	fileInfoDateMonth
    21  	fileInfoDateDay
    22  	fileInfoDateHour
    23  	fileInfoDateYear
    24  	fileInfoName
    25  )
    26  
    27  const (
    28  	fileIsoInfoPermission = iota
    29  	_
    30  	fileIsoInfoOwner
    31  	fileIsoInfoGroup
    32  	fileIsoInfoSize
    33  	fileIsoDate
    34  	fileIsoTime
    35  	fileIsoTimezone
    36  	fileIsoInfoName
    37  )
    38  
    39  //Parser represents fileinfo parser from stdout
    40  type Parser struct {
    41  	IsoTimeStyle bool
    42  }
    43  
    44  func (p *Parser) Parse(parsedURL *url.URL, stdout string, isURLDir bool) ([]storage.Object, error) {
    45  	var err error
    46  	var result = make([]storage.Object, 0)
    47  	if strings.Contains(stdout, "No such file or directory") {
    48  		return result, nil
    49  	}
    50  	for _, line := range strings.Split(stdout, "\n") {
    51  		if line == "" {
    52  			continue
    53  		}
    54  		var object storage.Object
    55  		if p.IsoTimeStyle {
    56  			if object, err = p.extractObjectFromIsoBasedTimeCommand(parsedURL, line, isURLDir); err != nil {
    57  				object, err = p.extractObjectFromNonIsoBaseTimeCommand(parsedURL, line, isURLDir)
    58  			}
    59  		} else {
    60  			if object, err = p.extractObjectFromNonIsoBaseTimeCommand(parsedURL, line, isURLDir); err != nil {
    61  				object, err = p.extractObjectFromIsoBasedTimeCommand(parsedURL, line, isURLDir)
    62  			}
    63  		}
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  		result = append(result, object)
    68  	}
    69  	return result, nil
    70  }
    71  
    72  func (p *Parser) HasNextTokenInout(nextTokenPosition int, line string) bool {
    73  	if nextTokenPosition >= len(line) {
    74  		return false
    75  	}
    76  	nextToken := []rune(string(line[nextTokenPosition:]))[0]
    77  	return !unicode.IsSpace(nextToken)
    78  }
    79  
    80  func (p *Parser) newObject(parsedURL *url.URL, name, permission, line, size string, modificationTime time.Time, isURLDirectory bool) (storage.Object, error) {
    81  	var URLPath = parsedURL.Path
    82  	var URL = parsedURL.String()
    83  	var pathPosition = strings.Index(URL, parsedURL.Host) + len(parsedURL.Host)
    84  	var URLPrefix = URL[:pathPosition]
    85  
    86  	fileMode, err := storage.NewFileMode(permission)
    87  	if err != nil {
    88  		return nil, fmt.Errorf("failed to parse line for lineinfo: %v, unable to file attributes: %v", line, err)
    89  	}
    90  	if isURLDirectory {
    91  		name = strings.Replace(name, URLPath, "", 1)
    92  		URLPath = toolbox.URLPathJoin(URLPath, name)
    93  	} else {
    94  		URLPath = name
    95  	}
    96  
    97  	var objectURL = URLPrefix + URLPath
    98  	fileInfo := storage.NewFileInfo(name, int64(toolbox.AsInt(size)), fileMode, modificationTime, fileMode.IsDir())
    99  	object := newStorageObject(objectURL, fileInfo, fileInfo)
   100  	return object, nil
   101  }
   102  
   103  //extractObjectFromNonIsoBaseTimeCommand extract file storage object from line,
   104  // it expects a file info without iso i.e  -rw-r--r--  1 awitas  1742120565   414 Jun  8 14:14:08 2017 id_rsa.pub
   105  func (p *Parser) extractObjectFromNonIsoBaseTimeCommand(parsedURL *url.URL, line string, isURLDirectory bool) (storage.Object, error) {
   106  	tokenIndex := 0
   107  	if strings.TrimSpace(line) == "" {
   108  		return nil, nil
   109  	}
   110  	var owner, name, permission, group, size, year, month, day, hour string
   111  	for i, aRune := range line {
   112  		if unicode.IsSpace(aRune) {
   113  			if p.HasNextTokenInout(i+1, line) {
   114  				tokenIndex++
   115  			}
   116  			continue
   117  		}
   118  
   119  		aChar := string(aRune)
   120  		switch tokenIndex {
   121  		case fileInfoPermission:
   122  			permission += aChar
   123  		case fileInfoOwner:
   124  			owner += aChar
   125  		case fileInfoGroup:
   126  			group += aChar
   127  		case fileInfoSize:
   128  			if size == "" && !unicode.IsNumber(aRune) {
   129  				tokenIndex--
   130  				group += " " + aChar
   131  				continue
   132  			}
   133  			size += aChar
   134  		case fileInfoDateMonth:
   135  			month += aChar
   136  		case fileInfoDateDay:
   137  			day += aChar
   138  		case fileInfoDateHour:
   139  			hour += aChar
   140  		case fileInfoDateYear:
   141  			year += aChar
   142  		case fileInfoName:
   143  			name += aChar
   144  		}
   145  	}
   146  
   147  	if name == "" {
   148  		return nil, fmt.Errorf("failed to parse line for fileinfo: %v\n", line)
   149  	}
   150  	dateTime := year + " " + month + " " + day + " " + hour
   151  	layout := toolbox.DateFormatToLayout("yyyy MMM ddd HH:mm:s")
   152  	modificationTime, err := time.Parse(layout, dateTime)
   153  	if err != nil {
   154  		return nil, fmt.Errorf("failed to extract file info from stdout: %v, err: %v", line, err)
   155  	}
   156  
   157  	return p.newObject(parsedURL, name, permission, line, size, modificationTime, isURLDirectory)
   158  }
   159  
   160  //extractObjectFromNonIsoBaseTimeCommand extract file storage object from line,
   161  // it expects a file info with iso i.e. -rw-r--r-- 1 awitas awitas 2002 2017-11-04 22:29:33.363458941 +0000 aerospikeciads_aerospike.conf
   162  func (p *Parser) extractObjectFromIsoBasedTimeCommand(parsedURL *url.URL, line string, isURLDirectory bool) (storage.Object, error) {
   163  	tokenIndex := 0
   164  	if strings.TrimSpace(line) == "" {
   165  		return nil, nil
   166  	}
   167  	var owner, name, permission, group, timezone, date, modTime, size string
   168  	line = vtclean.Clean(line, false)
   169  	for i, aRune := range line {
   170  
   171  		if unicode.IsSpace(aRune) {
   172  			if p.HasNextTokenInout(i+1, line) {
   173  				tokenIndex++
   174  			}
   175  			continue
   176  		}
   177  
   178  		aChar := string(aRune)
   179  		switch tokenIndex {
   180  		case fileIsoInfoPermission:
   181  			permission += aChar
   182  		case fileIsoInfoOwner:
   183  			owner += aChar
   184  		case fileIsoInfoGroup:
   185  			group += aChar
   186  		case fileIsoInfoSize:
   187  			if size == "" && !unicode.IsNumber(aRune) {
   188  				tokenIndex--
   189  				group += " " + aChar
   190  				continue
   191  			}
   192  			size += aChar
   193  		case fileIsoDate:
   194  			date += aChar
   195  		case fileIsoTime:
   196  			modTime += aChar
   197  		case fileIsoTimezone:
   198  			timezone += aChar
   199  		case fileIsoInfoName:
   200  			name += aChar
   201  		}
   202  		continue
   203  	}
   204  	timeLen := len(modTime)
   205  	if timeLen > 12 {
   206  		modTime = string(modTime[:12])
   207  	}
   208  	dateTime := date + " " + modTime + " " + timezone
   209  	layout := toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss.SSS ZZ")
   210  	if len(date+" "+modTime) <= len("yyyy-MM-dd HH:mm:ss") {
   211  		layout = toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss ZZ")
   212  	}
   213  	modificationTime, err := time.Parse(layout, dateTime)
   214  	if err != nil {
   215  		return nil, fmt.Errorf("failed to extract file info from stdout: %v, err: %v", line, err)
   216  	}
   217  	return p.newObject(parsedURL, name, permission, line, size, modificationTime, isURLDirectory)
   218  }