| 140 | } |
| 141 | |
| 142 | func (e *FileExporter) StartRun(meta Meta) (*Run, error) { |
| 143 | if meta.ID == "" { |
| 144 | return nil, errors.New("export run id is required") |
| 145 | } |
| 146 | if e.dir == "" { |
| 147 | return nil, errors.New("export directory is required") |
| 148 | } |
| 149 | resultsPath := filepath.Join(e.dir, resultsFilename) |
| 150 | summaryPath := filepath.Join(e.dir, summaryFilename(meta.ID)) |
| 151 | |
| 152 | tmpFile, err := os.CreateTemp(e.dir, ".results.csv.") |
| 153 | if err != nil { |
| 154 | return nil, fmt.Errorf("failed to create temp results file in %q: %w", e.dir, err) |
| 155 | } |
| 156 | tempResultsPath := tmpFile.Name() |
| 157 | if err := tmpFile.Chmod(0o644); err != nil { |
| 158 | _ = tmpFile.Close() |
| 159 | _ = os.Remove(tempResultsPath) |
| 160 | return nil, fmt.Errorf("failed to chmod temp results file %q: %w", tempResultsPath, err) |
| 161 | } |
| 162 | |
| 163 | var resultsExist bool |
| 164 | info, err := os.Stat(resultsPath) |
| 165 | if err == nil { |
| 166 | if info.IsDir() { |
| 167 | _ = tmpFile.Close() |
| 168 | _ = os.Remove(tempResultsPath) |
| 169 | return nil, fmt.Errorf("results path %q is a directory", resultsPath) |
| 170 | } |
| 171 | resultsExist = true |
| 172 | src, err := os.Open(resultsPath) |
| 173 | if err != nil { |
| 174 | _ = tmpFile.Close() |
| 175 | _ = os.Remove(tempResultsPath) |
| 176 | return nil, fmt.Errorf("failed to open results file %q: %w", resultsPath, err) |
| 177 | } |
| 178 | if _, err := io.Copy(tmpFile, src); err != nil { |
| 179 | _ = src.Close() |
| 180 | _ = tmpFile.Close() |
| 181 | _ = os.Remove(tempResultsPath) |
| 182 | return nil, fmt.Errorf("failed to copy results file %q: %w", resultsPath, err) |
| 183 | } |
| 184 | if err := src.Close(); err != nil { |
| 185 | _ = tmpFile.Close() |
| 186 | _ = os.Remove(tempResultsPath) |
| 187 | return nil, fmt.Errorf("failed to close results file %q: %w", resultsPath, err) |
| 188 | } |
| 189 | } else if !os.IsNotExist(err) { |
| 190 | _ = tmpFile.Close() |
| 191 | _ = os.Remove(tempResultsPath) |
| 192 | return nil, fmt.Errorf("failed to stat results file %q: %w", resultsPath, err) |
| 193 | } |
| 194 | |
| 195 | buf := bufio.NewWriter(tmpFile) |
| 196 | writer := csv.NewWriter(buf) |
| 197 | if !resultsExist { |
| 198 | if err := writer.Write(resultsHeader); err != nil { |
| 199 | _ = tmpFile.Close() |