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 }