@@ -125,56 +125,105 @@ func (b *BackupProgressEntry) Validate() error {
125
125
return nil
126
126
}
127
127
128
- // readBackupProgressEntries returns the summary event or an error if the command failed.
129
- func readBackupProgressEntries (output io.Reader , logger io.Writer , callback func (event * BackupProgressEntry )) (* BackupProgressEntry , error ) {
128
+ func (b * BackupProgressEntry ) IsError () bool {
129
+ return b .MessageType == "error"
130
+ }
131
+
132
+ func (b * BackupProgressEntry ) IsSummary () bool {
133
+ return b .MessageType == "summary"
134
+ }
135
+
136
+ type RestoreProgressEntry struct {
137
+ MessageType string `json:"message_type"` // "summary" or "status"
138
+ SecondsElapsed float64 `json:"seconds_elapsed"`
139
+ TotalBytes int64 `json:"total_bytes"`
140
+ BytesRestored int64 `json:"bytes_restored"`
141
+ TotalFiles int64 `json:"total_files"`
142
+ FilesRestored int64 `json:"files_restored"`
143
+ PercentDone float64 `json:"percent_done"`
144
+ }
145
+
146
+ func (e * RestoreProgressEntry ) Validate () error {
147
+ if e .MessageType != "summary" && e .MessageType != "status" {
148
+ return fmt .Errorf ("message_type must be 'summary' or 'status', got %v" , e .MessageType )
149
+ }
150
+ return nil
151
+ }
152
+
153
+ func (r * RestoreProgressEntry ) IsError () bool {
154
+ return r .MessageType == "error"
155
+ }
156
+
157
+ func (r * RestoreProgressEntry ) IsSummary () bool {
158
+ return r .MessageType == "summary"
159
+ }
160
+
161
+ type ProgressEntryValidator interface {
162
+ Validate () error
163
+ IsError () bool
164
+ IsSummary () bool
165
+ }
166
+
167
+ // processProgressOutput handles common JSON output processing logic with proper type safety
168
+ func processProgressOutput [T ProgressEntryValidator ](
169
+ output io.Reader ,
170
+ logger io.Writer ,
171
+ callback func (T )) (T , error ) {
172
+
130
173
scanner := bufio .NewScanner (output )
131
174
scanner .Split (bufio .ScanLines )
132
175
133
176
nonJSONOutput := bytes .NewBuffer (nil )
177
+ var captureNonJSON io.Writer = nonJSONOutput
178
+ if logger != nil {
179
+ captureNonJSON = io .MultiWriter (nonJSONOutput , logger )
180
+ }
134
181
135
- var summary * BackupProgressEntry
182
+ var summary * T
183
+ var nullT T
136
184
137
- // remaining events are parsed as JSON
138
185
for scanner .Scan () {
139
- var event BackupProgressEntry
140
- if err := json .Unmarshal (scanner .Bytes (), & event ); err != nil {
141
- nonJSONOutput .Write (scanner .Bytes ())
142
- if logger != nil {
143
- logger .Write (scanner .Bytes ())
144
- logger .Write ([]byte ("\n " ))
145
- }
186
+ line := scanner .Bytes ()
187
+ var event T
188
+
189
+ if err := json .Unmarshal (line , & event ); err != nil {
190
+ captureNonJSON .Write (line )
191
+ captureNonJSON .Write ([]byte ("\n " ))
146
192
continue
147
193
}
194
+
148
195
if err := event .Validate (); err != nil {
149
- nonJSONOutput .Write (scanner .Bytes ())
150
- if logger != nil {
151
- logger .Write (scanner .Bytes ())
152
- logger .Write ([]byte ("\n " ))
153
- }
196
+ captureNonJSON .Write (line )
197
+ captureNonJSON .Write ([]byte ("\n " ))
154
198
continue
155
199
}
156
- if event .MessageType == "error" && logger != nil {
157
- logger .Write (scanner .Bytes ())
158
- logger .Write ([]byte ("\n " ))
200
+
201
+ if event .IsError () && logger != nil {
202
+ captureNonJSON .Write (line )
203
+ captureNonJSON .Write ([]byte ("\n " ))
159
204
}
205
+
160
206
if callback != nil {
161
- callback (& event )
207
+ callback (event )
162
208
}
163
- if event . MessageType == "summary" {
164
- if logger != nil {
165
- logger .Write (scanner . Bytes () )
166
- logger .Write ([]byte ("\n " ))
167
- }
168
- summary = & event
209
+
210
+ if event . IsSummary () {
211
+ captureNonJSON .Write (line )
212
+ captureNonJSON .Write ([]byte ("\n " ))
213
+ eventCopy := event // Make a copy to avoid issues with loop variable
214
+ summary = & eventCopy
169
215
}
170
216
}
217
+
171
218
if err := scanner .Err (); err != nil {
172
- return summary , newErrorWithOutput (err , nonJSONOutput .String ())
219
+ return nullT , newErrorWithOutput (err , nonJSONOutput .String ())
173
220
}
221
+
174
222
if summary == nil {
175
- return nil , newErrorWithOutput (errors .New ("no summary event found" ), nonJSONOutput .String ())
223
+ return nullT , newErrorWithOutput (errors .New ("no summary event found" ), nonJSONOutput .String ())
176
224
}
177
- return summary , nil
225
+
226
+ return * summary , nil
178
227
}
179
228
180
229
type LsEntry struct {
@@ -248,80 +297,6 @@ func (r *ForgetResult) Validate() error {
248
297
return nil
249
298
}
250
299
251
- type RestoreProgressEntry struct {
252
- MessageType string `json:"message_type"` // "summary" or "status"
253
- SecondsElapsed float64 `json:"seconds_elapsed"`
254
- TotalBytes int64 `json:"total_bytes"`
255
- BytesRestored int64 `json:"bytes_restored"`
256
- TotalFiles int64 `json:"total_files"`
257
- FilesRestored int64 `json:"files_restored"`
258
- PercentDone float64 `json:"percent_done"`
259
- }
260
-
261
- func (e * RestoreProgressEntry ) Validate () error {
262
- if e .MessageType != "summary" && e .MessageType != "status" {
263
- return fmt .Errorf ("message_type must be 'summary' or 'status', got %v" , e .MessageType )
264
- }
265
- return nil
266
- }
267
-
268
- // readRestoreProgressEntries returns the summary event or an error if the command failed.
269
- func readRestoreProgressEntries (output io.Reader , logger io.Writer , callback func (event * RestoreProgressEntry )) (* RestoreProgressEntry , error ) {
270
- scanner := bufio .NewScanner (output )
271
- scanner .Split (bufio .ScanLines )
272
-
273
- nonJSONOutput := bytes .NewBuffer (nil )
274
-
275
- var summary * RestoreProgressEntry
276
-
277
- // remaining events are parsed as JSON
278
- for scanner .Scan () {
279
- var event RestoreProgressEntry
280
- if err := json .Unmarshal (scanner .Bytes (), & event ); err != nil {
281
- nonJSONOutput .Write (scanner .Bytes ())
282
- if logger != nil {
283
- logger .Write (scanner .Bytes ())
284
- logger .Write ([]byte ("\n " ))
285
- }
286
- continue
287
- }
288
- if err := event .Validate (); err != nil {
289
- // skip it. Best effort parsing, restic will return with a non-zero exit code if it fails.
290
- nonJSONOutput .Write (scanner .Bytes ())
291
- if logger != nil {
292
- logger .Write (scanner .Bytes ())
293
- logger .Write ([]byte ("\n " ))
294
- }
295
- continue
296
- }
297
-
298
- if event .MessageType == "error" && logger != nil {
299
- logger .Write (scanner .Bytes ())
300
- logger .Write ([]byte ("\n " ))
301
- }
302
- if callback != nil {
303
- callback (& event )
304
- }
305
- if event .MessageType == "summary" {
306
- if logger != nil {
307
- logger .Write (scanner .Bytes ())
308
- logger .Write ([]byte ("\n " ))
309
- }
310
- summary = & event
311
- }
312
- }
313
-
314
- if err := scanner .Err (); err != nil {
315
- return summary , newErrorWithOutput (err , nonJSONOutput .String ())
316
- }
317
-
318
- if summary == nil {
319
- return nil , newErrorWithOutput (errors .New ("no summary event found" ), nonJSONOutput .String ())
320
- }
321
-
322
- return summary , nil
323
- }
324
-
325
300
func ValidateSnapshotId (id string ) error {
326
301
if len (id ) != 64 {
327
302
return fmt .Errorf ("restic may be out of date (check with `restic self-upgrade`): snapshot ID must be 64 chars, got %v chars" , len (id ))
0 commit comments