{*******************************************************} { } { Responsive Software http://www.responsive.co.nz } { } { Copyright (c) 2003-2006 Responsive Software Limited } { } {*******************************************************} unit ClientCommunicatorUnit; interface uses Classes,DatabaseObjects, ScktComp, SyncObjs; const TerminatingChar = 'x'; type // client request type will indicate the type of request // being sent to the server // this will be the first two characters in the string which // allows for up to 100 types (00 to 99) TClientRequestType = (crtProgramVersion, crtDatabaseStatusString, crtFirebird, crtNotifyLoggedOnUser, crtNotifyInvalidLogin, crtMessagingData, crtCloseConnection, crtLoadGlobalConfiguration, crtSaveGlobalConfiguration, crtNoOfLoggedOnWorkstations, crtNoOfWorkstations, crtPopulateStringsWithDistinctValues, crtAcquireDatabaseCriticalUpdate, crtReleaseDatabaseCriticalUpdate, crtGenerateId, crtLoadDatabaseObjectById, crtLoadDatabaseObjectBySelectionString, crtSaveDatabaseObject, crtDeleteDatabaseObject, crtFullSaveDatabaseObject, crtLoadAllDatabaseObjects, crtLoadSomeDatabaseObjects, crtCountDatabaseObjects, crtSaveDatabaseObjects, crtDeleteDatabaseObjects, crtDeleteSomeDatabaseObjects, crtDeleteAndSaveSomeDatabaseObjects, crtUpdateSomeDatabaseObjects, crtOpenRemoteDataset, crtLoadDatabaseObjectsFromRemoteDataset, crtCloseRemoteDataset, crtLockDatabaseObject, crtUnlockDatabaseObject, crtGetAttachmentFileAge, crtGetAttachmentFileContents, crtSetServerThreadPriority); TClientCommunicator = class; // this class is the messaging thread used to send data // to and from other workstations TMessagingThread = class(TThread) private FClientCommunicator : TClientCommunicator; // reference to TClientCommunicator object using this thread public constructor Create (ClientCommunicator : TClientCommunicator); procedure Execute; override; end; // this class is used to provide client functions to connected clients // when the application is running in client mode TClientCommunicator = class private FClientSocket : TClientSocket; FClientSocketLock : TCriticalSection; FWinSocketStream : TWinSocketStream; FMessagingThread : TThread; FDataToSend : TStringList; FDataToSendLock : TCriticalSection; FDataReceived : TStringList; FDataReceivedLock : TCriticalSection; public ShowProgress : boolean; constructor Create (ServerIPAddress : string; ServerPortNumber : integer); destructor Destroy; override; // general routines for sending and receiving data // over the socket connection procedure SendDataToServer (Data : string); function ReceiveDataFromServer : string; // this function does a send and receive // protected by FClientSocketLock function TransferData (Data : string) : string; // used to send and receive messaging data between workstations procedure SendDataToWorkstations (Data : string); function ReceiveDataFromWorkstations : string; procedure AddToDataReceived (Str : string); function Connected : boolean; function CheckProgramVersion : boolean; function Firebird : boolean; function NotifyLoggedOnUser (UserName : string) : boolean; function NotifyInvalidLogin (UserName : string; Password : string) : boolean; procedure CreateMessagingThread; procedure TransferMessagingData; function CloseConnection : boolean; function LoadGlobalConfiguration : TGlobalConfiguration; function SaveGlobalConfiguration (GlobalConfiguration : TGlobalConfiguration) : boolean; function NoOfLoggedOnWorkstations : integer; function NoOfWorkstations : integer; procedure PopulateStringsWithDistinctValues (TableName : string; FieldName : string; Strings : TStrings; Add : boolean); function DatabaseStatusString : string; function AcquireDatabaseCriticalUpdate : boolean; function ReleaseDatabaseCriticalUpdate : boolean; function GenerateId (Increment : integer) : int64; function LoadDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; Id : int64) : TDatabaseObject; overload; function LoadDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string) : TDatabaseObject; overload; function SaveDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; DatabaseObject : TDatabaseObject) : boolean; function DeleteDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; Id : int64) : boolean; function FullSaveDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; DatabaseObject : TDatabaseObject; IncludeDetails : boolean; NewObject : boolean) : boolean; procedure LoadAllDatabaseObjects (DatabaseObjectCollection : TDatabaseObjectCollection; DatabaseObjectClass : TDatabaseObjectClass); procedure LoadSomeDatabaseObjects (DatabaseObjectCollection : TDatabaseObjectCollection; DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string); function CountDatabaseObjects (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string) : integer; function SaveDatabaseObjects (DatabaseObjectCollection : TDatabaseObjectCollection; DatabaseObjectClass : TDatabaseObjectClass) : boolean; function DeleteDatabaseObjects (DatabaseObjectCollection : TDatabaseObjectCollection; DatabaseObjectClass : TDatabaseObjectClass) : boolean; function DeleteSomeDatabaseObjects (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string) : boolean; function DeleteAndSaveSomeDatabaseObjects (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string; DatabaseObjectCollection : TDatabaseObjectCollection) : boolean; function UpdateSomeDatabaseObjects (DatabaseObjectClass : TDatabaseObjectClass; UpdateString : string; SelectionString : string) : boolean; function OpenRemoteDataset (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string) : integer; procedure LoadDatabaseObjectsFromRemoteDataset (DatabaseObjectCollection : TDatabaseObjectCollection; StartRecNo : integer; EndRecNo : integer); function CloseRemoteDataset : boolean; function LockDatabaseObject (Id : int64; var OtherUserName : string) : boolean; function UnlockDatabaseObject (Id : int64) : boolean; function GetAttachmentFileAge (AttachmentId : int64) : integer; function GetAttachmentFileContents (AttachmentId : int64) : string; function SetServerThreadPriority (Priority : TThreadPriority) : boolean; end; implementation uses SysUtils, DateUtils, Utilities, Progress, Globals, GeneralUtilities, ServerTest; {***** TMessagingThread methods ***********************************************} constructor TMessagingThread.Create (ClientCommunicator : TClientCommunicator); begin // create thread and run immediately inherited Create(false); // record reference to the client communicator object to which this thread belongs FClientCommunicator := ClientCommunicator; end; procedure TMessagingThread.Execute; var LastTime : TDateTime; begin LastTime := 0; while true do begin // send and receive data to and from other workstations // but because we are polling don't do this too often if Now > IncMillisecond(LastTime,MessagingPollTime) then begin FClientCommunicator.TransferMessagingData; LastTime := Now; end; // give other threads a chance Sleep(1); // check termination flag if Terminated then Exit; end; end; {***** TClientCommunicator methods ********************************************} constructor TClientCommunicator.Create (ServerIPAddress : string; ServerPortNumber : integer); begin // create the client socket FClientSocket := TClientSocket.Create(nil); if IsIPAddress(ServerIPAddress) then FClientSocket.Address := ServerIPAddress else FClientSocket.Host := ServerIPAddress; FClientSocket.Port := ServerPortNumber; // use synchronously within the main thread FClientSocket.ClientType := ctBlocking; // try to connect to server try FClientSocket.Active := true; except // ignore any error that occurs when trying to connect // we can test whether or not we are connected later end; // create the windows socket stream object FWinSocketStream := TWinSocketStream.Create(FClientSocket.Socket,ClientTimeout); // create data structures for messaging FDataToSend := TStringList.Create; FDataReceived := TStringList.Create; // create thread control objects FClientSocketLock := TCriticalSection.Create; FDataToSendLock := TCriticalSection.Create; FDataReceivedLock := TCriticalSection.Create; end; destructor TClientCommunicator.Destroy; begin if FMessagingThread <> nil then begin // kill the messaging thread FMessagingThread.Terminate; FMessagingThread.WaitFor; // destroy it FMessagingThread.Free; end; // send a message to the server to tell it we are closing if FClientSocket.Active then begin try // first send any pending messages TransferMessagingData; CloseConnection; except // ignore any exceptions end; end; // close the connection try FClientSocket.Close; except // ignore any exceptions end; // destroy thread control objects FClientSocketLock.Free; FDataToSendLock.Free; FDataReceivedLock.Free; // destroy messaging data structures FDataToSend.Free; FDataReceived.Free; // destroy the windows socket stream object FWinSocketStream.Free; // destroy the socket FClientSocket.Free; end; procedure TClientCommunicator.SendDataToServer (Data : string); const MaxBytesWrite = 1024; ShowProgressSize = 100000; var BytesToSend : integer; BytesSent : integer; Str : string; ShowProgress : boolean; begin // prepend the data length in curly brackets so that the // receiving socket knows how much data to expect if GlobalDataLock <> nil then GlobalDataLock.Acquire; try Str := '{' + IntToStr(Length(Data)) + '}' + Encrypt(Data,ClientServerEncryptRandSeed); finally if GlobalDataLock <> nil then GlobalDataLock.Release; end; ShowProgress := (Length(Str) >= ShowProgressSize) and Self.ShowProgress; // BytesSent := FWinSocketStream.Write(Str[1],Length(Str)); if ShowProgress then begin ProgressForm.SetPosition(0); ProgressForm.SetCaption('Transferring data. Please wait...'); ProgressForm.Show; end; try BytesSent := 0; while BytesSent < Length(Str) do begin BytesToSend := Length(Str) - BytesSent; if ShowProgress and (BytesToSend > MaxBytesWrite) then BytesToSend := MaxBytesWrite; BytesSent := BytesSent + FWinSocketStream.Write(Str[1+BytesSent],BytesToSend); if ShowProgress then ProgressForm.SetPosition(BytesSent * 100 div Length(Str)); end; finally if ShowProgress then ProgressForm.Hide; end; if not BytesSent = Length(Str) then raise Exception.Create('Incomplete data sent to server'); end; function TClientCommunicator.ReceiveDataFromServer : string; const MaxBytesRead = 1024; ShowProgressSize = 100000; var Buffer : string; ExpectedLengthStr : string; ExpectedLength : integer; ReceivedData : string; ReceivedString : string; BytesRead : integer; StartTime : TDateTime; ShowProgress : boolean; begin ExpectedLength := 0; ReceivedData := ''; StartTime := Now; ShowProgress := false; BytesRead := 0; try // keep reading data until we have it all while true do begin // initialise receive buffer SetLength(Buffer,MaxBytesRead); FillChar(Buffer[1],MaxBytesRead,0); // read data from socket if FWinSocketStream.WaitForData(ClientTimeout) then BytesRead := FWinSocketStream.Read(Buffer[1],MaxBytesRead) else raise Exception.Create('Timeout attempting to receive data from server'); // check for timeout if Now > IncMilliSecond(StartTime,ClientTimeout) then raise Exception.Create('Server failed to respond after a reasonable time'); // process data received if BytesRead > 0 then begin SetLength(Buffer,BytesRead); // add data received to received data string ReceivedData := ReceivedData + Buffer; // check to see if we have received the expected length yet if (ExpectedLength = 0) and (ReceivedData[1] = '{') and (Pos('}',ReceivedData) <> 0) then begin ExpectedLengthStr := Copy(ReceivedData,2,Pos('}', ReceivedData)-2); ExpectedLength := StrToIntDef(ExpectedLengthStr,0); Delete(ReceivedData,1,Pos('}', ReceivedData)); ShowProgress := (ExpectedLength >= ShowProgressSize) and Self.ShowProgress; if ShowProgress then begin ProgressForm.SetPosition(0); ProgressForm.SetCaption('Transferring data. Please wait...'); ProgressForm.Show; end; end; if ShowProgress then ProgressForm.SetPosition(Length(ReceivedData) * 100 div ExpectedLength); // check whether we have all the data we are expecting if (ExpectedLength > 0) and (Length(ReceivedData) >= ExpectedLength) then begin // only take the number of bytes expected and ignore the rest if ReceivedData <> '' then begin if GlobalDataLock <> nil then GlobalDataLock.Acquire; try ReceivedString := Encrypt(Copy(ReceivedData,1,ExpectedLength),ClientServerEncryptRandSeed); finally if GlobalDataLock <> nil then GlobalDataLock.Release; end; // check for the terminating character at the end of the string if ReceivedString[Length(ReceivedString)] <> TerminatingChar then raise Exception.Create('Incorrect data received from server'); // remove the terminating character Delete(ReceivedString,Length(ReceivedString),1); end else ReceivedString := ''; break; end; // just keep trying until we have it all StartTime := Now; end; end; finally if ShowProgress then ProgressForm.Hide; end; Result := ReceivedString; end; function TClientCommunicator.TransferData (Data : string) : string; begin // don't attempt transfer if not connected or connection was lost if not Connected then begin Result := ''; Exit; end; FClientSocketLock.Acquire; try SendDataToServer(Data); Result := ReceiveDataFromServer; finally FClientSocketLock.Release; end; end; procedure TClientCommunicator.SendDataToWorkstations (Data : string); begin // add string to list of data to send to other workstations FDataToSendLock.Acquire; try FDataToSend.Add(Data); finally FDataToSendLock.Release; end; // also send to this workstation AddToDataReceived(Data); end; function TClientCommunicator.ReceiveDataFromWorkstations : string; begin FDataReceivedLock.Acquire; try if FDataReceived.Count > 0 then begin Result := FDataReceived[0]; FDataReceived.Delete(0); end else Result := ''; finally FDataReceivedLock.Release; end; end; procedure TClientCommunicator.AddToDataReceived (Str : string); begin FDataReceivedLock.Acquire; try FDataReceived.Add(Str); finally FDataReceivedLock.Release; end; end; function TClientCommunicator.Connected : boolean; begin Result := FClientSocket.Active; end; function TClientCommunicator.CheckProgramVersion : boolean; var Str : string; begin // send request to server to return its program version Str := Format('%2d',[integer(crtProgramVersion)]); Str := TransferData(Str); if Str = ProgramVersion + ProgramName then Result := true else Result := false; end; function TClientCommunicator.Firebird : boolean; var Str : string; begin // send request to server to tell us if it is using // the Firebird database server Str := Format('%2d',[integer(crtFirebird)]); Str := TransferData(Str); Result := (Str = '1'); end; function TClientCommunicator.NotifyLoggedOnUser (UserName : string) : boolean; var Str : string; begin // notify server that user is logged on Str := Format('%2d',[integer(crtNotifyLoggedOnUser)]); Str := Str + UserName; Str := TransferData(Str); Result := (Str = '0'); end; function TClientCommunicator.NotifyInvalidLogin (UserName : string; Password : string) : boolean; var Str : string; StringStream : TStringStream; begin Str := Format('%2d',[integer(crtNotifyInvalidLogin)]); StringStream := TStringStream.Create(''); try WriteStrToStream(UserName,StringStream); WriteStrToStream(Password,StringStream); Str := Str + StringStream.DataString; finally StringStream.Free; end; Str := TransferData(Str); Result := (Str = '0'); end; procedure TClientCommunicator.CreateMessagingThread; begin // create the messaging thread and let it run FMessagingThread := TMessagingThread.Create(Self); end; procedure TClientCommunicator.TransferMessagingData; var Str : string; StringList : TStringList; i : integer; StringStream : TStringStream; Count : integer; begin // send any pending messaging data Str := Format('%2d',[integer(crtMessagingData)]); FDataToSendLock.Acquire; StringStream := TStringStream.Create(''); try Count := FDataToSend.Count; StringStream.Write(Count,SizeOf(Count)); for i := 0 to FDataToSend.Count - 1 do WriteStrToStream(FDataToSend[i],StringStream); Str := Str + StringStream.DataString; // attempt to send and receive try Str := TransferData(Str); FDataToSend.Clear; except // if there is an exeption then clear string Str := ''; end; finally StringStream.Free; FDataToSendLock.Release; end; // process any messaging data received from server if Str <> '' then begin StringList := TStringList.Create; StringStream := TStringStream.Create(Str); try try StringStream.Position := 0; StringStream.Read(Count,SizeOf(Count)); for i := 0 to Count - 1 do StringList.Add(ReadStrFromStream(StringStream)); except StringList.Clear; end; FDataReceivedLock.Acquire; try for i := 0 to StringList.Count - 1 do FDataReceived.Add(StringList[i]); finally FDataReceivedLock.Release; end; finally StringList.Free; StringStream.Free; end; end; end; function TClientCommunicator.CloseConnection : boolean; var Str : string; begin // send request to server telling it we are closing down Str := Format('%2d',[integer(crtCloseConnection)]); try Str := TransferData(Str); except Str := ''; end; Result := (Str = '0'); end; function TClientCommunicator.LoadGlobalConfiguration : TGlobalConfiguration; var Str : string; StringStream : TStringStream; begin // send request to server to return the global configuration Str := Format('%2d',[integer(crtLoadGlobalConfiguration)]); Str := TransferData(Str); StringStream := TStringStream.Create(Str); try StringStream.Position := 0; Result := TGlobalConfiguration.Create; try Result.LoadFromStream(StringStream); except Result.Free; Result := nil; end; finally StringStream.Free; end; end; function TClientCommunicator.SaveGlobalConfiguration (GlobalConfiguration : TGlobalConfiguration) : boolean; var Str : string; StringStream : TStringStream; begin // send request to server to update the global configuration Str := Format('%2d',[integer(crtSaveGlobalConfiguration)]); StringStream := TStringStream.Create(''); try GlobalConfiguration.SaveToStream(StringStream); Str := Str + StringStream.DataString; Str := TransferData(Str); Result := (Str = '0'); finally StringStream.Free; end; end; function TClientCommunicator.NoOfLoggedOnWorkstations : integer; var Str : string; begin // send request to server to return the number of logged on workstations Str := Format('%2d',[integer(crtNoOfLoggedOnWorkstations)]); Str := TransferData(Str); Result := StrToIntDef(Str,0); end; function TClientCommunicator.NoOfWorkstations : integer; var Str : string; begin // send request to server to return the total number of workstations Str := Format('%2d',[integer(crtNoOfWorkstations)]); Str := TransferData(Str); Result := StrToIntDef(Str,0); end; procedure TClientCommunicator.PopulateStringsWithDistinctValues (TableName : string; FieldName : string; Strings : TStrings; Add : boolean); var Str : string; StringStream : TStringStream; ReturnedStrings : TStringList; i : integer; begin Str := Format('%2d',[integer(crtPopulateStringsWithDistinctValues)]); StringStream := TStringStream.Create(''); try WriteStrToStream(TableName,StringStream); WriteStrToStream(FieldName,StringStream); Str := Str + StringStream.DataString; finally StringStream.Free; end; Str := TransferData(Str); StringStream := TStringStream.Create(Str); try ReturnedStrings := TStringList.Create; try ReturnedStrings.LoadFromStream(StringStream); if not Add then Strings.Clear; for i := 0 to ReturnedStrings.Count - 1 do begin if ReturnedStrings[i] <> '' then begin // if we are adding then check first to see if not already in list if (not Add) or (Strings.IndexOf(ReturnedStrings[i]) = -1) then Strings.Add(ReturnedStrings[i]); end; end; finally ReturnedStrings.Free; end; finally StringStream.Free; end; end; function TClientCommunicator.DatabaseStatusString : string; var Str : string; begin // send request to server to return the database status string Str := Format('%2d',[integer(crtDatabaseStatusString)]); try Str := TransferData(Str); except Str := 'Unknown'; end; Result := Str; end; function TClientCommunicator.AcquireDatabaseCriticalUpdate : boolean; var Str : string; begin // send request to server to acquire lock on complete database Str := Format('%2d',[integer(crtAcquireDatabaseCriticalUpdate)]); Str := TransferData(Str); Result := (Str = '0'); end; function TClientCommunicator.ReleaseDatabaseCriticalUpdate : boolean; var Str : string; begin // send request to server to release lock on complete database Str := Format('%2d',[integer(crtReleaseDatabaseCriticalUpdate)]); Str := TransferData(Str); Result := (Str = '0'); end; function TClientCommunicator.GenerateId (Increment : integer) : int64; var Str : string; begin // send request to server to generate a new id (or batch of) Str := Format('%2d',[integer(crtGenerateId)]); Str := Str + IntToStr(Increment); Str := TransferData(Str); Result := StrToInt64Def(Str,0); end; function TClientCommunicator.LoadDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; Id : int64) : TDatabaseObject; var Str : string; StringStream : TStringStream; begin // send request to server to return a database object Str := Format('%2d',[integer(crtLoadDatabaseObjectById)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + IntToStr(Id); Str := TransferData(Str); if Str = '-1' then Result := nil else begin StringStream := TStringStream.Create(Str); try StringStream.Position := 0; Result := DatabaseObjectClass.Create; try Result.LoadFromStream(StringStream); except Result.Free; Result := nil; end; finally StringStream.Free; end; end; end; function TClientCommunicator.LoadDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string) : TDatabaseObject; var Str : string; StringStream : TStringStream; begin // send request to server to return a database object Str := Format('%2d',[integer(crtLoadDatabaseObjectBySelectionString)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + SelectionString; Str := TransferData(Str); if Str = '-1' then Result := nil else begin StringStream := TStringStream.Create(Str); try StringStream.Position := 0; Result := DatabaseObjectClass.Create; try Result.LoadFromStream(StringStream); except Result.Free; Result := nil; end; finally StringStream.Free; end; end; end; function TClientCommunicator.SaveDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; DatabaseObject : TDatabaseObject) : boolean; var Str : string; StringStream : TStringStream; begin // send request to server to save an object to the database Str := Format('%2d',[integer(crtSaveDatabaseObject)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); StringStream := TStringStream.Create(''); try DatabaseObject.SaveToStream(StringStream); Str := Str + StringStream.DataString; Str := TransferData(Str); Result := (Str = '0'); finally StringStream.Free; end; end; function TClientCommunicator.DeleteDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; Id : int64) : boolean; var Str : string; begin // send request to server to delete an object from the database Str := Format('%2d',[integer(crtDeleteDatabaseObject)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + IntToStr(Id); Str := TransferData(Str); Result := (Str = '0'); end; function TClientCommunicator.FullSaveDatabaseObject (DatabaseObjectClass : TDatabaseObjectClass; DatabaseObject : TDatabaseObject; IncludeDetails : boolean; NewObject : boolean) : boolean; var Str : string; StringStream : TStringStream; begin // send request to server to save an object to the database Str := Format('%2d',[integer(crtFullSaveDatabaseObject)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + FormatBoolean(IncludeDetails); Str := Str + FormatBoolean(NewObject); StringStream := TStringStream.Create(''); try DatabaseObject.SaveToStream(StringStream); if IncludeDetails then DatabaseObject.SaveDetailsToStream(StringStream); Str := Str + StringStream.DataString; Str := TransferData(Str); Result := (Str = '0'); finally StringStream.Free; end; end; procedure TClientCommunicator.LoadAllDatabaseObjects (DatabaseObjectCollection : TDatabaseObjectCollection; DatabaseObjectClass : TDatabaseObjectClass); var Str : string; StringStream : TStringStream; begin // send request to server to return all database objects of given class // and add them to the collection Str := Format('%2d',[integer(crtLoadAllDatabaseObjects)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := TransferData(Str); StringStream := TStringStream.Create(Str); try StringStream.Position := 0; try DatabaseObjectCollection.LoadFromStream(StringStream); except DatabaseObjectCollection.Clear; end; finally StringStream.Free; end; end; procedure TClientCommunicator.LoadSomeDatabaseObjects (DatabaseObjectCollection : TDatabaseObjectCollection; DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string); var Str : string; StringStream : TStringStream; begin // send request to server to return the database objects of given class // matching the selection criteria and add them to the collection Str := Format('%2d',[integer(crtLoadSomeDatabaseObjects)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + SelectionString; Str := TransferData(Str); StringStream := TStringStream.Create(Str); try StringStream.Position := 0; try DatabaseObjectCollection.LoadFromStream(StringStream); except DatabaseObjectCollection.Clear; end; finally StringStream.Free; end; end; function TClientCommunicator.CountDatabaseObjects (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string) : integer; var Str : string; begin // send request to server to return the number of database objects of given class // matching the selection criteria Str := Format('%2d',[integer(crtCountDatabaseObjects)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + SelectionString; Str := TransferData(Str); Result := StrToIntDef(Str,0); end; function TClientCommunicator.SaveDatabaseObjects (DatabaseObjectCollection : TDatabaseObjectCollection; DatabaseObjectClass : TDatabaseObjectClass) : boolean; var Str : string; StringStream : TStringStream; begin // send request to server to save a collection of objects to the database Str := Format('%2d',[integer(crtSaveDatabaseObjects)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); StringStream := TStringStream.Create(''); try DatabaseObjectCollection.SaveToStream(StringStream); Str := Str + StringStream.DataString; Str := TransferData(Str); Result := (Str = '0'); finally StringStream.Free; end; end; function TClientCommunicator.DeleteDatabaseObjects (DatabaseObjectCollection : TDatabaseObjectCollection; DatabaseObjectClass : TDatabaseObjectClass) : boolean; var Str : string; StringStream : TStringStream; begin // send request to server to delete a collection of objects from the database Str := Format('%2d',[integer(crtDeleteDatabaseObjects)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); StringStream := TStringStream.Create(''); try DatabaseObjectCollection.SaveToStream(StringStream); Str := Str + StringStream.DataString; Str := TransferData(Str); Result := (Str = '0'); finally StringStream.Free; end; end; function TClientCommunicator.DeleteSomeDatabaseObjects (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string) : boolean; var Str : string; begin // send request to delete the database objects of given class // matching the selection criteria Str := Format('%2d',[integer(crtDeleteSomeDatabaseObjects)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + SelectionString; Str := TransferData(Str); Result := (Str = '0'); end; function TClientCommunicator.DeleteAndSaveSomeDatabaseObjects (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string; DatabaseObjectCollection : TDatabaseObjectCollection) : boolean; var Str : string; StringStream : TStringStream; begin // send request to delete the database objects of given class // matching the selection criteria Str := Format('%2d',[integer(crtDeleteAndSaveSomeDatabaseObjects)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); StringStream := TStringStream.Create(''); try WriteStrToStream(SelectionString,StringStream); DatabaseObjectCollection.SaveToStream(StringStream); Str := Str + StringStream.DataString; Str := TransferData(Str); Result := (Str = '0'); finally StringStream.Free; end; end; function TClientCommunicator.UpdateSomeDatabaseObjects (DatabaseObjectClass : TDatabaseObjectClass; UpdateString : string; SelectionString : string) : boolean; var Str : string; StringStream : TStringStream; begin // send request to update the database objects of given class // matching the selection criteria Str := Format('%2d',[integer(crtUpdateSomeDatabaseObjects)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); StringStream := TStringStream.Create(''); try WriteStrToStream(UpdateString,StringStream); WriteStrToStream(SelectionString,StringStream); Str := Str + StringStream.DataString; Str := TransferData(Str); Result := (Str = '0'); finally StringStream.Free; end; end; function TClientCommunicator.OpenRemoteDataset (DatabaseObjectClass : TDatabaseObjectClass; SelectionString : string) : integer; var Str : string; begin // send request to server to open a dataset on the server Str := Format('%2d',[integer(crtOpenRemoteDataset)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + SelectionString; Str := TransferData(Str); Result := StrToIntDef(Str,0); end; procedure TClientCommunicator.LoadDatabaseObjectsFromRemoteDataset (DatabaseObjectCollection : TDatabaseObjectCollection; StartRecNo : integer; EndRecNo : integer); var Str : string; StringStream : TStringStream; begin // send request to server to return the database objects // from the dataset previously opened with OpenRemoteDataset // in the specified record range Str := Format('%2d',[integer(crtLoadDatabaseObjectsFromRemoteDataset)]); StringStream := TStringStream.Create(''); try StringStream.Write(StartRecNo,SizeOf(StartRecNo)); StringStream.Write(EndRecNo,SizeOf(EndRecNo)); Str := Str + StringStream.DataString; finally StringStream.Free; end; Str := TransferData(Str); StringStream := TStringStream.Create(Str); try StringStream.Position := 0; try DatabaseObjectCollection.LoadFromStream(StringStream); except DatabaseObjectCollection.Clear; end; finally StringStream.Free; end; end; function TClientCommunicator.CloseRemoteDataset : boolean; var Str : string; begin // send request to server to close the dataset // previously opened with OpenRemoteDataset Str := Format('%2d',[integer(crtCloseRemoteDataset)]); Str := TransferData(Str); Result := (Str = '0'); end; function TClientCommunicator.LockDatabaseObject (Id : int64; var OtherUserName : string) : boolean; var Str : string; begin // send request to server to lock an object Str := Format('%2d',[integer(crtLockDatabaseObject)]); Str := Str + IntToStr(Id); Str := TransferData(Str); OtherUserName := Str; Result := (Str = ''); end; function TClientCommunicator.UnlockDatabaseObject (Id : int64) : boolean; var Str : string; begin // send request to server to unlock an object Str := Format('%2d',[integer(crtUnlockDatabaseObject)]); Str := Str + IntToStr(Id); Str := TransferData(Str); Result := (Str = '0'); end; function TClientCommunicator.GetAttachmentFileAge (AttachmentId : int64) : integer; var Str : string; begin // send request to server to return attachment file age Str := Format('%2d',[integer(crtGetAttachmentFileAge)]); Str := Str + IntToStr(AttachmentId); Str := TransferData(Str); Result := StrToIntDef(Str,0); end; function TClientCommunicator.GetAttachmentFileContents (AttachmentId : int64) : string; var Str : string; begin // send request to server to return attachment file contents Str := Format('%2d',[integer(crtGetAttachmentFileContents)]); Str := Str + IntToStr(AttachmentId); Str := TransferData(Str); Result := DecompressString(Str); end; function TClientCommunicator.SetServerThreadPriority (Priority : TThreadPriority) : boolean; var Str : string; begin // send request to server to adjust thread priority Str := Format('%2d',[integer(crtSetServerThreadPriority)]); Str := Str + IntToStr(integer(Priority)); Str := TransferData(Str); Result := (Str = '0'); end; end.