github.com/acornpublishing/functional-programming-go@v0.0.0-20220401005601-c3bd3786d5a1/Chapter06/04_onion/src/domain/file.go (about)

     1  package domain
     2  
     3  import (
     4  	"os"
     5  	"path"
     6  	"encoding/json"
     7  	"io/ioutil"
     8  	"os/exec"
     9  	. "utils"
    10  	"bytes"
    11  	"strings"
    12  	"path/filepath"
    13  	"fmt"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  const Parsed = "parsed"
    18  const NormalMode = 0666
    19  
    20  type File struct {
    21  	Id         int
    22  	Name       string `json:"name"`
    23  	ErrorMsg   string `json:"error"`
    24  	Contents   LogFile `json:"logFile"`
    25  	Bytes      []byte  `json:"bytes"`
    26  }
    27  
    28  type CloudFile struct {
    29  	Name       string `json:"name"`
    30  }
    31  type CloudFiles struct {
    32  	Names       []CloudFile
    33  }
    34  
    35  type CloudPath struct {
    36  	Path       string `json:"path"`
    37  }
    38  type CloudPaths struct {
    39  	Paths	[]CloudPath
    40  }
    41  
    42  func (f *File) ToJson() string {
    43  	b, _ := json.MarshalIndent(f, "", "    ")
    44  	return string(b)
    45  }
    46  
    47  // NewFile returns a value representing a file. In our case only contains the file name
    48  func NewFile(fileName string) *File {
    49  	fileName = path.Base(fileName)
    50  	return &File{
    51  		Name:       fileName,
    52  	}
    53  }
    54  
    55  // NameOnly returns the file name only (no path and no extension)
    56  func (f *File) NameOnly() string {
    57  	fileName := path.Base(f.Name)
    58  	extension := filepath.Ext(fileName)
    59  	nameOnly := fileName[0:len(fileName)-len(extension)]
    60  	return nameOnly
    61  }
    62  
    63  func (f *File) FullParsedFileName(i int) string {
    64  	return path.Join(f.LocalParsedPath(), fmt.Sprintf("%d.json", i))
    65  }
    66  
    67  // Exists returns true if the file exists locally and is visible to the current user.
    68  func (f *File) Exists() bool {
    69  	_, err := os.Stat(f.FullLocalPath())
    70  	return (err == nil)
    71  }
    72  
    73  // Path returns the file's path (no parent directory)
    74  func (f *File) Path() string {
    75  	return path.Join("")
    76  }
    77  
    78  // Path returns the file's full local path
    79  func (f *File) LocalPath() string {
    80  	return path.Join(Config.DownloadDir)
    81  }
    82  
    83  // Path returns the file's full host path
    84  func (f *File) HostPath(cloudDir string) string {
    85  	return path.Join(cloudDir)
    86  }
    87  
    88  // Path returns the file's full path with filename (less parent directory)
    89  func (f *File) FullPath() string {
    90  	return path.Join(f.Path(), f.Name)
    91  }
    92  
    93  // Path returns the file's parsed path (less parent directory)
    94  func (f *File) AllParsedPath() string {
    95  	return path.Join(f.LocalParsedPath(), Parsed)
    96  }
    97  
    98  // Path returns the file's full path with filename (less parent directory)
    99  func (f *File) AllFullParsedPath() string {
   100  	return path.Join(f.AllParsedPath(), f.Name)
   101  }
   102  
   103  // Path returns the file's full path (less parent directory)
   104  func (f *File) LocalParsedPath() string {
   105  	return path.Join(Config.DownloadDir, f.Path(), f.NameOnly())
   106  }
   107  
   108  // Path returns the file's full absolute path with filename.
   109  func (f *File) FullLocalPath() string {
   110  	return path.Join(f.LocalPath(), f.Name)
   111  }
   112  
   113  // Path returns the file's full absolute path with filename.
   114  func (f *File) FullHostPath(cloudDir string) string {
   115  	return path.Join(f.HostPath(cloudDir), f.Name)
   116  }
   117  
   118  // ContentsJson returns the file's Contents in json
   119  func (f *File) ContentsJson() string {
   120  	b, _ := json.MarshalIndent(f.Contents, "", "    ")
   121  	return string(b)
   122  }
   123  
   124  // Write writes out the file's Contents to disk.
   125  func (f *File) Write(bytes []byte) (err error) {
   126  	Debug.Println("Creating file: "+f.FullLocalPath())
   127  	osFile, err := os.Create(f.FullLocalPath())
   128  	if err != nil {
   129  		return errors.Wrapf(err, "unable to open %s", f.FullLocalPath())
   130  	}
   131  	defer osFile.Close()
   132  	_, err = osFile.Write(bytes)
   133  	if err != nil {
   134  		return errors.Wrapf(err, "unable to write to file %s", f.FullLocalPath())
   135  	}
   136  	return
   137  }
   138  
   139  
   140  // Read the downloaded file into a byte array
   141  func (f *File) Read() (bytes []byte, err error) {
   142  	file, err := os.Open(f.FullLocalPath())
   143  	defer file.Close()
   144  	if err != nil {
   145  		return nil, errors.Wrapf(err, "unable to open %s", f.FullLocalPath())
   146  	}
   147  	bytes, err = ioutil.ReadAll(file)
   148  	if err != nil {
   149  		return nil, errors.Wrapf(err, "unable to read %s", f.FullLocalPath())
   150  	}
   151  	return
   152  }
   153  
   154  // Read grabs the parsed data-packet json objects associated with the file
   155  func (f *File) ReadLogFiles() (logFiles []LogFile, multiStatus MultiStatus, err error) {
   156  	// Get parsed (data-packet) files
   157  	var msg string
   158  	Debug.Println("f.LocalParsedPath(): "+f.LocalParsedPath())
   159  	cmd := exec.Command("find", f.LocalParsedPath(), "-type", "f", "-name", "*.json")
   160  	cmdOutput := &bytes.Buffer{}
   161  	cmd.Stdout = cmdOutput
   162  	err = cmd.Run()
   163  	if err != nil {
   164  		msg = fmt.Sprintf("error running find %s -type f -name *.json", f.LocalParsedPath())
   165  		Error.Printf(msg)
   166  		return nil, MultiStatus{}, errors.Wrap(err, msg)
   167  	}
   168  	findParsedFilesResponse := string(cmdOutput.Bytes())
   169  	if len(findParsedFilesResponse) == 0 {
   170  		msg = fmt.Sprintf("no results from running find %s -type f -name *.json", f.LocalParsedPath())
   171  		Error.Printf(msg)
   172  		return nil, MultiStatus{}, errors.Wrap(err, msg)
   173  	}
   174  	parsedFilePaths := strings.Split(findParsedFilesResponse, "\n")
   175  	parsedFilePaths = parsedFilePaths[:len(parsedFilePaths)-1] // find adds newline that adds empty item at end
   176  	Debug.Printf("parsedFilePaths: %v", parsedFilePaths)
   177  	// Populate logFiles
   178  	multiStatus = MultiStatus{}
   179  	outcomeAndMsgs := []OutcomeAndMsg{}
   180  	var logFileSlice []LogFile
   181  	for i, parsedFilePath := range parsedFilePaths {
   182  		Debug.Printf("%d - encoding %s", i, parsedFilePath)
   183  		file, err := os.Open(parsedFilePath)
   184  		if err != nil {
   185  			msg = "unable to encode parsedFilePath: "+parsedFilePath
   186  			Error.Printf(msg)
   187  			outcomeAndMsgs = append(outcomeAndMsgs, OutcomeAndMsg{Success:false, Message:msg})
   188  			break
   189  		}
   190  		defer file.Close()
   191  		bytes, err := ioutil.ReadAll(file)
   192  		if err != nil {
   193  			msg = "ioutil.ReadAll failed for "+parsedFilePath
   194  			Error.Printf(msg)
   195  			outcomeAndMsgs = append(outcomeAndMsgs, OutcomeAndMsg{Success:false, Message:msg})
   196  			break
   197  		}
   198  		logFile, err := NewLogFile(string(bytes))
   199  		if err != nil {
   200  			msg = "failed to parse "+parsedFilePath
   201  			Error.Printf(msg)
   202  			outcomeAndMsgs = append(outcomeAndMsgs, OutcomeAndMsg{Success:false, Message:msg})
   203  			break
   204  		}
   205  		logFileSlice = append(logFileSlice, *logFile)
   206  	}
   207  	logFiles = logFileSlice
   208  	Debug.Printf("logFiles: %+v", logFiles)
   209  	multiStatus.OutcomeAndMsgs = outcomeAndMsgs
   210  	return
   211  }
   212  
   213  // FormatJson creates parsed directory and creates a proper .json file from the contents of the .jsonl file
   214  func (f *File) FormatJson() (newContents string, err error) {
   215  	fileName := f.Name
   216  	parsedFullPath := f.AllFullParsedPath() // AllFullParsedDirectory previously created
   217  	read, err := ioutil.ReadFile(f.FullLocalPath())
   218  	if err != nil {
   219  		return "", errors.Wrapf(err, "File.FormatJson: Unable to read (%s)", fileName)
   220  	}
   221  	newContents = "["+strings.Replace(string(read), "}{\"brandId", "},{\"brandId", -1)+"]"
   222  	Debug.Printf("newContents: %v", newContents)
   223  	err = ioutil.WriteFile(parsedFullPath, []byte(newContents), NormalMode)
   224  	if err != nil {
   225  		return "", errors.Wrapf(err, "File.FormatJson: Unable to write newContents (%s)", fileName)
   226  	}
   227  	return
   228  }
   229  
   230  // Parse parses the file Contents of JSONL (w/o new lines) into JSON
   231  func (f *File) Parse(fileBytes []byte) (logFiles *[]LogFile,  err error) {
   232  	logFilesJson, err := f.FormatJson()
   233  	if err != nil {
   234  		return nil, errors.Wrap(err, "unable to FormatJson")
   235  	}
   236  	logFiles = &[]LogFile{}
   237  	err = json.Unmarshal([]byte(logFilesJson), logFiles)
   238  	if err != nil {
   239  		return nil, errors.Wrap(err, "unable to unmarshall results")
   240  	}
   241  	return
   242  }