2
Parser.pas - Log file parser unit
4
Copyright (C) 2013 - 2015 Martin Bittermann (martinbittermann@gmx.de)
6
This file is part of ddrescueview.
8
ddrescueview is free software: you can redistribute it and/or modify
9
it under the terms of the GNU General Public License as published by
10
the Free Software Foundation, either version 3 of the License, or
11
(at your option) any later version.
13
ddrescueview is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
GNU General Public License for more details.
18
You should have received a copy of the GNU General Public License
19
along with ddrescueview. If not, see <http://www.gnu.org/licenses/>.
31
TLogParser = class(TObservablePersistent)
34
FRescueStatus : TRescueStatus;
35
FComments : TStringList; // comment lines of logfile
38
procedure logMsg(msg : String);
39
function isEmptyLine(strLine : String) : boolean;
40
function isCommentLine(strLine : String) : boolean;
42
destructor Destroy; override;
43
procedure OpenFile(filename : String);
46
function hasFile() : boolean;
47
property log : TLog read FLog;
48
property CommentLines : TStringList read FComments;
49
property rescueStatus : TRescueStatus read FRescueStatus;
50
property LogFileName :String read FFileName;
57
destructor TLogParser.Destroy;
63
procedure TLogParser.OpenFile(filename : String);
66
CloseFile(); // close already open file
67
// open the log file using shared read access, so ddrescue can still write to it.
68
FLogStream := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone);
69
FComments := TStringList.Create;
70
FFileName := filename;
71
parse(); // start parsing after opening
73
on E: Exception do begin
75
logMsg('Error: '+E.Message);
80
procedure TLogParser.CloseFile;
83
FreeAndNil(FComments);
84
FreeAndNil(FLogStream);
85
FRescueStatus := emptyRescueStatus;
87
FPONotifyObservers(self, ooDeleteItem, nil);
90
(* returns true if a line is empty *)
91
function TLogParser.isEmptyLine(strLine : String) : boolean;
93
strLine := TrimLeft(strLine);
94
result := (Length(strLine) = 0); // empty line
97
(* returns true if a line is a comment line *)
98
function TLogParser.isCommentLine(strLine : String) : boolean;
100
result := (strLine[1] = '#'); // comment line
104
(* add a log line to the application log *)
105
procedure TLogParser.logMsg(msg : String);
107
MainForm.DbgLog.Lines.Add(msg);
111
After parsing, the results are available in
112
the properties log and rescueStatus *)
113
procedure TLogParser.parse();
116
token : array[0..2] of string;
117
i, logEntry, lineIdx, idx : Integer;
118
logStrings : TStringList;
119
comments_end : boolean;
121
if not Assigned(FLogStream) then begin
122
logMsg('Parser: No log file opened.');
123
FPONotifyObservers(self, ooChange, nil);
128
FLogStream.Seek(0, soFromBeginning);
129
logStrings := TStringList.Create;
130
logStrings.LoadFromStream(FLogStream);
131
logMsg('Parsing log file: ' +IntToStr(logStrings.Count) + ' lines.');
133
on E : Exception do begin
134
logMsg('Error: Cannot read log file: '+E.Message+'('+E.ClassName+')');
135
FPONotifyObservers(self, ooChange, nil);
140
FRescueStatus := emptyRescueStatus;
143
comments_end := false;
148
line:=logStrings[lineIdx];
149
if isCommentLine(line) then begin
150
if comments_end or (pos('# current_pos', line) > 0) then
152
else FComments.Add(line);
153
// process comment info
154
if pos('Command line:', line) > 0 then begin
155
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);
170
end else if not isEmptyLine(line) then begin
171
// split line into maximum of 3 tokens
173
for i:= 0 to 2 do begin
174
idx:= Pos(' ', line);
175
if idx = 0 then begin
179
token[i] := Copy(line, 1, idx-1);
180
line := TrimLeft(Copy(line, idx, Length(line)-idx+1));
183
if (token[0] <> '') and (token[1] <> '') then begin
184
if token[2] <> '' then begin // standard block line (3 tokens)
185
if Length(FLog) = logEntry then SetLength(FLog, logEntry+256); // allocate more space
186
FLog[logEntry].offset:=StrToInt64(token[0]);
187
FLog[logEntry].length:=StrToInt64(token[1]);
188
FLog[logEntry].status:=token[2][1];
189
case FLog[logEntry].status of
190
'?' : inc(FRescueStatus.nontried, FLog[logEntry].length);
191
'+' : inc(FRescueStatus.rescued, FLog[logEntry].length);
192
'*' : inc(FRescueStatus.nontrimmed, FLog[logEntry].length);
193
'/' : inc(FRescueStatus.nonscraped, FLog[logEntry].length);
194
'-' : inc(FRescueStatus.bad, FLog[logEntry].length);
196
if FLog[logEntry].status in ['-', '*', '/'] then inc(FRescueStatus.errors);
198
end else begin // found the status line (2 components)
199
if FRescueStatus.pos = 0 then begin
200
FRescueStatus.pos:=StrToInt64(token[0]);
201
FRescueStatus.curOperation:=token[1][1];
202
FRescueStatus.strCurOperation:=OperationToText(FRescueStatus.curOperation);
204
logMsg('Parser: found more than one line with 2 tokens.');
209
until lineIdx = logStrings.Count-1;
210
SetLength(FLog, logEntry); // trim array to actually needed size
212
on E : Exception do logMsg('Error parsing log file: '+E.Message+'('+E.ClassName+')');
214
FreeAndNil(logStrings);
216
if logEntry > 0 then begin
217
// 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);
224
function TLogParser.hasFile: boolean;
226
hasFile:=Length(Flog)<>0;