25
25
uses Classes, Shared;
31
TLogParser = class(TObservablePersistent)
28
TSimpleParser = class(TObservablePersistent)
34
31
FRescueStatus : TRescueStatus;
35
32
FComments : TStringList; // comment lines of logfile
33
FVersion : String; // ddrescue version in first comment line / for future use?
36
34
FLogStream : TStream;
37
35
FFileName : String;
38
36
procedure logMsg(msg : String);
39
37
function isEmptyLine(strLine : String) : boolean;
40
38
function isCommentLine(strLine : String) : boolean;
42
41
destructor Destroy; override;
43
42
procedure OpenFile(filename : String);
44
43
procedure CloseFile;
46
45
function hasFile() : boolean;
46
property rescueStatus : TRescueStatus read FRescueStatus;
47
47
property log : TLog read FLog;
48
48
property CommentLines : TStringList read FComments;
49
property rescueStatus : TRescueStatus read FRescueStatus;
49
property Version : String read FVersion;
50
50
property LogFileName :String read FFileName;
53
// This parser can also embed a domain log file parser
54
TLogParser = class(TObservablePersistent)
56
FLogParser : TSimpleParser;
57
FDomParser : TSimpleParser;
59
FRescueStatus : TRescueStatus;
60
FContiguous : Boolean;
62
function getLog(): TLog;
63
procedure postParse();
64
procedure setContiguous(mode: Boolean);
67
destructor Destroy; override;
68
procedure OpenFile(filename : String);
69
procedure OpenDomainFile(filename : String);
72
function hasFile() : boolean;
73
function hasDomFile() : Boolean;
74
property rescueStatus : TRescueStatus read FRescueStatus;
75
property log : TLog read getLog;
76
property CommentLines : TStringList read FLogParser.FComments;
77
property Version : String read FLogParser.FVersion;
78
property LogFileName : String read FLogParser.FFileName;
79
property DomFileName : String read FDomParser.FFileName;
80
property ContiguousDomain : Boolean read FContiguous write setContiguous;
84
uses SysUtils, Math, GUI;
88
constructor TLogParser.Create;
91
FRescueStatus := emptyRescueStatus;
92
FLogParser:=TSimpleParser.Create;
93
FDomParser:=TSimpleParser.Create;
57
96
destructor TLogParser.Destroy;
63
procedure TLogParser.OpenFile(filename : String);
99
FreeAndNil(FDomParser);
100
FreeAndNil(FLogParser);
104
procedure TLogParser.OpenFile(filename: String);
107
FLogParser.OpenFile(filename);
111
procedure TLogParser.OpenDomainFile(filename: String);
113
FDomParser.OpenFile(filename);
114
if FDomParser.hasFile and FContiguous then begin
116
FPONotifyObservers(self, ooDeleteItem, nil);
121
procedure TLogParser.CloseFile;
123
FDomParser.CloseFile;
124
FLogParser.CloseFile;
125
FRescueStatus := emptyRescueStatus;
128
FPONotifyObservers(self, ooDeleteItem, nil);
131
procedure TLogParser.postParse;
133
ResLog, DomLog: TLog;
134
i, iRes, iDom : Longint;
135
newEntry : TLogEntry;
136
logCurOffset : Int64;
138
if FDomParser.hasFile then begin // post-process the logs
139
FRescueStatus:=emptyRescueStatus;
140
ResLog:=FLogParser.log;
141
DomLog:=FDomParser.log;
142
//build log and rescuestatus
143
i:=0; iRes:=0; iDom:=0; // current log indices
144
logCurOffset:=0; // needed for contiguous mode, but also used for normal mode
145
while iDom < Length(DomLog) do begin
146
if (DomLog[iDom].status<>'+') then begin // outside domain entry
147
if not FContiguous then begin
148
newEntry.status:='d';
149
newEntry.offset:=DomLog[iDom].offset;
150
newEntry.length:=DomLog[iDom].length;
151
inc(FRescueStatus.outsidedomain, DomLog[iDom].length);
152
logCurOffset:=newEntry.offset+newEntry.length;
153
if InRange(FLogParser.rescueStatus.pos, newEntry.offset, logCurOffset-1) then
154
FRescueStatus.pos:=FLogParser.rescueStatus.pos;
155
if Length(FLog) <= i then SetLength(FLog, i+1024);
159
end else begin // iterate over the ResEntries intersecting current DomEntry
160
while iRes < Length(ResLog) do begin
161
// increase iRes until intersecting dom block
162
if ResLog[iRes].offset+ResLog[iRes].length <= DomLog[iDom].offset then begin
166
// if intersecting current dom block, create new block
167
newEntry:=IntersectEntries(ResLog[iRes], DomLog[iDom]);
168
if newEntry.length > 0 then begin //can be 0 for rescue logfiles with gaps
169
if InRange(FLogParser.rescueStatus.pos, newEntry.offset,
170
newEntry.offset+newEntry.length-1) then
171
FRescueStatus.pos:=logCurOffset+FLogParser.rescueStatus.pos-newEntry.offset;
172
newEntry.offset:=logCurOffset;
173
logCurOffset+=newEntry.length;
174
if Length(FLog) <= i then SetLength(FLog, i+1024);
177
case newEntry.status of
178
'?' : inc(FRescueStatus.nontried, newEntry.length);
179
'+' : inc(FRescueStatus.rescued, newEntry.length);
180
'*' : inc(FRescueStatus.nontrimmed, newEntry.length);
181
'/' : inc(FRescueStatus.nonscraped, newEntry.length);
183
inc(FRescueStatus.bad, newEntry.length);
184
inc(FRescueStatus.errors);
188
// ensure iRes is set to last intersecting entry
189
if ResLog[iRes].offset+ResLog[iRes].length >=
190
DomLog[iDom].offset+DomLog[iDom].length then break;
197
FRescueStatus.curOperation:=FLogParser.rescueStatus.curOperation;
198
FRescueStatus.strCurOperation:=FLogParser.rescueStatus.strCurOperation;
199
if i > 0 then begin // calculate device's size
200
FRescueStatus.devicesize:=FLog[i-1].offset + FLog[i-1].length;
202
FRescueStatus.suggestedBlockSize:=FLogParser.rescueStatus.suggestedBlockSize;
204
FRescueStatus:=FLogParser.rescueStatus;
206
if (not FHasFile) and FLogParser.hasFile then begin // just been opened
208
FPONotifyObservers(self, ooAddItem, nil);
209
end else FPONotifyObservers(self, ooChange, nil)
212
procedure TLogParser.setContiguous(mode: Boolean);
214
if mode <> FContiguous then begin
216
if FDomParser.hasFile then begin
218
FPONotifyObservers(self, ooDeleteItem, nil);
224
procedure TLogParser.parse;
226
if FLogParser.hasFile() then FLogParser.parse();
227
if FDomParser.hasFile() then FDomParser.parse();
231
function TLogParser.hasFile: boolean;
236
function TLogParser.hasDomFile: Boolean;
238
result:=FHasFile and FDomParser.hasFile;
241
function TLogParser.getLog: TLog;
243
if not FDomParser.hasFile then result:=FLogParser.log
250
constructor TSimpleParser.Create;
253
FRescueStatus := emptyRescueStatus;
256
destructor TSimpleParser.Destroy;
262
procedure TSimpleParser.OpenFile(filename : String);
66
265
CloseFile(); // close already open file
90
289
(* returns true if a line is empty *)
91
function TLogParser.isEmptyLine(strLine : String) : boolean;
290
function TSimpleParser.isEmptyLine(strLine : String) : boolean;
93
292
strLine := TrimLeft(strLine);
94
293
result := (Length(strLine) = 0); // empty line
97
296
(* returns true if a line is a comment line *)
98
function TLogParser.isCommentLine(strLine : String) : boolean;
297
function TSimpleParser.isCommentLine(strLine : String) : boolean;
100
result := (strLine[1] = '#'); // comment line
299
result := (not isEmptyLine(strLine)) and (strLine[1] = '#'); // comment line
104
302
(* add a log line to the application log *)
105
procedure TLogParser.logMsg(msg : String);
303
procedure TSimpleParser.logMsg(msg : String);
107
305
MainForm.DbgLog.Lines.Add(msg);
308
(* Parse the log file.
111
309
After parsing, the results are available in
112
the properties log and rescueStatus *)
113
procedure TLogParser.parse();
310
the properties log, rescueStatus and Comments *)
311
procedure TSimpleParser.parse();
116
314
token : array[0..2] of string;
117
315
i, logEntry, lineIdx, idx : Integer;
118
logStrings : TStringList;
119
comments_end : boolean;
316
logStrings : TStringList = nil;
317
comments_end, prevHadFile : boolean;
121
if not Assigned(FLogStream) then begin
319
// make sure the file is open
320
if not hasFile then begin
122
321
logMsg('Parser: No log file opened.');
123
322
FPONotifyObservers(self, ooChange, nil);
326
// read file contents into string list
128
FLogStream.Seek(0, soFromBeginning);
129
328
logStrings := TStringList.Create;
329
if FLogStream.Seek(0, soFromBeginning) <> 0 then
330
raise Exception.Create('Seek error!');
130
331
logStrings.LoadFromStream(FLogStream);
131
logMsg('Parsing log file: ' +IntToStr(logStrings.Count) + ' lines.');
332
if (FLogStream.Size = 0) or (logStrings.Count = 0) then begin
333
logMsg('Parser: log file seems empty, trying to reopen.');
334
FreeAndNil(FLogStream);
335
FLogStream := TFileStream.Create(FFilename, fmOpenRead or fmShareDenyNone);
336
FLogStream.Seek(0, soFromBeginning);
337
logStrings.LoadFromStream(FLogStream);
339
logMsg('Reading log file: ' +IntToStr(logStrings.Count) + ' lines.');
133
341
on E : Exception do begin
134
342
logMsg('Error: Cannot read log file: '+E.Message+'('+E.ClassName+')');
343
FreeAndNil(logStrings);
135
344
FPONotifyObservers(self, ooChange, nil);
349
// initialize stuff to zero
140
350
FRescueStatus := emptyRescueStatus;
143
355
comments_end := false;
356
prevHadFile := Length(log) > 0;
358
// parse the logfile lines into log array, rescueStatus and Comments.
360
while lineIdx < logStrings.Count do begin
148
361
line:=logStrings[lineIdx];
149
362
if isCommentLine(line) then begin
363
// copy comment lines into FComments
150
364
if comments_end or (pos('# current_pos', line) > 0) then
151
365
comments_end:=true
152
366
else FComments.Add(line);
153
367
// process comment info
154
368
if pos('Command line:', line) > 0 then begin
155
369
repeat // not a loop, but goto replacement
156
// try to find ddrescue's block size argument
157
idx := pos(' -b', line);
158
if idx <> 0 then idx := idx+3 // point to start of number
160
idx := pos(' --block-size=', line);
161
if idx <> 0 then idx := idx+14 // point to start of number
162
else break; // argument not found, jump over the rest
164
token[0] :=TrimLeft(Copy(line, idx, Length(line)));
165
idx := pos(' ', token[0]); //space after argument
166
if idx <> 0 then token[0] :=Copy(token[0], 1, idx-1);
167
FRescueStatus.suggestedBlockSize:=StrToIntDef(token[0], 0);
370
// try to find ddrescue's block size argument
371
idx := pos(' -b', line);
372
if idx <> 0 then idx := idx+3 // point to start of number
374
idx := pos(' --block-size=', line);
375
if idx <> 0 then idx := idx+14 // point to start of number
376
else break; // argument not found, jump over the rest
378
token[0] :=TrimLeft(Copy(line, idx, Length(line)));
379
idx := pos(' ', token[0]); //space after argument
380
if idx <> 0 then token[0] :=Copy(token[0], 1, idx-1);
381
FRescueStatus.suggestedBlockSize:=StrToIntDef(token[0], DEF_BSIZE);
384
idx:=pos('ddrescue version ', line);
385
if idx > 0 then FVersion := Copy(line, idx+17, 1337);
170
386
end else if not isEmptyLine(line) then begin
171
387
// split line into maximum of 3 tokens
172
388
line:=Trim(line);
191
408
'+' : inc(FRescueStatus.rescued, FLog[logEntry].length);
192
409
'*' : inc(FRescueStatus.nontrimmed, FLog[logEntry].length);
193
410
'/' : inc(FRescueStatus.nonscraped, FLog[logEntry].length);
194
'-' : inc(FRescueStatus.bad, FLog[logEntry].length);
412
inc(FRescueStatus.bad, FLog[logEntry].length);
413
inc(FRescueStatus.errors);
196
if FLog[logEntry].status in ['-', '*', '/'] then inc(FRescueStatus.errors);
198
end else begin // found the status line (2 components)
417
end else begin // found the status line (2 tokens)
199
418
if FRescueStatus.pos = 0 then begin
200
FRescueStatus.pos:=StrToInt64(token[0]);
419
FRescueStatus.pos:=StrToInt64Def(token[0], 0);
201
420
FRescueStatus.curOperation:=token[1][1];
202
421
FRescueStatus.strCurOperation:=OperationToText(FRescueStatus.curOperation);
204
logMsg('Parser: found more than one line with 2 tokens.');
423
logMsg('Parser: Not enough tokens in line '+inttostr(lineIdx));
209
until lineIdx = logStrings.Count-1;
210
430
SetLength(FLog, logEntry); // trim array to actually needed size
212
on E : Exception do logMsg('Error parsing log file: '+E.Message+'('+E.ClassName+')');
432
on E : Exception do begin
433
logMsg('Error parsing log file: '+E.Message+'('+E.ClassName+')');
434
FRescueStatus := emptyRescueStatus; // some fail-safe values
437
FLog[0].length:=DEF_BSIZE*(1 shl 18); // one block of 2^18 bad sectors
439
FRescueStatus.bad:=FLog[0].length;
440
FRescueStatus.devicesize:=FLog[0].length;
214
443
FreeAndNil(logStrings);
216
if logEntry > 0 then begin
446
if Length(FLog) > 0 then begin
217
447
// calculate device's size from last block's offset and length
218
FRescueStatus.devicesize:=FLog[logEntry-1].offset + FLog[logEntry-1].length;
219
end else logMsg('Parser: No blocks in logfile!');
220
// notify any observers of the changes
221
FPONotifyObservers(self, ooChange, nil);
448
FRescueStatus.devicesize:=FLog[Length(FLog)-1].offset + FLog[Length(FLog)-1].length;
449
if prevHadFile then FPONotifyObservers(self, ooChange, nil)
450
else FPONotifyObservers(self, ooAddItem, nil); // notify of new file
452
logMsg('Parser: No blocks in logfile!');
453
FPONotifyObservers(self, ooChange, nil); // notify any observers of the changes
224
function TLogParser.hasFile: boolean;
457
function TSimpleParser.hasFile: boolean;
226
hasFile:=Length(Flog)<>0;
459
hasFile:=Assigned(FLogStream);