{**************************************************************************
 ***                                                                    ***
 ***                 KAMELEON VERSION/DATA UPDATES                      ***
 ***See updater.pas for program                                         ***
 **************************************************************************}
{Routines for updating data}
{$I compflgs}
unit update;

{$IFNDEF fixit} fixit MUST be set for reconstructall to properly hook things! {$ENDIF}

interface

uses files, jimmys;

type
	ConvertJimmy = procedure(var Jimmy : PJimmy; var RecSize : word);

procedure NulJimmyConverter(var Jimmy : PJimmy; var RecSize : word);

procedure FullJimmyUpdate(RemoveDeleted : boolean; Converter : COnvertJImmy; Recon : boolean);

procedure RePutJimmys; {simple run thru, getting/putting all jimmys}
procedure ReStoreSelfJimmys; {as above, but does storeself for each}

procedure SetNewIDPtr(var Ptr : longint);
function GetNewIDPtr(const OldID : longint) : longint;

var
	{so that upfrom42, etc can access newjimmystream}
	NewJImmyStream, OldJimmyStream : PDataStream;

implementation

uses
	tuimsgs,
	global,
	dosutils,

	kdirctry,
	alljimmy,
	kperson, kcompany, address, kletter, kqnote, kmnote, kcompsup, kjob,
	kforsale, kestimat, kinvoice, ordproc,
	kmemship,
	kdiary,

	jimhooks, indexes, jimindxs, indxutil,
	idindex,
	tasks,
	payments,
	fixit,

	objects,
	dbg,
	help,

	kamsetup,
	minilib;

{*********************************************************************
 ***                   get/put jiMMY UPDATE                        ***
 *********************************************************************}
procedure RePutJimmys;
begin
	ForAllJimmys('UPDATE','Re putting all jimmys',DoJimmyPut,OnNilDoStd);{}
end;

procedure ReStoreSelfJimmys;
begin
	ForAllJimmys('UPDATE','Re-indexing/hooking - complete re-storeself',DoJimmyStoreSelf,OnNilDoStd);{}
end;




{*********************************************************************
 ***                                                               ***
 ***                      FULL JIMMY UPDATE                        ***
 ***                                                               ***
 *********************************************************************}
{This method re-creates the jimmy file, by creating a new one, moving
each jimmy to there, then running through and resetting all jimmy ID
pointers, both from other jimmys and from indexes and hooks}

{Carried out in stages:
	The first reads all jimmys from the original jimmy file and writes into
	a new one.  While doing this it does any conversions required and, and
	places a re-pointer from the old position to the new, by replacing the
	first four bytes of the jimmy with the new pos

	Second stage changes all the jimmy pointers
		Pass 1: changes all jimmy pointers to the new ones

		Pass 2:  update indexes, chains, etc, either by doing a
							ReconstructAll (See fixit.pas), or by resetting individual pointers

	Third stage:
		Tidy up then renames new and old files}


{*********************************************
 ***    STAGE 1 - CREATE NEW JIMMY STREAM  ***
 *********************************************}
var
	PublicConverter : ConvertJimmy;

{--- Standard Nul Converter -------------------}
{Does nothing}
procedure NulJimmyConverter(var Jimmy : PJimmy; var RecSize : word);
begin end;

{--- A nil has been read -----------}
procedure OnNilDoClear(srType : longint; var RecNo : longint); far;
var ORecNo,ID : longint;
		B : byte;
begin
	ORecNo := RecNo;
	OnNilDoStd(srType, recNo); {check srtype & increase recno - see fixit}
	JimmyStream^.SeekRec(OREcNo);
	if RecNo=ORecNo+1 then begin {object type unknown}
		B := $FF;
		JimmyStream^.Write(B, 1); {makes negative}
{don't do this as it means it never finds the next jimmy
							if RecNo=ORecNO+1 then RecNo := RecNo +3; {move past -1 if not already done so}
	end else begin
		ID := -1; {object type known - mark as -1}
		JimmyStream^.Write(ID, 4);
	end;
end;

{Moves from old file to new, doing converter}
procedure DoJimmyStage1(var Jimmy : PJimmy; var RecSize : word); far;
var	NewID,OldID : longint;
begin
	{check recsize before conversion}
	if RecSize = 0 then begin
		ProgramWarning('Jimmy '+Jimmy^.DisplayLine(-1,0,40,1)+#13+'No record size! - Will be overwritten!',hcNoContext);
		RecSize := 3;
	end;

	OldID := Jimmy^.RecNo;

	if @PublicConverter<>nil then
		{if converter exists, call even if deleted - eg change of recsize}
		PublicConverter(Jimmy, Recsize)
	else
		if Jimmy^.Deleted then begin
			dispose(Jimmy, done);
			Jimmy := nil;
		end; {standard action - remove}

	if Jimmy<>nil then begin
		{Place at end of new file}
		NewID := NewJimmyStream^.NoRecs;
		NewJimmyStream^.PutAt(NewID, Jimmy);
	end else begin
		NewID := -1;
	end;

	{write repointer}
	JimmyStream^.SeekRec(OldID);
	JimmyStream^.Write(NewID, 4);

{	Debug.Writeln('->'+N2Str(NewID));{}
end;


procedure DoStage1ConvertJimmys(Converter : ConvertJimmy);
var Control : word;

begin
	PublicConverter := Converter;

	OldJimmyStream := nil;
	New(NewJimmyStream, init(DataPath+'JIMMYS.NEW',1, StreamBufSize));
	NewJimmyStream^.Seek(0);
	NewJimmyStream^.Truncate;

	{----- STAGE ONE ------}
	{Creating new jimmy file of new objects}
	Debug.Writeln(''); Debug.Writeln('Converting Jimmys');

	Control := ForAllJimmys('FULL UPDATE - Stage 1','Copying jimmys to new file',DoJimmyStage1,OnNilDoClear);

	if Control=cmCancel then begin
		ProgramWarning('You have abandoned the update'#13'Data corrupted - restore from backup',hcNoContext);
		halt(0);
	end;{}

	dispose(NewJimmyStream, done);{}
	NewJimmyStream := nil;

	{-- COPY/RENAME JIMMY FILES ----}
	FileAdmin(fiJimmys)^.Close;

	THinkingOn('Copying jimmy data to old file');
	CopyFile(DataPath+'JIMMYS.DAT', DataPath+'JIMMYS.OLD');
	CopyFile(DataPath+'JIMMYS.NEW', DataPath+'JIMMYS.DAT');
	DeleteFile(DataPath+'JIMMYS.NEW');
	ThinkingOff;

	if FileAdmin(fiJimmys)^.Count>0 then FileAdmin(fiJimmys)^.Open;
end;


{*********************************************
 ***    STAGE 2 - CONVERT ID POINTERS      ***
 *********************************************}


{=========== UTILITYS ========================}

{--- Sets Ptr from old ID position to new -----}
procedure SetNewIDPtr(var Ptr : longint);
begin
	if Ptr<>-1 then begin
		OldJimmyStream^.SeekRec(Ptr);
		OldJimmyStream^.Read(Ptr, 4);

		if OldJimmyStream^.Status<>stOK then begin
			Debug.Writeln('SetNewIDPtr('+N2Str(Ptr)+')  -> Stream Error'+N2Str(OldJimmyStream^.Status));
			OldJImmyStream^.Reset;
		end;

	end;
end;

function GetNewIDPtr(const OldID : longint) : longint;
var ID : longint;
begin
	if OldJimmyStream=nil then begin
		{still in the converting stage - oldjimmystream not created yet}
		JimmyStream^.SeekRec(oldID);
		JimmyStream^.Read(ID,4);

		if JimmyStream^.Status<>stOK then begin
			Debug.Writeln('GetNewIDPtr('+N2Str(OldID)+')  -> JimmyStream Error'+N2Str(JimmyStream^.Status));
			JImmyStream^.Reset;
			ID := -1;
		end;

		GetNewIDPtr := ID;
	end else
		if OldID<>-1 then begin
			OldJimmyStream^.SeekRec(OldID);
			OldJimmyStream^.Read(ID, 4);

			if OldJimmyStream^.Status<>stOK then begin
				Debug.Writeln('GetNewIDPtr('+N2Str(OldID)+')  -> OldJimmyStream Error'+N2Str(OldJimmyStream^.Status));
				OldJImmyStream^.Reset;
				ID := -1;
			end;

			GetNewIDPtr := ID;
		end else
			GetNewIDPtr := -1;
end;


{============ PASS 1 - JIMMYS ===================}

{--- Reset Jimmy-Jimmy ptrs --------}
procedure ResetJimmyPtrs(var Jimmy : PJimmy; var RecSize : word); far;
var PL : PLongint;
		jiType : byte;
begin
{RecordError('FIXIT','Jimmy '+N2Str(Jimmy^.RecNo)+' srtype='+N2Str(Jimmy^.srtype)+' RecSize='+N2Str(Jimmy^.RecSize),'');{}

	if Jimmy^.NumIDs = 0 then
		ProgramWarning(' Jimmy '+N2Str(Jimmy^.RecNo)+' sr'+N2Str(Jimmy^.srType)+' no ID ptrs?!',hcNoContext)
	else
		for jiType := 1 to Jimmy^.NumIDs do begin
			PL := Jimmy^.GetJimmyID(jiType);
			SetNewIDPtr(PL^);
		end;

	PutJimmy(Jimmy);
end;

procedure ResetAndClear(var Jimmy : PJimmy; var RecSize : word); far;
begin
	{Clear idx & hook pointers too}
	Jimmy^.ClearAllPtrs;

	if Jimmy^.srType=srInvoice then begin
		{clear moneys in preparation for rehooking items}
		if not PInvoice(Jimmy)^.Quick then
			PInvoice(Jimmy)^.TotallerGroup.Init;
		PInvoice(Jimmy)^.PaidTotal.Clear;
		POrder(Jimmy)^.CalculateTotals;
	end;

	ResetJimmyPtrs(Jimmy, RecSize);
end;

procedure DoStage2Pass1Jimmys(const Recon : boolean);
var Control : word;
begin
	Debug.Writeln(''); Debug.Writeln('Setting Jimmy-Jimmy Ptrs');
	if Recon then
		Control := ForAllJimmys('FULL UPDATE - Stage 2',
														'Setting Jimmy-to-jimmy ptrs'#13#10
														+'& Clearing idx & hook ptrs',
														ResetAndClear, OnNilDoStd)
	else
		Control := ForAllJimmys('FULL UPDATE - Stage 2',
														'Setting Jimmy-to-jimmy ptrs',
														ResetJimmyPtrs, OnNilDoStd);

	if Control=cmCancel then begin
		ProgramWarning('You have abandoned the update'#13'Data corrupted - restore from backup',hcNoContext);
		halt(0);
	end;{}

	if Control=cmSkip then
		ProgramWarning('You have skipped this step'#13'Data corrupted!',hcNoContext);
end;


{=============== PASS 2 - HOOKS ====================}
procedure DoStage2Pass2Hooks;
var
	ProgressBox : PProgressBox;
	Hook : PHook;
	HookRec : longint;

begin
	{Run through hooks file updating hooks}
	Debug.Writeln(''); Debug.Writeln('Converting Hooks');
	ProgressBox := NewProgressBox('FULL UPDATE - Stage 3 - Hooks','',mfCancelButton or mfSkipButton,hcNoContext);

	ProgressBox^.ResetTime;
	FileAdmin(fiHooks)^.LogOn;
	FileAdmin(fiJimmys)^.LogOn;

	for HookRec := 0 to HookFile^.NoRecs - 1 do begin

		if HookRec mod 10 = 0 then ProgressBox^.Update('Updating Hook Ptrs',HookRec, HookFile^.NoRecs);

		Hook := PHook(HookFile^.GetAt(Hookrec));

		with Hook^ do begin
			SetNewIDPtr(JimmyID);
			SetNewIDPtr(OwnerID);

			{set srtype and sort key}
			JimmyStream^.Seek(JimmyID);
			JimmyStream^.REad(Hook^.srType, 2); {read (maybe) new srtype)}

			if JimmyStream^.Status<>stOK then begin
				Debug.Writeln('Converting srtype of hook '+N2Str(HookRec)
												+', JimmyID='+N2Str(JimmyID)+'->'+N2Str(JimmyStream^.Status));
				JimmyStream^.Reset;
				JimmyID := -1;
			end;

			if JimmyID = -1 then begin
				{remove from chain}
			end;

		end;

		HookFile^.PutAt(HookRec, Hook);

		dispose(Hook, done);
		if ProgressBox^.Command <> cmOK then break;
	end;

	FileAdmin(fiHooks)^.LogOff;
	FileAdmin(fiJimmys)^.LogOff;

	if ProgressBox^.Command = cmCancel then begin
		ProgramWarning('You have abandoned the update'#13#10'Data corrupted - restore from backup',hcNoContext);
		halt(0);
	end;

	if ProgressBox^.Command = cmSkip then begin
		ProgramWarning('You have skipped part of the update'#13#10'Data corrupted!',hcNoContext);
	end;

	dispose(ProgressBox, done);
end;


{======== PASS 3 - INDEXES ================}
procedure DoStage2Pass3Indexes;
var
	ProgressBox : PProgressBox;
	IdxRec : longint;
	IndexItem : PIndexItem;
	fiType : word;

begin
	Debug.Writeln(''); Debug.Writeln('Converting Indexes');
	ProgressBox := NewProgressBox('FULL UPDATE - Stage 3 - Indexes','',mfCancelButton or mfSkipButton,hcNoContext);
	for fiType := 1 to 99 do
		if FileAdmin(fiType)<>nil then begin  {if there is an admin created}
			FileAdmin(fiType)^.Logon;
			if Stream(fiType)<>nil then {if the file exists}
				if Right(PDataStream(Stream(fiType))^.FileName,4)='.IDX' then {if it is an index file}

					{run through index changing pointers}
					for IdxRec := 0 to Stream(fiType)^.NoRecs -1 do begin

						if IdxRec mod 10 = 0 then
							ProgressBox^.Update('Updating Index Ptrs file '+N2Str(fiType),IdxRec, Stream(fiType)^.NoRecs);

						IndexItem := PIndexItem(Stream(fiType)^.GetAt(IdxRec));
						SetNewIDPtr(IndexItem^.Idx2Dat);
						if IndexItem^.Idx2Dat = -1 then IndexItem^.Hole := True;
						IndexItem^.KeyString := ucase(IndexItem^.KeyString); {stromsholm...}
						IndexStream(fiType)^.PutAt(IdxRec, IndexItem);

						dispose(IndexItem, done);

						if ProgressBox^.Command<>cmOK then break;
					end;

			FileAdmin(fiType)^.LogOff;
		end;

	if ProgressBox^.Command = cmCancel then begin
		ProgramWarning('You have abandoned the update'#13#10'Directory sub-indexes not completely created',hcNoContext);
		halt(0);
	end;

	dispose(ProgressBox, done);
end;

procedure DoStage2Pass4IDIndexes;
var
	ProgressBox,ProBox2 : PProgressBox;
	RefID, JimmyID : longint;
	IDIndex : PIDIndexFile;
	srType : word;

begin
	{run through id index files}
	Debug.Writeln(''); Debug.Writeln('Converting ID Refs');
	ProgressBox := NewProgressBox('FULL UPDATE - Stage 3 - ID Refs','',mfCancelButton or mfSkipButton,hcNoContext);
	for srtype := 1000 to 2000 do if ProgressBox^.COmmand=cmOK then begin
		if srType mod 10 = 0 then ProgressBox^.Update('ID Ref Ptrs ('+N2Str(srType)+')',0,0);
		if FileExists(DataPath+'ID'+N2Str(srType)+'.IDX') then{} begin
			New(IDIndex, init(srType, 0));
			ProBox2 := NewProgressBox('ID Index '+N2Str(srType),'',mfContinueButton, hcNoContext);
			ProBox2^.MoveTo(ProBox2^.Origin.X, ProBox2^.Origin.Y+5);
			for RefID := 1 to IDIndex^.GetNewID-1 do begin
				if (RefID mod 10) =0 then ProBox2^.Update('',RefID,IDIndex^.GetNewID-1);
				JimmyID := IDIndex^.GetIDPtr(RefID);
				SetNewIDPtr(JimmyID);
				IDIndex^.SetIDPtr(RefID, JimmyID);
			end;
			dispose(IDIndex, done);
			dispose(ProBox2, done);
		end;
	end;

	if ProgressBox^.Command = cmCancel then
		ProgramWarning('You have abandoned the update'#13#10'Database corrupted - ID Ptrs not properly reset',hcNoContext);

	dispose(ProgressBox, done);
end;

{**************************************
 **         CONTROL LOOP             **
 **************************************}

procedure FullJimmyUpdate(RemoveDeleted : boolean; Converter : COnvertJImmy; Recon : boolean);
var
	Control : word;

begin

	DoStage1ConvertJimmys(Converter);

	NewJimmyStream := nil;
	New(OldJimmyStream, init(DataPath+'JIMMYS.OLD',1, StreamBufSize));

	DoStage2Pass1Jimmys(Recon);{}


	if Recon then begin
		{---- STAGE THREE -----}
		Debug.Writeln(''); Debug.Writeln('Reconstructing');
		DeleteAdminFiles;
		Control := ForAllJimmys('RE-CONSTRUCT','Re-hooking & indexing',DoJimmyStoreSelf,OnNilDoStd);
		if Control<>cmOK then
			ProgramWarning('You have abandoned the reconstruction'#13'Data corrupted!',hcNoContext);
		exit;
	end;

	{otherwise...}
	DoStage2Pass2Hooks;
	DoStage2Pass3Indexes;
	DoStage2Pass4IDIndexes;

	dispose(OldJimmyStream, done);

	{----- STAGE III - Do StoreSelf -------------}
{	Control := ForAllJimmys('UPDATE III','Re-indexing/hooking - storeself',DoJimmyStoreSelf,OnNilDoClear);{}

end;


end.
