Files
lazarus-ccr/components/flashfiler/sourcelaz/Verify/frMain.pas

859 lines
24 KiB
ObjectPascal
Raw Normal View History

{*********************************************************}
{* FlashFiler: Main form for verification utility *}
{*********************************************************}
(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is TurboPower FlashFiler
*
* The Initial Developer of the Original Code is
* TurboPower Software
*
* Portions created by the Initial Developer are Copyright (C) 1996-2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** *)
{$I ffdefine.inc}
unit frMain;
interface
uses
Windows, Messages, SysUtils,
{$IFDEF DCC6OrLater}
Variants,
{$ENDIF}
Classes, Graphics, Controls, Forms,
Dialogs, Menus, ExtCtrls, ComCtrls, FFRepair, FFFileInt, StdCtrls;
{ TODO::
Tasks listed by order of development:
- UI: view individual blocks within the file
- display index block data
- display data block data
- test index verify/repair
- file interface needs property to identify if a file is currently opened.
- backup of existing file to another directory
- incorporate chain gang for verification of deleted block chain
- verify/repair data block
- unknown block type error should result in need to restructure
- verify/repair stream block
- BLOB verify/repair
- display file size
- allow max ram of repair engine to be adjusted
- display max ram being used while verify/repair in progress
- duration of verification & repair
FUTURE development tasks:
- handle multi-file tables
- BLOB stats
- View block map of file
}
type
TfrmMain = class(TForm)
pnlTop: TPanel;
mnuMain: TMainMenu;
mnuFile: TMenuItem;
mnuFileOpen: TMenuItem;
mnuFileClose: TMenuItem;
mnuFileSep1: TMenuItem;
mnuFileExit: TMenuItem;
mnuFileSep2: TMenuItem;
mnuFileVerify: TMenuItem;
mnuFileRepair: TMenuItem;
tvMain: TTreeView;
Splitter: TSplitter;
dlgOpen: TOpenDialog;
Notebook: TPageControl;
pgProps: TTabSheet;
lvProps: TListView;
pgData: TTabSheet;
lvData: TListView;
pgStatus: TTabSheet;
pnlStatusBottom: TPanel;
progressBar: TProgressBar;
memStatus: TMemo;
lblStatus: TLabel;
pgRawData: TTabSheet;
lvRawData: TListView;
mnuFileSep3: TMenuItem;
mnuFileViewBlock: TMenuItem;
mnuChain: TMenuItem;
mnuChainViewData: TMenuItem;
mnuChainViewFree: TMenuItem;
pgReadMe: TTabSheet;
memReadMe: TMemo;
mnuOptions: TMenuItem;
procedure FormShow(Sender: TObject);
procedure mnuFileOpenClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure mnuFileExitClick(Sender: TObject);
procedure tvMainClick(Sender: TObject);
procedure mnuFileCloseClick(Sender: TObject);
procedure tvMainGetSelectedIndex(Sender: TObject; Node: TTreeNode);
procedure mnuFileVerifyClick(Sender: TObject);
procedure mnuFileRepairClick(Sender: TObject);
procedure NotebookChange(Sender: TObject);
procedure mnuFileViewBlockClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure mnuChainViewDataClick(Sender: TObject);
procedure mnuChainViewFreeClick(Sender: TObject);
procedure mnuOptionsClick(Sender: TObject);
private
{ Private declarations }
FBlockNumToNodeMap : TStringList;
FCurNode : TTreeNode;
FDataBlocksNode : TTreeNode;
FFileHeaderBlock : IFileHeaderBlock;
FFileName : string;
FIndexBlocksNode : TTreeNode;
FLastItem : TffRepairItem;
FOtherBlocksNode : TTreeNode;
FOutputVersion : Longint;
FRepair : TffRepairEngine;
FState : TffRepairState;
FViewedBlocks : TInterfaceList;
procedure ClearAll;
procedure ClearData;
procedure ClearProps;
procedure ClearRawData;
procedure ClearRepair;
procedure ClearStatus;
procedure ClearTreeView;
procedure ClearUI;
procedure DisplayData(const Block : ICommonBlock);
procedure DisplayProps(const Block : ICommonBlock);
procedure DisplayRawData(const Block : ICommonBlock);
procedure LoadUI;
procedure OnComplete(Sender : TObject);
procedure OnProgress(Repairer : TffRepairEngine;
State : TffRepairState;
Item : TffRepairItem;
const ActionStr : string;
const Position, Maximum : Integer);
procedure OnReportError(Block : ICommonBlock;
const ErrCode : Integer;
const ErrorStr : string);
procedure OnReportFix(Block : ICommonBlock;
const ErrCode : Integer;
const RepairStr : string);
procedure PositionToNode(Node : TTreeNode);
procedure ReleaseBlocksAndNodes;
procedure SetCtrlStates;
procedure Status(const Msg : string; args : array of const);
procedure VerifyRepair;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
uses
frmBlock,
FFLLBase,
FFSrBase,
FFRepCnst, frmOptions;
const
csBlock = 'Block %d';
csDataBlocks = 'Data blocks';
csDataDict = 'Data dictionary';
csFileHeader = 'File header';
csIndexBlocks = 'Index blocks';
csIndexHeader = 'Index header';
csOtherBlocks = 'Other blocks';
csStatusSep = '============================================================';
function Singular(const Value : Integer;
const Singular, Plural : string) : string;
begin
Result := IntToStr(Value) + ' ';
if Value = 1 then
Result := Result + Singular
else
Result := Result + Plural;
end;
procedure TfrmMain.FormShow(Sender: TObject);
begin
ClearTreeView;
NoteBook.ActivePage := pgReadMe;
// NoteBook.ActivePage := pgProps;
SetCtrlStates;
end;
procedure TfrmMain.ClearAll;
begin
ClearRepair;
ClearTreeView;
ClearProps;
ClearData;
ClearRawData;
ClearStatus;
end;
procedure TfrmMain.ClearData;
begin
lvData.Columns.Clear;
lvData.Items.Clear;
end;
procedure TfrmMain.ClearProps;
begin
lvProps.Columns.Clear;
lvProps.Items.Clear;
end;
procedure TfrmMain.ClearRawData;
begin
lvRawData.Columns.Clear;
lvRawData.Items.Clear;
end;
procedure TfrmMain.ClearUI;
begin
ClearTreeView;
ClearProps;
ClearData;
ClearRawData;
{ Note: This method does not clear the status page. }
end;
procedure TfrmMain.ReleaseBlocksAndNodes;
begin
FFileHeaderBlock := nil;
FDataBlocksNode := nil;
FIndexBlocksNode := nil;
FOtherBlocksNode := nil;
FViewedBlocks.Clear;
end;
procedure TfrmMain.ClearRepair;
begin
if FRepair <> nil then begin
ReleaseBlocksAndNodes;
FRepair.Free;
FRepair := nil;
end;
end;
procedure TfrmMain.ClearStatus;
begin
memStatus.Clear;
FLastItem := riNone;
end;
procedure TfrmMain.ClearTreeView;
begin
FCurNode := nil;
tvMain.Items.Clear;
tvMain.Items.Add(nil, '<open a FlashFiler table>');
end;
procedure TfrmMain.DisplayData(const Block : ICommonBlock);
var
Col, ColCount, Row : Integer;
Column : TListColumn;
Item : TListItem;
begin
ClearData;
ColCount := Block.DataColCount;
for Col := 0 to Pred(ColCount) do begin
Column := lvData.Columns.Add;
Column.Caption := Block.DataColCaption[Col];
Column.Width := Block.DataColWidth[Col];
end;
for Row := 0 to Pred(Block.DataRowCount) do begin
Item := lvData.Items.Add;
for Col := 0 to Pred(ColCount) do begin
if Col = 0 then
Item.Caption := Block.DataCell[Row, Col]
else
Item.SubItems.Add(Block.DataCell[Row, Col]);
end; { for }
end; { for }
end;
procedure TfrmMain.DisplayProps(const Block : ICommonBlock);
var
Col, ColCount, Row : Integer;
Column : TListColumn;
Item : TListItem;
begin
ClearProps;
ColCount := Block.PropertyColCount;
for Col := 0 to Pred(ColCount) do begin
Column := lvProps.Columns.Add;
Column.Caption := Block.PropertyColCaption[Col];
Column.Width := Block.PropertyColWidth[Col];
end;
for Row := 0 to Pred(Block.PropertyRowCount) do begin
Item := lvProps.Items.Add;
for Col := 0 to Pred(ColCount) do begin
if Col = 0 then
Item.Caption := Block.PropertyCell[Row, Col]
else
Item.SubItems.Add(Block.PropertyCell[Row, Col]);
end; { for }
end; { for }
end;
procedure TfrmMain.DisplayRawData(const Block : ICommonBlock);
var
Row : Integer;
Column : TListColumn;
Item : TListItem;
RawData : PffBlock;
Strings : TStringList;
begin
ClearRawData;
RawData := Block.RawData;
Strings := TStringList.Create;
try
{ Format the raw data. }
GenerateHexLines(RawData, FFileHeaderBlock.BlockSize, Strings);
{ Set up the columns. }
Column := lvRawData.Columns.Add;
Column.Caption := 'Offset';
Column.Width := 70;
Column := lvRawData.Columns.Add;
Column.Caption := 'Bytes';
Column.Width := 475;
for Row := 0 to Pred(Strings.Count) do begin
Item := lvRawData.Items.Add;
Item.Caption := LongintToHex(Row * 16);
Item.SubItems.Add(Strings[Row]);
end;
finally
Strings.Free;
end;
end;
procedure TfrmMain.LoadUI;
var
DictRootNode,
FileHeaderNode,
RootNode : TTreeNode;
Inx : Integer;
DictBlock : IStreamBlock;
IndexHeaderBlock : IIndexHeaderBlock;
begin
{ Set up the tree view. Display a root node identifying the file. Add
child nodes that provide access to the header block, dictionary blocks,
& index header. }
tvMain.Items.Clear;
RootNode := tvMain.Items.Add(nil, ExtractFileName(FFileName));
FFileHeaderBlock := FRepair.GetFileHeaderBlock;
FileHeaderNode := tvMain.Items.AddChildObject(RootNode, csFileHeader,
Pointer(FFileHeaderBlock));
DictRootNode := tvMain.Items.AddChild(RootNode, csDataDict);
for Inx := 0 to Pred(FRepair.DictBlockCount) do begin
DictBlock := FRepair.DictBlocks[Inx];
tvMain.Items.AddChildObject(DictRootNode,
Format(csBlock,
[DictBlock.BlockNum]),
Pointer(DictBlock));
FViewedBlocks.Add(DictBlock);
end;
{ Create a node for the index header. }
IndexHeaderBlock := FRepair.GetIndexHeaderBlock;
tvMain.Items.AddChildObject(RootNode, csIndexHeader,
Pointer(IndexHeaderBlock));
FViewedBlocks.Add(IndexHeaderBlock);
{ Create nodes for viewed data, index, & other blocks. }
FDataBlocksNode := tvMain.Items.AddChild(RootNode, csDataBlocks);
FIndexBlocksNode := tvMain.Items.AddChild(RootNode, csIndexBlocks);
FOtherBlocksNode := tvMain.Items.AddChild(RootNode, csOtherBlocks);
{ By default, select the file header node & display its information. }
RootNode.Expand(True);
PositionToNode(FileHeaderNode);
end;
procedure TfrmMain.mnuFileOpenClick(Sender: TObject);
begin
if dlgOpen.Execute then begin
FFileName := dlgOpen.FileName;
ClearAll;
FRepair := TffRepairEngine.Create;
FRepair.Open(FFileName);
LoadUI;
end;
end;
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ClearRepair;
end;
procedure TfrmMain.mnuFileExitClick(Sender: TObject);
begin
Close;
end;
procedure TfrmMain.tvMainClick(Sender: TObject);
var
Node : TTreeNode;
begin
Node := tvMain.Selected;
if (Node <> nil) and (Node <> FCurNode) then begin
{ Set up the list view columns. Raw data will be displayed when the user
views that page. }
ClearRawData;
if Node.Data <> nil then begin
DisplayProps(ICommonBlock(Node.Data));
DisplayData(ICommonBlock(Node.Data));
if Notebook.ActivePage = pgRawData then
DisplayRawData(ICommonBlock(Node.Data))
else if (FState = rmIdle) and (NoteBook.ActivePage = pgStatus) then
{ If state is idle (i.e., we did not just finish repairing) &
on the status page then switch to the props page. }
NoteBook.ActivePage := pgProps;
end
else begin
ClearProps;
ClearData;
end;
FCurNode := Node;
end
else if (Node <> nil) and (FState = rmIdle) and
(Notebook.ActivePage = pgStatus) then
{ If user clicked on the current node & the status page is displayed then
flip over to the properties page. }
NoteBook.ActivePage := pgProps;
end;
procedure TfrmMain.mnuFileCloseClick(Sender: TObject);
begin
if FRepair <> nil then
ClearAll;
end;
procedure TfrmMain.tvMainGetSelectedIndex(Sender: TObject;
Node: TTreeNode);
begin
tvMainClick(Sender);
end;
procedure TfrmMain.OnComplete(Sender : TObject);
var
Action, HighestAction : TffRepairAction;
Inx : Integer;
SelfRepairing : Boolean;
AbortMsg,
Recommendation,
StatusMsg,
RepairedErrSummary,
Summary : string;
begin
progressBar.Position := 0;
Status(csStatusSep, []);
{ Determine the highest repair action. }
SelfRepairing := False;
HighestAction := raDecide;
for Inx := 0 to Pred(FRepair.ErrorCount) do begin
Action := rcAction[FRepair.ErrorCodes[Inx]];
if Action = raSelfRepair then
SelfRepairing := True;
if Action > HighestAction then
HighestAction := Action;
end; { for }
if FState = rmVerify then begin
lblStatus.Caption := 'Verification complete.';
if FRepair.ErrorCount = 0 then
StatusMsg := 'Verification complete. No errors were found.'
else begin
StatusMsg := Format('Verification complete. Found %s.',
[Singular(FRepair.ErrorCount, 'error', 'errors')]);
if FRepair.Aborted then
AbortMsg := 'The error limit was reached. There may be additional errors.';
{ Build a summary/recommended course of action. }
case HighestAction of
raSelfRepair :
begin
Summary := 'All errors can be successfully repaired without ' +
'packing the file.';
Recommendation := 'Allow this utility to repair the file.';
end;
raDecide, raPack :
begin
if SelfRepairing then begin
Summary := 'Some of the errors can be manually repaired ' +
'but other errors require the file to be packed.';
Recommendation := 'Allow this utility to repair and restructure ' +
'the file.';
end
else begin
Summary := 'The errors in the file require the file to be ' +
'packed.';
Recommendation := 'Allow this utility to pack the file.';
end;
end;
raUnsalvageable :
begin
Summary := 'The file and its data cannot be salvaged.';
Recommendation := 'Restore this file from the last known good backup.';
end;
end; { case }
if FRepair.Aborted then
StatusMsg := StatusMsg + #13#10#13#10 + AbortMsg;
StatusMsg := StatusMsg + #13#10#13#10 + Summary + #13#10#13#10 +
Recommendation;
end; { if }
end
else begin
lblStatus.Caption := 'Repair complete.';
if FRepair.ErrorCount = 0 then
StatusMsg := 'Repair complete. No errors were found.'
else begin
{ Generate a summary count for found & repaired errors. }
RepairedErrSummary := Format('Found %s and repaired %s.',
[Singular(FRepair.ErrorCount, 'error', 'errors'),
Singular(FRepair.FixCount, 'error', 'errors')]);
{ Did a pack or reindex fail? }
if HighestAction = raUnsalvageable then
StatusMsg := 'Repair did not complete successfully. ' +
RepairedErrSummary
else begin
{ No, the repair was entirely successful. Indicate if table was packed
or reindex. }
if HighestAction = raPack then
RepairedErrSummary := RepairedErrSummary +
' The table was packed.';
StatusMsg := 'Repair complete. ' + RepairedErrSummary;
end; { if..else }
end; { if..else }
end;
Status(StatusMsg, []);
Status(csStatusSep, []);
ShowMessage(StatusMsg);
end;
procedure TfrmMain.OnProgress(Repairer : TffRepairEngine;
State : TffRepairState;
Item : TffRepairItem;
const ActionStr : string;
const Position, Maximum : Integer);
begin
ProgressBar.Min := 1;
ProgressBar.Max := Maximum;
ProgressBar.Position := Position;
lblStatus.Caption := ActionStr;
if Item <> FLastItem then begin
Status(ActionStr, []);
FLastItem := Item;
end;
Application.ProcessMessages;
end;
procedure TfrmMain.OnReportError(Block : ICommonBlock;
const ErrCode : Integer;
const ErrorStr : string);
begin
if Block = nil then
Status('Error %d: %s', [ErrCode, ErrorStr])
else
Status('Block %d (%d): %s', [Block.BlockNum, ErrCode, ErrorStr]);
end;
procedure TfrmMain.OnReportFix(Block : ICommonBlock;
const ErrCode : Integer;
const RepairStr : string);
begin
if Block = nil then
Status('..Fix, code %d: %s', [ErrCode, RepairStr])
else
Status('..Block %d (%d): %s', [Block.BlockNum, ErrCode, RepairStr]);
end;
procedure TfrmMain.Status(const Msg : string; args : array of const);
begin
memStatus.Lines.Add(Format(Msg, args));
Application.ProcessMessages;
end;
procedure TfrmMain.mnuFileVerifyClick(Sender: TObject);
begin
if FState = rmIdle then begin
FState := rmVerify;
try
VerifyRepair;
finally
Application.ProcessMessages;
FState := rmIdle;
end;
end
else
ShowMessage('Verify can be performed only when this utility is Idle.');
end;
procedure TfrmMain.SetCtrlStates;
var
Opened : Boolean;
begin
Opened := (FRepair <> nil);
mnuFileClose.Enabled := Opened;
mnuFileVerify.Enabled := Opened;
mnuFileRepair.Enabled := Opened;
mnuChainViewData.Enabled := Opened;
mnuChainViewFree.Enabled := Opened;
mnuFileViewBlock.Enabled := Opened;
end;
procedure TfrmMain.mnuFileRepairClick(Sender: TObject);
begin
if FState = rmIdle then begin
FState := rmRepair;
try
ReleaseBlocksAndNodes;
ClearUI;
Application.ProcessMessages;
VerifyRepair;
finally
LoadUI;
Application.ProcessMessages;
FState := rmIdle;
end;
end
else
ShowMessage('Repair can be performed only when this utility is Idle.');
end;
procedure TfrmMain.VerifyRepair;
var
SavCursor : TCursor;
begin
if FRepair <> nil then begin
Notebook.ActivePage := pgStatus;
SavCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
try
ClearStatus;
FRepair.OnComplete := OnComplete;
FRepair.OnProgress := OnProgress;
FRepair.OnReportError := OnReportError;
FRepair.OnReportFix := OnReportFix;
if FState = rmVerify then
FRepair.Verify
else begin
FRepair.OutputVersion := FOutputVersion;
FRepair.Repair;
end;
finally
Screen.Cursor := SavCursor;
end;
end; { if }
end;
procedure TfrmMain.NotebookChange(Sender: TObject);
var
Node : TTreeNode;
begin
if (Notebook.ActivePage = pgRawData) and (lvRawData.Items.Count = 0) then begin
Node := tvMain.Selected;
if (Node <> nil) and (Node.Data <> nil) then
DisplayRawData(ICommonBlock(Node.Data));
end;
end;
procedure TfrmMain.mnuFileViewBlockClick(Sender: TObject);
var
BlockNumber : TffWord32;
Block : ICommonBlock;
Inx : Integer;
Node : TTreeNode;
begin
{ Have the user enter the block number. }
if Assigned(FFileHeaderBlock) then
with TfrmBlockNum.Create(nil) do
try
MaxBlockNum := Pred(FFileHeaderBlock.UsedBlocks);
ShowModal;
BlockNumber := BlockNum;
{ If a block number was specified, see if it is the same as
an existing node or if a new node must be added. }
if BlockNumber <> ffc_W32NoValue then begin
(* { TODO:: If this is a preloaded block then go to the appropriate tree node. }
if BlockNumber = xxx then
else if BlockNumber = xxx then
else if BlockNumber = xxx then
else if BlockNumber = xxx then
else if BlockNumber = xxx then*)
{ Determine if this is already available via an existing node in the
tree. }
Inx := FBlockNumToNodeMap.IndexOf(IntToStr(BlockNumber));
if Inx > -1 then begin
end
else begin
{ The block has not been viewed. Load the block & put it into the
tree view. }
Block := FRepair.GetBlock(BlockNumber);
FViewedBlocks.Add(Block);
if Block.Signature = ffc_SigDataBlock then begin
{ Add this under the data blocks node. }
Node := tvMain.Items.AddChildObject(FDataBlocksNode,
Format(csBlock,
[Block.BlockNum]),
Pointer(Block));
end
else if Block.Signature = ffc_SigIndexBlock then begin
{ Add this under the index blocks node. }
Node := tvMain.Items.AddChildObject(FIndexBlocksNode,
Format(csBlock,
[Block.BlockNum]),
Pointer(Block));
end
else begin
{ Add this under the other blocks node. }
Node := tvMain.Items.AddChildObject(FOtherBlocksNode,
Format(csBlock,
[Block.BlockNum]),
Pointer(Block));
end; { if..else }
{ Add this block to the blocknumber-to-node map. }
FBlockNumToNodeMap.AddObject(IntToStr(BlockNumber), Node);
{ Position the tree view to the node. }
PositionToNode(Node);
end;
end;
finally
Free;
end;
end;
procedure TfrmMain.PositionToNode(Node : TTreeNode);
begin
tvMain.Selected := Node;
{$IFDEF DCC6OrLater}
tvMain.Select(Node);
{$ELSE}
tvMain.Selected := Node;
{$ENDIF}
Node.Focused := True;
Node.Selected := True;
FCurNode := Node;
SetCtrlStates;
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FBlockNumToNodeMap := TStringList.Create;
with TffVerifyOptions.Create do
try
FOutputVersion := OutputVersion;
finally
Free;
end;
FViewedBlocks := TInterfaceList.Create;
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
FViewedBlocks.Free;
FBlockNumToNodeMap.Free;
end;
procedure TfrmMain.mnuChainViewDataClick(Sender: TObject);
var
SavCursor : TCursor;
begin
if FRepair <> nil then begin
Notebook.ActivePage := pgStatus;
SavCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
FState := rmAcquireInfo;
try
ClearStatus;
memStatus.Text := FRepair.GetDataChainDetails.Text;
finally
FState := rmIdle;
Screen.Cursor := SavCursor;
end;
end;
end;
procedure TfrmMain.mnuChainViewFreeClick(Sender: TObject);
var
SavCursor : TCursor;
begin
if FRepair <> nil then begin
Notebook.ActivePage := pgStatus;
SavCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
FState := rmAcquireInfo;
try
ClearStatus;
memStatus.Text := FRepair.GetFreeChainDetails.Text;
finally
FState := rmIdle;
Screen.Cursor := SavCursor;
end;
end;
end;
procedure TfrmMain.mnuOptionsClick(Sender: TObject);
var
Options : TfrmOptionsConfig;
begin
Options := TfrmOptionsConfig.Create(nil);
try
Options.ShowModal;
if Options.ModalResult = mrOK then
FOutputVersion := Options.OutputVersion;
finally
Options.Free;
end;
end;
end.