mmsengine/mmsserver/src/mmslog.cpp
changeset 0 72b543305e3a
child 26 ebe688cedc25
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mmsengine/mmsserver/src/mmslog.cpp	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,752 @@
+/*
+* Copyright (c) 2002-2006 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 handling MMS log entries
+*     (sent messages and delivery reports)
+*
+*/
+
+
+
+// INCLUDE FILES
+#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS
+#include <logwraplimits.h>
+#endif
+#include    <badesca.h>
+#include    <msvids.h>
+#include    <logcli.h>
+#include    <logview.h>
+// common component
+#include    <sysutil.h>
+#include    <LogsApiConsts.h>
+
+// MMS specific
+#include    "mmsconst.h"
+#include    "mmslog.h"
+#include    "mmsgenutils.h"
+#include    "mmsservercommon.h" // needed for logging
+
+// EXTERNAL DATA STRUCTURES
+
+// EXTERNAL FUNCTION PROTOTYPES  
+extern void gPanic(TMmsPanic aPanic);
+
+// CONSTANTS
+_LIT(KMmsLogEventTypeName, "MMS");
+
+// 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.
+//
+CMmsLog::CMmsLog( CLogClient& aLogClient, CLogViewEvent& aLogViewEvent )
+    :CMsgActive( EPriorityStandard ),
+    iState( EMmsLogIdle ),
+    iError( KErrNone ),
+    iLogClient( aLogClient ),
+    iLogViewEvent( aLogViewEvent ),
+    iLastMatchedLink( KLogNullLink )
+    {
+    }
+
+// Symbian OS default constructor can leave.
+void CMmsLog::ConstructL( RFs& aFs )
+    {
+    
+    iFs = aFs;
+    iEntryId = KMsvNullIndexEntryId;
+    iLogUpdatedEvent = CLogEvent::NewL();
+    iFilterList = new ( ELeave ) CLogFilterList;
+
+    CActiveScheduler::Add(this);
+
+    }
+
+// Two-phased constructor.
+CMmsLog* CMmsLog::NewL( CLogClient& aLogClient, CLogViewEvent& aLogViewEvent, RFs& aFs )
+    {
+    CMmsLog* self = new (ELeave) CMmsLog( aLogClient, aLogViewEvent );
+    
+    CleanupStack::PushL( self );
+    self->ConstructL( aFs );
+    CleanupStack::Pop( self );
+
+    return self;
+    }
+
+    
+// Destructor
+CMmsLog::~CMmsLog()
+    {
+    Cancel();
+    // we don't delete those pointers that the caller owns.
+    if( iFilterList )
+        {
+        iFilterList->ResetAndDestroy();
+        delete iFilterList;
+        }
+    delete iLogUpdatedEvent;
+    delete iLogEventType;
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::StartL
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::StartL(
+    CLogEvent& aLogEvent,
+    CDesCArray& aRemoteParties,
+    TRequestStatus& aStatus)
+    {
+    __ASSERT_DEBUG(iState==EMmsLogIdle,gPanic(EMmsAlreadyBusy));
+
+    // We must save this because we are a state machine,
+    // and we keep running around in circles ...
+    iLastMatchedLink = KLogNullLink; 
+    iLogEvent = &aLogEvent;
+    iRemoteParties = &aRemoteParties;
+    iCurrentRemoteParty = iRemoteParties->MdcaCount() - 1;
+    if ( iCurrentRemoteParty < 0 )
+        {
+        // if no entries nothing to do
+        TRequestStatus* status = &aStatus;
+        aStatus = KRequestPending;
+        User::RequestComplete( status, KErrNone );
+        return;
+        }
+        
+    TTime now;
+    // the dates in log must be in universal time, not local time
+    now.UniversalTime();
+    if ( iLogEvent->Time().Int64() == 0 )
+        {
+        iLogEvent->SetTime( now );
+        }
+        
+    iFilterList->ResetAndDestroy(); // Clear old filters - just in case
+
+    // try adding event type only once
+    iEventTypeAdded = EFalse;
+
+    // Unfortunately neither Link nor Data (message id) can be used
+    // in the filter.
+    // We must filter for each remote party and match the id's separately
+
+    Queue( aStatus );
+    iStatus=KRequestPending;
+
+    SetActive();
+
+    // Pretend that we called an asynchronous service
+    // in order to get into the state machine loop
+    TRequestStatus* status = &iStatus;
+    User::RequestComplete( status, KErrNone );
+
+    }
+    
+
+// ---------------------------------------------------------
+// CMmsLog::GetLink()
+// Returns the link id which is the same as TMsvId of the 
+// related message
+// ---------------------------------------------------------
+//
+TLogLink CMmsLog::GetLink()
+{
+    return iLastMatchedLink;	
+}
+
+// ---------------------------------------------------------
+// CMmsLog::RunL()
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::RunL()
+//
+// When the AO is state driven, this form of Run() is very effective
+// DoRunL() takes the AO through the states, queuing another asynch step as required
+// if DoRunL() detects the end of the cycle it returns without queuing another cycle.
+//
+// If Run() would exit without queuing another cycle it reports completion to the client.
+// This is true if the asynch step or DoRunL() fails, or the state cycle is complete
+//
+    {
+    iError = iStatus.Int();
+    
+#ifndef _NO_MMSS_LOGGING_
+    if ( iError != KErrNone )
+        {
+        TMmsLogger::Log( _L("CMmsLog RunL iError = %d"), iError);
+        }
+#endif
+    
+    if ( iError >= KErrNone ||
+        ( iError == KErrNotFound && iState == EMmsLogCreatingEntry ) )
+        {
+        TRAPD( error,DoRunL() );      // continue operations, may re-queue
+        __ASSERT_DEBUG( error==KErrNone || !IsActive(),User::Invariant() );   // must not requeue in error situations
+        if ( IsActive() )             // requeued
+            return;
+        iError = error;
+        }
+    Complete( iError );
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::DoRunL()
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::DoRunL()
+    {
+
+    // This routine takes the state machine through the states
+    // until an error is encountered or the cycle completes.
+
+    if ( iError != KErrNone &&
+        !( iState == EMmsLogCreatingEntry && iError == KErrNotFound 
+           && iEventTypeAdded == EFalse ) )
+        {
+        // We encountered an error, and cannot continue
+        iStatus = iError;
+        iState = EMmsLogIdle;
+        // If we return from DoRunL without becoming active again,
+        // RunL completes.
+        return;
+        }
+
+    if ( iError == KErrNotFound && iState == EMmsLogCreatingEntry )
+        {
+        iState = EMmsLogAddingEventType;
+        iError = KErrNone;
+        iEventTypeAdded = ETrue;
+        // CreateEntryL decrements remote party
+        // We get here only if we have tried creating entry and failed
+        // after we have tried adding the event type, we retry
+        // creating log entry
+        iCurrentRemoteParty++;
+        }
+    else
+        {
+        SelectNextState();
+        }
+
+    if ( iState != EMmsLogFinal )
+        {
+        // If appropriate, ChangeState makes us active again
+        ChangeStateL();
+        // If we return from DoRunL without becoming active again,
+        // RunL completes.
+        }
+    else
+        {
+        iState = EMmsLogIdle;
+        // If we return from DoRunL without becoming active again,
+        // RunL completes.
+        }
+
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::DoComplete
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::DoComplete( TInt& /* aStatus */ )
+    {
+    // We are exiting the loop - we say we are idle now
+    // This is needed when the mms event type does not exist
+    // in the log database.
+    // In that case the entries cannot be logged, and the
+    // state machine exits before reaching the final state.
+    iState = EMmsLogIdle;
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::SelectNextState
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::SelectNextState()
+   {
+
+    // If appropriate, the functions called within the switch statement
+    // will make us active again. If all is done, the asynchronous request
+    // will complete
+
+    switch ( iState )
+        {
+        case EMmsLogIdle:
+            iState = EMmsLogFiltering;
+            break;
+        case EMmsLogFiltering:
+            if ( iEvents )
+                {
+                // Check if any of found entries matches our criteria
+                iState = EMmsLogMatchingEntry;
+#ifndef _NO_MMSS_LOGGING_
+                TMmsLogger::Log( _L("CMmsLog matching log entry"));
+#endif
+                }
+            else
+                {
+                iState = EMmsLogCreatingEntry;
+                }
+            break;
+        case EMmsLogMatchingEntry:
+            if ( iEventMatched )
+                {
+                iState = EMmsLogUpdatingEntry;
+                }
+            else if ( iEvents )
+                {
+                // Something found from the database
+                // Check if any of found entries matches our criteria
+                iState = EMmsLogMatchingEntry;
+                }
+            else
+                {
+                // no match, create new entry
+                iState = EMmsLogCreatingEntry;
+                }
+            break;
+        case EMmsLogUpdatingEntry:
+            if ( iCurrentRemoteParty >= 0 )
+                {
+                iState = EMmsLogFiltering;
+                }
+            else
+                {
+                // done
+                iState = EMmsLogFinal;
+                }
+            break;
+        case EMmsLogCreatingEntry:
+            if ( iCurrentRemoteParty >= 0 )
+                {
+                iState = EMmsLogFiltering;
+                }
+            else
+                {
+                // done
+                iState = EMmsLogFinal;
+                }
+            break;
+        case EMmsLogAddingEventType:
+            iState = EMmsLogCreatingEntry;
+            break;
+        case EMmsLogFinal:
+            break;
+        default:
+            break;
+        }
+
+   }
+
+// ---------------------------------------------------------
+// CMmsLog::ChangeState
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::ChangeStateL()
+    {
+
+    switch ( iState )
+        {
+        case EMmsLogIdle:
+            break;
+        case EMmsLogFiltering:
+            FilterL();
+            break;
+        case EMmsLogMatchingEntry:
+            MatchEntryL();
+            break;
+        case EMmsLogUpdatingEntry:
+            UpdateEntryL();
+            break;
+        case EMmsLogCreatingEntry:
+            CreateEntryL();
+            break;
+        case EMmsLogAddingEventType:
+            AddEventTypeL();
+            break;
+        case EMmsLogFinal:
+            break;
+        default:
+            break;
+        }
+
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::Filter
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::FilterL()
+    {
+
+    iEvents = EFalse;
+    iEventMatched = EFalse;
+    
+    // Try filtering by status
+    // We cannot filter by remote party as it may be either a phone number
+    // or an email address.
+    // The best filter would be message id but it is stored in data field
+    // and events cannot be filtered by data field.
+    
+    // We need to use several statuses depending on what we are doing
+    // - If we are adding an entry in pending state, we only search pending entries
+    // - If we are handling a delivery or read report, we must use all states
+        
+    TLogString logString;
+    iLogClient.GetString( logString, R_LOG_DEL_PENDING );
+    
+    iFilterList->ResetAndDestroy(); // Clear old filters - just in case
+    
+    CLogFilter* filter = CLogFilter::NewL();
+    CleanupStack::PushL( filter );
+    filter->SetEventType( iLogEvent->EventType() );
+    // We always filter with pending entries
+    iLogClient.GetString( logString, R_LOG_DEL_PENDING );
+    filter->SetStatus( logString );
+    iFilterList->AppendL(filter);
+    // Filter is now in the list - popped from cleanup stack		
+    CleanupStack::Pop( filter );
+#ifndef _NO_MMSS_LOGGING_
+    TMmsLogger::Log( _L("CMmsLog Filtering with status %S"), &filter->Status() );
+#endif
+    filter = NULL; // this is out of our hands now
+
+    // If we are handling reports, we must filter other statuses, too
+    // logstring was set to pending, and that is the state when
+    // creating new entries after sending.
+    // If we are handling a delivery report or a read report, the status is
+    // R_LOG_DEL_DONE, R_LOG_DEL_FAILED, or KLogsMsgReadText
+    // We don't care about the failed ones.
+    // If the entry has gone into failed state, we create a new one.
+    // That should never happen - no new reports should arrive once something has
+    // been put into failed state.
+    
+    if ( iLogEvent->Status().CompareF( logString ) != 0 )
+        {
+        // Filter entries that are already in delivered state
+        filter = CLogFilter::NewL();
+        CleanupStack::PushL( filter );
+        filter->SetEventType( iLogEvent->EventType() );
+        iLogClient.GetString( logString, R_LOG_DEL_DONE );
+        filter->SetStatus( logString );
+        iFilterList->AppendL(filter);
+        // Filter is now in the list - popped from cleanup stack		
+        CleanupStack::Pop( filter );
+#ifndef _NO_MMSS_LOGGING_
+        TMmsLogger::Log( _L("CMmsLog Filtering with status %S"), &filter->Status() );
+#endif
+        filter = NULL; // this is out of our hands now
+        
+        // Filter entries in "read" state just to prevent creation of a new one
+        filter = CLogFilter::NewL();
+        CleanupStack::PushL( filter );
+        filter->SetEventType( iLogEvent->EventType() );
+        logString.Copy( KLogsMsgReadText );
+        filter->SetStatus( logString );
+        iFilterList->AppendL(filter);
+        // Filter is now in the list - popped from cleanup stack		
+        CleanupStack::Pop( filter );
+#ifndef _NO_MMSS_LOGGING_
+        TMmsLogger::Log( _L("CMmsLog Filtering with status %S"), &filter->Status() );
+#endif
+        filter = NULL; // this is out of our hands now
+        }
+    
+#ifndef _NO_MMSS_LOGGING_
+    if ( iLogEvent->EventType() == KLogMmsEventTypeUid )
+        {
+        TMmsLogger::Log( _L(" - and event type MMS") );
+        }
+    else
+        {
+        TMmsLogger::Log( _L("wrong event type %d"), iLogEvent->EventType() );
+        }
+#endif
+    
+    iEvents = iLogViewEvent.SetFilterL( *iFilterList, iStatus );
+
+    if ( iEvents )
+        {
+        SetActive();
+        }
+    else
+        {
+        // If there are no events, CLogViewEvent will not issue an asynchronous request
+        // In that case we must complete ourselves
+#ifndef _NO_MMSS_LOGGING_
+        TMmsLogger::Log( _L("CMmsLog found no MMS events in any state searched") );
+#endif
+        iStatus = KRequestPending;
+        SetActive();
+        TRequestStatus* status = &iStatus;
+        User::RequestComplete( status, KErrNone );
+        }
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::MatchEntryL
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::MatchEntryL()
+    {
+
+    iEventMatched = EFalse;
+    // When a view is created, it is positioned on the first event
+    // If we know our message entry id, we try to match it
+    if ( iLogEvent->Link() != KLogNullLink )
+        {
+        if ( iLogEvent->Link() == iLogViewEvent.Event().Link() )
+            {
+            iEventMatched = ETrue;
+            }
+        }
+    else
+        {
+        // Our message id (returned by MMSC) is stored to Data field.
+        if ( iLogEvent->Data().Compare( iLogViewEvent.Event().Data() ) == 0 )
+            {
+            iEventMatched = ETrue;
+            }
+        }
+
+    TPtrC dummy;
+    TPtrC remoteParty;
+    dummy.Set( iRemoteParties->MdcaPoint( iCurrentRemoteParty ) );
+    if ( TMmsGenUtils::IsValidMMSPhoneAddress( dummy, ETrue ) )
+        {
+        dummy.Set( dummy.Right( Min( KMmsNumberOfDigitsToMatch, dummy.Length() ) ) );
+        remoteParty.Set( iLogViewEvent.Event().Number().Right(
+            Min ( dummy.Length(), iLogViewEvent.Event().Number().Length() ) ) );
+        }
+    else
+        {
+        dummy.Set( dummy.Right( Min( KLogMaxRemotePartyLength, dummy.Length() ) ) );
+        remoteParty.Set( iLogViewEvent.Event().RemoteParty() );
+        }
+
+    if ( dummy.Compare( remoteParty ) != 0 )
+        {
+        iEventMatched = EFalse;
+        }
+        
+    iStatus = KRequestPending;
+    if ( iEventMatched )
+        {
+        iLastMatchedLink = iLogViewEvent.Event().Link();
+        
+        // found matching event, switch state to updating
+        iLogUpdatedEvent->CopyL( iLogViewEvent.Event() );
+        SetActive();
+        TRequestStatus* status = &iStatus;
+        User::RequestComplete( status, KErrNone );
+        }
+    else
+        {
+        // get next entry
+        iEvents = iLogViewEvent.NextL( iStatus );
+        SetActive();
+        if ( iEvents == EFalse )
+            {
+            // If the LogViewEvent did not issue an asynchronous
+            // request, we must complete ourselves
+            TRequestStatus* status = &iStatus;
+            User::RequestComplete( status, KErrNone );
+            }
+        }
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::UpdateEntryL
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::UpdateEntryL()
+    {
+
+    // Update status and message id
+    // If contact database id is not defined, try to update it too
+    
+    // We update the date only if it is later than what is already in the log
+    // Otherwise we take the date that is already in the log
+    if ( iLogUpdatedEvent->Time() < iLogEvent->Time() )
+        {
+        iLogUpdatedEvent->SetTime( iLogEvent->Time() );
+        }
+        
+#ifndef _NO_MMSS_LOGGING_
+    TMmsLogger::Log( _L("CMmsLog updating log"));
+    TTime time = iLogUpdatedEvent->Time(); 
+    TBuf<KMmsDateBufferLength> dateString;
+    time.FormatL(dateString,(_L("%*E%*D%X%*N%*Y %1 %2 '%3")));
+    TMmsLogger::Log( _L(" - event date %S"), &dateString );
+    time.FormatL(dateString,(_L("%-B%:0%J%:1%T%:2%S%:3%+B")));
+    TMmsLogger::Log( _L(" - event time %S"), &dateString );
+#endif
+
+    // This will mark the entry as "sent" or "delivered" or "failed"
+    // or something similar...
+    TBool doNotUpdate = EFalse;
+    if ( iLogUpdatedEvent->Status().CompareF( KLogsMsgReadText ) == 0 )
+        {
+        // If the status is already "read" we don't change it
+        // This is a case where a delivery report arrives after a read report.
+        // Highly unlikely, but possible
+        doNotUpdate = ETrue;
+#ifndef _NO_MMSS_LOGGING_
+        TMmsLogger::Log( _L("Status already updated to read - do not update again"));
+#endif
+        }
+    else
+        {
+        iLogUpdatedEvent->SetStatus( iLogEvent->Status() );
+        // Clear the event read flag in case the user has cleared the log view
+        // We want this to become visible again.
+        iLogUpdatedEvent->ClearFlags( KLogEventRead );
+        }
+    // if we have a message id, store it
+    if ( ( iLogUpdatedEvent->Data().Length() <= 0 ) &&
+        ( iLogEvent->Data().Length() > 0 ) )
+        {
+        iLogUpdatedEvent->SetDataL( iLogEvent->Data() );
+        }
+
+    // done with this remote party
+    iCurrentRemoteParty--;
+    
+    if ( !doNotUpdate )
+        {
+        // update the entry
+        iLogClient.ChangeEvent( *iLogUpdatedEvent, iStatus );
+        SetActive();
+        }
+    else
+        {
+        // backward change - do not touch
+        iStatus = KRequestPending;
+        TRequestStatus* status = &iStatus;
+        SetActive();
+        User::RequestComplete( status, KErrNone );
+        }
+
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::CreateEntry
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::CreateEntryL()
+    {
+
+    TPtrC dummy;
+    iLogUpdatedEvent->CopyL( *iLogEvent );
+    dummy.Set( iRemoteParties->MdcaPoint( iCurrentRemoteParty ) );
+    iLogUpdatedEvent->SetRemoteParty( dummy.Left(
+        Min( dummy.Length(), KLogMaxRemotePartyLength ) ) );
+        
+#ifndef _NO_MMSS_LOGGING_
+    TMmsLogger::Log( _L("CMmsLog creating log entry"));
+#endif
+
+    TInt error = KErrNone;
+
+    // search contact database only if remote party is phone number
+    // we don't log email recipients when sending.
+    // However, if we get a delivery report from an email address, we log it.
+
+    if ( TMmsGenUtils::IsValidMMSPhoneAddress( dummy, ETrue ) )
+        {
+        iLogUpdatedEvent->SetNumber( dummy.Right(
+            Min( dummy.Length(), KLogMaxRemotePartyLength ) ) );
+        error = TMmsGenUtils::GetAlias(
+            iRemoteParties->MdcaPoint( iCurrentRemoteParty ),
+            iAlias,
+            KLogMaxRemotePartyLength,
+            iFs );
+            
+        if ( error == KErrNone )
+            {
+            if ( iAlias.Length() > 0 )
+                {
+                iLogUpdatedEvent->SetRemoteParty( iAlias );
+                }
+            }
+        }
+
+    // done with this remote party
+    TInt size = KMmsIntegerSize; // room for integer types - estimate - 32 bytes
+    size += iLogUpdatedEvent->RemoteParty().Size();
+    size += iLogUpdatedEvent->Direction().Size();
+    size += iLogUpdatedEvent->Status().Size();
+    size += iLogUpdatedEvent->Number().Size();
+    size += iLogUpdatedEvent->Description().Size();
+    size += iLogUpdatedEvent->Data().Size();
+    iCurrentRemoteParty--;
+
+    // Query about disk space.
+    if ( TMmsGenUtils::DiskSpaceBelowCriticalLevelL( &iFs, size, EDriveC ) )
+        {
+        // we use standard error code here
+        iError = KErrDiskFull;
+        }
+    if ( iError != KErrDiskFull )
+        {
+        iLogClient.AddEvent( *iLogUpdatedEvent, iStatus );
+        SetActive();
+        }
+    else
+        {
+        User::Leave( iError );
+        }
+    }
+
+// ---------------------------------------------------------
+// CMmsLog::AddEventTypeL
+//
+// ---------------------------------------------------------
+//
+void CMmsLog::AddEventTypeL()
+    {
+    // Event type is added if create event returns KErrNotFound
+    iLogEventType = CLogEventType::NewL();
+    iLogEventType->SetUid( KLogMmsEventTypeUid );
+    iLogEventType->SetDescription( KMmsLogEventTypeName );
+    iLogEventType->SetLoggingEnabled( ETrue );
+    iLogClient.AddEventType( *iLogEventType, iStatus );
+    SetActive();
+    }
+
+// ================= OTHER EXPORTED FUNCTIONS ==============
+
+//  End of File  
+