diff -r 000000000000 -r 72b543305e3a mmsengine/mmsserver/src/mmsreceivemessage.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mmsengine/mmsserver/src/mmsreceivemessage.cpp Thu Dec 17 08:44:11 2009 +0200 @@ -0,0 +1,3028 @@ +/* +* Copyright (c) 2002-2007 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: +* State machine for message receiving +* +*/ + + + +// INCLUDE FILES +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// mms private headers +#include "mmsconst.h" +#include "mmsreceivemessage.h" +#include "mmssession.h" +#include "mmssettings.h" +#include "mmsservercommon.h" +#include "mmsheaders.h" +#include "mmsdecode.h" +#include "mmsencode.h" +#include "mmsscheduledentry.h" +#include "mmserrors.h" +#include "mmsserverentry.h" +#include "mmsconninit.h" +#include "MmsEnginePrivateCRKeys.h" +#include "MmsPhoneClient.H" + +// EXTERNAL DATA STRUCTURES + +// EXTERNAL FUNCTION PROTOTYPES +extern void gPanic(TMmsPanic aPanic); + +// CONSTANTS +// return codes for handling of messages addressed to an application +const TInt KMmsMessageForUnregisteredApplication = 1; +const TInt KMmsMessageMovedToApplicationFolder = 2; + +// MACROS + +// LOCAL CONSTANTS AND MACROS + +// MODULE DATA STRUCTURES + +// LOCAL FUNCTION PROTOTYPES + +// ==================== LOCAL FUNCTIONS ==================== + + +// ================= MEMBER FUNCTIONS ======================= + +// C++ default constructor can NOT contain any code, that +// might leave. +// +CMmsReceiveMessage::CMmsReceiveMessage( RFs& aFs ) + :CMmsBaseOperation( aFs ) + { + } + +// Symbian OS default constructor can leave. +void CMmsReceiveMessage::ConstructL( CMmsSettings* aMmsSettings ) + { + iFileOpen = EFalse; + CMmsBaseOperation::ConstructL( aMmsSettings ); + iMmsHeaders = CMmsHeaders::NewL( iMmsSettings->MmsVersion() ); + iNotification = CMmsHeaders::NewL( iMmsSettings->MmsVersion() ); + iBad = new( ELeave ) CMsvEntrySelection; + TRAPD ( error, {iMessageDrive = MessageServer::CurrentDriveL( iFs );}); + if ( error != KErrNone ) + { + // if cannot ask, use default + iMessageDrive = EDriveC; + } + + CActiveScheduler::Add( this ); + } + +// Two-phased constructor. +CMmsReceiveMessage* CMmsReceiveMessage::NewL( RFs& aFs, CMmsSettings* aMmsSettings ) + { + CMmsReceiveMessage* self = new ( ELeave ) CMmsReceiveMessage( aFs ); + + CleanupStack::PushL( self ); + self->ConstructL( aMmsSettings ); + CleanupStack::Pop( self ); + + return self; + } + + +// Destructor +CMmsReceiveMessage::~CMmsReceiveMessage() + { + + Cancel(); + + delete iMmsHeaders; + delete iNotification; + delete iBad; + // This is the message for local mode testing + // It is closed here only to make sure that it is + // always closed even if we encounter a serious error. + // otherwise it should already be closed by now. + if ( iFileOpen ) + { + iFile.Close(); + } + } + + +// --------------------------------------------------------- +// CMmsReceiveMessage::StartL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::StartL( + CMsvEntrySelection& aSelection, + CMsvServerEntry& aServerEntry, + TMsvId aService, + TRequestStatus& aStatus ) + { + __ASSERT_DEBUG( iState == EMmsOperationIdle, gPanic( EMmsAlreadyBusy ) ); + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("ReceiveMsg StartL") ); +#endif + // just initialize common member variables + CMmsBaseOperation::InitializeL( aSelection, aServerEntry, aService ); + + iReceivingMode = iMmsSettings->ReceivingModeHome(); + + iNotificationParent = KMsvNullIndexEntryId; + iBad->Reset(); + iNotification->Reset(); + iResponse->Reset(); // This sets the correct version + iMmsHeaders->Reset(); + iEntryUnderConstruction = KMsvNullIndexEntryId; + iAlreadyDeferred = EFalse; + iFileOpen = EFalse; + + // selection is supposed to point to a bunch of notifications + // containing the URIs the messages are to be fetched from + TInt count = iCurrentMessageNo; + iError = KErrNone; + + while ( count > 0 && ( iNotificationParent == KMsvNullIndexEntryId ) ) + { + // Load the notification + // iCurrentMessageNo (and therefore also count) has originally been + // set to iSelection->Count() so the index is always valid. + count--; // indexes are zero based + if ( count < iSelection->Count() ) + { + iError = iServerEntry->SetEntry( iSelection->At( count ) ); + } + else + { + iError = KErrNotFound; + } + if ( iError == KErrNone ) + { + TMsvEntry entry = iServerEntry->Entry(); + iNotificationParent = entry.Parent(); + } + } + + // release the last notification + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + + if ( iNotificationParent == KMsvNullIndexEntryId ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- could not access any entry to get parent") ); +#endif + // nothing to receive. Give up immediately + aStatus = KRequestPending; + TRequestStatus* status = &aStatus; + User::RequestComplete( status, iError ); + return; + } + + // Initialize everything into the failed list. + // Very pessimistic indeed. + + iBad->SetReserveL( iCurrentMessageNo ); + + Queue( aStatus ); + + // We complete ourselves to get into the state machine loop. + // The first thing to do is to check the roaming state. + FallThrough(); + + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::DoComplete +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::DoComplete(TInt& aError ) + { + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg DoComplete")); +#endif + // We should get here if we are cancelled, or if + // the cycle has completed (with or without error) + // Make sure the store is released + iDecoder->RelaseDataSink(); + + // If we have an error, we must delete the entry + // under construction. + // If we have successfully created the entry, we have set + // iEntryUnderConstruction to KMsvNullIndexEntryId. + // If iEntryUnderConstruction still points to an entry, it means + // that the cycle has been aborted, and the incomplete entry + // must be deleted. + if ( iEntryUnderConstruction != KMsvNullIndexEntryId ) + { + if ( iServerEntry->SetEntry( KMsvGlobalInBoxIndexEntryId ) == KErrNone ) + { + iServerEntry->DeleteEntry ( iEntryUnderConstruction ); + } + iEntryUnderConstruction = KMsvNullIndexEntryId; + } + + // Set error to entries that are still left + // If some of the entries already have an error set, + // it is not overridden. + UnSetSendMask( *iFailed, aError ); + + // If we are supporting MMBox, we must also clear the duplicates + // if we have notifications in bad list + + if ( iMmsSettings->MMBoxFolder() != KMsvNullIndexEntryId ) + { + TInt i = 0; +#ifndef _NO_MMSS_LOGGING_ + if ( iBad->Count() > 0 ) + { + TMmsLogger::Log( _L("- %d notifications on bad list "), iBad->Count() ); + } +#endif + + for ( i = 0; i < iBad->Count(); i++ ) + { + if ( iServerEntry->SetEntry( iBad->At( i )) == KErrNone ) + { + // we don't want to leave here, we do our best + TRAP_IGNORE( ClearDuplicateEntryOperationL() ); + } + } + } + } + +// start of ROAMING CHECK handling +// --------------------------------------------------------- +// CMmsReceiveMessage::RoamingCheck +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::RoamingCheck() + { + + TInt error = KErrNone; + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg roaming check start")); +#endif + + if ( !iMmsSettings->LocalMode() && !iMmsSettings->FetchOverride() && + ( ( iMmsSettings->ReceivingModeHome() != iMmsSettings->ReceivingModeForeign() ) || + ( iMmsSettings->ReceivingModeForeign() == EMmsReceivingManual ) || + ( iMmsSettings->ReceivingModeForeign() == EMmsReceivingPostpone ) ) ) + { + // In offline mode we cannot ask if we are roaming - just give up + if ( !NetworkOperationsAllowed() ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- Offline mode - cannot check network status")); +#endif + error = KMmsErrorOfflineMode; + iError = error; + } + else + { + // if we get an error we have not been able to connect to phone module + // and we cannot ask if we are roaming + TRAP( error, iPhone = CMmsPhoneClient::NewL() ) + } + + if ( error == KErrNone ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- Receivemsg getting network status")); +#endif + iPhone->Roaming( iStatus ); + } + } + else + { + // no roaming check needed +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- Roaming check not needed")); +#endif + TRequestStatus* status = &iStatus; + iStatus = KRequestPending; + // 0 = home network. If we don't care, we behave as at home + User::RequestComplete( status, 0 ); + } + + if ( error != KErrNone ) + { + // we got an error - iPhone is not going to complete us + TRequestStatus* status = &iStatus; + iStatus = KRequestPending; + User::RequestComplete( status, error ); + } + SetActive(); + + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::GetRoamingState +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::GetRoamingState() + { + + // if current receiving modes are valid, we have to set the message fetch state + // according to the receiving mode of the current (home or foreign ) network + + // Set the message fetch state according to current network value + if( iRegistrationStatus != 0 ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("roaming") ); +#endif + iReceivingMode = iMmsSettings->ReceivingModeForeign(); + } + else + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("in home network") ); +#endif + iReceivingMode = iMmsSettings->ReceivingModeHome(); + } + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- message receiving mode is %d"), iReceivingMode ); +#endif + + // if something is wrong, entries are marked failed. + if ( iError != KErrNone ) + { + // Set error to all entries + // If some of the entries already have an error set, + // it is not overridden. + UnSetSendMask( *iFailed, iError ); + + } + // if we have something in iError, we shall not continue + // to open IAP, and RunL will complete. + // All entries will be rescheduled + + } +// end of ROAMING CHECK handling + +// --------------------------------------------------------- +// CMmsReceiveMessage::DecodeResponseL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::DecodeResponseL() + { + FallThrough(); + return; + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::ConnectToIapL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::ConnectToIAPL() + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg open IAP") ); +#endif + if ( !iMmsSettings->LocalMode() ) + { + iError = iMmsSettings->ValidateSettings(); + } + CMmsBaseOperation::ConnectToIAPL(); + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::InitializeSessionL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::InitializeSessionL() + { + + // Here we should connect to MMSC + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg Connecting") ); +#endif + + // for testing purposes our messages are in files in the folder + // defined in iMmsSettings->iRootFolder. + if ( iMmsSettings->LocalMode() ) + { + HBufC* filename = HBufC::NewL( iMmsSettings->LocalModeIn().Length() ); + CleanupStack::PushL( filename ); + TPtr fileNamePtr = filename->Des(); + fileNamePtr.Copy( iMmsSettings->LocalModeIn() ); + iFs.SetSessionPath( fileNamePtr ); + CleanupStack::PopAndDestroy( filename ); + } + + CMmsBaseOperation::InitializeSessionL(); + + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::SubmitTransactionL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::SubmitTransactionL() + { + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg Receiving") ); +#endif + iEntryUnderConstruction = KMsvNullIndexEntryId; + iResponse->Reset(); // Reset sets the correct version + iAlreadyDeferred = EFalse; + iMmsHeaders->Reset(); + iEncodeBuffer->Reset(); + + // We don't totally give up in case of an error. + // We may have a long list of entries to handle. + // One entry may be garbage while others will be ok. + + // Call to IsConnected() clears error for a new entry or + // sets the error if we are not connected. If we are not + // connected, we must terminate the state machine loop + if ( !IsConnected() ) + { + return; + } + + TMsvEntry entry; + + // Load the notification. If an error occurs, we cannot continue + // with this notification. + if ( !LoadNotificationL( entry ) ) + { + // LoadNotificationL sets the state machine active if we can continue + // to next entry. + // If the situation is hopeless, the state machine is not active, and + // the loop terminates + return; + } + + + TInt size = iNotification->MessageSize(); +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- message size %d"), size); +#endif + // If the size in notification is bigger than the maximum size we can receive + // use the maximum size instead for the memory checks (we are not receiving + // more than that anyway) + if ( size > iMmsSettings->MaximumReceiveSize() ) + { + // reduce the size to maximum allowed + size = iMmsSettings->MaximumReceiveSize(); + } + + // Now we must check if the message should be rejected or deferred + // The function IsNotificationInsaneL() checks if the notification + // has incorrect message type or incorrect major version number. + // If the message is unrecognized, the response will be "unrecognized" + // The funtion sets the status, and later the response will be sent + // but there is no need to check for expiration or disk space in + // case the message will be rejected anyway. + + if ( !IsNotificationInsaneL() ) + { + // Check if message has expired. + // If it has, and we are in automatic mode, there is no need + // to send a response. + if ( !CheckExpirationL() ) + { + return; + } + + // Check disk space. + // we must add the safety margin here... + size += KMmsDiskSafetyMargin; + + // Query about disk space. + if ( TMmsGenUtils::DiskSpaceBelowCriticalLevelL( + &iFs, size, iMessageDrive ) ) + { + iNotification->SetStatus( KMmsMessageStatusDeferred ); + iError = KErrDiskFull; + } + } + + // if we have got an error for this message (KErrNoMemory or + // KErrDiskFull) we must store the information in the notification + // entry to discern these situations from cases where there + // is something wrong with the actual transaction. + if ( iError != KErrNone ) + { + TInt error = iServerEntry->SetEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + // If we cannot update our entry - that's too bad + if ( error == KErrNone ) + { + TMsvEntry tEntry; + tEntry = iServerEntry->Entry(); + tEntry.iError = iError; + iServerEntry->ChangeEntry( tEntry ); + } + // we clear the error now. The rest of the branching + // is not based on iError but on the notification status + iError = KErrNone; + // Release the entry + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + } + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- notification status %d"), iNotification->Status() ); + switch ( iNotification->Status() ) + { + case KMmsMessageStatusRejected: + TMmsLogger::Log( _L("- rejected") ); + break; + case KMmsMessageStatusDeferred: + TMmsLogger::Log( _L("- deferred") ); + break; + case KMmsMessageStatusRetrieved: + TMmsLogger::Log( _L("- retrieved") ); + break; + case KMmsMessageStatusUnrecognized: + TMmsLogger::Log( _L("- unrecognized") ); + break; + default : + TMmsLogger::Log( _L("- whatever") ); + break; + } +#endif + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- receiving mode: %d"), iReceivingMode ); +#endif + + // Sending the response tells the MMSC that the + // notification has been received and understood. + // If we do not want to fetch the message the + // WantToFetchThisL() subroutine will change the state + // so that after return the response will be sent to MMSC + + if ( !WantToFetchThisL( entry ) ) + { + return; + } + + // Now it seems that we are supposed to fetch something + // The notification has cleared all our tests and fetching is on + + // Now we should start a GET transaction with the + // URI we obtain from the headers + + // If we fail to get anything because the message has expired, + // we set iEntryUnderConstruction to KMsvNullIndexEntryId + // Otherwise we must reschedule the receive + + // At this point we will invoke the transaction that sends + // our get request to MMSC and when the transaction completes, + // the data has been stored in our encode buffer, if the status + // is KErrNone. Otherwise the completion status will contain the + // error code. + + if ( iMmsSettings->LocalMode() ) + { + LocalModeFetchL(); + // if only local mode, we complete ourselves +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- local mode, completing fetch with status %d"), iError ); +#endif + if ( !IsActive() ) + { + // If Local mode fetch fails it already calls ChangeStateL() and becomes active + // we cannot set us active a second time + FallThrough(); + } + } + else // global mode + { + // Chunked mode in global mode, too + // chunked mode is turned on here - other changes are generic + DoCreateEntryL(); + iDecoder->InitializeChunkedMode( *iEntryWrapper, *iMmsHeaders, *iEncodeBuffer ); + // end of chunked preparation + + // we get this far only if we are connected + HBufC* uri = HBufC::NewL( iNotification->ContentLocation().Length() + KMMSBufferExtra ); + CleanupStack::PushL( uri ); + uri->Des().Copy( iNotification->ContentLocation() ); +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("...Fetching from URI: %S"), uri ); +#endif + iMmsSession->FetchMessageL( + *uri, + *iEncodeBuffer, +// Use this buffer size when in chunked mode + KMmsChunkedBufferSize, // tell that only this buffer size is needed + *iEncoder, + *iDecoder, + iStatus ); + CleanupStack::PopAndDestroy( uri ); + SetActive(); + } + + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +TBool CMmsReceiveMessage::IsConnected() + { + if ( iConnected ) + { + // We clear the error at this point to tell that this message + // is all right so far. Notifications for failed messages + // remain in the selection, and they are rescheduled later + // The error is saved in the schedule data for the notification. + iError = KErrNone; + } + else + { + // We are in a totally hopeless mess. + // We don't have a connection. iError should contain the reason + // all we can do is give up + // If we return from RunL without becoming active again + // RunL completes. + // That's all we can do here... + // The selection arrays have information about + // which messages have succeeded and which not. + + // DoComplete sets an error code to those entries in the + // failed list that do not already have their error set. + // If we don't know what went wrong, we just say "disconnected" + if ( iError == KErrNone ) + { + iError = KErrDisconnected; + } + } + return iConnected; + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +TBool CMmsReceiveMessage::LoadNotificationL( TMsvEntry& aEntry ) + { + // set entry to next notification in list + iError = iServerEntry->SetEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + if ( iError != KErrNone ) + { + // The code called by SkipEntryL will make us active again + // We may now try the next entry. + SkipEntryL(); + return EFalse; + } + + aEntry = iServerEntry->Entry(); + if ( aEntry.iMtmData2 & KMmsNotifyResponseSent ) + { + iAlreadyDeferred = ETrue; + } + + CMsvStore* store = NULL; + + TRAP (iError, {store = iServerEntry->ReadStoreL();}); + // We have managed to generate some mysterious notifications + // where the ReadStoreL fails. + // These must be deleted, as they are garbage. + // I assume they are used by someone else. + // This means we just delete them from our list and forget them + // If we cannot get a read store, we cannot do anything with the + // schedule either. + if ( iError == KErrNoMemory ) + { + // Out of memory - hopeless. + // We return without becoming active again and the + // whole loop completes. + // We should try to disconnect, but if there is a serious + // memory problem I doubt that anything will work. + + // Release the entry + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + return EFalse; + } + + if ( iError != KErrNone ) + { + // don't discard the entry completely now + delete store; + store = NULL; + // The code called by SkipEntryL will make us active again + SkipEntryL(); + return EFalse; + } + + // We have access to the notification + CleanupStack::PushL( store ); + iNotification->RestoreL( *store ); + CleanupStack::PopAndDestroy( store ); + + // We must clear deferred status always: + // If we did not have enough memory the first time, + // the message was deferred. If we now have enough + // memory the old status must be cleared + + iNotification->SetResponseStatus( 0 ); // clear possible old status + iNotification->SetResponseTextL( TPtrC() ); // clear possible old text + + if ( iNotification->Status() == KMmsMessageStatusDeferred ) + { + iNotification->SetStatus( 0 ); + } + + // We must check if message fetching is deferred or totally off. + // if iReceivingMode == EMmsReceivingReject, notification has been deleted already. + if ( !iMmsSettings->FetchOverride() && + iNotification->Status() == 0 && + ( iReceivingMode == EMmsReceivingManual || iReceivingMode == EMmsReceivingPostpone ) ) + { + iNotification->SetStatus( KMmsMessageStatusDeferred ); + } + + // We get this far only if we are connected + aEntry.SetConnected( ETrue ); + aEntry.SetSendingState( KMsvSendStateSending ); + aEntry.iError = KErrNone; // optimistic... + iServerEntry->ChangeEntry( aEntry ); + + // Release the entry + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + + // Continue with this notification + return ETrue; + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +TBool CMmsReceiveMessage::CheckExpirationL() + { + + // If the message has expired, it can be rejected without further ado + // We allow a little slack here, if it expired just a few seconds ago, + // we still have hope... + // use universal time in case user changes location + TTime now; + TMsvEntry entry; + TBool canContinue = ETrue; // optimistic + + now.UniversalTime(); +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("MMS terminal universal datetime: ") ); + CMmsBaseOperation::LogDateL( now ); + TMmsLogger::Log( _L("MMS message expiry datetime:") ); + CMmsBaseOperation::LogNetworkFormatDateL( iNotification->ExpiryDate() ); +#endif + if ( ( ( iNotification->ExpiryDate() + iMmsSettings->ExpiryOvershoot() ) * KMmsMillion ) < + now.MicroSecondsFrom( TTime( KMmsYear1970String ) ).Int64() ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("message has expired by %d seconds"), + ( now.MicroSecondsFrom( TTime( KMmsYear1970String ) ).Int64())/KMmsMillion - + iNotification->ExpiryDate() - iMmsSettings->ExpiryOvershoot() ); +#endif + // If the notification is in inbox or in MMBox folder, fetch will continue. + if( entry.Parent() == KMsvGlobalInBoxIndexEntryId || + entry.Parent() == iMmsSettings->MMBoxFolder() ) + { + // Mark the notification expired + iError = iServerEntry->SetEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + if ( iError == KErrNone ) + { + entry = iServerEntry->Entry(); + entry.iMtmData2 |= KMmsMessageExpired; + iServerEntry->ChangeEntry( entry ); + } + // Release the entry + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Expired notification. Fetch continues") ); +#endif + } + else + { + // iError == KErrNone, the last stage will just remove our entry from the list + // There is no need to send any reply to an expired message. + // The MMSC will not be interested in getting a "reject" + // response to an expired message. + iResponse->SetStatus( KMmsMessageStatusRejected ); + iBad->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + // return to the beginning of the state machine loop + iState = EMmsOperationUpdatingEntryStatus; + // The code called by ChangeStateL will make us active again + ChangeStateL(); + canContinue = EFalse; // loop to next state + } + } + return canContinue; // can continue + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +TBool CMmsReceiveMessage::IsNotificationInsaneL() + { + TBool isInsane = EFalse; // optimistic + // If the message is unrecognized, the response will be "unrecognized" + // Either the message type is unknown, or the major version number differs + if ( ( iNotification->MmsVersion() & KMmsMajorVersionMask ) != + ( iMmsSettings->MmsVersion() & KMmsMajorVersionMask ) ) + { + // major versions differ, send back 1.0 version response + iResponse->SetMmsVersion( KMmsVersion1 ); + iNotification->SetStatus( KMmsMessageStatusUnrecognized ); + iBad->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + isInsane = ETrue; + } + else if ( iNotification->MessageType() != KMmsMessageTypeMNotificationInd && + iNotification->MessageType() != KMmsMessageTypeMBoxDescr ) + { + // We have been pushed something we cannot handle. + // delivery reports have been picked out earlier, so they cause + // no problems here. + iNotification->SetStatus( KMmsMessageStatusUnrecognized ); + iBad->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + isInsane = ETrue; + } + return isInsane; + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +TBool CMmsReceiveMessage::WantToFetchThisL( TMsvEntry& aEntry ) + { + if ( ( iReceivingMode != EMmsReceivingAutomatic && !iMmsSettings->FetchOverride() ) || + iNotification->Status() == KMmsMessageStatusRejected || + iNotification->Status() == KMmsMessageStatusDeferred || + iNotification->Status() == KMmsMessageStatusRetrieved || + iNotification->Status() == KMmsMessageStatusUnrecognized ) + { + // We don't want this message for one reason or another + // We shall send reject message or deferred message and + // remove this notification from the scheduling. + // If the fetching is deferred, the notification should remain + // in the machine waiting for better times. + // If the message is rejected, the notification is deleted. + // In both cases the schedule must be removed. A deferred + // notification is not rescheduled before the message fetching + // is turned on again. + + // If the whole message is in the extended notification, + // the status is "retrieved" + + // However, if the "deferred" response has already been sent, + // we should not send it again + + iResponse->SetStatus( iNotification->Status() ); + + if ( iResponse->Status() != KMmsMessageStatusRejected && + iResponse->Status() != KMmsMessageStatusDeferred && + iResponse->Status() != KMmsMessageStatusRetrieved && + iResponse->Status() != KMmsMessageStatusUnrecognized ) + { + // We have an unlucky message. The notification arrived + // while we were supposed to fetch it, but before it was + // actually fetched, receiving was turned off. + // We try to defer it hoping it can be fetched before it + // expires. + // A message should be scheduled for fetching only a few + // seconds after the notification arrives, so that if the + // user manages to turn the fetching off before we fetch + // the message we can assume that he does not want this one either + // (at least not now). We are a bit conservative, though, + // we don't reject this one as it was supposed to be fetched + // before it fell into the time trap. + iResponse->SetStatus( KMmsMessageStatusDeferred ); + } + + if ( aEntry.iMtmData2 & KMmsNotifyResponseSent ) + { + // if the notify response has already been sent for this message, + // we don't send it again. + // When the message fetch state is switched back on, + // server Mtm must check the pending notifications and + // reschedule them for fetching. + // An already deferred notification should not be in our + // list, but I'm paranoid and check in case some notification + // has fallen into a time trap. + iAlreadyDeferred = ETrue; + iState = EMmsOperationStoringResponseData; + } + else + { + // The function called by ChangeStateL will make us active again + iState = EMmsOperationStoringResponseData; + } + // We are not fetching this message. + // Either we send a response to MMSC or we just move to next notification. + ChangeStateL(); + return EFalse; + } + return ETrue; + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::DoCreateEntryL() + { + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg DoCreateEntryL") ); +#endif + + // We create a message entry into the inbox + // it is invisible and in preparation until + // we decide that we have successfully fetched the message + // The entry must exist before the message is fetched, because + // decode puts data into an existing entry, and + // creates children to the entry for attachments. + + // If we have failed in storing data to the entry, it may be non-zero + // We try to delete it before creating a new one + + if ( iEntryUnderConstruction != KMsvNullIndexEntryId ) + { + if ( iServerEntry->SetEntry( KMsvGlobalInBoxIndexEntryId ) == KErrNone ) + { + iServerEntry->DeleteEntry ( iEntryUnderConstruction ); + } + iEntryUnderConstruction = KMsvNullIndexEntryId; + } + + // If the creation of the entry is successful, we + // set our entry to point to the newly created entry + // to get data content to it. + if ( iError == KErrNone ) + { + TMsvId targetId = KMsvGlobalInBoxIndexEntryId; + + TMsvEntry tEntry; + + // set all relevant flags in tMsvEntry + tEntry.iType = KUidMsvMessageEntry; + tEntry.iMtm = KUidMsgTypeMultimedia; + tEntry.iServiceId = iService; + tEntry.SetUnread( ETrue ); + tEntry.SetNew( ETrue ); + tEntry.SetVisible( EFalse ); + tEntry.SetComplete( EFalse ); + tEntry.SetInPreparation( ETrue ); + tEntry.SetReadOnly( EFalse ); + + // Query about disk space. + if ( TMmsGenUtils::DiskSpaceBelowCriticalLevelL( + &iFs, KMmsIndexEntryExtra, iMessageDrive ) ) + { + // we use standard error code here + iError = KErrDiskFull; + } + if ( iError == KErrNone ) + { + iError = iServerEntry->SetEntry( targetId ); + } + + if ( iError == KErrNone ) + { + iError = iServerEntry->CreateEntry( tEntry ); + } + iEntryUnderConstruction = tEntry.Id(); + if ( iError == KErrNone ) + { + iError = iEntryWrapper->SetCurrentEntry( iEntryUnderConstruction ); + } + } + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::StoreResponseL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::StoreResponseL() + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg Store Response") ); +#endif + TInt error; + TInt fatal = EFalse; // fatal error, no use to retry + + // If we have got this far, we don't return to CreateEntry anymore + // even if we encounter backup/restore problems later. + + if ( iError == KErrNone ) + { + // if we have no error, me must retry to make the entry visible + iCriticalState = EMmsOperationStoringResponseData; + } + else + { + iCriticalState = EMmsOperationSendingAck; + } + + // At the moment we should not receive any extra message types, + // so that the only error of this type would + // be wrong type of message either from WAP stack or from MMSC. + // Send request is all right in local mode, as we get back + // the messages we send ourselves. + // We accept the send requests as retrieve confirmations, + // as they are importand during testing phase: + // We can put any message someone has sent into the folder + // representing the simulated MMSC and try to fetch it as + // if it were a real message. + // We can't check the message type if we have encountered an error. + // In that case the headers may not have been correctly decoded + if ( iError == KErrNone && + iResponse->Status() != KMmsMessageStatusRejected && + iResponse->Status() != KMmsMessageStatusDeferred && + iResponse->Status() != KMmsMessageStatusRetrieved && + iMmsHeaders->MessageType() != KMmsMessageTypeMRetrieveConf && + iMmsHeaders->MessageType() != KMmsMessageTypeMSendReq ) + { + iResponse->SetStatus( KMmsMessageStatusUnrecognized ); + iError = KMmsErrorStatusUnsupportedMessage; + } + + // If we have managed to decode the response, it may contain a status + // that must be mapped to an error. The error may be permanent or temporary + + if ( iError == KErrNone ) + { + // we have got a response from MMSC, and decoded it, but in version 1.1 + // or later the response may still contain an error code. + // if response text length was 0, it was deleted + + // - if error is permanent, notification will be deleted. + // - if error is transient, response text is copied to notification + // and UI components may show it to the user if needed. + + // iError is set to the correct value depending on response status + // "fatal" indicates if the error is permanent or temporary + fatal = MapErrorStatus( iMmsHeaders->ResponseStatus() ); + } + + // If we get a transient failure from MMSC, we store the possible text and status code + // into our notification. UI may show the text to the user in case there is something + // the user might do about the failure. Permament failures are stored in the message + // entry. + // The application id is also copied to the notification in case application id is present + // in the message but not in the notification. + // If the application has not been registered or memory runs out, the user may be + // informed that the notification refers to a message that belongs to an application. + + CopyDataFromMessageToNotificationL(); + + // If there has been an error and logging is allowed, dump the buffer into a file + // to support error analysis later + DumpIfNeededL(); + + // We must try to finalize even if we have encountered an error. + // If the decode has failed, iError contains something like "KErrCorrupt". + // If the operation has been successful, we make the entry visible + // and remove the in preparation flag. The user should then be able + // to see the new message entry. + + // free the memory we don't need anymore + if ( iEncodeBuffer ) + { + iEncodeBuffer->Reset(); + } + TMsvEntry tEntry; + + // Check if the message is going to an unregistered application. + // If it is, it's going to be rejected + iRegistered = EFalse; + + if ( iMmsHeaders->ApplicId().Length() > 0 ) + { + iRegistered = RegisteredL( iMmsHeaders->ApplicId() ); + if ( !iRegistered ) + { + // not registered - reject it + iResponse->SetStatus( KMmsMessageStatusRejected ); + // we clear whatever error we may have because we are + // going to reject the message anyway. + iError = KErrNone; + } + } + + // If the message is going to an application we must check if we can free disk + // space by deleting older messages addressed to the same application. + + if ( iError == KErrDiskFull && iRegistered ) + { + // Check if we can free disk space and reschedule the fetch. + // In manual mode the fetch will not be rescheduled. + // If we can free memory we will change the error code to KMmsErrorApplicationDiskFull + // If we cannot free memory, the error code is left as is and the user must + // delete something else in order to be able to receive the message. + DeleteApplicationMessagesL(); + } + + // If we have tried to fetch a message, and got + // EMRUExceeded, we'll reject the message. If it + // was already deferred, no new response is sent. + // The notification will be deleted, it is hopeless. + + if ( iError == KMmsErrorEMRUExceeded ) + { + iResponse->SetStatus( KMmsMessageStatusRejected ); + iFailed->Delete( iCurrentMessageNo - 1 ); + iBad->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + } + + if ( iError != KErrNone || + iResponse->Status() == KMmsMessageStatusRejected || + iResponse->Status() == KMmsMessageStatusDeferred || + iResponse->Status() == KMmsMessageStatusRetrieved || + iResponse->Status() == KMmsMessageStatusUnrecognized ) + { + // If the failure was fatal (permanent error from MMSC) + // we don't send any response back. If the failure was + // not fatal, we send "deferred" hoping we can retry later + if ( fatal ) + { + // The notification is deleted, as we have got an MMS message. + iFailed->Delete( iCurrentMessageNo - 1 ); + iBad->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + // we don't send back a response if failing was fatal. + iResponse->Reset(); + } + else if ( iError == KMmsErrorHTTPConfiguration || + iError == KMmsErrorHTTPNotFound || + iError == KMmsErrorHTTPServerDown || + iError == KMmsErrorHTTPClientError ) + { + // if we have got a HTTP error, there is no sense to send ack back + iResponse->Reset(); + } + else if ( !iAlreadyDeferred && + iResponse->Status() != KMmsMessageStatusRejected && + iResponse->Status() != KMmsMessageStatusRetrieved && + iResponse->Status() != KMmsMessageStatusUnrecognized) + { + iResponse->SetStatus( KMmsMessageStatusDeferred ); + EncodeNotifyResponseL(); + } + else if ( iResponse->Status() == KMmsMessageStatusUnrecognized || + iResponse->Status() == KMmsMessageStatusRejected) + { + EncodeNotifyResponseL(); + } + else if ( iResponse->Status() == KMmsMessageStatusRetrieved ) + { + EncodeNotifyResponseL(); + iFailed->Delete( iCurrentMessageNo - 1 ); + iSuccessful->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + } + else + { + // We have nothing to send, we hope we can try + // again later. + iResponse->Reset(); + } + } + +#ifndef _NO_MMSS_LOGGING_ + // log the response status we are about to send + TMmsLogger::Log( _L("- Response status to be: %d"), iResponse->Status() ); +#endif + + // If we have no entry, the fetching has failed. If the failing was fatal, + // the notification has been appended to the iBad list. + // It will be deleted from the iFailed list later. + // Those entries that end in the iBad list are just + // deleted, not rescheduled. + + TInt applicationMessageStatus = 0; + + // If we got an error, we delete the entry under construction. + // But if the error is "fatal" it means MMSC returned some error message, + // and the error message entry must be kept and shown to the user + // The corresponding notification remains in the failed list + // in order to be rescheduled. + // If the notification was bad, the corresponding notification + // has already been appended to bad list to be removed from + // task scheduler. + if ( iEntryUnderConstruction != KMsvNullIndexEntryId && iError != KErrNone && !fatal ) + { + if ( iMmsSettings->LocalMode() ) + { + CloseLocalFileL( EFalse ); + } + // if setting server entry to inbox fails, deletion of entry will fail, + // but there is nothing we can do about it. + iServerEntry->SetEntry( KMsvGlobalInBoxIndexEntryId ); + iServerEntry->DeleteEntry ( iEntryUnderConstruction ); + iEntryUnderConstruction = KMsvNullIndexEntryId; + } + + if ( iEntryUnderConstruction != KMsvNullIndexEntryId ) + { + // We don't care if this leaves. We just do our best. + // If this function fails, our message will have some extra parts + TRAP( error, HandleMultipartAlternativeL() ); + } + + // If we have an entry we have either successfully fetched a message + // or received an error message about a fatal (permanent) error from MMSC + // The entry must now be made visible to the user. + if ( iEntryUnderConstruction != KMsvNullIndexEntryId ) + { + error = iServerEntry->SetEntry( iEntryUnderConstruction ); + + if ( error == KErrNone ) + { + // we don't check disk space here, as this + // is local mode only: We just save the message id + // for testing purposes. The message id is about 5 bytes + // And we clear possible TID, that makes message shorter than before. + CMsvStore* store = NULL; + TRAP ( error, store = iServerEntry->EditStoreL() ) + if ( store ) + { + CleanupStack::PushL( store ); + } + if ( iMmsSettings->LocalMode() && + iMmsHeaders->MessageId().Length() == 0 ) + { + iMmsHeaders->SetMessageIdL( + iNotification->ContentLocation().Mid( + iMmsSettings->LocalModeIn().Length() ) ); + } + if ( iAlreadyDeferred ) + { + // we need to save the TID from the message to the response + iResponse->SetTidL( iMmsHeaders->Tid() ); + } + // clear TID from the message to prevent the same TID from being used later + // in case the message is copied around. + iMmsHeaders->SetTidL( TPtrC8() ); + // Data is copied in case the message does not contain sender or subject + // Data is copied only if disk space is not below critical level + CopyDataFromNotificationToMessageL(); + if ( store ) + { + TRAP ( error, + { + iMmsHeaders->StoreL(*store); + store->CommitL(); + }); + // If we managed to create the store without leaving, it is on cleanup stack + CleanupStack::PopAndDestroy( store ); + store = NULL; + } + if ( iAlreadyDeferred ) + { + // restore the TID + iMmsHeaders->SetTidL( iResponse->Tid() ); + } + + tEntry = iServerEntry->Entry(); + + // If message contains application id, check, + // if a folder, the name of which is application id is found + // and move the message to this folder. + // If the application is not registered, the message is deleted + applicationMessageStatus = MoveToApplicationFolderIfNeededL( tEntry ); + + if ( applicationMessageStatus != KMmsMessageForUnregisteredApplication ) + { + // reload the entry in case the message was moved + tEntry = iServerEntry->Entry(); + + SetIndexEntryBitsForReceivedMessage( tEntry ); + + // if we cannot make our entry visible because of backup/restore, we must retry. + error = iServerEntry->ChangeEntry( tEntry ); + + // This is the critical point. + // We must return before iEntryUnderConstruction is set to KMsvNullIndexEntryId + // and the notification list is edited + + if ( error <= (TInt) KMsvMediaUnavailable && + error >= (TInt) KMsvIndexRestore && + !iDoNotWaitForBackupEnd ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- must wait for backup end ") ); +#endif + iState = EMmsOperationWaitingForBackupEnd; + // This will take us back to RunL + WaitForBackupEnd(); + return; + } + } + + if ( iMmsSettings->LocalMode() ) + { + CloseLocalFileL( ETrue ); + } + + // done with this entry + iEntryUnderConstruction = KMsvNullIndexEntryId; + } + + // The task manager seems to handle deletion of entries + // gracefully nowadays. We can just delete handled entries + // We delete the notification as soon as possible + // as we don't want it hanging around after the message + // has been successfully fetched + + if ( !fatal ) + { + // Check duplicate before deleting notification entry + if ( iNotificationParent == KMsvGlobalInBoxIndexEntryIdValue || + ( iNotificationParent == iMmsSettings->MMBoxFolder() )) + { + // For the time being Java wants these notifications to be deleted + // and applicationMessageStatus is always 0. + // This may change for other type of applications. + ClearOperationL( applicationMessageStatus ); + } + else + { + // If the notification is not visible to the user, + // we don't need to save the application routing status. + // The rest of the handling can proceed normally - + // the notification will be deleted + applicationMessageStatus = 0; + } + + // If the notification does not need to be left around to inform user, + // it can be deleted now + if ( applicationMessageStatus == 0 ) + { + error = iServerEntry->SetEntry( iNotificationParent ); + if ( error == KErrNone ) + { + iServerEntry->DeleteEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + } + else + { + // we use this list as backup: + // if we cannot delete now, we try again later. + iSuccessful->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + } + } + // Even if the notification was left in the inbox, + // it is deleted from the failed list because it needs no more handling + iFailed->Delete( iCurrentMessageNo - 1 ); + + // All went well. We can acknowledge the delivery. + // If the MMSC is not interested in the acknowledgement, + // it has sent us no TID. + // If there is no TID, the response won't be sent. + // If Retrieve confirmation has not been sent, it must be + // sent instead of acknowledge indication + + // We don't send an ack back if we have received a response + // from MMSC that indicates a fatal failure. + // Most likely the message was expired or has disappeared + // for some other reason (or did not exist in the first place) + + iResponse->SetStatus( KMmsMessageStatusRetrieved ); + if ( !iAlreadyDeferred ) + { + EncodeNotifyResponseL(); + } + else + { + EncodeAcknowledgeIndicationL(); + } + } + } + + if ( iFileOpen ) + { + iFile.Close(); // just in case... + iFileOpen = EFalse; + } + + // release entry in case we were holding the service entry + // We should not leave our entry pointing to the serive entry + // when we become active, others may want to use it. + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + + iStatus = KRequestPending; + SetActive(); + TRequestStatus* status = &iStatus; + // We propagate the original error no matter what has happened here + User::RequestComplete( status, iError ); + + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::SendAckL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::SendAckL() + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg Sending response to receive request") ); +#endif + + // Send response to MMSC. + // response is in iResponse. + // If no reply should actually be sent, length of TID is 0. + + // We may try to send response even if we have encountered an error. + // We try to send "deferred" if we couldn't fetch the message. + // We hope the MMSC will try to keep the message in that case. + + // iResponse has Tid, if it contains something reasonable. + + if ( iResponse->Tid().Length() > 0 ) + { + if ( !iMmsSettings->LocalMode() ) + { + // send the response + iMmsSession->SendMessageL( + iUri->Des(), + *iEncodeBuffer, + *iEncoder, + *iDecoder, + iStatus ); + SetActive(); + } + else + { + iStatus = KRequestPending; + SetActive(); + TRequestStatus* status = &iStatus; + // We propagate the original error no matter what has happened here + User::RequestComplete( status, iError ); + } + } + else + { + // No response to send. + // Just continue to next state. + iStatus = KRequestPending; + SetActive(); + TRequestStatus* status = &iStatus; + // We propagate the original error no matter what has happened here + User::RequestComplete( status, iError ); + } + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::UpdateEntryStatusL() +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::UpdateEntryStatusL() + { + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg Checking response success") ); +#endif + + TInt error; + TMsvEntry tEntry; + TInt count = iBad->Count() - 1; + + // update scheduling info for this notification + error = iServerEntry->SetEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + if ( error == KErrNone ) + { + tEntry = iServerEntry->Entry(); + error = tEntry.iError; + if ( error == KErrNone ) + { + error = iError; + } + // In some rare cases accessing the store for the notification may fail + // in that case the schedule is not updated. + TRAP( error, + { + CMsvStore* store = iServerEntry->EditStoreL(); + CleanupStack::PushL( store ); + CMmsScheduledEntry* mmsScheduledEntry = + CMmsScheduledEntry::NewL( iServerEntry->Entry() ); + CleanupStack::PushL( mmsScheduledEntry ); + mmsScheduledEntry->RestoreL( *store ); + UpdateRecipient( iError, *mmsScheduledEntry ); + mmsScheduledEntry->StoreL( *store ); + store->CommitL(); + CleanupStack::PopAndDestroy( mmsScheduledEntry ); + CleanupStack::PopAndDestroy( store ); + }); + } + + // If the current notification has entered the "bad" list, + // it should not be rescheduled. + // For example, the message may be "deferred" because it is too + // big to ever fit in our phone. It must be deleted from the "failed" + // list, because it should not be rescheduled. + + TInt count2 = iFailed->Count() - 1; + if ( count >= 0 && + count2 >= ( iCurrentMessageNo - 1 ) && + iBad->At( count ) == iFailed->At( iCurrentMessageNo - 1 ) ) + { + // should not be rescheduled. + iFailed->Delete( iCurrentMessageNo - 1 ); + } + else if ( iError == KErrNone ) + { + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg all is well") ); +#endif + // All is well. + // If message was rejected the notification can be thrown away + + if ( iResponse->Status() == KMmsMessageStatusRejected || + iResponse->Status() == KMmsMessageStatusUnrecognized ) + { + // The notifications in the received list will be deleted. + // If the message was rejected, the notification will be + // deleted. + error = iServerEntry->SetEntry( iNotificationParent ); + if ( error == KErrNone ) + { + iServerEntry->DeleteEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + } + else + { + // we use this list as backup: + // if we cannot delete now, we try again later. + iSuccessful->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + } + iFailed->Delete( iCurrentMessageNo - 1 ); + } + + if ( iResponse->Status() == KMmsMessageStatusDeferred ) + { + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg responsestatus is deferred.") ); +#endif + // we have sent a "deferred" response. + // We should keep the notification, but not reschedule it. + // If we delete it from failed list, it wont get rescheduled. + // If we don't add it into received list, it will not be deleted. + // We must mark, that the "deferred" response was successfully sent + // so that we will not send it again later. + + error = iServerEntry->SetEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + // If we cannot update our entry - that's too bad + // it just means that the response may be sent a second time... + // The system must not get mad about that. + // The system may be unreliable anyway, and some response may get lost. + // We just do our best. + if ( error == KErrNone ) + { + iNotification->SetStatus( KMmsMessageStatusDeferred ); + CMsvStore * store = iServerEntry->EditStoreL(); + CleanupStack::PushL( store ); + iNotification->StoreL( *store ); + store->CommitL(); + CleanupStack::PopAndDestroy( store ); + tEntry = iServerEntry->Entry(); + tEntry.iMtmData2 |= KMmsNotifyResponseSent; + tEntry.iMtmData2 &= ~KMmsDeferredButResponseNotSent; + tEntry.SetConnected( EFalse ); + tEntry.SetFailed( EFalse ); + tEntry.SetSendingState( KMsvSendStateUnknown ); + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg notify response sent") ); +#endif + + // change the iMtm type to notification + // and move the notification to the inbox + TBool moveToInbox = EFalse; + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- message receiving mode %d"), iReceivingMode ); +#endif + if( iReceivingMode == EMmsReceivingManual && !iMmsSettings->FetchOverride() ) + { + // Change the iMtm from KUidMsgTypeMultimedia to + // KUidMsgMMSNotification + tEntry.iMtm = KUidMsgMMSNotification; +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("marking as a notification") ); +#endif + +#ifndef _NO_MMSS_LOGGING_ +TMmsLogger::Log( _L("tentry.iMtm is %d"), tEntry.iMtm.iUid ); +#endif + tEntry.SetVisible( ETrue ); + tEntry.SetComplete( ETrue ); + tEntry.SetInPreparation( EFalse ); + tEntry.SetReadOnly( ETrue ); + // This is a notification, therefore there is no sending time available. + // We can only display the arrival time + tEntry.iDate.UniversalTime(); // this is arrival time + + tEntry.iMtmData1 |= KMmsMessageMobileTerminated; + + // Subject, sender and size are set in CMmsDecode. + // Notification cannot contain multiple recipients + // no need to check or set the multiple recipient bit + + moveToInbox = ETrue; + } + iServerEntry->ChangeEntry( tEntry ); + + + // if manual mode is on and the fetchoverride is not on. + // we have received a notification and sent a notifyresponse to the MMSC + // the notification should be moved to the inbox + if ( moveToInbox ) + { + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg notification entry to Inbox") ); +#endif + + if ( iServerEntry->SetEntry( tEntry.Parent() ) == KErrNone ) + { + iServerEntry->MoveEntryWithinService( tEntry.Id(), + KMsvGlobalInBoxIndexEntryIdValue ); + } + } + } + + if ( iReceivingMode != EMmsReceivingAutomatic && !iMmsSettings->FetchOverride() ) + { + // If fetching is on, the message was deferred because of an error. + // In that case we let it remain in the failed list for retries. + // If fetching is deferred, manual or off, we don't keep the failed list, + // The receiving mode must be changed before we retry the operation. + + // Should we remove the schedule... + iFailed->Delete( iCurrentMessageNo - 1 ); +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg deleting from the iFailedList") ); +#endif + } + } + } + else + { + // keep LINT happy + } + + // release entry in case we were holding the service entry + // We should not leave our entry pointing to the serive entry + // when we become active, others may want to use it. + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + // just return back to the start of the loop + // to handle next message + iStatus = KRequestPending; + SetActive(); + TRequestStatus* status = &iStatus; + // We propagate the original error no matter what has happened here + User::RequestComplete( status, iError ); + + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::EncodeNotifyResponseL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::EncodeNotifyResponseL() + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- encoding m-notifyresp-ind") ); +#endif + iResponse->SetMessageType( KMmsMessageTypeMNotifyRespInd ); + iResponse->SetTidL( iNotification->Tid() ); + // message status has been set before we are called. + iResponse->SetReportAllowed( iMmsSettings->DeliveryReportSendingAllowed() ); + iEncoder->EncodeHeadersL( *iResponse, *iEncodeBuffer ); + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::EncodeAcknowledgeIndicationL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::EncodeAcknowledgeIndicationL() + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- encoding m-acknowledge-ind") ); +#endif + iResponse->SetMessageType( KMmsMessageTypeAcknowledgeInd ); + + // The TID has already been set to iResponse earlier, do not override it here + + // version value is set by the encoder. + // This response has no status. It is only sent when the status is "Retrieved" + iResponse->SetReportAllowed( iMmsSettings->DeliveryReportSendingAllowed() ); + iEncoder->EncodeHeadersL( *iResponse, *iEncodeBuffer ); + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::HandleMultipartAlternativeL() +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::HandleMultipartAlternativeL() + { + // This is just "best effort" + // If we cannot properly clear the extra attachments, they are just left "as is" + + if ( iMmsHeaders->MultipartType() != KMmsAssignedApplicationVndWapMultipartAlternative ) + { + return; // nothing to do + } + + User::LeaveIfError( iEntryWrapper->SetCurrentEntry( iEntryUnderConstruction ) ); + CMsvStore* store = iEntryWrapper->EditStoreL(); + CleanupStack::PushL( store ); + + MMsvAttachmentManager& attachMan = store->AttachmentManagerL(); + MMsvAttachmentManagerSync& attachManSync = store->AttachmentManagerExtensionsL(); + + TInt count = attachMan.AttachmentCount(); + + if ( count <= 1 ) + { + CleanupStack::PopAndDestroy( store ); + return; // only one part, keep it + } + + TInt i; + CMsvMimeHeaders* mimeHeaders = CMsvMimeHeaders::NewL(); + CleanupStack::PushL( mimeHeaders ); + + for ( i = count; i > 0; i-- ) + { + CMsvAttachment* attachInfo = attachMan.GetAttachmentInfoL( i - 1 ); + CleanupStack::PushL( attachInfo ); + TPtrC8 pointer; + pointer.Set( attachInfo->MimeType() ); + HBufC8* temp = NULL; + if ( pointer.Length() == 0 ) + { + // mime type not set. Try mime headers + mimeHeaders->RestoreL( *attachInfo ); + temp = HBufC8::NewL( mimeHeaders->ContentType().Length() + + mimeHeaders->ContentSubType().Length() + 1 ); + temp->Des().Copy( mimeHeaders->ContentType() ); + temp->Des().Append( KMmsSlash8 ); + temp->Des().Append( mimeHeaders->ContentSubType() ); + pointer.Set( temp->Des() ); + } + CleanupStack::PushL( temp ); + if ( pointer.CompareF( KMmsTextPlain ) != 0 ) + { + attachManSync.RemoveAttachmentL( i - 1 ); + count--; + } + CleanupStack::PopAndDestroy( temp ); + CleanupStack::PopAndDestroy( attachInfo ); + temp = NULL; + attachInfo = NULL; + } + + if ( count > 0 ) + { + // we have something left in the selection + // When we commit the store, deleted attachments should disappear + store->CommitL(); + } + + CleanupStack::PopAndDestroy( mimeHeaders ); + CleanupStack::PopAndDestroy( store ); + iEntryWrapper->SetCurrentEntry( KMsvNullIndexEntryId ); + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::::EncodePDUL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::EncodePDUL() + { + // This state is used to check what must be done to the notification +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("CMmsReceiveMessage EncodePDUL - Finalize of continue")); +#endif + + iError = iServerEntry->SetEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + + if ( iError != KErrNone ) + { + // The code called by SkipEntryL will make us active again + SkipEntryL(); + return; + } + + TMsvEntry tEntry; + tEntry = iServerEntry->Entry(); + + if ( iReceivingMode == EMmsReceivingAutomatic || iMmsSettings->FetchOverride() ) + { + // if notification is in Inbox, set the fetch flags on + if ( tEntry.Parent() == KMsvGlobalInBoxIndexEntryIdValue ) + { + // mark the flags to reserve the notification for fetch + MarkNotificationOperationReserved( tEntry, KMmsOperationFetch ); + + iServerEntry->ChangeEntry( tEntry ); + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("mark notification that is being fetched") ); + #endif + } + // continue fetching + FallThrough(); + return; + } + + // mode is deferred of reject + + // entry is in inbox - clear flags and leave it + if ( tEntry.Parent() == KMsvGlobalInBoxIndexEntryIdValue ) + { + tEntry.iMtmData2 &= ~KMmsNewOperationForbidden; + tEntry.SetReadOnly( ETrue ); + iServerEntry->ChangeEntry( tEntry ); + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("notification in inbox. Let it be") ); + #endif + LoopToNextEntryL(); + return; + } + + // entry is in mms folder or mmbox folder + + // already deferred - leave it and loop to next entry + if ( tEntry.iMtmData2 & KMmsNotifyResponseSent ) + { + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("notification already deferred. Let it be") ); + #endif + tEntry.iMtmData2 &= ~KMmsNewOperationForbidden; + iServerEntry->ChangeEntry( tEntry ); + LoopToNextEntryL(); + return; + } + + // reject mode is on + if ( iReceivingMode == EMmsReceivingReject ) + { + // Let the notification be, if we have received this notification + // earlier in the manual or postpone mode when we have roamed. + // In that case KMmsDeferredButResponseNotSent flag is set. + // Delete it, if it is a new one that has arrives while we are in reject mode + tEntry.iMtmData2 &= ~KMmsNewOperationForbidden; + iServerEntry->ChangeEntry( tEntry ); + if ( !( tEntry.iMtmData2 & KMmsDeferredButResponseNotSent ) ) + { + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("deleting the notification") ); + #endif + // we ignore the error. If setting entry to parent fails, deletion will fail + iServerEntry->SetEntry( tEntry.Parent() ); + iServerEntry->DeleteEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + } + LoopToNextEntryL(); + return; + } + + // deferred and response not yet sent + + if ( iRegistrationStatus == 0 ) // home network - continue + { + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("in homenw and deferred mode") ); + #endif + // continue fetching - will send "deferred" response + FallThrough(); + return; + } + + // state is deferred, and we are roaming + + // if the status of the notification is retrieved, this + // notification will be deleted. + // This happens if the notification was extended notification + // and the whole message arrived in the notification. + // When roaming, no response is sent. The extended notification + // is already in the inbox. This notification is deleted. + + CMsvStore* store = iServerEntry->ReadStoreL(); + CleanupStack::PushL( store ); + iNotification->RestoreL( *store ); + CleanupStack::PopAndDestroy( store ); + store = NULL; + + if ( iNotification->Status() == KMmsMessageStatusRetrieved ) + { + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L(" the whole extended notification received") ); + TMmsLogger::Log( _L("deleting the notification") ); + #endif + // ignore error, if setting parent fails, deletion will fail + iServerEntry->SetEntry( tEntry.Parent() ); + iServerEntry->DeleteEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + LoopToNextEntryL(); + return; + } + + // The notification is not completely fetched, finalize flags + + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Finalizing the notification") ); + #endif + iNotification->SetStatus( KMmsMessageStatusDeferred ); + store = iServerEntry->EditStoreL(); + CleanupStack::PushL( store ); + iNotification->StoreL( *store ); + store->CommitL(); + CleanupStack::PopAndDestroy( store ); + store = NULL; + + tEntry.SetConnected( EFalse ); + tEntry.SetFailed( EFalse ); + tEntry.SetSendingState( KMsvSendStateUnknown ); + + tEntry.iMtmData1 |= KMmsMessageMobileTerminated; + + // When roaming it is not allowed to send a notify response + // to MMSC. + tEntry.iMtmData2 |= KMmsDeferredButResponseNotSent; + + // The receiving mode is either manual or postpone, when roaming. + // If the manual mode is on, the mtm type is changed to notification + // and the notification is moved to Inbox. + // + if ( iMmsSettings->ReceivingModeForeign() != EMmsReceivingManual ) + { + // this is all we need + iServerEntry->ChangeEntry( tEntry ); + } + else + { + // Change the iMtm from KUidMsgTypeMultimedia to + // KUidMsgMMSNotification + tEntry.iMtm = KUidMsgMMSNotification; + tEntry.iMtmData2 &= ~KMmsNewOperationForbidden; + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("marking as a notification") ); + #endif + // Readonly flag has to be set on in inbox. + // Not in mms folder + tEntry.SetReadOnly( ETrue ); + iServerEntry->ChangeEntry( tEntry ); + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("moving to inbox") ); + #endif + // Ignore error from set entry. If it fails, move will fail (not panic) + // Setting service entry to a folder should not fail. + iServerEntry->SetEntry( tEntry.Parent() ); + iServerEntry->MoveEntryWithinService( tEntry.Id(), KMsvGlobalInBoxIndexEntryIdValue ); + } + + LoopToNextEntryL(); + + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::::SkipEntryL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::SkipEntryL() + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("receivemsg - can't access notification") ); +#endif + // If the notification cannot be accessed, we must try the next one + // If the notification was not found, or was used by someone else, + // then nothing can be done. Nothing can be done to the scheduling data. + + // We completely discard only entries that cannot be found + if ( iError == KErrNotFound ) + { + iFailed->Delete( iCurrentMessageNo - 1 ); + } + // we must return to the beginning of the loop without doing anything + iState = EMmsOperationUpdatingEntryStatus; + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + // The code called by ChangeStateL will make us active again + ChangeStateL(); + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::::LoopToNextEntryL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::LoopToNextEntryL() + { + // release entry + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + // loop to next entry + iFailed->Delete( iCurrentMessageNo - 1 ); + iState = EMmsOperationUpdatingEntryStatus; + SelectNextState(); + // The code called by ChangeStateL will make us active again + ChangeStateL(); + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::FolderEntryL +// +// --------------------------------------------------------- +// +TInt CMmsReceiveMessage::FolderEntryL( + TMsvId aParent, const TDesC& aFolderName, TMsvId& aFolderId ) + { + aFolderId = KMsvNullIndexEntryId; + + TInt error = KErrNone; + + // get a new entry, don't mess up with the original entry. + CMsvServerEntry* workingEntry = NULL; + TRAP( error, workingEntry = iServerEntry->NewEntryL( aParent ) ); + CleanupStack::PushL( workingEntry ); + + if ( error != KErrNone ) + { + CleanupStack::PopAndDestroy( workingEntry ); // workingEntry + return error; + } + + // Show invisible entries + TMsvSelectionOrdering ordering = + TMsvSelectionOrdering( KMsvNoGrouping, EMsvSortByIdReverse, ETrue ); + workingEntry->SetSort( ordering ); + + CMsvEntrySelection* selection = new ( ELeave ) CMsvEntrySelection; + error = workingEntry->GetChildrenWithType( KUidMsvFolderEntry, *selection ); + if ( error == KErrNone ) + { + // If selection contains folders, check if the folder is found. + TInt count = selection->Count(); + for ( TInt i = 0; i < count && aFolderId == KMsvNullIndexEntryId; i++ ) + { + error = workingEntry->SetEntry( selection->At( i ) ); + if ( error == KErrNone ) + { + // must be exact match + if ( workingEntry->Entry().iDetails.Compare( aFolderName ) == 0 ) + { + aFolderId = selection->At( i ); + } + } + } + } + delete selection; + selection = NULL; + CleanupStack::PopAndDestroy( workingEntry ); + workingEntry = NULL; + + return error; + + } + + // ----------------------------------------------------------------------------- +// CMmsReceiveMessage::CreateFolderEntryL +// +// ----------------------------------------------------------------------------- +// +TInt CMmsReceiveMessage::CreateFolderEntryL( + TMsvId aParentFolder, const TDesC& aFolderName, TMsvId& aFolderId ) + { + + // Check if the folder already exists under parent folder. + TInt error = FolderEntryL( aParentFolder, aFolderName, aFolderId ); + if ( aFolderId != KMsvNullIndexEntryId || error != KErrNone ) + { + // if error == KErrNone and aFolderId not equal to KMsvNullIndexEntryId + // the folder already exists, and we don't need to create anything + return error; + } + + CMsvServerEntry* serverEntry = NULL; + TRAP( error, serverEntry = iServerEntry->NewEntryL( aParentFolder ) ); + CleanupStack::PushL( serverEntry ); + + if ( error != KErrNone ) + { + CleanupStack::PopAndDestroy( serverEntry ); + return error; + } + + // Create a new folder. + error = serverEntry->SetEntry( aParentFolder ); + + if ( error == KErrNone ) + { + TMsvEntry entry; + entry.iType = KUidMsvFolderEntry; + entry.iMtm = KUidMsvLocalServiceMtm; + entry.iDetails.Set( aFolderName ); + entry.SetVisible( EFalse ); + entry.SetInPreparation( EFalse ); + entry.iServiceId = KMsvLocalServiceIndexEntryId; + error = serverEntry->CreateEntry( entry ); + aFolderId = entry.Id(); + } + CleanupStack::PopAndDestroy( serverEntry ); + serverEntry = NULL; + return error; + + } + + +// --------------------------------------------------------- +// CMmsReceiveMessage::FindDuplicateNotificationL +// +// --------------------------------------------------------- +// +TInt CMmsReceiveMessage::FindDuplicateNotificationL( + TMsvId aParent, CMmsHeaders& aHeaders, TMsvId& aDuplicate ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("CMmsReceiveMessage::FindDuplicateNotificationL") ); +#endif + + aDuplicate = KMsvNullIndexEntryId; + + if ( aParent == KMsvNullIndexEntryId ) + { + return KErrNotSupported; + } + + // Get a new entry, dont mess up with our original entry + TInt error = KErrNone; + + CMsvServerEntry* workingEntry = NULL; + TRAP( error, workingEntry = iServerEntry->NewEntryL( aParent )); + CleanupStack::PushL( workingEntry ); + + if ( error != KErrNone ) + { + CleanupStack::PopAndDestroy( workingEntry ); + return error; + } + + CMsvEntrySelection* selection = new ( ELeave ) CMsvEntrySelection; + CleanupStack::PushL( selection ); + + error = workingEntry->GetChildrenWithMtm( KUidMsgMMSNotification, *selection ); + + TInt count = selection->Count(); + if ( count == 0 ) + { + error = KErrNotSupported; + } + + if ( error != KErrNone ) + { + CleanupStack::PopAndDestroy( selection ); + CleanupStack::PopAndDestroy( workingEntry ); + return error; + } + + CMmsHeaders* mmsHeaders = CMmsHeaders::NewL( iMmsSettings->MmsVersion() ); + CleanupStack::PushL( mmsHeaders ); + + for ( TInt i = count; i > 0 && ( aDuplicate == KMsvNullIndexEntryId ); i-- ) + { + error = workingEntry->SetEntry( selection->At( i - 1 ) ); + if ( error == KErrNone ) + { + CMsvStore* store = workingEntry->ReadStoreL(); + CleanupStack::PushL( store ); + mmsHeaders->RestoreL( *store ); + CleanupStack::PopAndDestroy( store ); + + // content location must match + if ( mmsHeaders->ContentLocation().Compare( aHeaders.ContentLocation() ) == 0 ) + { + // Identical. This probably means that we have not sent a response yet, + // and MMSC has sent us a new notification. + +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- content locations match") ); +#endif + TMsvEntry entry = workingEntry->Entry(); + aDuplicate = entry.Id(); + } + } + } + + CleanupStack::PopAndDestroy( mmsHeaders ); + CleanupStack::PopAndDestroy( selection ); + CleanupStack::PopAndDestroy( workingEntry ); + + return error; + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::LocalModeFetchL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::LocalModeFetchL() + { + + HBufC* buffer = HBufC::NewL( KMaxFileName ); + CleanupStack::PushL( buffer ); + if ( iNotification->ContentLocation().Length() <= KMaxPath && + iMmsSettings->LocalMode() ) + { + buffer->Des().Copy( iNotification->ContentLocation() ); + } + TPtr temp = buffer->Des(); + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("...Fetching from : %S"), &temp ); + #endif + + HBufC* filename = HBufC::NewL( iMmsSettings->LocalModeIn().Length() ); + CleanupStack::PushL( filename ); + TPtr fileNamePtr = filename->Des(); + fileNamePtr.Copy( iMmsSettings->LocalModeIn() ); + + if ( iNotification->ContentLocation().Length() <= KMaxPath && + buffer->Des().FindF( fileNamePtr ) == 0 ) + { + // The file reading code is for local mode (simulated MMSC) + // Only one can try to get the file at a time + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("opening file") ); + #endif + iError = iFile.Open( iFs, temp, EFileShareExclusive ); + + if ( iError == KErrNone ) + { + #ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("file opened") ); + #endif + iFileOpen = ETrue; + TInt size = 0; + iError = iFile.Size( size ); + + // Here we try to use chunked buffer. + // Read the message in chunks and keep calling decode + // Before we can do that, we must create the message entry + // Because it must be ready to receive data + // If receiving fails, the entry must be removed + + DoCreateEntryL(); + iDecoder->InitializeChunkedMode( *iEntryWrapper, *iMmsHeaders, *iEncodeBuffer ); + TInt chunkSize = KMmsChunkedBufferSize; + if ( iError == KErrNone ) + { + TRAP( iError, iEncodeBuffer->ResizeL( chunkSize ) ); + } + if ( iError != KErrNone ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("Receivemsg can't resize buffer, error %d"), iError ); + TMmsLogger::Log( _L("- message size %d"), chunkSize ); +#endif + iFile.Close(); + iFileOpen = EFalse; + iDecoder->RelaseDataSink(); + CleanupStack::PopAndDestroy( filename ); + CleanupStack::PopAndDestroy( buffer ); + return; + } + + // We now have an open file. + // We read it in chunks and feed it to decoder piecewise + TPtr8 pos = iEncodeBuffer->Ptr( 0 ); + + TInt position = 0; + TInt readData = 0; + TInt amount = 0; + TBool lastChunk = EFalse; + TInt error = KErrNone; + + while ( readData < size && error == KErrNone ) + { + chunkSize = KMmsChunkedBufferSize; + const TInt KMmsTwo = 2; + if ( chunkSize - amount < KMmsChunkedBufferSize / KMmsTwo ) + { + // if almost all data has been left to buffer, increase buffer size + chunkSize = amount + KMmsChunkedBufferSize / KMmsTwo; + } + iEncodeBuffer->ResizeL( chunkSize ); + pos.Set( iEncodeBuffer->Ptr( amount ) ); + pos.SetLength( chunkSize - amount ); + error = iFile.Read( pos, chunkSize - amount ); + amount += pos.Length(); + readData += pos.Length(); + if ( readData >= size ) + { + lastChunk = ETrue; + } + if ( error == KErrNone ) + { + // this is how much data we have + iEncodeBuffer->ResizeL( amount ); + error = iDecoder->NextDataPart( *iEncodeBuffer, position, lastChunk ); + } + if ( position > iEncodeBuffer->Size() ) + { + position = iEncodeBuffer->Size(); + } + // Check if we can continue and shift the buffer if needed + if ( error == KErrNone ) + { + // now position points to the beginning of unhandled data + // it must be shifted to the beginning + amount -= position; // this much left unhandled + if ( amount < 0 ) + { + amount = 0; + } + iEncodeBuffer->Delete( 0, position ); + // start from the beginning the next time + position = 0; + } + } + if ( error != KErrNone ) + { + iError = error; + } + // when the loop ends, all data should be handled and all attachments should be created + // Decode has also saved message headers. + // The changed state indicates what is complete + iDecoder->RelaseDataSink(); + iState = EMmsOperationDecodingResponse; + } + else + { + // This corresponds to the situation, where the message + // has expired or for some other reason is no longer available. + // We lie to RunL that we have already decoded the message + iFile.Close(); + iFileOpen = EFalse; + iState = EMmsOperationDecodingResponse; + if ( iError != KErrInUse ) + { + iBad->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + } + } + // We don't close the file yet. + // We want to keep it exclusively to ourselves until we are done with it + } + else + { + // No directory found in notification + // this notification cannot be handled. + // It will be marked bad and deleted. + iError = KErrNotFound; + iState = EMmsOperationDecodingResponse; + iBad->AppendL( iSelection->At( iCurrentMessageNo - 1 ) ); + } + CleanupStack::PopAndDestroy( filename ); + CleanupStack::PopAndDestroy( buffer ); + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::DumpIfNeededL +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::DumpIfNeededL() + { +#ifndef _NO_MMSS_LOGGING_ + TInt error = KErrNone; + TBool dumpIncoming = EFalse; + // + // Reading binary dumping from CenRep + // + CRepository* repository = NULL; + TInt retval = KErrNone; + TRAP( retval, repository = CRepository::NewL( KUidMmsServerMtm ) ); // *** + if( retval == KErrNone ) + { + CleanupStack::PushL( repository ); + TInt temp = 0; + retval = repository->Get( KMmsEngineBinaryDump, temp ); + if( retval == KErrNone ) + { + dumpIncoming = (TBool)temp; + } + CleanupStack::PopAndDestroy( repository ); + } + + TMmsLogger::Log( _L("Receivemsg Dumping Entry") ); + // If the result was bad, we dump the binary data into file + // - unless decode already did it. + if ( ( ( !dumpIncoming ) && iError != KErrNone ) && + iEncodeBuffer && + iEncodeBuffer->Size() > 0 ) + { + TParse parse; + TFileName fileName; + fileName.Copy( KMmsDefaultLogDirectory ); + TUint att; + if ( iFs.Att( fileName, att ) == KErrNone ) + { + _LIT( KRelated, "Rec.mms"); + parse.Set( fileName, &KRelated, NULL ); + fileName = parse.FullName(); + error = CApaApplication::GenerateFileName( iFs, fileName ); + if ( error == KErrNone ) + { + RFile file; + error = file.Create( iFs, fileName, EFileWrite | EFileShareExclusive ); + // for message id generation + parse.Set( fileName, NULL, NULL ); + + if ( error == KErrNone ) + { + // the data is supposed to be in the encode buffer + TPtr8 ptr = iEncodeBuffer->Ptr( 0 ); + file.Write( ptr ); + file.Flush(); + } + + // done - close files + file.Close(); + } + } + } +#endif + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::CloseLocalFile +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::CloseLocalFileL( TBool aDeleteFile ) + { + HBufC* buffer = HBufC::NewL( KMaxFileName ); + CleanupStack::PushL( buffer ); + buffer->Des().Copy( iNotification->ContentLocation() ); + HBufC* filename = HBufC::NewL( iMmsSettings->LocalModeIn().Length() ); + CleanupStack::PushL( filename ); + TPtr fileNamePtr = filename->Des(); + fileNamePtr.Copy( iMmsSettings->LocalModeIn() ); + if ( buffer->Des().FindF( fileNamePtr ) == 0 ) + { + // this was our local message file + iFile.Close(); + iFileOpen = EFalse; + if ( aDeleteFile ) + { + iFs.Delete( *buffer ); + } + } + CleanupStack::PopAndDestroy( filename ); + CleanupStack::PopAndDestroy( buffer ); + } + +// --------------------------------------------------------- +// CMmsReceiveMessage::MoveToApplicationFolderIfNeededL +// +// --------------------------------------------------------- +// +TInt CMmsReceiveMessage::MoveToApplicationFolderIfNeededL( TMsvEntry& aEntry ) + { + TInt error = KErrNone; + TInt returnCode = 0; + + if ( iMmsHeaders->ApplicId().Length() > 0 ) + { + // check if application id is registered + if ( iRegistered ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- message to registered application") ); +#endif + TMsvId targetFolder = KMsvNullIndexEntryId; + // target folder's parent is application folder + TMsvId applicationFolder = iMmsSettings->ApplicationFolder(); + // code scanner gives false positive from the next line + error = CreateFolderEntryL( applicationFolder, iMmsHeaders->ApplicId(), targetFolder ); + if ( targetFolder != KMsvNullIndexEntryId && error == KErrNone ) + { + error = iServerEntry->SetEntry( aEntry.Parent() ); + if ( error == KErrNone ) + { + error = iServerEntry->MoveEntryWithinService( aEntry.Id(), targetFolder ); + } + if ( error == KErrNone ) + { + // Message was successfully moved to application folder + // If we want to keep the notification in inbox we return + // KMmsMessageMovedToApplicationFolder. + // As long as we serve only Java applications, the notifications + // will disappear from inbox if the message was moved to application folder. + // Java will start the applet if it is not running, and that should + // be sufficient information for the user. +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- moved to application folder") ); +#endif + returnCode = 0; + } + // If we cannot set our entry back to the one we are handling, + // we are in deep trouble + User::LeaveIfError( iServerEntry->SetEntry( iEntryUnderConstruction )); + } + } + else + { + // There was an application id, but the application was not registered + // Message will be deleted + // If deletion fails, the message remains invisible and under construction + // and will be deleted by message server at next boot. +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- message to unregistered application - deleted") ); +#endif + error = iServerEntry->SetEntry( aEntry.Parent() ); + if ( error == KErrNone ) + { + iServerEntry->DeleteEntry( aEntry.Id() ); + } + returnCode = KMmsMessageForUnregisteredApplication; + } + } + // If no application id, the message is normal + return returnCode; + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::ClearDuplicateEntryOperationL() + { + CMsvStore* store = iServerEntry->ReadStoreL(); + CleanupStack::PushL( store ); + iMmsHeaders->RestoreL( *store ); + CleanupStack::PopAndDestroy( store ); + TMsvId duplicate = KMsvNullIndexEntryId; + TMsvId mmboxFolder = iMmsSettings->MMBoxFolder(); + TMsvEntry dupEntry; + + if ( iNotificationParent == KMsvGlobalInBoxIndexEntryIdValue ) + { + // if duplicate is found from mmbox folder, + // mark it as "deleted from mmbox server" + if ( mmboxFolder != KMsvNullIndexEntryId ) + { + FindDuplicateNotificationL( mmboxFolder, *iMmsHeaders, duplicate ); + } + if ( duplicate != KMsvNullIndexEntryId ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- duplicate found ") ); +#endif + if ( iServerEntry->SetEntry( duplicate ) == KErrNone ) + { + // Mark duplicate as deleted from mmbox server. + // If it was successfully fetched it is no longer in MMBox. + // This is not exactly correct for full MMBox support but as long as + // we don't have full MMBox support this works correctly + dupEntry = iServerEntry->Entry(); + MarkNotificationDeletedFromMmbox( dupEntry ); + iServerEntry->ChangeEntry( dupEntry ); +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- duplicate marked as deleted from mmbox ") ); +#endif + } + } + } + else if ( iNotificationParent == mmboxFolder ) + { + duplicate = iMmsHeaders->RelatedEntry(); + if ( duplicate != KMsvNullIndexEntryId ) + { +#ifndef _NO_MMSS_LOGGING_ + TMmsLogger::Log( _L("- duplicate found ") ); +#endif + if ( iServerEntry->SetEntry( duplicate ) == KErrNone ) + { + // Mark it deleted just in case the actual deletion fails. + // This is not exactly correct for full MMBox support but as long as + // we don't have full MMBox support this works correctly + dupEntry = iServerEntry->Entry(); + MarkNotificationDeletedFromMmbox( dupEntry ); + iServerEntry->ChangeEntry( dupEntry ); + // delete the duplicate notification from inbox + if ( iServerEntry->SetEntry( KMsvGlobalInBoxIndexEntryIdValue ) + == KErrNone ) + { + TInt err = iServerEntry->DeleteEntry( duplicate ); +#ifndef _NO_MMSS_LOGGING_ + if ( err == KErrNone ) + { + TMmsLogger::Log( _L("- duplicate deleted from inbox ") ); + } +#endif + } + } + } + } + else + { + // nothing to do - just keep compiler and code analyzer tools happy + } + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::ClearOperationL( TInt aApplicationMessageStatus ) + { + if ( iServerEntry->SetEntry( iSelection->At( iCurrentMessageNo - 1 ) ) == KErrNone ) + { + if ( aApplicationMessageStatus != 0 ) + { + // The entry is not deleted if it is in inbox or MMBox + // and has been routed to application. + // We save the error code or routing status to the entry. + TMsvEntry tEntry = iServerEntry->Entry(); + MarkNotificationDeletedFromMmbox( tEntry ); + tEntry.SetConnected( EFalse ); + tEntry.SetReadOnly( ETrue ); + + if ( aApplicationMessageStatus == KMmsMessageMovedToApplicationFolder ) + { + // This is a successful case + tEntry.iMtmData2 |= KMmsMessageRoutedToApplication; + } + if ( aApplicationMessageStatus == + KMmsMessageForUnregisteredApplication ) + { + // This will be marked as failed operation + // even if the message was actually successfully fetched + // but it was discarded because it does not belong to anybody + tEntry.iError = KMmsErrorUnregisteredApplication; + // The earlier operation cleared the possible MMBox bit + // This operation marks the result as failed. + MarkNotificationOperationFailed( tEntry ); + } + + // error status not checked here - we just do our best + iServerEntry->ChangeEntry( tEntry ); + } + ClearDuplicateEntryOperationL(); + } + } + + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::CopyDataFromNotificationToMessageL() + { + // Only subject and sender take up space when headers are added + TInt size = iNotification->Sender().Length() + + iNotification->Subject().Length(); + if ( size > 0 && + TMmsGenUtils::DiskSpaceBelowCriticalLevelL( &iFs, size, iMessageDrive ) ) + { + size = 0; + } + + // We copy data from notification to message if the values are missing + // But if the message is already complete (status == OK) + // we do not copy anything. + if ( iMmsHeaders->ResponseStatus() > KMmsResponseStatusOK ) + { + // The critical disk space is checked only when adding the subject. + // The sender number is important and only takes a small amount of space + // The other fields take the same amount of space regardless of the value. + // As the headers have been saved already, only subject may consume + // significantly more disk space. + if ( size > 0 ) + { + if ( iMmsHeaders->Subject().Length() == 0 ) + { + iMmsHeaders->SetSubjectL( iNotification->Subject() ); + } + } + if ( iMmsHeaders->Sender().Length() == 0 ) + { + iMmsHeaders->SetSenderL( iNotification->Sender() ); + } + if ( iMmsHeaders->MessageClass() == 0 ) + { + iMmsHeaders->SetMessageClass( iNotification->MessageClass() ); + } + if ( iMmsHeaders->MessagePriority() == 0 ) + { + iMmsHeaders->SetMessagePriority( iNotification->MessagePriority() ); + } + // Copy the expiry, too + if ( iMmsHeaders->ExpiryDate() == 0 && iMmsHeaders->ExpiryInterval() == 0 ) + { + // No expiration data in the message - use what is in the notification + iMmsHeaders->SetExpiryDate( iNotification->ExpiryDate() ); + } + } + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::CopyDataFromMessageToNotificationL() + { + TBool saveNotification = EFalse; + TInt error = KErrNone; + + iNotification->SetResponseTextL( iMmsHeaders->ResponseText() ); + iNotification->SetResponseStatus( iMmsHeaders->ResponseStatus() ); + + // Notifications do not originally contain any response texts. + // If the length is not 0, we have added something + if ( iNotification->ResponseText().Length() > 0 || iNotification->ResponseStatus() != 0 ) + { + saveNotification = ETrue; + } + + if ( iNotification->ApplicId().Length() == 0 ) + { + iNotification->SetApplicIdL( iMmsHeaders->ApplicId() ); + if ( iNotification->ApplicId().Length() > 0 ) + { + // If we copied the application id, we must save it. + saveNotification = ETrue; + } + } + + // update the notification + if ( saveNotification ) + { + // We update the notification if we added something + error = iServerEntry->SetEntry( iSelection->At( iCurrentMessageNo - 1 ) ); + if ( error == KErrNone ) + { + TRAP( error, + { + CMsvStore* store = iServerEntry->EditStoreL(); + CleanupStack::PushL( store ); + iNotification->StoreL( *store ); + store->CommitL(); + CleanupStack::PopAndDestroy( store ); + }) + } + // If we cannot update the entry, the user does not see the error text, but that's not fatal + } + // Release the entry + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +TBool CMmsReceiveMessage::MapErrorStatus( const TInt32 aErrorStatus ) + { + TBool fatal = EFalse; + + switch ( aErrorStatus ) + { + case 0: // if the header is not present, it is 0 + case KMmsStatusOk: + // No error, don't change iError + break; + case KMmsErrorPermanentFailure: + fatal = ETrue; + iError = KMmsErrorStatusPermanentFailure; + break; + case KMmsErrorTransientFailure: + iError = KMmsErrorStatusTransientFailure; + break; + case KMmsErrorReceivePermanentMessageNotFound: + fatal = ETrue; + iError = KMmsErrorStatusMessageNotFound; + break; + case KMmsErrorReceiveTransientMessageNotFound: + iError = KMmsErrorStatusTransientMessageNotFound; + break; + case KMmsErrorReceiveTransientNetworkProblem: + iError = KMmsErrorStatusNetworkProblem; + break; + case KMmsErrorPermanentServiceDenied: + fatal = ETrue; + iError = KMmsErrorStatusServiceDenied; + break; + case KMmsErrorReceivePermanentContentUnsupported: + fatal = ETrue; + iError = KMmsErrorStatusContentUnsupported; + break; + default: + // Unknown error, if not transient, it is fatal + if ( ( aErrorStatus & KMmsErrorRangeMask ) == KMmsErrorTransient ) + { + iError = KMmsErrorStatusTransientFailure; + } + else + { + iError = KMmsErrorPermanentFailure; + fatal = ETrue; + } + break; + } + + return fatal; + } + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::SetIndexEntryBitsForReceivedMessage( TMsvEntry& aEntry ) + { + aEntry.iMtmData1 &= ~KMmsMessageTypeMask; + // We override message type for now. + // We get send requests instead of retrieve confirmations if in local mode + aEntry.iMtmData1 |= KMmsMessageMRetrieveConf | KMmsMessageMobileTerminated; + + aEntry.SetVisible( ETrue ); + aEntry.SetComplete( ETrue ); + aEntry.SetInPreparation( EFalse ); + aEntry.SetReadOnly( ETrue ); + aEntry.iDate.UniversalTime(); // this is arrival time + + if ( iMmsSettings->ShowSentTime() ) + { + // show the time the message was received by MMSC instead of + // the time the message was received by the terminal + if ( iMmsHeaders->Date() > 0 ) + { + // date is given as seconds from 1.1.1970, UTC time + TTime time = TTime( KMmsYear1970String ) + + TTimeIntervalMicroSeconds( iMmsHeaders->Date() * KMmsMillion ); + aEntry.iDate = time.Int64(); + } + } + // subject, sender and size are set in CMmsDecode + // - except if we get an empty error message. + // In that case the fields were copied from the notification, + // and we set sender and subject here. + if ( aEntry.iDetails.Length() == 0 ) + { + aEntry.iDetails.Set( iMmsHeaders->Sender() ); + } + if ( aEntry.iDescription.Length() == 0 ) + { + aEntry.iDescription.Set( iMmsHeaders->Subject() ); + } + + // Set multiple recipients + if ( iMmsHeaders->ToRecipients().MdcaCount() + + iMmsHeaders->CcRecipients().MdcaCount() + + iMmsHeaders->BccRecipients().MdcaCount() > 1 ) + { + aEntry.SetMultipleRecipients( ETrue ); + } + } + + +// --------------------------------------------------------- +// +// --------------------------------------------------------- +// +void CMmsReceiveMessage::DeleteApplicationMessagesL() + { + // target folder's parent is application folder + TMsvId messageFolder = KMsvNullIndexEntryId; + TMsvId applicationFolder = iMmsSettings->ApplicationFolder(); + // code scanner gives false positive from the next line + TInt error = KErrNone; + error = FolderEntryL( applicationFolder, iMmsHeaders->ApplicId(), messageFolder ); + + if ( messageFolder == KMsvNullIndexEntryId || error != KErrNone ) + { + // We could not find the folder - we cannot free space + return; + } + + // List all messages in the folder and try to see if we can free enough room. + TInt size = iNotification->MessageSize(); + if ( size > iMmsSettings->MaximumReceiveSize() ) + { + // We cannot fetch more than this anyway + size = iMmsSettings->MaximumReceiveSize(); + } + // The safety margin must be added to the indicated size as the size in the + // notification only specifies the payload, not all headers and space taken + // by message entry and folder entry needed. + size += KMmsDiskSafetyMargin; + + error = iServerEntry->SetEntry( messageFolder ); + if ( error != KErrNone ) + { + // no can do + return; + } + + // sort entries by date + TMsvSelectionOrdering ordering = + TMsvSelectionOrdering( KMsvNoGrouping, EMsvSortByDate, EFalse ); + iServerEntry->SetSort( ordering ); + + CMsvEntrySelection* selection = new ( ELeave ) CMsvEntrySelection; + CleanupStack::PushL( selection ); + error = iServerEntry->GetChildrenWithType( KUidMsvMessageEntry, *selection ); + if ( error != KErrNone ) + { + // no can do + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + CleanupStack::PopAndDestroy( selection ); + } + + TInt i; + TInt deleteCount = 0; // entries to be deleted to free space + TInt oldSize = 0; + for ( i = 0; i < selection->Count() && deleteCount == 0; i++ ) + { + error = iServerEntry->SetEntry( selection->At( i ) ); + if ( error == KErrNone ) + { + // If we could not access some entry, it will end up in the + // delete list anyway. It may or may not be deleted. + oldSize += iServerEntry->Entry().iSize; + } + if ( oldSize >= size ) + { + deleteCount = i + 1; + } + } + + // Check if we fond enough old messages to free space. + if ( deleteCount > 0 ) + { + for ( i = selection->Count(); i > deleteCount; i-- ) + { + // We may be able to leave some entries + selection->Delete( i - 1 ); + } + + error = iServerEntry->SetEntry( messageFolder ); + if ( error == KErrNone ) + { + error = iServerEntry->DeleteEntries( *selection ); + } + // If we get an error because some entry is locked or open + // the original KErrNoDisk remains and no entries are deleted + // The situation will follow normal low disk space handling + // If the entries are being accessed by the application, the + // space may be freed soon by the application. + if ( error == KErrNone ) + { + // We have successfully deleted enough entries + // The new message should now fit. + // It can be rescheduled or fetch can be manually restarted + iError = KMmsErrorApplicationDiskFull; + } + } + iServerEntry->SetEntry( KMsvNullIndexEntryId ); + CleanupStack::PopAndDestroy( selection ); + } + + +// ================= OTHER EXPORTED FUNCTIONS ============== + +// End of File +