{*******************************************************} { } { Responsive Software http://www.responsive.co.nz } { } { Copyright (c) 2003-2006 Responsive Software Limited } { } {*******************************************************} unit CommunicationsManager; // this module contains routines used to manage data sent to and received from // other workstations via either the TCommunicator class // or the TClientCommunicator class interface uses DatabaseObjects; // send data to another workstation procedure SendData (Data : string; WorkstationId : int64); // receive any data from another workstation function ReceiveData : string; // update the cached accounts on all workstations procedure UpdateCombinedEntryOnLoggedOnWorkstations (CombinedEntry : TCombinedEntry); // delete a combined entry from the cached accounts on all workstations procedure DeleteCombinedEntryFromLoggedOnWorkstations (CombinedEntryId : int64); // update a database object on all workstations procedure UpdateDatabaseObjectOnLoggedOnWorkstations (DatabaseObjectClass : TDatabaseObjectClass; DatabaseObject : TDatabaseObject); // delete a database object from all workstations procedure DeleteDatabaseObjectFromLoggedOnWorkstations (DatabaseObjectClass : TDatabaseObjectClass; Id : int64); // update the global configuration on all workstations procedure UpdateGlobalConfigurationOnLoggedOnWorkstations; // send a test packet of data to all workstations procedure SendLargeDataToAllWorkstations; // called repeatedly by the idle procedure // to process any incoming data from other workstations procedure ProcessReceivedData; implementation uses SysUtils, Classes, Dialogs, Globals, Main, Accounts, Utilities; type // data type will be the first two characters in the string which allows // for up to 100 types (00 to 99) TCommunicationsDataType = (cdtUpdateCombinedEntry, cdtDeleteCombinedEntry, cdtUpdateDatabaseObject, cdtDeleteDatabaseObject, cdtUpdateGlobalConfiguration, cdtLargeData); {******************************************************************************} procedure SendData (Data : string; WorkstationId : int64); begin if Communicator <> nil then Communicator.SendData(Data,WorkstationId) else if ClientCommunicator <> nil then ClientCommunicator.SendDataToWorkstations(Data) else if ServerCommunicator <> nil then ServerCommunicator.SendDataToWorkstations(Data); end; function ReceiveData : string; begin if Communicator <> nil then Result := Communicator.ReceiveData else if ClientCommunicator <> nil then Result := ClientCommunicator.ReceiveDataFromWorkstations else Result := ''; end; {******************************************************************************} procedure UpdateCombinedEntryOnLoggedOnWorkstations (CombinedEntry : TCombinedEntry); var Str : string; StringStream : TStringStream; begin // send combined entry so that all workstations can update // their accounts cache without having to go to the database Str := Format('%2d',[integer(cdtUpdateCombinedEntry)]); StringStream := TStringStream.Create(''); try CombinedEntry.SaveToStream(StringStream); Str := Str + StringStream.DataString; SendData(Str,0); finally StringStream.Free; end; // ensure all data is processed immediately ProcessReceivedData; end; procedure ProcessUpdateCombinedEntry (Str : string); var StringStream : TStringStream; CombinedEntry : TCombinedEntry; begin StringStream := TStringStream.Create(Str); try StringStream.Position := 2; CombinedEntry := TCombinedEntry.Create; try CombinedEntry.LoadFromStream(StringStream); AccountsCache.DeleteCombinedEntry(CombinedEntry.Id); AccountsCache.AddEntries(CombinedEntry.Entries); finally CombinedEntry.Free; end; finally StringStream.Free; end; if MainForm <> nil then begin // update account display also in case one of the entries // on the displayed account has changed MainForm.AccountsFrame.UpdateDisplay; // update the graph MainForm.GraphFrame.UpdateDisplay; end; end; {******************************************************************************} procedure DeleteCombinedEntryFromLoggedOnWorkstations (CombinedEntryId : int64); var Str : string; begin // send combined entry id to all workstations // so that they can delete it from their accounts cache Str := Format('%2d',[integer(cdtDeleteCombinedEntry)]); Str := Str + IntToStr(CombinedEntryId); SendData(Str,0); // ensure all data is processed immediately ProcessReceivedData; end; procedure ProcessDeleteCombinedEntry (Str : string); var CombinedEntryId : int64; begin CombinedEntryId := StrToInt64Def(Copy(Str,3,Length(Str)),-1); if CombinedEntryId = -1 then raise Exception.Create( 'Invalid combined entry id received from other workstation.') else AccountsCache.DeleteCombinedEntry(CombinedEntryId); if MainForm <> nil then begin // update account display also in case one of the entries // on the displayed account has changed MainForm.AccountsFrame.UpdateDisplay; // update the graph MainForm.GraphFrame.UpdateDisplay; end; end; {******************************************************************************} procedure UpdateDatabaseObjectOnLoggedOnWorkstations (DatabaseObjectClass : TDatabaseObjectClass; DatabaseObject : TDatabaseObject); var Str : string; StringStream : TStringStream; begin // send database object so that all workstations can update // their global collections without having to go to the database Str := Format('%2d',[integer(cdtUpdateDatabaseObject)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); StringStream := TStringStream.Create(''); try DatabaseObject.SaveToStream(StringStream); Str := Str + StringStream.DataString; SendData(Str,0); finally StringStream.Free; end; // ensure all data is processed immediately ProcessReceivedData; end; procedure ProcessUpdateDatabaseObject (Str : string); var DatabaseObjectClass : TDatabaseObjectClass; StringStream : TStringStream; DatabaseObject : TDatabaseObject; begin DatabaseObjectClass := ConvertIntegerToDatabaseObjectClass(StrToIntDef(Copy(Str,3,3),-1)); StringStream := TStringStream.Create(Str); try StringStream.Position := 5; DatabaseObject := DatabaseObjectClass.Create; DatabaseObject.LoadFromStream(StringStream); finally StringStream.Free; end; try if DatabaseObjectCollections[ConvertDatabaseObjectClassToInteger(DatabaseObjectClass)] <> nil then DatabaseObjectCollections[ConvertDatabaseObjectClassToInteger(DatabaseObjectClass)].Update(DatabaseObject); DatabaseObject.ProcessUpdate; finally DatabaseObject.Free; end; end; {******************************************************************************} procedure DeleteDatabaseObjectFromLoggedOnWorkstations (DatabaseObjectClass : TDatabaseObjectClass; Id : int64); var Str : string; begin // send database object class and id to all workstations // so that they can delete it from their global collections Str := Format('%2d',[integer(cdtDeleteDatabaseObject)]); Str := Str + Format('%3d',[integer(ConvertDatabaseObjectClassToInteger(DatabaseObjectClass))]); Str := Str + IntToStr(Id); SendData(Str,0); // ensure all data is processed immediately ProcessReceivedData; end; procedure ProcessDeleteDatabaseObject (Str : string); var DatabaseObjectClass : TDatabaseObjectClass; Id : int64; begin DatabaseObjectClass := ConvertIntegerToDatabaseObjectClass(StrToIntDef(Copy(Str,3,3),-1)); Id := StrToInt64Def(Copy(Str,6,Length(Str)),-1); if Id = -1 then raise Exception.Create( 'Invalid database object id received from other workstation.') else begin if DatabaseObjectCollections[ConvertDatabaseObjectClassToInteger(DatabaseObjectClass)] <> nil then DatabaseObjectCollections[ConvertDatabaseObjectClassToInteger(DatabaseObjectClass)].DeleteById(Id); DatabaseObjectClass.ProcessDelete(Id); end; end; {******************************************************************************} procedure UpdateGlobalConfigurationOnLoggedOnWorkstations; var Str : string; StringStream : TStringStream; begin // send global configuration so that all workstations can update // their global configuration without having to go to the database Str := Format('%2d',[integer(cdtUpdateGlobalConfiguration)]); StringStream := TStringStream.Create(''); try GlobalConfiguration.SaveToStream(StringStream); Str := Str + StringStream.DataString; SendData(Str,0); finally StringStream.Free; end; // ensure all data is processed immediately ProcessReceivedData; end; procedure ProcessUpdateGlobalConfiguration (Str : string); var StringStream : TStringStream; begin StringStream := TStringStream.Create(Str); try StringStream.Position := 2; GlobalConfiguration.LoadFromStream(StringStream); finally StringStream.Free; end; end; {******************************************************************************} procedure SendLargeDataToAllWorkstations; var i : integer; CombinedEntry : TCombinedEntry; Entry : TEntry; Str : string; StringStream : TStringStream; begin CombinedEntry := TCombinedEntry.Create; CombinedEntry.Id := 0; CombinedEntry.Date := Now; for i := 1 to 2000 do begin Entry := TEntry.Create; Entry.Description := 'Entry No ' + IntToStr(i); CombinedEntry.Entries.Add(Entry); end; // send large combined entry to all workstations Str := Format('%2d',[integer(cdtLargeData)]); StringStream := TStringStream.Create(''); CombinedEntry.SaveToStream(StringStream); Str := Str + StringStream.DataString; SendData(Str,0); StringStream.Free; CombinedEntry.Free; // ensure all data is processed immediately ProcessReceivedData; end; procedure ProcessLargeData (Str : string); var StringStream : TStringStream; CombinedEntry : TCombinedEntry; begin StringStream := TStringStream.Create(Str); StringStream.Position := 2; CombinedEntry := TCombinedEntry.Create; CombinedEntry.LoadFromStream(StringStream); // check that everything is okay and display message if CombinedEntry.Entries.Count = 2000 then ShowMessage('Large Data Ok'); CombinedEntry.Free; StringStream.Free; end; {******************************************************************************} procedure ProcessReceivedData; var Str : string; DataType : TCommunicationsDataType; begin // this procedure is called by the idle procedure and will process // all queued data received from other workstations Str := ReceiveData; while Str <> '' do begin // length of string should be at least 2 characters which tell us what // type of data is being received if Length(Str) >= 2 then begin // first determine what type of data has been received DataType := TCommunicationsDataType(StrToIntDef(Copy(Str,1,2),-1)); if DataType = cdtUpdateCombinedEntry then ProcessUpdateCombinedEntry(Str) else if DataType = cdtDeleteCombinedEntry then ProcessDeleteCombinedEntry(Str) else if DataType = cdtUpdateDatabaseObject then ProcessUpdateDatabaseObject(Str) else if DataType = cdtDeleteDatabaseObject then ProcessDeleteDatabaseObject(Str) else if DataType = cdtUpdateGlobalConfiguration then ProcessUpdateGlobalConfiguration(Str) else if DataType = cdtLargeData then ProcessLargeData(Str); // just ignore anything we don't recognise end; Str := ReceiveData; end; end; end.