diff -r 000000000000 -r 8e480a14352b messagingfw/scheduledsendmtm/schedulesendexe/src/SchSendExe.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/messagingfw/scheduledsendmtm/schedulesendexe/src/SchSendExe.cpp Mon Jan 18 20:36:02 2010 +0200 @@ -0,0 +1,787 @@ +// Copyright (c) 2001-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 +#include +#include +#include "SchSendExe.h" +#include +#include +#include +#include +#include +#include + + +#ifndef _MSG_NO_LOGGING + #include + _LIT(KSchSendExeLogFile, "SchSendExe.txt"); + const TFileLoggingMode KSchSendExeLoggingMode = EFileLoggingModeAppend; +#endif + + +_LIT_SECURE_ID(KSchSendExeSid, 0x100056A1); +_LIT_SECURE_ID(KTaskSchedulerSid, 0x10005399); + +_LIT(KSchSendThreadName, "SchSend"); + +#include +#include + + +GLDEF_C TInt E32Main() + { + if(User::CreatorSecureId() != KTaskSchedulerSid) + { + // Not launched by Task Scheduler - exit + return KErrPermissionDenied; + } + + TBuf<0x100> fileName; + RFile taskFile; + // Adopt the task file using the RFs handle into environment slot + // 1 and the RFile handle into slot 2 + TInt err = taskFile.AdoptFromCreator(1,2); + if(KErrNone !=err) + { + return err; + } + taskFile.Name(fileName); + return Execute(fileName, taskFile); + + } + + + +TInt Execute(const TDesC& aFileName, RFile& aFile ) + { + __UHEAP_MARK; + CTrapCleanup* cleanup = CTrapCleanup::New(); + SCHSENDLOG(CMsvSendExe::FLog(aFileName, _L("Processing file %S"), &aFileName)); + + TRAPD(leaveValue, DoExecuteL(aFileName, aFile )); + + if (leaveValue != KErrNone) + { + // DoExecuteL will only leave in the unlikely event that available memory is too low + // to create an instance of the active scheduler or the CMsvSendExe object or in the + // event that we cannot rename the thread. Once the CMsvSendObject has been created + // leaves are handled cleanly by the object. We could retry the call to DoExecuteL, + // but it's unlikely to succeed a second time around. + } + + SCHSENDLOG(CMsvSendExe::FLog(aFileName, _L("DoExecuteL completed with error %d"), leaveValue)); + + delete cleanup; + __UHEAP_MARKEND; + + return leaveValue; + + } + +void DoExecuteL(const TDesC& aFileName, RFile& aFile ) + { + CleanupClosePushL(aFile); + + User::LeaveIfError(User::RenameThread(KSchSendThreadName)); + + CActiveScheduler* activeSch = new (ELeave) CActiveScheduler(); + CleanupStack::PushL(activeSch); + + CActiveScheduler::Install(activeSch); + + CMsvSendExe::StartLC(aFileName, aFile ); + + CleanupStack::PopAndDestroy(3, &aFile); + } + +// +// CMsvSendExe Implementation +// + +/** + +The CMsvSendExe Constructor + +@return +@internalAll +@param aFile +*/ +CMsvSendExe::CMsvSendExe(const TDesC& aFileName, RFile& aFile ) + : CActive(KDefaultPriority), iFileName(aFileName), iTaskFile(aFile ) + { + //Add this to the Active Scheduler. + //This is a requirement if a class is derived from CActive + CActiveScheduler::Add(this); + iCreated.UniversalTime(); + } + +CMsvSendExe::~CMsvSendExe() +/* + CMsvSendExe Destructor +*/ + { + Cancel(); + delete iSession; + + iOperations.ResetAndDestroy(); + iPackages.Reset(); + } + + +/** + +The CMsvSendExe::StartLC method +1. Constructs CMsvSendExe +2. Processes the task scheduler file +3. Attempts to send the messages + +@return +@internalAll +@param aFile File containing the task information +*/ +CMsvSendExe* CMsvSendExe::StartLC(const TDesC& aFileName, RFile& aFile ) + { + CMsvSendExe* self = new (ELeave) CMsvSendExe(aFileName,aFile ); + + CleanupStack::PushL(self); + + self->ConstructL(); + return self; + } + +void CMsvSendExe::ConstructL() + { + TRAPD(err, ProcessFileL()); + + if(err == KErrPermissionDenied) + { + //security violation + User::Leave(err); + } + + if (err != KErrNone) + RescheduleAllL(err); + + } + +void CMsvSendExe::ProcessFileL() +/* + ProcessFileL +*/ + { + RetrievePackagesL(); + RetrieveMessagesL(); + + if (iOperations.Count()) + { + CallMtmL(); + iActiveSchedulerWait.Start(); + } + else + { + SCHSENDLOG(FLog(iFileName, _L("\tNO msg to send!"))); + } + } + + +void CMsvSendExe::RetrievePackagesL() + { + + //open the filestore + CFileStore* store = CDirectFileStore::FromLC(iTaskFile);//pushes store + + RStoreReadStream instream; + instream.OpenLC(*store,store->Root());//pushes instream + + //get task count + TInt taskCount = instream.ReadInt32L(); + + SCHSENDLOG(FLog(iFileName, _L("\tTask Count=%d"), taskCount)); + + for (TInt curTask = 0; curTask < taskCount; curTask++) + { + CScheduledTask* task = CScheduledTask::NewLC(instream); + + AddPackageL(*task); + + CleanupStack::PopAndDestroy(task); + } + + CleanupStack::PopAndDestroy(2, store); + } + +#ifndef _MSG_NO_LOGGING +void CMsvSendExe::FLog(const TDesC& aFileName, TRefByValue aFmt, ...) + { + VA_LIST list; + VA_START(list, aFmt); + + TParse parse; + parse.Set(aFileName, NULL, NULL); + + TBuf<256> buf; + buf.Append(parse.Name()); + buf.Append(_L(": ")); + buf.AppendFormatList(aFmt, list); + + RFileLogger::Write(KSchSendLogDir, KSchSendExeLogFile, KSchSendExeLoggingMode, + buf); + } +#endif + + +/** + +The CMsvSendExe::CallMtmL method +Attempts to send the messages in the operation selection + +@internalAll +*/ +void CMsvSendExe::CallMtmL() + { + __ASSERT_DEBUG(iOperations.Count(), gPanic(ENoMessagesInSelection)); + + TInt count = iOperations.Count(); + + while (count--) + { + CMsvSendExeActive& active = *iOperations[count]; + SCHSENDLOG(FLog(iFileName, _L("\tStarting operation for %d msgs (Mtm=%d CommandId=%d)"), active.MsgCount(), active.Mtm().iUid, active.Package().iCommandId)); + active.StartL(); + SCHSENDLOG(FLog(iFileName, _L("\tStarted (Mtm=%d CommandId=%d)"), active.Mtm().iUid, active.Package().iCommandId)); + } + + iStatus = KRequestPending; + SetActive(); + } + + +/** + +The CMsvSendExe::DoCancel method +Cancels all outstanding operations and stops the active scheduler. + +@return +@internalAll +*/ +void CMsvSendExe::DoCancel() + { + SCHSENDLOG(FLog(iFileName, _L("\tOperations cancelled"), iStatus.Int())); + + TInt count = iOperations.Count(); + while (count--) + iOperations[count]->Cancel(); + + iActiveSchedulerWait.AsyncStop(); + } + + +/** + +The CMsvSendExe::RunL method +Called when all operations in iOperations have completed. +Stops the active scheduler. + +@return +@internalAll +*/ +void CMsvSendExe::RunL() + { + SCHSENDLOG(FLog(iFileName, _L("All operations completed"))); + iActiveSchedulerWait.AsyncStop(); + } + +void CMsvSendExe::AddPackageL(const CScheduledTask& aTask) + { + TMsvSchedulePackage pkg; + TSecurityInfo securityInfo = aTask.SecurityInfo(); + + if( (securityInfo.iSecureId.iId != KSchSendExeSid) && (securityInfo.iSecureId.iId != KMsvServerId) ) + { + if( PlatSec::ConfigSetting(PlatSec::EPlatSecDiagnotics) ) + { + _LIT(KMessage,"CMsvSendExe::AddPackageL ERROR %d"); + RDebug::Print(KMessage, KErrPermissionDenied); + } + if( PlatSec::ConfigSetting(PlatSec::EPlatSecEnforcement) ) + { + //security violation + User::Leave(KErrPermissionDenied); + } + } + + + pkg.UnpackL(aTask.Info(), aTask.Data()); + User::LeaveIfError(iPackages.Append(pkg)); + } + +/** + +The CMsvSendExe::RetrieveMessagesL method +Retrieves the message selection from the task file. + +@internalAll +*/ +void CMsvSendExe::RetrieveMessagesL() + { + iSession = CMsvSession::OpenSyncL(*this); + TInt count = iPackages.Count(); + + while(count--) + { + + AddTaskL(iPackages[count]); + + iPackages.Remove(count); + } + } + + +void CMsvSendExe::AddTaskL(const TMsvSchedulePackage& aPackage) + { + CMsvEntry* cEntry = NULL; + TRAPD(err, cEntry = iSession->GetEntryL(aPackage.iId)); + + if (err != KErrNotFound) + { + User::LeaveIfError(err); + CleanupStack::PushL(cEntry); + + TMsvEntry entry = cEntry->Entry(); + const TInt sendState = entry.SendingState(); + + //Only send the message if sending state is Scheduled or Resend. + if (entry.Scheduled() && (sendState == KMsvSendStateScheduled || sendState == KMsvSendStateResend)) + { + entry.SetSendingState(KMsvSendStateWaiting); + + // Fix for DEF000924: Need to be able to send/cancel an sms while another is being sent + if (entry.iServiceId == KMsvLocalServiceIndexEntryId && entry.iRelatedId != KMsvNullIndexEntryId) + { + SCHSENDLOG( FLog(iFileName, _L("Changing service from %x to %x"), entry.iServiceId, entry.iRelatedId)); + entry.iServiceId = entry.iRelatedId; + } + else + { + SCHSENDLOG( FLog(iFileName, _L("Not changing service from %x (related=%x)"), entry.iServiceId, entry.iRelatedId)); + } + // End of fix + + cEntry->ChangeL(entry); + AddTaskL(aPackage, entry.iMtm); + SCHSENDLOG(FLog(iFileName, _L("\t\tMsg=%d [Mtm=%d SendState=%d]"), aPackage.iId, entry.iMtm.iUid, entry.SendingState())); + } + else + { + SCHSENDLOG(FLog(iFileName, _L("\t\tIGNORING Msg=%d (Mtm=%d SendState=%d Scheduled=%d)"), aPackage.iId, entry.iMtm.iUid, sendState, entry.Scheduled())); + } + + CleanupStack::PopAndDestroy(cEntry); + } + else + { + SCHSENDLOG(FLog(iFileName, _L("\t\tIGNORING Msg=%d: NOT FOUND"), aPackage.iId)); + } + } + +/** + +The CMsvSendExe::AddTaskL method +Finds a CMsvSendExeActive in iOperations that corresponds to aPackage and aMtm. +If not found, then creates a new CMsvSendExeActive, and appends it to iOperations. +Then the message in aPackage is appended to the CMsvSendExeActive. + +@internalAll +@param aPackage Information about what operation to start +@param aMtm The MTM of the message identified in aPackage +*/ +void CMsvSendExe::AddTaskL(const TMsvSchedulePackage& aPackage, const TUid& aMtm) + { + //Add the task to an object in iOperations + + CMsvSendExeActive* active = NULL; + TInt count = iOperations.Count(); + + while (active == NULL && count--) + { + CMsvSendExeActive* tempActive = iOperations[count]; + + if (tempActive->Mtm() == aMtm && + tempActive->Package().iCommandId == aPackage.iCommandId && + tempActive->Package().iParameter == aPackage.iParameter && + tempActive->Package().iPollProgress == aPackage.iPollProgress) + active = tempActive; + } + + if (active == NULL) + { + active = CMsvSendExeActive::NewLC(*iSession, *this, aPackage, aMtm, Priority()); + User::LeaveIfError(iOperations.Append(active)); + CleanupStack::Pop(active); + } + + active->AppendL(aPackage.iId); + } + + +/** + +The CMsvSendExe::OperationComplete method +Called by a CMsvSendExeActive when it completes +If all operations in iOperations are complete, then CMsvSendExe completes itself + +@return +@internalAll +@param aActive The operation that has just completed +@param aError +*/ +void CMsvSendExe::OperationComplete(const CMsvSendExeActive& aActive, TInt aError) + { + SCHSENDLOG(FLog(iFileName, _L("\tOperation Complete (Mtm=%d CommandId=%d Err=%d)"), aActive.Mtm().iUid, aActive.Package().iCommandId, aError)); + + if (aError != KErrNone) + RescheduleOnError(aActive, aError); + +#ifdef _DEBUG + iOperationsComplete++; +#endif + + __ASSERT_DEBUG(!aActive.IsActive(), gPanic(EOperationCompletedTooSoon)); + + TInt count = iOperations.Count(); + + while (count--) + { + if (iOperations[count]->IsActive()) + { + __ASSERT_DEBUG(iOperationsComplete < iOperations.Count(), gPanic(ETooManyOperationsCompleted)); + return; + } + } + + TRequestStatus* status = &iStatus; + User::RequestComplete(status, KErrNone); + } + +_LIT(KSchSendExePanic, "SchSendExe"); + +GLDEF_C void gPanic(TSchSendExePanic aPanic) + { + User::Panic(KSchSendExePanic,aPanic); + } + +GLDEF_C void gPanic(TInt aPanic) + { + User::Panic(KSchSendExePanic,aPanic); + } + + +void CMsvSendExe::RescheduleOnError(const CMsvSendExeActive& aActive, TInt aError) + { + SCHSENDLOG(FLog(iFileName, _L("\tRescheduleOnError (Mtm=%d CommandId=%d InCount=%d)"), aActive.Mtm().iUid, aActive.Package().iCommandId, aActive.Selection().Count())); + RescheduleOnError(aActive.Selection(), aActive.Package(), aError); + } + +void CMsvSendExe::RescheduleAllL(TInt aError) + { + SCHSENDLOG(FLog(iFileName, _L("\tRescheduleAllL [aError=%d]"), aError)); + RScheduler scheduler; + TSchedulerItemRef ref; + ref.iHandle = KErrNotFound; + TInt count = 0; + TInt err = KErrNone; + TTime start; + start.UniversalTime(); + + TInt activeCount = iOperations.Count(); + while (activeCount--) + { + CMsvSendExeActive& active = *iOperations[activeCount]; + active.Cancel(); + TRAP(err, DoResheduleOnErrorL(scheduler, active.Selection(), active.Package(), aError, ref, count, start)); //ignore error + FailOnError(active.Selection(), aError); + } + + activeCount = iPackages.Count(); + CMsvEntrySelection* sel = new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(sel); + while (activeCount--) + { + const TMsvSchedulePackage& package = iPackages[activeCount]; + sel->Reset(); + sel->AppendL(package.iId); + TRAP(err, DoResheduleOnErrorL(scheduler, *sel, package, aError, ref, count, start)); //ignore error + FailOnError(*sel, aError); + } + CleanupStack::PopAndDestroy(sel); + + CompleteReschedule(scheduler, ref, count); + } + +void CMsvSendExe::CompleteReschedule(RScheduler& aScheduler, const TSchedulerItemRef& aRef, TInt aCount) + { + if (aCount != 0) + { + aScheduler.EnableSchedule(aRef.iHandle); + } + else if (aRef.iHandle != KErrNotFound) + { + aScheduler.DeleteSchedule(aRef.iHandle); + } + aScheduler.Close(); + + SCHSENDLOG(FLog(iFileName, _L("\tCompleteReschedule [Ref=%d OutCount=%d]"), aRef.iHandle, aCount)); + } + +void CMsvSendExe::RescheduleOnError(const CMsvEntrySelection& aSelection, const TMsvSchedulePackage& aPackage, TInt aError) + { + RScheduler scheduler; + TTime start; + start.UniversalTime(); + TSchedulerItemRef ref; + ref.iHandle = KErrNotFound; + TInt count = 0; + TRAPD(err, DoResheduleOnErrorL(scheduler, aSelection, aPackage, aError, ref, count, start)); //ignore error + + CompleteReschedule(scheduler, ref, count); + FailOnError(aSelection, aError); //fails outstanding messages that weren't rescheduled. Ignore error + } + + +void CMsvSendExe::DoResheduleOnErrorL(RScheduler& aScheduler, const CMsvEntrySelection& aSelection, const TMsvSchedulePackage& aPackage, TInt aError, TSchedulerItemRef& aRef, TInt& aCount, TTime& aStartTime) + { + SCHSENDLOG(FLog(iFileName, _L("\tDoRescheduleOnError [aError=%d]"), aError)); + + CMsvScheduleSettings* settings = CMsvScheduleSettings::NewL(); + CleanupStack::PushL(settings); + + TMsvSchedulePackage package(aPackage); + TInt selCount = aSelection.Count(); + TTaskInfo info; + TInt err = KErrNone; + + //Schedule each message + while (selCount--) + { + const TMsvId id = aSelection[selCount]; + + TBool scheduleMessage = ETrue; + CMsvEntry* cEntry = NULL; + TMsvEntry entry; + + if (iSession != NULL) + TRAP(err, cEntry = iSession->GetEntryL(id)); + + SCHSENDLOG(FLog(iFileName, _L("\t\tScheduling Task [Id=%d Err=%d iSession=0x%X cEntry=0x%X]..."), id, err, iSession, cEntry)); + + if (cEntry != NULL) + { + CleanupStack::PushL(cEntry); + entry = cEntry->Entry(); + switch (entry.SendingState()) + { + case KMsvSendStateWaiting: + case KMsvSendStateScheduled: + case KMsvSendStateResend: + + scheduleMessage = ETrue; + break; + + case KMsvSendStateFailed: + case KMsvSendStateSent: + case KMsvSendStateSuspended: + default: + + scheduleMessage = EFalse; + SCHSENDLOG(FLog(iFileName, _L("\t\tNot Scheduled Task [Id=%d State=%d Err=%d]"), id, entry.SendingState(), entry.iError)); + break; + } + } //end if + + if (scheduleMessage) + { + if (aRef.iHandle == KErrNotFound) + { + //Find or create the schedule + if (cEntry != NULL) + { + TRAP(err, RestoreScheduleSettingsL(entry.iServiceId, entry.iMtm, *settings)); //use default if err != KErrNone + SCHSENDLOG(FLog(iFileName, _L("\t\tRestoreScheduleSettings [Err=%d]"), err)); + } + + CMsvScheduleSend::ConnectAndRegisterL(aScheduler, *settings); + + if (FindorCreateScheduleL(aScheduler, aStartTime, *settings, aRef)) + aCount++; + + User::LeaveIfError(aScheduler.DisableSchedule(aRef.iHandle)); + } + + //Schedule the message + package.iId = id; + CMsvScheduleSend::ScheduleEntryL(aScheduler, aRef, package, info); + SCHSENDLOG(FLog(iFileName, _L("\t\tScheduleEntryL [Id=%d Task=%d]"), id, info.iTaskId)); + + if (cEntry != NULL) + { + //Update the message + entry.iError = aError; + TRAP(err, UpdateEntryL(*cEntry, entry, aRef, info, aStartTime)); + SCHSENDLOG(FLog(iFileName, _L("\t\tEntry updated [Err=%d]"), err)); + } + + aCount++; + + SCHSENDLOG(FLog(iFileName, _L("\t\tScheduled Task Complete [Id=%d Task=%d]"), id, info.iTaskId)); + } + + if (cEntry) + CleanupStack::PopAndDestroy(cEntry); + } //end while + + CleanupStack::PopAndDestroy(settings); + } + +TBool CMsvSendExe::FindorCreateScheduleL(RScheduler& aScheduler, TTime& aStartTime, const CMsvScheduleSettings& aSettings, TSchedulerItemRef& aRef) + { + aStartTime.UniversalTime(); + aStartTime += (TTimeIntervalMicroSeconds32) (KSchSendExeMinReschedule - KMsvSendExeOneMinute); + TInt err = KErrNotFound; + const TInt max = (KSchSendExeMaxReschedule - KSchSendExeMinReschedule) / KMsvSendExeOneMinute; + + for (TInt i = 0; err != KErrNone && i < max; i++) + { + aStartTime += (TTimeIntervalMicroSeconds32) KMsvSendExeOneMinute; + CMsvScheduleSend::RoundUpToMinute(aStartTime); + TRAP(err, CMsvScheduleSend::FindScheduleL(aScheduler, aStartTime, aRef)); + } + + if (err != KErrNone) + { + CMsvScheduleSend::CreateScheduleL(aScheduler, aSettings, aStartTime, aSettings.ValidityPeriod(), aRef); + SCHSENDLOG(FLog(iFileName, _L("\t\tSchedule Created (Ref=%d) for:"), aRef.iHandle)); + } + else + SCHSENDLOG(FLog(iFileName, _L("\t\tSchedule Found (Ref=%d) for:"), aRef.iHandle)); + +#ifndef _MSG_NO_LOGGING + TBuf<32> bufDate; + aStartTime.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(iFileName, _L("\t\t%S"), &bufDate)); +#endif + + return (err == KErrNone); + } + +void CMsvSendExe::RestoreScheduleSettingsL(TMsvId aServiceId, const TUid& aMtm, CMsvScheduleSettings& aSettings) + { + TMsvSelectionOrdering order; + order.SetShowInvisibleEntries(ETrue); + CMsvEntry* cEntry = CMsvEntry::NewL(*iSession, KMsvRootIndexEntryId, order); + CleanupStack::PushL(cEntry); + + if (aServiceId == KMsvLocalServiceIndexEntryId) + { + const TInt count = cEntry->Count(); + + for (TInt i = 0; i < count; i++) + { + const TMsvEntry& entry = (*cEntry)[i]; + if (entry.iType == KUidMsvServiceEntry && entry.iMtm == aMtm) + { + aServiceId = entry.Id(); + break; + } + } + } + + if (aServiceId == KMsvLocalServiceIndexEntryId) + User::Leave(KErrNotFound); + + CRepository* repository = CRepository::NewLC(aMtm); + TMsvScheduleSettingsUtils::LoadScheduleSettingsL(aSettings, *repository); + CleanupStack::PopAndDestroy(2, cEntry); // repository, cEntry + } + +void CMsvSendExe::FailOnError(const CMsvEntrySelection& aSelection, TInt aError) + { + if (iSession == NULL) + return; + + TInt selCount = aSelection.Count(); + TInt err = KErrNone; + CMsvEntry* cEntry = NULL; + + while (selCount--) + { + const TMsvId id = aSelection[selCount]; + TRAP(err, cEntry = iSession->GetEntryL(id)); + //no need for the cleanup stack + + if (err == KErrNone) + { + TMsvEntry entry(cEntry->Entry()); + switch (entry.SendingState()) + { + case KMsvSendStateWaiting: + case KMsvSendStateScheduled: + case KMsvSendStateResend: + + if (entry.iDate < iCreated) + { + //Message has not been rescheduled + entry.SetSendingState(KMsvSendStateFailed); + entry.SetScheduled(EFalse); + entry.SetFailed(ETrue); + entry.iError = aError; + TRAP(err, cEntry->ChangeL(entry)); //ignore error + } + + break; + + default: + break; + } + + delete cEntry; + cEntry = NULL; + } //end if + } //end while + } + +void CMsvSendExe::UpdateEntryL(CMsvEntry& aMsvEntry, TMsvEntry& aEntry, const TSchedulerItemRef& aRef, const TTaskInfo& aInfo, const TTime& aStartTime) + { + CMsvStore* store = aMsvEntry.EditStoreL(); + CleanupStack::PushL(store); + + TMsvEntryScheduleData data; + data.RestoreL(*store); + + CMsvScheduleSend::UpdateEntryAfterSchedule(aRef, aInfo, aStartTime, KMsvSendStateResend, aEntry, data); + + data.StoreL(*store); + store->CommitL(); + + CleanupStack::PopAndDestroy(store); + aMsvEntry.ChangeL(aEntry); + } +