messagingfw/scheduledsendmtm/schedulesendmtm/src/MsvScheduleSend.cpp
changeset 22 bde600d88860
parent 0 8e480a14352b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/messagingfw/scheduledsendmtm/schedulesendmtm/src/MsvScheduleSend.cpp	Fri Jun 04 10:32:16 2010 +0100
@@ -0,0 +1,1661 @@
+// Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+//
+
+#ifdef _DEBUG
+#undef _MSG_NO_LOGGING
+#endif
+
+#include <msvschedulesend.h>
+
+#include <msventry.h>
+#include <msvids.h>
+#include <bautils.h>
+#include <e32property.h>
+#include <centralrepository.h>
+
+#include <msvschedulesettings.h>
+#include <msvoffpeaktime.h>
+#include <msvsysagentaction.h>
+#include <schsend_panic.h>
+#include <tmsvschedulesettingsutils.h>
+#include <schinfointernal.h>
+
+#ifndef _MSG_NO_LOGGING
+#include <flogger.h>
+#endif
+
+
+_LIT(KSchSendExe, "schsendexe.exe");
+
+LOCAL_C TBool operator==(const CArrayFixFlat<TTaskSchedulerCondition>& aConditions1, const CArrayFixFlat<TTaskSchedulerCondition>& aConditions2)
+	{
+	TInt count1 = aConditions1.Count();
+	TInt count2 = aConditions2.Count();
+	
+	// Compare the conditions.	
+	if( count1 != count2 )
+		{
+		// Different number of conditions - no match.
+		return EFalse;
+		}
+		
+	// Search for all the conditions in info1 are in info2
+	for( TInt ii = 0; ii < count1; ++ii )
+		{
+		TTaskSchedulerCondition condition1 = aConditions1[ii];
+		TTaskSchedulerCondition condition2;
+		TBool found = EFalse;
+		TInt jj = 0;
+		while( !found && jj < count2 )
+			{
+			condition2 = aConditions2[jj];
+			
+			if( condition2.iKey == condition1.iKey )
+				found = ETrue;
+			else
+				++jj;
+			}
+		if( !found || condition2.iState != condition1.iState || condition2.iType != condition1.iType )
+			{
+			// Either aConditions2 has not got this condition or it has but it 
+			// does not match - no match.
+			return EFalse;
+			}
+		}
+	return ETrue;
+	}
+
+/**
+Constructor.
+
+@param aServerEntry 
+The CMsvServerEntry of the server MTM of which this CMsvScheduleSend 
+object is a member.
+*/
+EXPORT_C CMsvScheduleSend::CMsvScheduleSend(CMsvServerEntry& aServerEntry)
+	: iServerEntry(aServerEntry)
+	{
+	}
+
+/**
+Second phase constructor.
+
+This constructor creates instances of the following classes: 
+CMsvScheduleSettings, CMsvOffPeakTimes, CMsvSendErrorActions,
+CMsvSysAgentActions and CMsvScheduledEntries.
+*/
+EXPORT_C void CMsvScheduleSend::ConstructL()
+	{
+	iSettings			= CMsvScheduleSettings::NewL();
+	iOffPeakTimes		= new (ELeave) CMsvOffPeakTimes();
+	iErrorActions		= CMsvSendErrorActions::NewL();
+	iAgentActions		= new (ELeave) CMsvSysAgentActions();
+	iSchEntries			= new (ELeave) CMsvScheduledEntries(KMsvSchsendArrayGrowth);
+
+	iSchEntryInfo		= new (ELeave) CArrayFixFlat<TScheduleEntryInfo2>(KMsvSchsendArrayGrowth);
+	iSchTaskInfo		= new (ELeave) CArrayFixFlat<TTaskInfo>(KMsvSchsendArrayGrowth);
+	}
+
+/**
+Destructor.
+*/
+EXPORT_C CMsvScheduleSend::~CMsvScheduleSend()
+	{
+	delete iSettings;
+	delete iOffPeakTimes;
+	delete iErrorActions;
+	delete iAgentActions;
+
+	if (iScheduler.Handle())
+		iScheduler.Close();
+
+	if (iSchEntries)
+		iSchEntries->ResetAndDestroy();
+
+	delete iSchEntries;
+
+	delete iSchTaskInfo;
+	delete iSchEntryInfo;
+	}
+
+/**
+Schedules messages on the task scheduler.
+
+Messages that are successfully scheduled have their sending state set to
+KMsvSendStateScheduled.
+
+@param aSelection 
+Array of message IDs that need to be scheduled. This array cannot be empty.
+All the messages identified in the selection must belong to the same MTM;
+be scheduled for the same time; have the same setting for their OffPeak() 
+flag; have the scheduled time stored in the iDate member of their 
+corresponding TMsvEntry. 
+
+@param aPackage 
+Scheduling options
+
+@leave Any error code but KErrLocked and KErrNotFound
+The method overloading CMsvScheduledEntry::GetMessageL() left with an error,
+i.e. the scheduling info of one of the messages from the selection could not
+be retrieved from the message server.
+
+@leave Any error code
+Unable to reset the previous scheduling info for a message.
+
+@panic ScheduleSend-DLL 0
+The array of message IDs is empty.
+Debug build only.
+
+@panic ScheduleSend-DLL 1
+At least one of the selected messages is scheduled for a different time
+as the others.
+Debug build only.
+
+@panic ScheduleSend-DLL 2
+At least one of the selected messages does not belong to the same MTM.
+Debug build only.
+
+@panic ScheduleSend-DLL 3
+At least one of the selected messages does not have the same off-peak 
+settings as the others.
+Debug build only.
+*/
+EXPORT_C void CMsvScheduleSend::ScheduleL(const CMsvEntrySelection& aSelection, const TMsvSchedulePackage& aPackage)
+	{
+	__ASSERT_DEBUG(aSelection.Count() > 0, gPanic(EMessageSelectionEmpty));
+
+	iPackage = aPackage;
+	GetMessagesL(aSelection);	//Leaves with KErrNotFound if there
+																	//are no messages returned in schEntries
+	TInt entryCount = iSchEntries->Count();
+	SCHSENDLOG(FLog(_L8("Asked to schedule %d msgs"), entryCount));
+	
+	if (entryCount)
+		{
+		TTime startTime;
+
+#if defined(_DEBUG)
+		CMsvScheduledEntry* firstEntry = (*iSchEntries)[0];
+#endif
+
+		while (entryCount--)
+			{
+			CMsvScheduledEntry* message = iSchEntries->At(entryCount);
+			__ASSERT_DEBUG(firstEntry->ScheduleDate() == message->ScheduleDate(), gPanic(EMessagesNotSameTime));
+
+			startTime = message->ScheduleDate();
+
+			// Reset previous scheduling info
+			DeleteScheduleForEntryL(*message);
+			ResetScheduleInfoForEntryL(*message, EFalse);
+			}
+
+		//Schedule the messages
+		DoScheduleL(*iSchEntries, KMsvSendStateScheduled, startTime, EFalse);
+		}
+	}
+
+/** 
+Determines when the messages should be re-scheduled on the task scheduler, 
+then schedules the messages at the new time(s).
+
+Messages that are successfully re-scheduled are updated. The pending conditions
+flag indicates whether the message has been schedule for a set of conditions 
+being met (or a timeout occuring) or scheduled for a specified time/date.
+
+NOTE - conditions scheduling is only supoprted from 8.1 onwards.
+
+In the case of time-scheduling, the date field is the scheduled time/date. In 
+the case of conditions-scheduling, the date field reflects the timeout value.
+
+There are several cases when messages are not re-scheduled. If all recipients 
+have been sent to - in this case the message's sending state set to 
+KMsvSendStateSent. If, more commonly, the message's maximum number of re-tries
+has been exceeded or the error action was ESendActionFail then the message is 
+not changed.
+
+@param aSelection 
+Array of message IDs that need to be re-scheduled. This array cannot be empty. 
+All the messages identified must belong to the same MTM. It is not a 
+precondition that each message has already been scheduled on the task 
+scheduler.
+
+@param aPackage
+Scheduling options.
+
+@param aErrorAction
+The specific action to take with the messages. If this argument is omitted, 
+then ReScheduleL() uses the iError member of each TMsvEntry to find the 
+related TMsvSendErrorAction in iErrorActions.
+
+@panic ScheduleSend-DLL 0
+The array of message IDs is empty.
+Debug build only.
+*/
+EXPORT_C void CMsvScheduleSend::ReScheduleL(const CMsvEntrySelection& aSelection, const TMsvSchedulePackage& aPackage, const TMsvSendErrorAction* aErrorAction)
+	{
+	__ASSERT_DEBUG(aSelection.Count() > 0, gPanic(EMessageSelectionEmpty));
+	iPackage = aPackage;
+	GetMessagesL(aSelection);
+
+	TInt entryCount = iSchEntries->Count();
+	SCHSENDLOG(FLog(_L8("Asked to re-schedule %d msgs"), entryCount));
+
+	if (entryCount)
+		{
+		TTime curTime;
+		curTime.UniversalTime();
+		curTime += iSettings->Latency();
+
+		while (entryCount--)
+			{
+			CMsvScheduledEntry* message = iSchEntries->At(entryCount);
+	
+			SCHSENDLOG(FLog(_L8("\tAttempting to Re-Schedule msg %d"), message->Id()));
+
+			if (!SetMessageStartTime(*message, curTime, aErrorAction))
+				{
+				SCHSENDLOG(FLog(_L8("\t\tCannot Re-Schedule msg %d (new sending state %d)"), message->Id(), message->SendingState()));
+				DeleteScheduleForEntryL(*message);
+				ResetScheduleInfoForEntryL(*message, ETrue);
+				delete message;
+				iSchEntries->Delete(entryCount);
+				}
+			}
+
+		//Send the messages if there are any left to send
+		if (iSchEntries->Count())
+			{
+			DoReScheduleL(*iSchEntries);
+			}
+		else
+			{
+			SCHSENDLOG(FLog(_L8("\tNo messages to Re-Schedule")));
+			}
+		}
+	}
+
+/**
+Delete the schedules for the specified messages from the task scheduler. 
+
+The messages themselves are not deleted.
+
+@param aSelection 
+Array of message IDs that need to be deleted from the task scheduler.
+
+@leave Any error code
+Unable to connect and register with the scheduler.
+
+@leave Any error code but KErrLocked and KErrNotFound
+The method overloading CMsvScheduledEntry::GetMessageL() left with an error,
+i.e. the scheduling info of one of the messages from the selection could not
+be retrieved from the message server.
+
+@leave Any error code
+Unable to reset the previous scheduling info for a message.
+*/
+EXPORT_C void CMsvScheduleSend::DeleteScheduleL(const CMsvEntrySelection& aSelection)
+	{
+	ConnectAndRegisterL();
+
+	GetMessagesL(aSelection);
+
+	TInt entryCount = iSchEntries->Count();
+	SCHSENDLOG(FLog(_L8("Asked to delete schedule of %d msgs"), entryCount));
+
+	while (entryCount--)
+		{
+		CMsvScheduledEntry* message = iSchEntries->At(entryCount);
+		SCHSENDLOG(FLog(_L8("\tDelete schedule for msg %d (mtm %d, oldSendState %d, newSendState %d)"), message->Id(), message->Mtm().iUid, message->SendingState(), KMsvSendStateSuspended));
+		message->SetSendingState(KMsvSendStateSuspended);
+		DeleteScheduleForEntryL(*message);
+		ResetScheduleInfoForEntryL(*message, ETrue);
+		}
+	}
+
+/*
+	Sets the message's scheduled flag to EFalse
+	Resets the schedule data associated with each message
+	Resets the number of retries for each recipient
+	Stores the data and recipients (in a stream associated with the TMsvEntry)
+*/
+void CMsvScheduleSend::SendingCompleteL(CMsvScheduledEntry& aMessage, const TBool aChangeEntry)
+	{
+	__ASSERT_DEBUG(iServerEntry.Entry().Id() == aMessage.Id(), gPanic(EServerEntryNotSetToCorrectEntry));
+	
+	aMessage.SetScheduled(EFalse);
+	aMessage.iData.Reset();
+	aMessage.RecipientsResetRetries();
+
+	if (aChangeEntry)
+		{
+		TMsvEntry entry = iServerEntry.Entry();
+		aMessage.Entry(entry);
+		User::LeaveIfError(iServerEntry.ChangeEntry(entry));
+		}
+
+	CMsvStore* store = iServerEntry.EditStoreL();
+	CleanupStack::PushL(store);
+	aMessage.StoreL(*store);
+	store->CommitL();
+	CleanupStack::PopAndDestroy(store); 
+	}
+
+/**
+Tells the scheduler that sending is complete.
+
+This method sets the messages's scheduled flag to false, resets the schedule
+data associated with each message and the number of retries for each 
+recipient and stores the data and recipients in a stream associated with 
+the TMsvEntry.
+
+@param aSelection 
+Messages that were either successfully sent or which failed all the attempts
+to send.
+*/
+EXPORT_C void CMsvScheduleSend::SendingCompleteL(const CMsvEntrySelection& aSelection)
+	{
+	TInt count = aSelection.Count();
+
+	while (count--)
+		{
+		CMsvScheduledEntry* schEntry = GetMessageL(aSelection[count]);
+		CleanupStack::PushL(schEntry);
+		SendingCompleteL(*schEntry, ETrue);
+		CleanupStack::PopAndDestroy(); //schEntry
+		}
+	}
+
+/**
+Tells the scheduler that sending is complete.
+
+This function must be called when a message that had previously been scheduled 
+is either sent or has failed. This function:
+
+1. Deletes the TMsvEntryScheduleData associated with the message
+
+2. Sets the Scheduled flag to EFalse
+
+3. If required, calls ChangeEntry() on the message server entry
+
+Note: SendingCompleteL() does not change the sending state of each message, 
+nor delete each message from the task scheduler.
+
+@param aEntry 
+The message which was either successfully sent or which failed (all the 
+attempts) to send. It is not a precondition that the message has already 
+been scheduled on the task scheduler.
+
+@param aChangeEntry 
+If aChangeEntry is ETrue then SendingCompleteL() will call 
+CMsvServerEntry::ChangeEntry() to update the message on the message server. 
+
+@panic ScheduleSend-DLL 24
+The server entry is not set to the correct entry.
+Debug build only.
+*/
+EXPORT_C void CMsvScheduleSend::SendingCompleteL(TMsvEntry& aEntry, const TBool aChangeEntry)
+	{
+	__ASSERT_DEBUG(iServerEntry.Entry().Id() == aEntry.Id(), gPanic(EServerEntryNotSetToCorrectEntry));
+
+	CMsvScheduledEntry* schEntry = GetMessageL(aEntry.Id());
+	CleanupStack::PushL(schEntry);
+
+	SendingCompleteL(*schEntry, EFalse);
+	schEntry->Entry(aEntry);
+
+	if (aChangeEntry)
+		{
+		User::LeaveIfError(iServerEntry.ChangeEntry(aEntry));
+		}
+
+	CleanupStack::PopAndDestroy(); //schEntry
+	}
+
+
+/**
+Loads schedule settings from CenRep
+
+@param aRepository 
+CenRep repository to load settings from
+*/
+EXPORT_C void CMsvScheduleSend::LoadScheduleSettingsL(CRepository& aRepository)
+	{
+	TMsvScheduleSettingsUtils::LoadScheduleSettingsL(*iSettings, aRepository);
+	TMsvScheduleSettingsUtils::LoadOffPeakSettingsL(*iOffPeakTimes, aRepository);
+	TMsvScheduleSettingsUtils::LoadSendErrorSettingsL(*iErrorActions, aRepository);
+	TMsvScheduleSettingsUtils::LoadSysAgentSettingsL(*iAgentActions, aRepository);	
+	}
+
+/**
+Verifies that the schedule information stored in specified messages is the 
+same as that on the task scheduler.
+
+@param aSelection 
+Array of message IDs that need to be checked against the task scheduler.
+
+@panic ScheduleSend-DLL 0
+The array of message IDs is empty.
+Debug build only.
+*/
+EXPORT_C void CMsvScheduleSend::CheckScheduleL(const CMsvEntrySelection& aSelection)
+	{
+	__ASSERT_DEBUG(aSelection.Count(), gPanic(EMessageSelectionEmpty));
+
+	GetMessagesL(aSelection);	//Leaves with KErrNotFound if there are no messages returned in iSchEntries
+	TInt entryCount = iSchEntries->Count();
+	SCHSENDLOG(FLog(_L8("Asked to check schedule for %d msgs"), entryCount));
+	
+	ConnectAndRegisterL();
+
+	while (entryCount--)
+		{
+		TBool found = EFalse;
+		TTsTime schTime;
+
+		CMsvScheduledEntry& sEntry = *iSchEntries->At(entryCount);
+
+		if (!sEntry.iData.IsReset())
+			{
+			TSchedulerItemRef ref;
+			TInt size = 0;
+			TTaskInfo info;
+			
+			TInt err = iScheduler.GetTaskDataSize(sEntry.iData.iTaskId, size);
+
+			if (!err)
+				{
+				HBufC* buf = HBufC::NewLC(size);
+				TPtr ptr = buf->Des();
+
+				User::LeaveIfError(iScheduler.GetTaskInfoL(sEntry.iData.iTaskId, info, ptr, ref, schTime));
+
+				CleanupStack::PopAndDestroy(buf);
+				found = ETrue;
+				}
+			else if (err != KErrNotFound)
+				{
+				User::Leave(err);
+				} 
+			} 
+			
+		if (iServerEntry.SetEntry(sEntry.Id()) == KErrNone)
+			{
+			TMsvEntry entry = iServerEntry.Entry();
+			TInt sendingState = entry.SendingState();
+
+			if (sendingState == KMsvSendStateScheduled || sendingState == KMsvSendStateResend || entry.Scheduled())
+				{
+				if (found)
+					{
+					entry.SetScheduled(ETrue);
+					entry.iDate = schTime.GetUtcTime();
+					User::LeaveIfError(iServerEntry.ChangeEntry(entry));
+					}
+				else
+					{
+					entry.SetScheduled(EFalse);
+					entry.SetSendingState(KMsvSendStateUnknown);
+					entry.iDate.UniversalTime();
+					User::LeaveIfError(iServerEntry.ChangeEntry(entry));
+					SendingCompleteL(sEntry, EFalse);
+					} 
+				} 
+			} 
+		} 
+	}
+
+/**
+Determines the schedule time for a message.
+
+The schedule time is determined by the error action. If no error action is
+supplied, then the error action is obtained from the iErrorActions. This is a 
+list of error codes associated with an error action. The error code with which
+the message failed to be sent is used to determine the error action from this
+list.
+
+This function also increments the number of re-tries that the message has 
+undergone.
+
+A value of ETrue is returned if the message can be sent or a value of EFalse if
+the message cannot be sent. 
+
+If the message can be sent, then its schedule date and pending conditions flag 
+are updated.
+
+@see CMsvScheduleSend::ReScheduleL().
+
+@param	aMessage
+The message to be re-scheduled.
+
+@param	aFromTime
+The current time. Used to calculate the absolute schedule time once the interval
+has been established.
+
+@param	aErrorAction
+The error action applied to the message. This can be NULL in which case the error
+action is obtained from iErrorActions.
+
+@return
+A value of ETrue if the message should be sent or EFalse if it should not
+*/
+TBool CMsvScheduleSend::SetMessageStartTime(CMsvScheduledEntry& aMessage, const TTime& aFromTime, const TMsvSendErrorAction* aErrorAction)
+	{
+	TBool sendMsg = EFalse;
+	TMsvSendErrorAction action;
+
+	if( aErrorAction != NULL )
+		{
+		sendMsg = aMessage.CanSendToAnyRecipients(*aErrorAction);
+		action = *aErrorAction;
+		}
+	else 
+		{
+		sendMsg = aMessage.CanSendToAnyRecipients(*iErrorActions, action);
+		}
+
+	SCHSENDLOG(FLog(_L8("\t\tCanSendToAnyRecipients() ret %d"), sendMsg));
+
+	if( sendMsg )
+		{
+		// Increase the number of times this message has been retried.
+		if( action.iRetries != ESendRetriesInfinite )
+			{
+			aMessage.iData.IncreaseRetries();
+			aMessage.RecipientsIncreaseRetries();
+			}
+			
+		TBool retryConditionMet = action.iAction == ESendActionRetryConditionMet;
+		
+		if( retryConditionMet && aMessage.PendingConditions() )
+			{
+			// There are two cases in which the message being re-scheduled has 
+			// already been scheduled for pending conditions met and the error 
+			// action is retry when conditions met.
+			// 1.	The timeout has expired - current time exceeds the message's 
+			//		schedule time (assuming that the message has a timeout).
+			// 2.	The conditions were temporarily met and by the time the 
+			//		waiting message tried to be sent the conditions were no 
+			//		longer true.
+			if( aMessage.ScheduleDate()!= Time::MaxTTime() && aFromTime > aMessage.ScheduleDate() )
+				{
+				// Case 1 - the message should not be re-scheduled (this marks
+				// the message as failed).
+				sendMsg = EFalse;
+				}
+			// NOTE - the else is case 2; the message should be re-scheduled,
+			// but the timeout value should NOT be re-calculated -> do nothing.
+			}
+		else
+			{
+			TTimeIntervalSeconds interval;
+			if( GetNextRetry(aMessage, action, interval) )
+				{
+				if( interval.Int() == 0 && retryConditionMet )
+					{
+					// The timeout was set to zero - set a schedule time of
+					// Time::MaxTTime().
+					aMessage.SetScheduleDate(Time::MaxTTime());
+					}
+				else
+					{
+					TTime schTime = aFromTime + interval;
+					RoundUpToMinute(schTime);
+					aMessage.SetScheduleDate(schTime);
+					}
+				aMessage.SetPendingConditions(retryConditionMet);
+				}
+			else
+				{
+				sendMsg = EFalse;
+				}
+
+			SCHSENDLOG(FLog(_L8("\t\tGetNextRetry() ret %d"), sendMsg));
+			}
+		}
+
+	if( !sendMsg )
+		{
+		TBool failed = ETrue;
+		TInt state	 = KMsvSendStateFailed;
+
+		if( aMessage.RecipientsAllSent() )
+			{
+			failed	= EFalse;
+			state	= KMsvSendStateSent;
+			}
+		else
+			{
+			aMessage.RecipientsSetFailed();
+			}
+
+		aMessage.SetFailed(failed);
+		aMessage.SetSendingState(state);
+		}
+
+	return sendMsg;
+	}
+
+/**
+Utility function that rounds a specified time up to the nearest minute.
+
+@param aTime 
+On return, the passed value rounded up to the nearest minute.
+*/
+
+EXPORT_C void CMsvScheduleSend::RoundUpToMinute(TTime& aTime)
+	{
+	TDateTime dt(aTime.DateTime());
+
+	if (dt.MicroSecond() != 0 || dt.Second() != 0)
+		{
+		dt.SetMicroSecond(0);
+		dt.SetSecond(0);
+		aTime = dt;
+		aTime += (TTimeIntervalMinutes) 1;
+		}
+	}
+
+/*
+	Groups messages in aSchEntries by the date (time) they are to be scheduled
+	and if they are pending conditions met or not and then schedules each group.
+*/
+
+void CMsvScheduleSend::DoReScheduleL(CMsvScheduledEntries& aSchEntries)
+	{
+	const TInt entryCount = aSchEntries.Count();
+	__ASSERT_DEBUG(entryCount > 0, gPanic(EMessageSelectionEmpty));
+
+	// Sort aSchEntries by CMsvScheduledEntry::iEntry.iDate and if they are 
+	// pending conditions or not.
+	SortByDateAndPendingConditionsL(aSchEntries);
+
+	//New selection of messages that will be used to store messages
+	//that are to be sent at the same time.
+	CMsvScheduledEntries* sel = new (ELeave) CMsvScheduledEntries(KMsvSchsendArrayGrowth);
+	CleanupStack::PushL(sel);
+
+	sel->SetReserveL(entryCount); // so following AppendL()s won't leave
+
+	CMsvScheduledEntry* curMessage = aSchEntries[0];
+	TTime lastTime = curMessage->ScheduleDate();
+	TBool lastPending = curMessage->PendingConditions();
+	sel->AppendL(curMessage);
+
+	// Delete old schedule
+	DeleteScheduleForEntryL(*curMessage);
+
+	for( TInt curMsg = 1; curMsg < entryCount; ++curMsg ) // must forward traverse the array
+		{
+		curMessage = aSchEntries[curMsg];
+		TTime thisTime = curMessage->ScheduleDate();
+		TBool thisPending = curMessage->PendingConditions();
+
+		// Delete old schedule
+		DeleteScheduleForEntryL(*curMessage);
+		if( thisTime == lastTime && lastPending == thisPending )
+			{
+			sel->AppendL(curMessage);
+			}
+		else
+			{
+			// Schedule the messages in the selection
+			DoScheduleL(*sel, KMsvSendStateResend, lastTime, lastPending);
+			sel->Reset(); // not ResetandDestroy because the messages are still in schEntries
+			sel->AppendL(curMessage);
+			lastTime = thisTime;
+			lastPending = thisPending;
+			}
+		}
+
+	if( sel->Count() )
+		{
+		// Schedule the remaining messages
+		DoScheduleL(*sel, KMsvSendStateResend, lastTime, lastPending);
+		}
+
+	sel->Reset(); // not ResetandDestroy because the messages are still in schEntries
+	CleanupStack::PopAndDestroy(sel);
+	}
+
+/*
+	Gets the Message ID stored against the task scheduler task aTaskId
+
+	@param aTaskId ID of task scheduler task
+*/
+
+TMsvId CMsvScheduleSend::GetMessageIdForTaskL(TInt aTaskId)
+	{
+	TInt taskSize = 0;
+	TTaskInfo taskInfo;
+	TSchedulerItemRef taskSch;
+	TTsTime taskDue;
+
+	User::LeaveIfError(iScheduler.GetTaskDataSize(aTaskId, taskSize));
+
+	HBufC* taskData = HBufC::NewLC(taskSize);
+	TPtr ptr(taskData->Des());
+
+ 	User::LeaveIfError(iScheduler.GetTaskInfoL(aTaskId, taskInfo, ptr, taskSch, taskDue));
+ 
+ 	//Restore the TMsvSchedulePackage stored against the task scheduler task
+ 	TMsvSchedulePackage taskPackage;
+ 	taskPackage.UnpackL(taskInfo, *taskData);
+ 
+ 	CleanupStack::PopAndDestroy(taskData);
+ 
+ 	return taskPackage.iId;
+ 	}
+ 
+/*
+	Checks whether the message ID stored against task scheduler task aMessage.iData.iTaskId equals aMessage.Id().
+*/
+
+TBool CMsvScheduleSend::TaskAndMessageMatchL(const CMsvScheduledEntry& aMessage)
+	{
+	TMsvId id = 0;
+	TRAPD(err, id = GetMessageIdForTaskL(aMessage.iData.iTaskId));
+
+	SCHSENDLOG(FLog(_L8("\tGetMessageIdForTask [taskId=%d err=%d id=%d aMessage.Id=%d"), aMessage.iData.iTaskId, err, id, aMessage.Id()));
+	TBool match = EFalse;
+ 
+	if (err == KErrNone)
+		{
+		match = (id == aMessage.Id());
+		}
+	else if (err != KErrNotFound)
+		{
+		User::Leave(err);
+		}
+ 
+	return match;
+	}
+	
+void CMsvScheduleSend::ResetScheduleInfoForEntryL(CMsvScheduledEntry& aMessage, const TBool aChangeEntry)
+	{
+	// Reset the scheduling info in the entry.
+	TMsvId oldId = iServerEntry.Entry().Id();
+	User::LeaveIfError(iServerEntry.SetEntry(aMessage.Id()));
+	SendingCompleteL(aMessage, aChangeEntry);	
+	iServerEntry.SetEntry(oldId); // ignore any error	
+	}
+	
+void CMsvScheduleSend::DeleteScheduleForEntryL(CMsvScheduledEntry& aMessage)
+	{
+	//Connect and register with the task scheduler
+	//Delete the task from the task scheduler
+	ConnectAndRegisterL();
+
+	if (TaskAndMessageMatchL(aMessage))
+		{
+		TInt err = iScheduler.DeleteTask(aMessage.iData.iTaskId);
+
+		SCHSENDLOG(FLog(_L8("\tDeleteTask Task=%d, Err=%d"), aMessage.iData.iTaskId, err));
+
+		if (err != KErrNotFound)
+			User::LeaveIfError(err);
+
+		//Delete the schedule if there are no more tasks assigned to it
+
+		//Declare variable to pass into GetScheduleL()
+		TScheduleState2 schState;
+		TTsTime schDueTime;
+
+		// Get the schedule Type
+		TScheduleType scheduleType;
+		iScheduler.GetScheduleTypeL(aMessage.iData.iRef.iHandle, scheduleType);
+		iSchTaskInfo->Reset();
+		
+		// Depends on the Schedule Type
+		if (scheduleType == ETimeSchedule)
+		    {
+			//Get details of the existing time schedule
+			err = iScheduler.GetScheduleL(aMessage.iData.iRef.iHandle, schState, *iSchEntryInfo, *iSchTaskInfo, schDueTime);
+
+			SCHSENDLOG(FLog(_L8("\tGetScheduleL Sch=%d, Err=%d"), aMessage.iData.iRef.iHandle, err));
+		    }
+
+		else if (scheduleType == EConditionSchedule)
+		    {
+		    CArrayFixFlat<TTaskSchedulerCondition>* schConditions =
+		        new (ELeave) CArrayFixFlat<TTaskSchedulerCondition>(KMsvSchsendArrayGrowth);
+
+		    CleanupStack::PushL(schConditions);
+			//Get details of the existing condition schedule
+		    err = iScheduler.GetScheduleL(aMessage.iData.iRef.iHandle, schState, *schConditions, schDueTime, *iSchTaskInfo);
+
+			SCHSENDLOG(FLog(_L8("\tGetScheduleL Sch=%d, Err=%d"), aMessage.iData.iRef.iHandle, err));
+
+		    CleanupStack::PopAndDestroy(schConditions);
+
+		    }
+		else
+		    {
+		    User::Leave(KErrNotFound);
+		    }
+		    
+		if (!err)
+			{
+			if (iSchTaskInfo->Count() == 0)
+				{
+				SCHSENDLOG(FLog(_L8("\tDeleting schedule %d"), aMessage.iData.iRef.iHandle));
+				iScheduler.DeleteSchedule(aMessage.iData.iRef.iHandle); //ignore error
+				}
+			}
+		else if (err != KErrNotFound)
+			{
+			User::Leave(err);
+			}
+		}
+	}
+
+/**
+Determines whether the message should be sent and if so the time interval (in
+seconds) that must elapse before aMessage should be sent.
+
+The time interval is determined by the error action supplied. In the case of an
+error action of ESendActionRetryConditionMet, the time interval indicates how
+long the message can be pending conditions to be met before the message is failed.
+
+The actual time period is defined in the iSettings object. This will be either
+iPendingConditionsTimeout, iShortInterval, iLongInterval or an element of 
+iVariableIntervals.
+
+If the message should be sent then a value of ETrue is returned. A value of 
+EFalse is returned if the message should not be sent. In this case the output
+argument aInterval is not valid.
+
+@param	aMessage
+The message to be re-scheduled.
+
+@param	aErrorAction
+The error action that determines the re-schedule behaviour.
+
+@param	aInterval
+An output argument that holds the time interval to when the message should be
+re-sent or when the message should be failed.
+
+@return
+A value of ETrue if the message should be sent or EFalse if it should not
+*/
+TBool CMsvScheduleSend::GetNextRetry(CMsvScheduledEntry& aMessage, const TMsvSendErrorAction& aErrorAction, TTimeIntervalSeconds& aInterval) const
+	{
+	TBool retVal = (aErrorAction.iAction != ESendActionFail);
+	aInterval = 0;
+	
+	if( retVal )
+		{
+		if( aErrorAction.iAction == ESendActionRetryConditionMet )
+			{
+			// Interval given by the agent actions timeout value.
+			aInterval = iSettings->PendingConditionsTimeout().Int() * 60;
+			}
+		else if( aErrorAction.iAction == ESendActionRetryImmediately )
+			{
+			aInterval = iSettings->ShortInterval();
+			}
+		else
+			{
+			if( aErrorAction.iRetrySpacing == ESendRetrySpacingVariable )
+				{
+				//Retrieve the variable spacing associated with the retry count
+				const CArrayFixFlat<TTimeIntervalSeconds>& varIntervals = iSettings->VariableIntervals();
+				const TInt count = varIntervals.Count();
+				const TInt retry = aMessage.iData.Retries();
+
+				if( count == 0 )
+					{
+					aInterval = iSettings->LongInterval();
+					}
+				else if( retry < count )
+					{
+					aInterval = varIntervals[retry];
+					}
+				else
+					{
+					aInterval = varIntervals[count - 1];
+					}
+				}
+			else 
+				{
+				// Got here then aScheduleAction.iRetrySpacing == EStatic.
+				aInterval = iSettings->LongInterval();
+				}
+
+			__ASSERT_DEBUG(aInterval.Int() > 0, gPanic(ERetryIntervalMustByPositive));
+			}
+		}
+	return retVal;
+	}
+
+/**
+Searches the scheduler for an existing schedule item with a schedule time that
+matches with time supplied.
+
+@see RScheduler::GetScheduleL()
+
+@param aScheduler
+Handle to the scheduler.
+
+@param aStartTime
+Schedule start time.
+
+@param aRef
+On return, the schedule item.
+
+@leave KErrNotFound
+No schedule found matching the schedule time.
+*/
+EXPORT_C void CMsvScheduleSend::FindScheduleL(RScheduler& aScheduler, const TTime& aStartTime, TSchedulerItemRef& aRef)
+	{
+	CArrayFixFlat<TScheduleEntryInfo2>* entryInfos = new (ELeave) CArrayFixFlat<TScheduleEntryInfo2>(1);
+	CleanupStack::PushL(entryInfos);
+
+	CArrayFixFlat<TTaskInfo>* taskInfos = new (ELeave) CArrayFixFlat<TTaskInfo>(1);
+	CleanupStack::PushL(taskInfos);
+
+	aRef.iHandle = KErrNotFound;
+
+	CArrayFixFlat<TSchedulerItemRef>* refs = new (ELeave) CArrayFixFlat<TSchedulerItemRef>(KMsvSchsendArrayGrowth);
+	CleanupStack::PushL(refs);
+
+	User::LeaveIfError(aScheduler.GetTaskRefsL(*refs, EAllSchedules, EMyTasks));
+
+	TInt count = refs->Count();
+	TScheduleState2 state;
+	TTsTime nextDue;
+
+	while (count-- && aRef.iHandle == KErrNotFound)
+		{
+		const TSchedulerItemRef& tempRef = (*refs)[count];
+
+		TScheduleType type;
+		User::LeaveIfError(aScheduler.GetScheduleTypeL(tempRef.iHandle, type));
+		if( type == ETimeSchedule )
+			{
+			entryInfos->Reset();
+			taskInfos->Reset();
+			const TInt err = aScheduler.GetScheduleL(tempRef.iHandle, state, *entryInfos, *taskInfos, nextDue);
+			if( err == KErrNone && nextDue.GetUtcTime() == aStartTime )
+				aRef = tempRef;
+			}
+		}
+
+	CleanupStack::PopAndDestroy(3, entryInfos);
+	if (aRef.iHandle == KErrNotFound)
+		User::Leave(KErrNotFound);
+	}
+
+/**
+Searches the scheduler for an existing conditions schedule item with a set of 
+pending conditions and timeout value that matches with those supplied.
+
+@see RScheduler::GetScheduleL
+
+@param aScheduler
+Handle to the scheduler.
+
+@param aConditions
+The set of System Agent conditions that are required to be met to trigger the
+schedule.
+
+@param aTimeout
+The timeout value for the schedule.
+
+@param aRef
+On return, the schedule item.
+
+@leave KErrNotFound
+No schedule found matching the schedule conditions and timeout.
+*/
+EXPORT_C void CMsvScheduleSend::FindScheduleL(
+								RScheduler&										aScheduler, 
+								const CArrayFixFlat<TTaskSchedulerCondition>&	aConditions, 
+								const TTime&									aTimeout, 
+								TSchedulerItemRef& 								aRef)
+	{
+	CArrayFixFlat<TTaskSchedulerCondition>* schConditions = new (ELeave) CArrayFixFlat<TTaskSchedulerCondition>(KMsvSchsendArrayGrowth);
+	CleanupStack::PushL(schConditions);
+
+	CArrayFixFlat<TTaskInfo>* taskInfos = new (ELeave) CArrayFixFlat<TTaskInfo>(1);
+	CleanupStack::PushL(taskInfos);
+
+	aRef.iHandle = KErrNotFound;
+
+	CArrayFixFlat<TSchedulerItemRef>* refs = new (ELeave) CArrayFixFlat<TSchedulerItemRef>(KMsvSchsendArrayGrowth);
+	CleanupStack::PushL(refs);
+	
+	User::LeaveIfError(aScheduler.GetTaskRefsL(*refs, EAllSchedules, EMyTasks));
+
+	TInt count = refs->Count();
+	TScheduleState2 state;
+	TTsTime nextTimeout;
+	
+	while( count-- && aRef.iHandle == KErrNotFound )
+		{
+		const TSchedulerItemRef& tempRef = (*refs)[count];
+
+		TScheduleType type;
+		User::LeaveIfError(aScheduler.GetScheduleTypeL(tempRef.iHandle, type));
+		if( type == EConditionSchedule )
+			{
+			taskInfos->Reset();
+			const TInt err = aScheduler.GetScheduleL(tempRef.iHandle, state, *schConditions, nextTimeout, *taskInfos);
+			if( err == KErrNone && nextTimeout.GetUtcTime() == aTimeout && *schConditions == aConditions )
+				aRef = tempRef;
+			}
+		}
+
+	CleanupStack::PopAndDestroy(3, schConditions);
+	if (aRef.iHandle == KErrNotFound)
+		User::Leave(KErrNotFound);
+	}	
+
+void CMsvScheduleSend::FindScheduleL(const TTime& aTime, const CArrayFixFlat<TTaskSchedulerCondition>& aSchConditions, TBool aPendingConditions, TSchedulerItemRef& aRef)
+	{
+	if( aPendingConditions )
+		FindScheduleL(iScheduler, aSchConditions, aTime, aRef);
+	else
+		FindScheduleL(iScheduler, aTime, aRef);
+	}
+	
+/**
+Does the actual scheduling of the supplied messages.
+
+This function is called by ScheduleL() and DoReScheduleL(). The supplied messages
+are assumed to have the values for the following data - MTM UID, off-peak flag,
+schedule date and pending conditions flag. In debug mode, if these info is 
+matching in all the messages then a panic will occur.
+
+@param	aSchEntries
+An array with the messages to be scheduled.
+
+@param	aCommandId
+The command id that must be used by the Send.Exe to eventually send the messages.
+
+@param	aFinalState
+The sending state to set to the messages to if the message is successfully 
+scheduled.
+
+@param	aTime
+For conditions-scheduled messages this is the timeout, for time-scheduled messages
+this is the scheduled time.
+
+@param 	aPendingConditions
+A flag indicating whether the schedule should be pending conditions.
+*/
+void CMsvScheduleSend::DoScheduleL(CMsvScheduledEntries& aSchEntries, const TInt aFinalState, const TTime& aTime, TBool aPendingConditions)
+	{
+	__ASSERT_DEBUG(aSchEntries.Count(), gPanic(EMessageSelectionEmpty));
+	
+#ifdef _DEBUG
+	// Check that the mtm, time, off peak flag and pending conditions flag are 
+	// the same for every message.
+	TInt count = aSchEntries.Count();
+	CMsvScheduledEntry* schEntry = aSchEntries[0];
+
+	while (count--)
+		{
+		CMsvScheduledEntry* entry = aSchEntries[count];
+
+		__ASSERT_DEBUG(entry->Mtm() == schEntry->Mtm(), gPanic(EMessagesNotSameMtm));
+		__ASSERT_DEBUG((entry->OffPeak() && schEntry->OffPeak()) || (!entry->OffPeak() && !schEntry->OffPeak()), gPanic(EMessagesNotSameOffPeak));
+		__ASSERT_DEBUG(entry->ScheduleDate() == schEntry->ScheduleDate(), gPanic(EMessagesNotSameTime));
+		__ASSERT_DEBUG(entry->PendingConditions() == schEntry->PendingConditions(), gPanic(EMessagesNotSamePendingConditions));
+		schEntry = entry;
+		}
+#endif
+
+	// Connect and register with the task scheduler
+	ConnectAndRegisterL();
+	
+	// Determine the start time and validity period
+	TTime latencyTime;
+	latencyTime.UniversalTime();
+	latencyTime += iSettings->Latency();
+	TTime startTime = latencyTime;
+
+	if( aTime > startTime )
+		{
+		// The schedule time is in the future or there is no schedule time
+		// (the messages are pending conditions) - update the start time.
+		startTime = aTime;
+		}
+
+	TTimeIntervalMinutes valPeriod = iSettings->ValidityPeriod();
+	CMsvScheduledEntry* firstMessage = aSchEntries[0];
+
+	// Determine the start time and validity period of the new schedule - this
+	// is not applicable if the messages are schedule for pending conditions.
+	if( firstMessage->OffPeak() && !aPendingConditions )
+		{
+		GetOffPeakL(startTime, startTime, valPeriod);
+		}
+
+	CArrayFixFlat<TTaskSchedulerCondition>* schConditions = new (ELeave) CArrayFixFlat<TTaskSchedulerCondition>(KMsvSchsendArrayGrowth);
+	CleanupStack::PushL(schConditions);
+	if( aPendingConditions )
+		PopulateScheduleConditionsL(*schConditions);
+
+	// Create a schedule
+	TSchedulerItemRef schItemRef;
+	TBool schFound = EFalse;
+
+	// Check to see if a schedule already exists for this particular schedule.
+	// Need to consider both the start time and if the schedule is for pending
+	// conditions met.
+	// NOTE - if the schedule time is not in the future, then there is no need 
+	// to search for an existing schedule.	
+	if( startTime > latencyTime )
+		{
+		if( startTime != Time::MaxTTime() )
+			RoundUpToMinute(startTime);
+		
+		TRAPD(err,FindScheduleL(startTime, *schConditions, aPendingConditions, schItemRef));
+		if( err == KErrNone )
+			{
+			SCHSENDLOG(FLog(_L("\tFound schedule %d"), schItemRef.iHandle));
+			schFound = ETrue;
+			}
+		// Else ignore the error and create and new schedule.
+		}
+
+	if( !schFound )
+		{
+		// The appropriate schedule.
+		CreateScheduleL(startTime, valPeriod, *schConditions, aPendingConditions, schItemRef);
+		}
+
+	CleanupStack::PopAndDestroy(schConditions);
+	
+	// Disable the schedule so that it doesn't fire while scheduling
+	User::LeaveIfError(iScheduler.DisableSchedule(schItemRef.iHandle));
+
+	// Schedule the messages
+	const TInt messageCount = aSchEntries.Count();
+	TInt schErr = KErrNone;
+	TInt curMsg;
+
+	for( curMsg = 0; curMsg < messageCount; ++curMsg ) // must forward traverse the array
+		{
+		CMsvScheduledEntry* message = aSchEntries[curMsg];
+
+		message->SetScheduleDate(startTime);
+		TRAP(schErr, ScheduleEntryL(*message, aFinalState, startTime, schItemRef, aPendingConditions));
+
+		if( schErr != KErrNone )
+			break;
+		}
+
+	if( schErr != KErrNone && curMsg == 0 && !schFound )
+		{
+		iScheduler.DeleteSchedule(schItemRef.iHandle); // ignore error
+		}
+	else
+		{
+		User::LeaveIfError(iScheduler.EnableSchedule(schItemRef.iHandle));
+		}
+		
+	User::LeaveIfError(schErr);
+	}
+
+/**
+Creates a new schedule on the task scheduler with which each message can be 
+associated. 
+
+The schedule is triggered by a start time being reached.
+
+@see RScheduler::CreatePersistentSchedule
+
+@param aScheduler
+Handle to scheduler to update.
+
+@param aSettings
+Scheduler settings.
+
+@param aStartTime
+Schedule start time.
+
+@param aValidityPeriod
+Schedule validity period.
+
+@param aRef 
+On return, the new schedule.
+*/
+EXPORT_C void CMsvScheduleSend::CreateScheduleL(RScheduler& aScheduler, const CMsvScheduleSettings& aSettings, const TTime& aStartTime, const TTimeIntervalMinutes& aValidityPeriod, TSchedulerItemRef& aRef)
+	{
+	//Determine the new schedule entry	
+	TScheduleEntryInfo2 taskInfo;
+	taskInfo.SetIntervalType(aSettings.IntervalType());
+	taskInfo.SetInterval(1);
+	TTsTime startTime(aStartTime,ETrue);
+	taskInfo.SetStartTime(startTime);
+	taskInfo.SetValidityPeriod(aValidityPeriod);
+	
+	//Create the TScheduleEntryInfo arrag that is required by CreatePersistentSchedule
+	CArrayFixFlat<TScheduleEntryInfo2>* entryInfos = new (ELeave) CArrayFixFlat<TScheduleEntryInfo2>(1);
+	CleanupStack::PushL(entryInfos);
+
+	//Append the new schedule entry
+	entryInfos->AppendL(taskInfo);
+
+	//Create the schedule
+	User::LeaveIfError(aScheduler.CreatePersistentSchedule(aRef, *entryInfos));
+
+	CleanupStack::PopAndDestroy(entryInfos);
+	}
+
+/**
+Creates a new schedule on the task scheduler with which each message can be 
+associated.
+
+The schedule is triggered by either a set of conditions being met or a timeout
+being reached.
+
+@see RScheduler::CreatePersistentSchedule
+@see TSysAgentCondition
+
+@param aScheduler
+Handle to scheduler to update.
+
+@param aConditions
+The set of System Agent conditions that are required to be met to trigger the
+schedule.
+
+@param aTimeout
+The timeout value for the schedule.
+
+@param aRef 
+On return, the new schedule.
+*/
+EXPORT_C void CMsvScheduleSend::CreateScheduleL(
+								RScheduler& 									aScheduler, 
+								const CArrayFixFlat<TTaskSchedulerCondition>&	aConditions, 
+								const TTime& 									aTimeout,
+								TSchedulerItemRef& 								aRef)
+	{
+	TTsTime timeout(aTimeout,ETrue);
+	User::LeaveIfError(aScheduler.CreatePersistentSchedule(aRef, aConditions, timeout));	
+	}
+		
+void CMsvScheduleSend::CreateScheduleL(const TTime& aTime, const TTimeIntervalMinutes& aValidityPeriod, const CArrayFixFlat<TTaskSchedulerCondition>& aSchConditions, TBool aPendingConditions, TSchedulerItemRef& aRef)
+	{
+	if( aPendingConditions )
+		{
+		CreateScheduleL(iScheduler, aSchConditions, aTime, aRef);
+	#ifndef _MSG_NO_LOGGING
+		TBuf<32> bufDate;
+		aTime.FormatL(bufDate, _L("%D%M%Y%/0%1%/1%2%/2%3%/3 %-B%:0%J%:1%T%:2%S%.%*C4%:3%+B"));
+		SCHSENDLOG(FLog(_L("\tCreated Schedule %d for pending %d conditions or %S"), aRef.iHandle, aSchConditions.Count(), &bufDate));
+	#endif		
+		}
+	else
+		{		
+		CreateScheduleL(iScheduler, *iSettings, aTime, aValidityPeriod, aRef);
+	#ifndef _MSG_NO_LOGGING
+		TBuf<32> bufDate;
+		aTime.FormatL(bufDate, _L("%D%M%Y%/0%1%/1%2%/2%3%/3 %-B%:0%J%:1%T%:2%S%.%*C4%:3%+B"));
+		SCHSENDLOG(FLog(_L("\tCreated Schedule %d for %S"), aRef.iHandle, &bufDate));
+	#endif
+		}
+	}
+	
+/**
+Adds an entry to an existing schedule.
+
+@see RScheduler::ScheduleTask()
+
+@param aScheduler 
+Scheduler to access.
+
+@param aRef 
+Id of the schedule.
+
+@param aPackage 
+Scheduler settings.
+
+@param aInfo 
+Information about the entry to be added to the schedule.
+
+@leave KErrNotFound
+No existing schedule with the specified Id.
+*/
+EXPORT_C void CMsvScheduleSend::ScheduleEntryL(RScheduler& aScheduler, const TSchedulerItemRef& aRef, const TMsvSchedulePackage& aPackage, TTaskInfo& aInfo)
+	{
+	aInfo.iPriority	= EDefaultTaskPriority;
+	aInfo.iRepeat	= EDefaultTaskRepeat;
+
+	HBufC* hBuf = NULL;
+	aPackage.PackLC(aInfo, hBuf);
+
+	//Schedule the task
+	User::LeaveIfError(aScheduler.ScheduleTask(aInfo, *hBuf, aRef.iHandle));
+
+	CleanupStack::PopAndDestroy(hBuf);
+	}
+
+/**
+Schedules aMessage on the schedule referred to by aRef.
+
+Updates the message entry and the schedule data using the utility function 
+UpdateEntryAfterSchedule().
+
+@see	CMsvScheduleSend::UpdateEntryAfterSchedule
+
+@param	aMessage
+The message to be scheduled.
+
+@param	aFinalState
+The sending state to set to the messages to if the message is successfully 
+scheduled.
+
+@param	aFromTime
+The time to schedule the messages for sending.
+
+@param	aRef
+The ID of the schedule to add this task to.
+
+@param	aPendingConditions
+A flag indicating whether this message is schedule for conditions.
+*/
+void CMsvScheduleSend::ScheduleEntryL(CMsvScheduledEntry& aMessage, const TInt aFinalState, const TTime& aStartTime, const TSchedulerItemRef& aRef, TBool aPendingConditions)
+	{
+	//Create a new task to associate with the schedule
+	TTaskInfo taskInfo;
+	iPackage.iId = aMessage.Id();
+	
+	ScheduleEntryL(iScheduler, aRef, iPackage, taskInfo);
+
+	// Change the scheduled flag and sending state of the message
+	if( iServerEntry.SetEntry(aMessage.Id()) == KErrNone )
+		{
+		TMsvEntry entry(iServerEntry.Entry());
+
+		aMessage.Entry(entry);
+		UpdateEntryAfterSchedule(aRef, taskInfo, aStartTime, aFinalState, entry, aMessage.iData);
+		entry.SetPendingConditions(aPendingConditions);
+
+		SCHSENDLOG(FLog(_L8("\t\tScheduled msg %d (Mtm=%d, State=%d, Sch=%d, Task=%d, Pending=%d)"), entry.Id(), entry.iMtm.iUid, aFinalState, aMessage.iData.iRef.iHandle, aMessage.iData.iTaskId, entry.PendingConditions()));
+
+		User::LeaveIfError(iServerEntry.ChangeEntry(entry));
+
+		//Store the message data
+		CMsvStore* store = iServerEntry.EditStoreL();
+		CleanupStack::PushL(store);
+		aMessage.StoreL(*store);
+		store->CommitL();
+		CleanupStack::PopAndDestroy(store);
+		iServerEntry.SetEntry(KMsvNullIndexEntryId);
+		}
+	}
+
+/**
+Utility function that updates message index entry fields to reflect 
+the message's scheduling.
+
+@param aRef 
+Scheduler item.
+
+@param aInfo 
+Scheduler task information.
+
+@param aTime 
+Schedule start time.
+
+@param aFinalState 
+Sending state flag.
+
+@param aEntry 
+On return, updated index entry.
+
+@param aData 
+On return, populated messaging scheduling data.
+*/
+EXPORT_C void CMsvScheduleSend::UpdateEntryAfterSchedule(const TSchedulerItemRef& aRef, const TTaskInfo& aInfo, const TTime& aTime, TInt aFinalState, TMsvEntry& aEntry, TMsvEntryScheduleData& aData)
+	{
+	aEntry.SetScheduled(ETrue);
+	aEntry.SetFailed(EFalse);
+	aEntry.SetSendingState(aFinalState);
+	aEntry.SetConnected(EFalse);
+	aEntry.iDate = aTime;
+	aData.iRef = aRef;
+	aData.iTaskId = aInfo.iTaskId;
+	}
+
+/**
+Sorts the supplied array of messages.
+
+The messages are sorted by their schedule date (held in the iEntry.iDate member
+of CMsvScheduledEntry). Messages with the same schedule time are then further
+sorted by if they to be scheduled for pending conditions.
+
+@param	aSchEntries
+The input/output parameter that holds the messages to be sorted. This will 
+contain the sorted list once the function completes.
+*/
+void CMsvScheduleSend::SortByDateAndPendingConditionsL(CMsvScheduledEntries& aSchEntries)
+	{
+	//TO DO: Test this function!!!
+
+	TInt count = aSchEntries.Count();
+	CMsvScheduledEntry* curEntry = NULL;
+	CMsvScheduledEntry* compEntry = NULL;
+
+	for (TInt i = 0; i < count - 1; ++i)
+		{
+		curEntry = aSchEntries[i];
+
+		for (TInt j = i + 1; j < count; ++j)
+			{
+			compEntry = aSchEntries[j];
+
+			//Compare the dates of the two entries
+			if( curEntry->ScheduleDate() > compEntry->ScheduleDate() ||
+				(curEntry->ScheduleDate() == compEntry->ScheduleDate() &&
+				curEntry->PendingConditions() && !compEntry->PendingConditions()) )
+				{
+				//Swap them
+				aSchEntries.Delete(i);
+				aSchEntries.InsertL(i, compEntry);
+				aSchEntries.Delete(j);
+				aSchEntries.InsertL(j, curEntry);
+				}
+			}
+		}
+	}
+
+void CMsvScheduleSend::GetMessagesL(const CMsvEntrySelection& aSelection)
+	{
+	iSchEntries->ResetAndDestroy();
+
+	const TInt entryCount = aSelection.Count();
+
+	iSchEntries->SetReserveL(entryCount); //so following AppendL()s won't leave
+
+	CMsvScheduledEntry* schEntry = NULL;
+
+	for (TInt curMsg = 0; curMsg < entryCount; ++curMsg) //not while because must transverse forward
+		{
+		//Retrieve each message from the message server
+		TMsvId msvId = aSelection.At(curMsg);
+
+		TRAPD(error, schEntry = GetMessageL(msvId));
+
+		if (error == KErrNone)
+			{
+			CleanupStack::PushL(schEntry);
+			iSchEntries->AppendL(schEntry);
+			CleanupStack::Pop(schEntry);
+			}
+		else if (error != KErrLocked && error != KErrNotFound)
+			{
+			User::Leave(error);
+			}
+		}
+	}
+
+/**
+Connects to and registers with the task scheduler.
+
+@param aScheduler 
+Handle to the scheduler to connect to.
+
+@param aSettings 
+Schedule settings.
+
+@leave Any error code
+RScheduler::Connect() returned an error.
+
+@leave Any error code
+RFs::Connect() returned an error.
+
+@leave KErrPathNotFound
+The .EXE file to be called by the Task Scheduler when messages are due to 
+be sent cannot be found. The filename is part of the schedule settings.
+
+@leave Any error code
+RScheduler::Register() returned an error.
+*/
+
+EXPORT_C void CMsvScheduleSend::ConnectAndRegisterL(RScheduler& aScheduler, const CMsvScheduleSettings& aSettings)
+	{
+	//Connect to the task scheduler
+	User::LeaveIfError(aScheduler.Connect());
+
+	// Register the schsendexe.exe as the executable to run when the schedule
+	// fires.
+	User::LeaveIfError(aScheduler.Register(KSchSendExe(), aSettings.Priority()));
+	}
+
+
+void CMsvScheduleSend::ConnectAndRegisterL()
+	{
+	//Check to see whether already registered.
+	if (iRegistered)
+		{
+		return;
+		}
+
+	ConnectAndRegisterL(iScheduler, *iSettings);
+
+	//No errors, so set iRegistered to true.
+	iRegistered = ETrue;
+	}
+
+/*
+	Retrieves the system off-peak times and
+	updates aStartTime and aValidityPeriod
+*/
+
+void CMsvScheduleSend::GetOffPeakL(TTime aFromTime, TTime& aStartTime, TTimeIntervalMinutes& aValidityPeriod) const
+	{
+	__ASSERT_DEBUG(iOffPeakTimes->Count() > 0, gPanic(EOffPeakTimesEmpty));
+	
+	TMsvOffPeakTime opTime;
+
+	TTime offPeakStart;
+	User::LeaveIfError(iOffPeakTimes->GetNextOffPeakTime(aFromTime, opTime, offPeakStart));
+
+	aValidityPeriod = opTime.ValidityPeriod();
+
+	if (offPeakStart < aFromTime) 
+		{ //aFromTime is within an off peak time
+		aStartTime = aFromTime;
+
+		TTimeIntervalMinutes mins;
+		aStartTime.MinutesFrom(offPeakStart, mins);
+		TInt minsInt = mins.Int();
+
+		__ASSERT_DEBUG(minsInt >= 0, gPanic(EInvalidValidityPeriod));
+
+		aValidityPeriod = opTime.ValidityPeriod();
+
+		aValidityPeriod = (TTimeIntervalMinutes) (aValidityPeriod.Int() - minsInt);
+		}
+	else
+		{
+		aStartTime = offPeakStart;
+		}
+
+	//To Do: Remove next line after testing
+	__ASSERT_DEBUG(aValidityPeriod.Int() > 0, gPanic(EInvalidValidityPeriod));
+	}
+
+void CMsvScheduleSend::PopulateScheduleConditionsL(CArrayFixFlat<TTaskSchedulerCondition>& aSchConditions)
+	{
+	aSchConditions.Reset();
+
+	TInt count = iAgentActions->Count();
+	TTaskSchedulerCondition condition;
+	
+	// All system agent condition have a UID of KUidSystemCategory.
+	condition.iCategory = KUidSystemCategory;
+	
+	for( TInt i = 0; i < count; ++i )
+		{
+
+		TMsvCondition agentCondition = iAgentActions->At(i).iCondition;
+
+		condition.iKey	 = agentCondition.iVariable.iUid;
+		condition.iState = agentCondition.iState;
+		
+		switch( agentCondition.iType )
+			{
+		case TMsvCondition::EMsvSchSendEquals:
+			condition.iType = TTaskSchedulerCondition::EEquals;
+			break;
+		case TMsvCondition::EMsvSchSendNotEquals:
+			condition.iType = TTaskSchedulerCondition::ENotEquals;
+			break;
+		case TMsvCondition::EMsvSchSendGreaterThan:
+			condition.iType = TTaskSchedulerCondition::EGreaterThan;
+			break;
+		case TMsvCondition::EMsvSchSendLessThan:
+			condition.iType = TTaskSchedulerCondition::ELessThan;
+			break;
+		default:
+			User::Invariant();
+			break;
+			}
+			
+		aSchConditions.AppendL(condition);
+		}
+	}
+	
+_LIT(KScheduleSendPanic, "ScheduleSend-DLL");
+
+GLDEF_C void gPanic(TScheduleSendPanic aPanic)
+	{
+	User::Panic(KScheduleSendPanic,aPanic);
+	}
+
+
+#ifndef _MSG_NO_LOGGING
+void CMsvScheduleSend::FLog(TRefByValue<const TDesC> aFormat, ...)
+	{
+	VA_LIST list;
+	VA_START(list, aFormat);
+	RFileLogger::WriteFormat(KSchSendLogDir, KSchSendLogFile, EFileLoggingModeAppend,
+		aFormat, list);
+	}
+
+void CMsvScheduleSend::FLog(TRefByValue<const TDesC8> aFormat, ...)
+	{
+	VA_LIST list;
+	VA_START(list, aFormat);
+	RFileLogger::WriteFormat(KSchSendLogDir, KSchSendLogFile, EFileLoggingModeAppend,
+		aFormat, list);
+	}
+#endif