github.com/jhalter/mobius@v0.12.1/hotline/files.go (about)

     1  package hotline
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"io/fs"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strings"
    11  )
    12  
    13  func fileTypeFromFilename(filename string) fileType {
    14  	fileExt := strings.ToLower(filepath.Ext(filename))
    15  	ft, ok := fileTypes[fileExt]
    16  	if ok {
    17  		return ft
    18  	}
    19  	return defaultFileType
    20  }
    21  
    22  func fileTypeFromInfo(info fs.FileInfo) (ft fileType, err error) {
    23  	if info.IsDir() {
    24  		ft.CreatorCode = "n/a "
    25  		ft.TypeCode = "fldr"
    26  	} else {
    27  		ft = fileTypeFromFilename(info.Name())
    28  	}
    29  
    30  	return ft, nil
    31  }
    32  
    33  const maxFileSize = 4294967296
    34  
    35  func getFileNameList(path string, ignoreList []string) (fields []Field, err error) {
    36  	files, err := os.ReadDir(path)
    37  	if err != nil {
    38  		return fields, nil
    39  	}
    40  
    41  	for _, file := range files {
    42  		var fnwi FileNameWithInfo
    43  
    44  		if ignoreFile(file.Name(), ignoreList) {
    45  			continue
    46  		}
    47  
    48  		fileCreator := make([]byte, 4)
    49  
    50  		fileInfo, err := file.Info()
    51  		if err != nil {
    52  			return fields, err
    53  		}
    54  
    55  		// Check if path is a symlink.  If so, follow it.
    56  		if fileInfo.Mode()&os.ModeSymlink != 0 {
    57  			resolvedPath, err := os.Readlink(filepath.Join(path, file.Name()))
    58  			if err != nil {
    59  				return fields, err
    60  			}
    61  
    62  			rFile, err := os.Stat(resolvedPath)
    63  			if errors.Is(err, os.ErrNotExist) {
    64  				continue
    65  			}
    66  			if err != nil {
    67  				return fields, err
    68  			}
    69  
    70  			if rFile.IsDir() {
    71  				dir, err := os.ReadDir(filepath.Join(path, file.Name()))
    72  				if err != nil {
    73  					return fields, err
    74  				}
    75  
    76  				var c uint32
    77  				for _, f := range dir {
    78  					if !ignoreFile(f.Name(), ignoreList) {
    79  						c += 1
    80  					}
    81  				}
    82  
    83  				binary.BigEndian.PutUint32(fnwi.FileSize[:], c)
    84  				copy(fnwi.Type[:], "fldr")
    85  				copy(fnwi.Creator[:], fileCreator)
    86  			} else {
    87  				binary.BigEndian.PutUint32(fnwi.FileSize[:], uint32(rFile.Size()))
    88  				copy(fnwi.Type[:], fileTypeFromFilename(rFile.Name()).TypeCode)
    89  				copy(fnwi.Creator[:], fileTypeFromFilename(rFile.Name()).CreatorCode)
    90  			}
    91  		} else if file.IsDir() {
    92  			dir, err := os.ReadDir(filepath.Join(path, file.Name()))
    93  			if err != nil {
    94  				return fields, err
    95  			}
    96  
    97  			var c uint32
    98  			for _, f := range dir {
    99  				if !ignoreFile(f.Name(), ignoreList) {
   100  					c += 1
   101  				}
   102  			}
   103  
   104  			binary.BigEndian.PutUint32(fnwi.FileSize[:], c)
   105  			copy(fnwi.Type[:], "fldr")
   106  			copy(fnwi.Creator[:], fileCreator)
   107  		} else {
   108  			// the Hotline protocol does not support file sizes > 4GiB due to the 4 byte field size, so skip them
   109  			if fileInfo.Size() > maxFileSize {
   110  				continue
   111  			}
   112  
   113  			hlFile, err := newFileWrapper(&OSFileStore{}, path+"/"+file.Name(), 0)
   114  			if err != nil {
   115  				return nil, err
   116  			}
   117  
   118  			copy(fnwi.FileSize[:], hlFile.totalSize())
   119  			copy(fnwi.Type[:], hlFile.ffo.FlatFileInformationFork.TypeSignature)
   120  			copy(fnwi.Creator[:], hlFile.ffo.FlatFileInformationFork.CreatorSignature)
   121  		}
   122  
   123  		strippedName := strings.ReplaceAll(file.Name(), ".incomplete", "")
   124  		strippedName, err = txtEncoder.String(strippedName)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  
   129  		nameSize := make([]byte, 2)
   130  		binary.BigEndian.PutUint16(nameSize, uint16(len(strippedName)))
   131  		copy(fnwi.NameSize[:], nameSize)
   132  
   133  		fnwi.name = []byte(strippedName)
   134  
   135  		b, err := fnwi.MarshalBinary()
   136  		if err != nil {
   137  			return nil, err
   138  		}
   139  		fields = append(fields, NewField(FieldFileNameWithInfo, b))
   140  	}
   141  
   142  	return fields, nil
   143  }
   144  
   145  func CalcTotalSize(filePath string) ([]byte, error) {
   146  	var totalSize uint32
   147  	err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error {
   148  		if err != nil {
   149  			return err
   150  		}
   151  
   152  		if info.IsDir() {
   153  			return nil
   154  		}
   155  
   156  		totalSize += uint32(info.Size())
   157  
   158  		return nil
   159  	})
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	bs := make([]byte, 4)
   165  	binary.BigEndian.PutUint32(bs, totalSize)
   166  
   167  	return bs, nil
   168  }
   169  
   170  func CalcItemCount(filePath string) ([]byte, error) {
   171  	var itemcount uint16
   172  	err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error {
   173  		if err != nil {
   174  			return err
   175  		}
   176  
   177  		if !strings.HasPrefix(info.Name(), ".") {
   178  			itemcount += 1
   179  		}
   180  
   181  		return nil
   182  	})
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	bs := make([]byte, 2)
   188  	binary.BigEndian.PutUint16(bs, itemcount-1)
   189  
   190  	return bs, nil
   191  }
   192  
   193  func EncodeFilePath(filePath string) []byte {
   194  	pathSections := strings.Split(filePath, "/")
   195  	pathItemCount := make([]byte, 2)
   196  	binary.BigEndian.PutUint16(pathItemCount, uint16(len(pathSections)))
   197  
   198  	bytes := pathItemCount
   199  
   200  	for _, section := range pathSections {
   201  		bytes = append(bytes, []byte{0, 0}...)
   202  
   203  		pathStr := []byte(section)
   204  		bytes = append(bytes, byte(len(pathStr)))
   205  		bytes = append(bytes, pathStr...)
   206  	}
   207  
   208  	return bytes
   209  }
   210  
   211  func ignoreFile(fileName string, ignoreList []string) bool {
   212  	// skip files that match any regular expression present in the IgnoreFiles list
   213  	matchIgnoreFilter := 0
   214  	for _, pattern := range ignoreList {
   215  		if match, _ := regexp.MatchString(pattern, fileName); match {
   216  			matchIgnoreFilter += 1
   217  		}
   218  	}
   219  	return matchIgnoreFilter > 0
   220  }