You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5438 8e941d3f-bd1b-0410-a28a-d453659cc2b4
973 lines
40 KiB
ObjectPascal
973 lines
40 KiB
ObjectPascal
{*********************************************************}
|
|
{* FlashFiler: TffDataConvertClass used to convert a *}
|
|
{* FlashFiler 1.xx table to a FlashFiler 2 *}
|
|
{* table. *}
|
|
{*********************************************************}
|
|
|
|
(* ***** 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 ***** *)
|
|
|
|
unit FFConvrt;
|
|
|
|
{$I FFDEFINE.INC}
|
|
|
|
{$IFDEF DCC6OrLater}
|
|
!!! Conversion utilities should be compiled only with Delphi 5 or lower, and
|
|
!!! C++Builder 5 or lower. Using Delphi 6 or higher, or C++Builder 6 or higher
|
|
!!! would lead to an error because the D6 streams are incompatible with streams
|
|
!!! from D5 and lower.
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
WinTypes, Classes, DB, FFLLDict, FFLLBase, FFLLEng, FFDB, FFLLExcp,
|
|
FFSRMgr;
|
|
|
|
type
|
|
TffDataConverter = class; {forward declaration}
|
|
|
|
{ FlashFiler v1.x DLL function types. }
|
|
TFF1TableDataDictionary = procedure(var aDict : TStream); stdcall;
|
|
TFF1TableFirst = procedure; stdcall;
|
|
TFF1TableNext = procedure; stdcall;
|
|
TFF1TableFieldValue = function(aFieldNo : Integer) : Variant; stdcall;
|
|
TFF1DirOpen = procedure(aPath : PChar); stdcall;
|
|
TFF1TableOpen = function(aTableName : PChar) : Integer; stdcall;
|
|
TFF1TableClose = procedure; stdcall;
|
|
TFF1TableEOF = function : boolean; stdcall;
|
|
TFF1TableRecordCount = function : Integer; stdcall;
|
|
TFF1IsFileBLOB = function(aFieldNo : Integer;
|
|
var aBuffer : array of Byte) : Boolean; stdcall;
|
|
TFF1SetNewMemMgr = function(aMemManager : TMemoryManager) : TMemoryManager; stdcall;
|
|
TFF1SetOldMemMgr = procedure(aMemMgr : TMemoryManager); stdcall;
|
|
TFF1GetAutoInc = function : Longint; stdcall;
|
|
|
|
|
|
{ TProtOptions is a record that holds settings for all the protocol
|
|
options.}
|
|
TffProtOptions = packed record
|
|
IsSingleUser : Boolean;
|
|
IsIPXSPX : Boolean;
|
|
IPXSPXLFB : Boolean;
|
|
IsTCPIP : Boolean;
|
|
TCPIPLFB : Boolean;
|
|
TCPIPPort : Longint;
|
|
UDPPortSr : Longint;
|
|
UDPPortCl : Longint;
|
|
IPXSocketSr : Longint;
|
|
IPXSocketCl : Longint;
|
|
SPXSocket : Longint;
|
|
TCPIntf : Longint;
|
|
end;
|
|
|
|
EffConverterException = class(EffException);
|
|
|
|
{ Event Types }
|
|
TffDataConverterEvent = procedure(aSender : TffDataConverter) of object;
|
|
{ Event type used for status events during the execution of the
|
|
converter}
|
|
TffDCNetBiosEvent = procedure(aSender : TffDataConverter;
|
|
var aCanceled : Boolean;
|
|
var aOptions : TffProtOptions) of object;
|
|
{ Since the NetBIOS protocol isn't supported in FF2, we raise this
|
|
type of event to give the application a chance to change the
|
|
protocol and provide options for the new protocol.}
|
|
|
|
|
|
{---FF1 to FF2 Converter Class---}
|
|
|
|
{ This class contains the business logic for converting a FlashFiler 1.x
|
|
file to the FlashFiler 2.0 file format.
|
|
Call the Convert method to convert a file. The converter opens the source
|
|
file in exclusive mode hence the file may not be opened by a server.
|
|
}
|
|
TffDataConverter = class
|
|
private
|
|
FAfterConvert : TffDataConverterEvent;
|
|
{ The method called after successfully completing the Convert Records
|
|
stage. }
|
|
FBeforeConvert : TffDataConverterEvent;
|
|
{ The method called before starting the Convert Records stage. }
|
|
FCanceled : Boolean;
|
|
{ Flag to stop the conversion process.}
|
|
FClient : TffClient;
|
|
{ The FF2 client used for the conversion. }
|
|
FCommitFrequency : TffWord32;
|
|
{ The number of records that must be converted before a
|
|
transaction is committed.}
|
|
FDatabase : TffDatabase;
|
|
{ The FF2 database used for the conversion. }
|
|
FDLLHandle : THandle;
|
|
{ Handle to the FF1 DLL.}
|
|
FFF2Table : TffTable;
|
|
{ The new FF2 table.}
|
|
FOnCancel : TffDataConverterEvent;
|
|
{ Event called if a conversion is aborted.}
|
|
FOnComplete : TffDataConverterEvent;
|
|
{ The method called after all operations are complete on a single
|
|
table.}
|
|
FOnNetBios : TffDCNetBiosEvent;
|
|
{ Since the NetBIOS protocol isn't supported in FF2, we raise
|
|
this event to give the application a chance to change the
|
|
protocol and provide options for the new protocol.}
|
|
FOnProgress : TffDataConverterEvent;
|
|
{ The method called during the conversion of records. It is
|
|
raised after converting the number of records specified by
|
|
ProgressFrequency. This event is raised at the very end of
|
|
the conversion if less than ProgressFrequency records were
|
|
processed since the last OnProgress event. }
|
|
FProgressFrequency : TffWord32;
|
|
{ The number of records that must be converted before the
|
|
OnProgress event may be raised. }
|
|
FBufferSize : TffWord32;
|
|
{ How big of a buffer to allow the converter to use. This is
|
|
used to determine how often transactions are committed.}
|
|
FRecordsProcessed : TffWord32;
|
|
{ This is the total number of records converted.}
|
|
FServerEngine : TffBaseServerEngine;
|
|
{ The FF2 server used for the conversion. }
|
|
FSession : TffSession;
|
|
{ The FF2 session used for the conversion. }
|
|
FSource : string;
|
|
{ The directory and name of the file being converted. }
|
|
FDestination : string;
|
|
{ The directory and name of the new file being created from the old
|
|
file. }
|
|
FTotalRecords : TffWord32;
|
|
{ The total number of records in the table that must be converted. }
|
|
|
|
procedure FFTableAfterOpen(aDataSet : TDataSet);
|
|
{ Used to get access to the FF2 table after it's opened.}
|
|
function IsFileBLOB(aField : TField; aFieldNo : Integer) : Boolean;
|
|
{ Fields that are stored as file BLOBs must be converted in a
|
|
different way than other fields. This function is used to
|
|
check for file-BLOB field types.}
|
|
procedure LoadFF1DLL;
|
|
{ Load the FF1 server from a DLL since we can't have a FF1 and
|
|
FF2 server in the same application.}
|
|
procedure ProcessGenInfo(const aFileName : string);
|
|
{ The FFSINFO is a FlashFiler system table that can't be handled
|
|
by the standard routine below. This procedure will convert
|
|
the FFSINFO table correctly.}
|
|
procedure SetBufferSize(aSize : TffWord32);
|
|
{ This function is called by the BufferSize property to set the
|
|
buffer size.}
|
|
|
|
{==FF1 Routine Types==}
|
|
protected
|
|
public
|
|
constructor Create(aServerEngine : TffBaseServerEngine);
|
|
destructor Destroy; override;
|
|
|
|
procedure Cancel;
|
|
{ Call this method to abort the conversion process.}
|
|
procedure Convert(const aSource : string;
|
|
const aDest : string);
|
|
{ Call this method to convert a file in the old format to a file
|
|
in the new format. This method raises an exception if an error
|
|
occurs.
|
|
aSource - The absolute path to an existing FFD file
|
|
in the old format. (Ex: c:\MyApp\MyTable.FFD)
|
|
aDest - The absolute path of the directory to which
|
|
aSource is being converted to. If a file
|
|
exists in aDest with the same filename that
|
|
is in aSource it will be overwritten.
|
|
(Ex: c:\MyNewApp) }
|
|
property AfterConvert : TffDataConverterEvent
|
|
read FAfterConvert
|
|
write FAfterConvert;
|
|
{ This event is raised after the record conversion stage has successfully
|
|
finished. If an error occurs during convert records then this event is
|
|
not raised. }
|
|
property BeforeConvert : TffDataConverterEvent
|
|
read FBeforeConvert
|
|
write FBeforeConvert;
|
|
{ This event is raised before the file is converted. When this method
|
|
is called, the converter will have opened the file and determined
|
|
how many records need to be converted. }
|
|
property BufferSize : TffWord32
|
|
read FBufferSize
|
|
write SetBufferSize
|
|
default 1024 * 1024;
|
|
{ Size of the buffer used by the converter. This number is used
|
|
to determine how often transactions are committed.}
|
|
property Canceled : Boolean read FCanceled;
|
|
{ Check if conversion was canceled.}
|
|
property OnCancel : TffDataConverterEvent
|
|
read FOnCancel
|
|
write FOnCancel;
|
|
{ The event called when a conversion is aborted.}
|
|
property OnComplete : TffDataConverterEvent
|
|
read FOnComplete
|
|
write FOnComplete;
|
|
{ The method called after all operations are complete on a table.}
|
|
property OnProgress : TffDataConverterEvent
|
|
read FOnProgress
|
|
write FOnProgress;
|
|
{ This event is raised after converting the number of records
|
|
specified by ProgressFrequency. This event is also raised at
|
|
the end of the conversion if fewer then ProgressFrequency
|
|
records were processed since the last OnProgress event. }
|
|
property OnNetBios : TffDCNetBiosEvent
|
|
read FOnNetBios
|
|
write FOnNetBios;
|
|
{ Since the NetBIOS protocol isn't supported in FF2, we raise
|
|
this event to give the application a chance to change the
|
|
protocol and provide options for the new protocol.}
|
|
property ProgressFrequency : TffWord32
|
|
read FProgressFrequency
|
|
write FProgressFrequency default 100;
|
|
{ The number of records that must be converted before the
|
|
OnProgress event will be raised. }
|
|
property RecordsProcessed : TffWord32 read FRecordsProcessed;
|
|
{ The number of records converted. This number is accurate at
|
|
the time OnProgress is raised. }
|
|
property Source : string read FSource;
|
|
{ The directory and name of the file being converted. }
|
|
property Destination : string read FDestination;
|
|
{ The drive and path of the location to place the new FF2 tables.}
|
|
property TotalRecords : TffWord32 read FTotalRecords;
|
|
{ The total number of records to be processed in the Convert Records
|
|
stage. }
|
|
property ServerEngine : TffBaseServerEngine read FServerEngine;
|
|
{ The FF2 server engine used to make the new (converted) table.}
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils,
|
|
Dialogs,
|
|
Winsock,
|
|
{$IFDEF DCC6OrLater} {!!.06 - Start}
|
|
Variants,
|
|
{$ENDIF} {!!.06 - End}
|
|
FFClintf;
|
|
|
|
const
|
|
ffc_ConvAlias = 'ConvAlias';
|
|
|
|
var
|
|
ffStrResConverter : TffStringResource;
|
|
|
|
{ Functions mapped to FF1 DLL}
|
|
FF1DirOpen : TFF1DirOpen;
|
|
FF1TableClose : TFF1TableClose;
|
|
FF1TableDataDictionary : TFF1TableDataDictionary;
|
|
FF1TableEOF : TFF1TableEOF;
|
|
FF1TableFieldValue : TFF1TableFieldValue;
|
|
FF1TableFirst : TFF1TableFirst;
|
|
FF1TableNext : TFF1TableNext;
|
|
FF1TableOpen : TFF1TableOpen;
|
|
FF1TableRecordCount : TFF1TableRecordCount;
|
|
FF1IsFileBLOB : TFF1IsFileBLOB;
|
|
FF1SetNewMemMgr : TFF1SetNewMemMgr;
|
|
FF1SetOldMemMgr : TFF1SetOldMemMgr;
|
|
FF1GetAutoInc : TFF1GetAutoInc;
|
|
|
|
{$I FFCvCNST.INC}
|
|
{$R FFCVCNST.RES}
|
|
|
|
{===TffDataConverter=================================================}
|
|
procedure TffDataConverter.Cancel;
|
|
begin
|
|
FCanceled := True;
|
|
end;
|
|
{--------}
|
|
procedure TffDataConverter.Convert(const aSource : string;
|
|
const aDest : string);
|
|
var
|
|
FF2Dict : TffDataDictionary;
|
|
FF1DictStream : TMemoryStream;
|
|
Value : Variant;
|
|
OldFileName : AnsiString;
|
|
SourceDir : AnsiString;
|
|
Msg : TMsg;
|
|
FieldNumber : Integer;
|
|
FieldCount : Integer;
|
|
Data : Pointer;
|
|
begin
|
|
FTotalRecords := 0;
|
|
FRecordsProcessed := 0;
|
|
FSource := aSource;
|
|
OldFileName := ExtractFileName(aSource);
|
|
FDestination := aDest + '\' + ChangeFileExt(OldFileName, {!!.03}
|
|
'.' + ffc_ExtForData); {!!.03}
|
|
FCanceled := False;
|
|
|
|
{setup a FF2 table}
|
|
FFF2Table := TffTable.Create(nil);
|
|
FFF2Table.AfterOpen := FFTableAfterOpen;
|
|
try
|
|
FFF2Table.DatabaseName := FDatabase.DatabaseName;
|
|
FFF2Table.SessionName := FSession.SessionName;
|
|
FFF2Table.Timeout := -1;
|
|
|
|
{parse out the directory to the source file(s)}
|
|
SourceDir := ExtractFilePath(aSource);
|
|
{remove the trailing backslash from the directory}
|
|
Delete(SourceDir, Length(SourceDir), 1);
|
|
FF1DirOpen(PChar(SourceDir));
|
|
{extract the FF1 table name and remove its extension}
|
|
Delete(OldFileName, Length(OldFileName) - 3, 4);
|
|
{if we are able to open the FF1 table we'll start the conversion
|
|
process}
|
|
if FF1TableOpen(PChar(OldFileName)) <> 0 then begin
|
|
FFRaiseExceptionNoData(EffConverterException,
|
|
ffStrResConverter,
|
|
ffcverrFF1TableOpen)
|
|
end else begin
|
|
{add our alias if we haven't added it already}
|
|
if not FSession.IsAlias(ffc_ConvAlias) then begin
|
|
FSession.AddAlias(ffc_ConvAlias, PChar(aDest), False); {!!.11}
|
|
FDatabase.AliasName := ffc_ConvAlias;
|
|
end;
|
|
FDatabase.Open;
|
|
|
|
FTotalRecords := FF1TableRecordCount;
|
|
|
|
{ the rest of this routine will not properly convert a FF1
|
|
FFSINFO system table so we'll convert it in a separate procedure}
|
|
if UpperCase(OldFileName) = 'FFSINFO' then begin
|
|
ProcessGenInfo(OldFileName);
|
|
exit;
|
|
end;
|
|
{create a dictionary from the FF1 table that will be used in our
|
|
new FF2 table}
|
|
FF2Dict := TffDataDictionary.Create(4096);
|
|
{read the FF1 dictionary into a stream and then read it into the
|
|
new dictionary}
|
|
FF1DictStream := TMemoryStream.Create;
|
|
FF1TableDataDictionary(TStream(FF1DictStream));
|
|
FF1DictStream.Position := 0;
|
|
FF2Dict.ReadFromStream(FF1DictStream);
|
|
FF2Dict.FileDescriptor[0]^.fdExtension := ffc_ExtForData;
|
|
|
|
try
|
|
{create the new table}
|
|
if FFDbiCreateTable(FDatabase, True, OldFileName, FF2Dict) = 0 then begin
|
|
try
|
|
{don't prceed if the conversion has been canceled}
|
|
if not FCanceled then begin
|
|
{execute the BeforeConvert event if assigned}
|
|
if Assigned(FBeforeConvert) then
|
|
FBeforeConvert(self);
|
|
{name and open the new table}
|
|
FFF2Table.TableName := OldFileName;
|
|
FFF2Table.Exclusive := True;
|
|
FFF2Table.Open;
|
|
{now move to the first record in the FF1 table and iterate
|
|
through them - adding each record to the FF2 table, field-
|
|
by-field}
|
|
FF1TableFirst;
|
|
FDatabase.StartTransaction;
|
|
while ((not FF1TableEOF) and (not FCanceled)) do begin
|
|
FFF2Table.Insert;
|
|
{copy the value of each field to the FF2 record we're
|
|
inserting}
|
|
FieldCount := pred(FFF2Table.FieldCount);
|
|
for FieldNumber := 0 to FieldCount do begin
|
|
{we have to handle file BLOBs differently than other
|
|
field types else they will be added to the new table
|
|
as "normal" BLOBs -- and folks wouldn't like that. The
|
|
file BLOB process is contained within the call to
|
|
IsFileBLOB(..) for efficiency.}
|
|
if (not IsFileBLOB(FFF2Table.Fields[FieldNumber], FieldNumber)) then
|
|
try {!!.01}
|
|
if (FFF2Table.Dictionary.FieldType[FieldNumber] <> fftByteArray) then {!!.06 - Start}
|
|
FFF2Table.Fields[FieldNumber].Value :=
|
|
FF1TableFieldValue(FieldNumber)
|
|
else begin
|
|
Value := FF1TableFieldValue(FieldNumber);
|
|
if (Value <> NULL) then begin {!!.07 - Start}
|
|
Data := VarArrayLock(Value);
|
|
try
|
|
FFF2Table.Fields[FieldNumber].SetData(Data);
|
|
finally
|
|
VarArrayUnlock(Value);
|
|
end;
|
|
end; {!!.07 - End}
|
|
end; {!!.06 - End}
|
|
except {!!.01}
|
|
FCanceled := False; {!!.01}
|
|
raise; {!!.01}
|
|
end; {!!.01}
|
|
end; {for}
|
|
{post the new record}
|
|
FFF2Table.Post;
|
|
inc(FRecordsProcessed);
|
|
{move to the next record}
|
|
FF1TableNext;
|
|
{execute the OnProgress event if assigned and we're at one
|
|
of the progress points}
|
|
if ((Assigned(FOnProgress)) and (FProgressFrequency <> 0) and
|
|
(FRecordsProcessed mod FProgressFrequency = 0)) then begin
|
|
FOnProgress(self);
|
|
end;
|
|
if ((FCommitFrequency <> 0) and
|
|
(FRecordsProcessed mod FCommitFrequency = 0)) then begin
|
|
try
|
|
FDatabase.Commit;
|
|
except
|
|
{no need to rollback because we're deleting the table}
|
|
FCanceled := True;
|
|
raise;
|
|
end;
|
|
{process messages: there could have been a Cancel raised.}
|
|
if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
|
|
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
|
|
DispatchMessage(Msg);
|
|
FDatabase.StartTransaction;
|
|
end;
|
|
end; {while}
|
|
|
|
{we have to commit the outstanding transaction even if it
|
|
was canceled}
|
|
try
|
|
if FDatabase.InTransaction then
|
|
FDatabase.Commit;
|
|
if FFF2Table.Dictionary.HasAutoIncField(FieldNumber) then
|
|
FFDbiSetTableAutoIncValue(FFF2Table, FF1GetAutoInc);
|
|
except
|
|
{no need to rollback because we're deleting the table}
|
|
FCanceled := True;
|
|
raise;
|
|
end;
|
|
|
|
{only proceed if not canceled}
|
|
if not FCanceled then begin
|
|
{execute the OnProgress event if assigned to ensure we get
|
|
a final count on the number of records converted}
|
|
if ((Assigned(FOnProgress)) and
|
|
(FProgressFrequency <> 0) and
|
|
(FRecordsProcessed mod FProgressFrequency > 0)) then
|
|
FOnProgress(self);
|
|
{now we need to call the AfterConvert event}
|
|
if Assigned(FAfterConvert) then
|
|
FAfterConvert(self);
|
|
end; {if not canceled}
|
|
end; {if not canceled}
|
|
finally
|
|
{if an exception was raised during a conversion, it's
|
|
possible to have an open transaction. We need to see if
|
|
there's an open transaction and roll it back if so}
|
|
if FDatabase.InTransaction then
|
|
FDatabase.Rollback;
|
|
FFF2Table.Close;
|
|
FDatabase.Close;
|
|
if not FCanceled then begin
|
|
{we didn't complete the conversion if it was canceled.}
|
|
if Assigned(FOnComplete) then
|
|
FOnComplete(self);
|
|
end else begin
|
|
{if canceled, we raise the Canceled event, delete the
|
|
aborted table, and reset the canceled flag.}
|
|
if Assigned(FOnCancel) then
|
|
FOnCancel(self);
|
|
FDatabase.Open;
|
|
FFF2Table.DeleteTable;
|
|
FFF2Table.Close;
|
|
FDatabase.Close;
|
|
FCanceled := False;
|
|
end; {if..else}
|
|
FFF2Table.Free;
|
|
FFF2Table := nil; {!!.01}
|
|
FSession.DeleteAlias(ffc_ConvAlias);
|
|
FF1TableClose;
|
|
FF1DictStream.Free;
|
|
FF2Dict.Free;
|
|
end; {try..finally}
|
|
end else
|
|
FFRaiseException(EffConverterException, ffStrResConverter,
|
|
ffcverrFF2TableCreate,
|
|
[format('Couldn''t create new %s', [FDestination])])
|
|
except
|
|
on E: Exception do
|
|
if E.ClassType <> EffConverterException then
|
|
FFRaiseException(EffConverterException,
|
|
ffStrResConverter,
|
|
ffcverrFF2TableCreate,
|
|
[E.Message])
|
|
else
|
|
raise;
|
|
end;
|
|
end; {if}
|
|
except
|
|
on E: Exception do begin
|
|
FFF2Table.Free;
|
|
if E.ClassType <> EffConverterException then begin
|
|
FFRaiseExceptionNoData(EffConverterException,
|
|
ffStrResConverter,
|
|
ffcverrFF1TableOpen)
|
|
end else
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
{--------}
|
|
constructor TffDataConverter.Create(aServerEngine: TffBaseServerEngine);
|
|
begin
|
|
FCanceled := False;
|
|
FServerEngine := aServerEngine;
|
|
LoadFF1DLL;
|
|
BufferSize := 1024 * 1024;
|
|
FCommitFrequency := 1000;
|
|
{setup our client}
|
|
FClient := TffClient.Create(nil);
|
|
FClient.ClientName := 'ConvClient' + IntToStr(GetCurrentThreadID);
|
|
FClient.ServerEngine := aServerEngine;
|
|
{setup our session}
|
|
FSession := TffSession.Create(nil);
|
|
FSession.ClientName := FClient.ClientName;
|
|
FSession.SessionName := 'ConvSess' + IntToStr(GetCurrentThreadID);
|
|
FSession.Open;
|
|
{setup a database}
|
|
FDatabase := TffDatabase.Create(nil);
|
|
FDatabase.SessionName := FSession.SessionName;
|
|
FDatabase.DatabaseName := ffc_ConvAlias;
|
|
end;
|
|
{--------}
|
|
destructor TffDataConverter.Destroy;
|
|
begin
|
|
{free the database}
|
|
FDatabase.Free;
|
|
{free the session}
|
|
FSession.Free;
|
|
{free the client}
|
|
FClient.Free;
|
|
|
|
if FDLLHandle <> 0 then
|
|
FreeLibrary(FDLLHandle);
|
|
|
|
inherited;
|
|
end;
|
|
{--------}
|
|
procedure TffDataConverter.FFTableAfterOpen(aDataSet : TDataSet);
|
|
var
|
|
TempFreq : Integer;
|
|
begin
|
|
if ((FBufferSize <= 0) or
|
|
(aDataSet = nil)) then
|
|
Exit;
|
|
if aDataSet.Active then begin
|
|
TempFreq := Integer(FBufferSize) div
|
|
TffTable(aDataSet).Dictionary.RecordLength;
|
|
{Begin !!.03}
|
|
{ensure we have a min commit freq of 10 records}
|
|
if TempFreq > 10 then begin
|
|
if TffTable(aDataSet).Dictionary.HasBLOBFields then
|
|
FCommitFrequency := 10
|
|
else
|
|
FCommitFrequency := TempFreq;
|
|
end
|
|
else
|
|
FCommitFrequency := 10;
|
|
{End !!.03}
|
|
end else
|
|
FCommitFrequency := 1000;
|
|
end;
|
|
{--------}
|
|
function TffDataConverter.IsFileBLOB(aField : TField;
|
|
aFieldNo : Integer) : Boolean;
|
|
var
|
|
FileName : string[255];
|
|
Buffer : array[0..255] of Byte;
|
|
begin
|
|
Result := False;
|
|
if aField is TBLOBField then begin
|
|
Result := FF1IsFileBLOB(aFieldNo, Buffer);
|
|
if Result then begin
|
|
SetLength(FileName, Buffer[0]);
|
|
Move(Buffer[1], FileName[1], Buffer[0]);
|
|
FFDbiAddFileBLOB(FFF2Table, succ(aFieldNo), FileName);
|
|
end;
|
|
end; {if}
|
|
end;
|
|
{--------}
|
|
procedure TffDataConverter.LoadFF1DLL;
|
|
var
|
|
Msg, Msg2 : string;
|
|
ErrorMode : Word;
|
|
begin
|
|
{ Use setErrorMode to prohibit the Windows error dialog that appears if the
|
|
DLL is not found. Load the DLL dynamically. }
|
|
ErrorMode := SetErrorMode(SEM_NoOpenFileErrorBox);
|
|
FDllHandle := LoadLibrary('FF1Intfc.DLL');
|
|
SetErrorMode(ErrorMode);
|
|
|
|
FDLLHandle := GetModuleHandle('FF1Intfc.DLL');
|
|
|
|
if FDllHandle = 0 then
|
|
begin
|
|
Msg := 'Unable to load DLL FF1Intfc. ';
|
|
case GetLastError of
|
|
0 : Msg2 := 'System out of memory, executable corrupt, ' +
|
|
'or relocations invalid.';
|
|
2 : Msg2 := 'File not found.';
|
|
3 : Msg2 := 'Path not found.';
|
|
8 : Msg2 := 'There is insufficient memory to load the DLL.';
|
|
10 : Msg2 := 'The Windows version of the DLL is incorrect.';
|
|
else
|
|
Msg2 := '';
|
|
end; { case }
|
|
raise Exception.Create(Msg + Msg2 + ' Unable to run conversion.');
|
|
end { if dll not loaded }
|
|
else begin
|
|
{map our function calls to the FF1 DLL}
|
|
@FF1TableDataDictionary := GetProcAddress(FDLLHandle, 'FF1TableDataDictionary');
|
|
@FF1TableFirst := GetProcAddress(FDLLHandle, 'FF1TableFirst');
|
|
@FF1TableNext := GetProcAddress(FDLLHandle, 'FF1TableNext');
|
|
@FF1TableFieldValue := GetProcAddress(FDLLHandle, 'FF1TableFieldValue');
|
|
@FF1DirOpen := GetProcAddress(FDLLHandle, 'FF1DirOpen');
|
|
@FF1TableOpen := GetProcAddress(FDLLHandle, 'FF1TableOpen');
|
|
@FF1TableClose := GetProcAddress(FDLLHandle, 'FF1TableClose');
|
|
@FF1TableEOF := GetProcAddress(FDLLHandle, 'FF1TableEOF');
|
|
@FF1TableRecordCount := GetProcAddress(FDLLHandle, 'FF1TableRecordCount');
|
|
@FF1IsFileBLOB := GetProcAddress(FDLLHandle, 'FF1IsFileBLOB');
|
|
@FF1SetNewMemMgr := GetProcAddress(FDLLHandle, 'FF1SetNewMemManager');
|
|
@FF1SetOldMemMgr := GetProcAddress(FDLLHandle, 'FF1SetOldMemManager');
|
|
@FF1GetAutoInc := GetProcAddress(FDLLHandle, 'FF1GetAutoInc');
|
|
end;
|
|
end;
|
|
{--------}
|
|
procedure TffDataConverter.ProcessGenInfo(const aFileName : string);
|
|
var
|
|
FF1DictStream : TMemoryStream;
|
|
FF1Dict : TffDataDictionary;
|
|
FF2Dict : TffDataDictionary;
|
|
ProtocolString : string;
|
|
NewFileName : string;
|
|
FieldNumber : Integer;
|
|
IsNotCanceled : Boolean;
|
|
SkipProtocols : Boolean;
|
|
ProtOptions : TffProtOptions;
|
|
begin
|
|
{since some of the earlier FF1 tables don't have all the fields that
|
|
v1.56 has we need FF1's dictionary so we can get its field count.}
|
|
FF1DictStream := TMemoryStream.Create;
|
|
FF1TableDataDictionary(TStream(FF1DictStream));
|
|
FF1Dict := TffDataDictionary.Create(4096);
|
|
FF1DictStream.Position := 0;
|
|
FF1Dict.ReadFromStream(FF1DictStream);
|
|
{we'll build the dictionary to build our new FF2 table}
|
|
FF2Dict := TffDataDictionary.Create(4096);
|
|
with FF2Dict do begin
|
|
AddField('ServerName', '', fftShortString,
|
|
pred(sizeof(TffNetName)), 0, true, nil);
|
|
AddField('MaxPages', '', fftWord32, 0, 0, True, nil);
|
|
AddField('IsSecure', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('AutoUp', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('AutoMini', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('DebugLog', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('UseSingleUser', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('UseIPXSPX', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('IPXSPXLFB', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('UseTCPIP', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('TCPIPLFB', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('TCPPort', '', fftInt32, 0, 0, True, nil);
|
|
AddField('UDPPortSr', '', fftInt32, 0, 0, True, nil);
|
|
AddField('UDPPortCl', '', fftInt32, 0, 0, True, nil);
|
|
AddField('IPXSocketSr', '', fftInt32, 0, 0, True, nil);
|
|
AddField('IPXSocketCl', '', fftInt32, 0, 0, True, nil);
|
|
AddField('SPXSocket', '', fftInt32, 0, 0, True, nil);
|
|
AddField('UseEncrypt', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('ReadOnly', '', fftBoolean, 0, 0, True, nil);
|
|
AddField('LstMsgIntvl', '', fftInt32, 0, 0, True, nil);
|
|
AddField('KAInterval', '', fftInt32, 0, 0, True, nil);
|
|
AddField('KARetries', '', fftInt32, 0, 0, True, nil);
|
|
AddField('Priority', '', fftInt32, 0, 0, True, nil);
|
|
AddField('TCPInterface', '', fftInt32, 0, 0, True, nil);
|
|
AddField('NoAutoSaveCfg', '', fftBoolean, 0, 0, True, nil);
|
|
Addfield('TempStoreSize', '', fftInt32, 0, 0, True, nil);
|
|
AddField('CollectEnabld', '', fftBoolean, 0, 0, True, nil); {!!.01}
|
|
AddField('CollectFreq', '', fftInt32, 0, 0, True, nil); {!!.01}
|
|
end;
|
|
{create the new table}
|
|
NewFileName := ExtractFileName(FDestination);
|
|
if FFDbiCreateTable(FDatabase, True, aFileName, FF2Dict) = 0 then begin
|
|
try
|
|
{execute the BeforeConvert event if assigned}
|
|
if Assigned(FBeforeConvert) then
|
|
FBeforeConvert(self);
|
|
{name and open the new table}
|
|
FFF2Table.TableName := NewFileName;
|
|
FFF2Table.Open;
|
|
{now we'll move to the first record in the FF1 table and
|
|
iterate through them - adding each record to the FF2 table}
|
|
FF1TableFirst;
|
|
|
|
FFF2Table.Insert;
|
|
{we know the first six fields will match so we'll just copy
|
|
those over to the new table.}
|
|
FFF2Table.Fields[0].Value := FF1TableFieldValue(0); {ServerName}
|
|
{we are going to assume that all the old RAM pages were for a
|
|
4K block size and then round up to turn the memory used for
|
|
the old RAM pages into megabytes of RAM in the new table.}
|
|
FFF2Table.Fields[1].Value := (((FF1TableFieldValue(1) * 4096) +
|
|
pred(1024 * 1024)) {to prevent 0 MB RAM}
|
|
div (1024 * 1024));
|
|
for FieldNumber := 2 to 5 do
|
|
FFF2Table.Fields[FieldNumber].Value := FF1TableFieldValue(FieldNumber);
|
|
{setup the protocols}
|
|
SkipProtocols := False;
|
|
ProtocolString := FF1TableFieldValue(6);
|
|
if ProtocolString = '' then begin
|
|
FFF2Table.Fields[6].Value := True; {SingleUser}
|
|
FFF2Table.Fields[7].Value := False; {IPXSPX}
|
|
FFF2Table.Fields[8].Value := False; {IPXSPXLFB}
|
|
FFF2Table.Fields[9].Value := False; {TCPIP}
|
|
FFF2Table.Fields[10].Value := False; {TCPIPLFB}
|
|
end else if ProtocolString = 'TCP/IP' then begin
|
|
FFF2Table.Fields[6].Value := False;
|
|
FFF2Table.Fields[7].Value := False;
|
|
FFF2Table.Fields[8].Value := False;
|
|
FFF2Table.Fields[9].Value := True;
|
|
FFF2Table.Fields[10].Value := FF1TableFieldValue(7);
|
|
end else if ProtocolString = 'IPX/SPX' then begin
|
|
FFF2Table.Fields[6].Value := False;
|
|
FFF2Table.Fields[7].Value := True;
|
|
FFF2Table.Fields[8].Value := FF1TableFieldValue(7);
|
|
FFF2Table.Fields[9].Value := False;
|
|
FFF2Table.Fields[10].Value := False;
|
|
end else if ProtocolString = 'SINGLE' then begin
|
|
FFF2Table.Fields[6].Value := True;
|
|
FFF2Table.Fields[7].Value := False;
|
|
FFF2Table.Fields[8].Value := False;
|
|
FFF2Table.Fields[9].Value := False;
|
|
FFF2Table.Fields[10].Value := False;
|
|
end else if ProtocolString = 'NETBIOS' then begin
|
|
{NetBios has been removed from FF2 so we need to have the
|
|
user select a new protocol before converting the table or
|
|
find a way to have the application select new protocol and
|
|
assign it during the conversion.}
|
|
SkipProtocols := True;
|
|
if Assigned(FOnNetBios) then begin
|
|
{yes. initialize ProtOptions and raise the FOnNetBIOS event
|
|
so the using application can get updated protocol options
|
|
and update ProtOptions. We will use ProtOptions to
|
|
initialize the protocol options of the table.}
|
|
with ProtOptions do begin
|
|
IsSingleUser := False;
|
|
IsIPXSPX := False;
|
|
IPXSPXLFB := False;
|
|
IsTCPIP := False;
|
|
TCPIPLFB := False;
|
|
{FF1 stored the TCPIP port incorrectly, so we'll convert
|
|
it now. We are also changing the defaults in FF2.}
|
|
TCPIPPort := htons(FF1TableFieldValue(8));
|
|
if TCPIPPort = 24677 then
|
|
TCPIPPort := 25445;
|
|
UDPPortSr := htons(FF1TableFieldValue(9));
|
|
if UDPPortSr = 24677 then
|
|
UDPPortSr := 25445;
|
|
UDPPortCl := htons(FF1TableFieldValue(10));
|
|
if UDPPortCl = 24933 then
|
|
UDPPortCl := 25701;
|
|
IPXSocketSr := htons(FF1TableFieldValue(11));
|
|
if IPXSocketSr = 24677 then
|
|
IPXSocketSr := 25445;
|
|
IPXSocketCl := htons(FF1TableFieldValue(12));
|
|
if IPXSocketCl = 24933 then
|
|
IPXSocketCl := 25701;
|
|
SPXSocket := htons(FF1TableFieldValue(13));
|
|
if SPXSocket = 25189 then
|
|
SPXSocket := 25957;
|
|
if FF1Dict.FieldCount > 20 then
|
|
TCPIntf := FF1TableFieldValue(20)
|
|
else
|
|
TCPIntf := 0;
|
|
|
|
{now that we've setup the previous protocol options we
|
|
can raise the event with the previous settings}
|
|
FOnNetBIOS(self, IsNotCanceled, ProtOptions);
|
|
|
|
{assign the values returned to the appropriate FF2 field}
|
|
FFF2Table.Fields[6].Value := IsSingleUser;
|
|
FFF2Table.Fields[7].Value := IsIPXSPX;
|
|
FFF2Table.Fields[8].Value := IPXSPXLFB;
|
|
FFF2Table.Fields[9].Value := IsTCPIP;
|
|
FFF2Table.Fields[10].Value := TCPIPLFB;
|
|
FFF2Table.Fields[11].Value := TCPIPPort;
|
|
FFF2Table.Fields[12].Value := UDPPortSr;
|
|
FFF2Table.Fields[13].Value := UDPPortCl;
|
|
FFF2Table.Fields[14].Value := IPXSocketSr;
|
|
FFF2Table.Fields[15].Value := IPXSocketCl;
|
|
FFF2Table.Fields[16].Value := SPXSocket;
|
|
FFF2Table.Fields[23].Value := TCPIntf;
|
|
end; {with}
|
|
end else begin
|
|
{if the FOnNetBIOS isn't assigned, setup all protocol
|
|
settings to defaults.}
|
|
FFF2Table.Fields[6].Value := True;
|
|
FFF2Table.Fields[7].Value := False;
|
|
FFF2Table.Fields[8].Value := False;
|
|
FFF2Table.Fields[9].Value := False;
|
|
FFF2Table.Fields[10].Value := False;
|
|
FFF2Table.Fields[11].Value := 25445;
|
|
FFF2Table.Fields[12].Value := 25445;
|
|
FFF2Table.Fields[13].Value := 25701;
|
|
FFF2Table.Fields[14].Value := 25445;
|
|
FFF2Table.Fields[15].Value := 25701;
|
|
FFF2Table.Fields[16].Value := 25957;
|
|
FFF2Table.Fields[23].Value := 0;
|
|
end;
|
|
end;
|
|
{we can match up FF1 fields 8 through 13 with FF2 fields
|
|
12 through 17. We will skip this section if we've already
|
|
setup the protocols.}
|
|
if not SkipProtocols then begin
|
|
{since FF1 stored the TCP/IP port incorrectly, correct it now}
|
|
FFF2Table.Fields[11].Value := htons(FF1TableFieldValue(8));
|
|
if FFF2Table.Fields[11].Value = 24677 then
|
|
FFF2Table.Fields[11].Value := 25445;
|
|
FFF2Table.Fields[12].Value := htons(FF1TableFieldValue(9));
|
|
if FFF2Table.Fields[12].Value = 24677 then
|
|
FFF2Table.Fields[12].Value := 25445;
|
|
FFF2Table.Fields[13].Value := htons(FF1TableFieldValue(10));
|
|
if FFF2Table.Fields[13].Value = 24933 then
|
|
FFF2Table.Fields[13].Value := 25701;
|
|
FFF2Table.Fields[14].Value := htons(FF1TableFieldValue(11));
|
|
if FFF2Table.Fields[14].Value = 24677 then
|
|
FFF2Table.Fields[14].Value := 25445;
|
|
FFF2Table.Fields[15].Value := htons(FF1TableFieldValue(12));
|
|
if FFF2Table.Fields[15].Value = 24933 then
|
|
FFF2Table.Fields[15].Value := 25701;
|
|
FFF2Table.Fields[16].Value := htons(FF1TableFieldValue(13));
|
|
if FFF2Table.Fields[16].Value = 25189 then
|
|
FFF2Table.Fields[16].Value := 25957;
|
|
end;
|
|
{we may be able to match up the rest of the FF1 fields, but
|
|
all fields may not be present in all FF1 tables depending on
|
|
what version of FF the tables were created with. We will
|
|
assign default values for any fields not in the FF1 table.}
|
|
|
|
{AllowEncrypt?}
|
|
if FF1Dict.FieldCount > 14 then
|
|
FFF2Table.Fields[17].Value := FF1TableFieldValue(14)
|
|
else
|
|
FFF2Table.Fields[17].Value := False;
|
|
{ReadOnly? - Although this is the same name as the old setting
|
|
it a new setting to turn off all output from the server}
|
|
FFF2Table.Fields[18].Value := False;
|
|
if FF1Dict.FieldCount > 16 then begin
|
|
for FieldNumber := 19 to 21 do
|
|
FFF2Table.Fields[FieldNumber].Value :=
|
|
FF1TableFieldValue(FieldNumber - 3);
|
|
end else begin
|
|
{set to defaults if they weren't in the FF1 table}
|
|
FFF2Table.Fields[19].Value := 5000; {LastMsgInterval}
|
|
FFF2Table.Fields[20].Value := 2500; {KAInterval}
|
|
FFF2Table.Fields[21].Value := 5; {KARetries}
|
|
end;
|
|
if FF1Dict.FieldCount > 19 then
|
|
FFF2Table.Fields[22].Value := FF1TableFieldValue(19)
|
|
else
|
|
{set the priority to "normal" if it wasn't in the FF1 table}
|
|
FFF2Table.Fields[22].Value := 2;
|
|
{set the default TCP and IPX interfaces}
|
|
if not SkipProtocols then begin
|
|
if FF1Dict.FieldCount > 20 then
|
|
FFF2Table.Fields[23].Value := FF1TableFieldValue(20)
|
|
else
|
|
FFF2Table.Fields[23].Value := 0;
|
|
end;
|
|
{NoAutoSaveCfg - we set this value according to the old
|
|
ReadOnly setting since the functionality matches}
|
|
FFF2Table.Fields[24].Value := FF1TableFieldValue(15);
|
|
{New settings added for FF2 and their defaults}
|
|
FFF2Table.Fields[25].Value := ffcl_TempStorageSize; {Temp storage size (MB)}
|
|
FFF2Table.Fields[26].Value := True; {Garbage collection enabled}
|
|
FFF2Table.Fields[27].Value := ffcl_CollectionFrequency; {Garbage collection frequency (ms)}
|
|
{post the new record}
|
|
FFF2Table.Post;
|
|
inc(FRecordsProcessed);
|
|
|
|
{execute the OnProgress event if assigned and we're at one
|
|
of the progress points}
|
|
if ((Assigned(FOnProgress)) and
|
|
(FRecordsProcessed mod FProgressFrequency = 0)) then
|
|
FOnProgress(self);
|
|
{now we need to call the AfterConvert event}
|
|
if Assigned(FAfterConvert) then
|
|
FAfterConvert(self);
|
|
finally
|
|
FFF2Table.Close;
|
|
FDatabase.Close; {!!.01}
|
|
if not FCanceled then begin
|
|
{we didn't complete the conversion if it was canceled.}
|
|
if Assigned(FOnComplete) then
|
|
FOnComplete(self);
|
|
end else begin
|
|
{if canceled, we raise the Canceled event, delete the
|
|
aborted table, and reset the canceled flag.}
|
|
if Assigned(FOnCancel) then
|
|
FOnCancel(self);
|
|
FFF2Table.DeleteTable;
|
|
FCanceled := False;
|
|
end; {if..else}
|
|
FFF2Table.Free;
|
|
{FDatabase.Close;} {!!.01 Moved above}
|
|
FSession.DeleteAlias(ffc_ConvAlias);
|
|
FF1TableClose;
|
|
FF2Dict.Free;
|
|
FF1DictStream.Free;
|
|
FF1Dict.Free;
|
|
end;
|
|
end else
|
|
FFRaiseException(EffConverterException, ffStrResConverter,
|
|
ffcverrFF2TableCreate,
|
|
[format('Couldn''t create new %s', [FDestination])])
|
|
end;
|
|
{--------}
|
|
procedure TffDataConverter.SetBufferSize(aSize : TffWord32);
|
|
begin
|
|
FBufferSize := aSize;
|
|
if aSize <= 0 then
|
|
FFRaiseExceptionNoData(EffConverterException,
|
|
ffStrResConverter,
|
|
FFCvErrZeroCommitFreq);
|
|
FFTableAfterOpen(FFF2Table);
|
|
end;
|
|
{====================================================================}
|
|
procedure InitializeUnit;
|
|
begin
|
|
ffStrResConverter := nil;
|
|
ffStrResConverter := TffStringResource.Create(hInstance, 'FF_CONVERTER_STRINGS');
|
|
end;
|
|
|
|
procedure FinalizeUnit;
|
|
begin
|
|
ffStrResConverter.Free;
|
|
end;
|
|
|
|
initialization
|
|
InitializeUnit;
|
|
|
|
finalization
|
|
FinalizeUnit;
|
|
{====================================================================}
|
|
end.
|