dvrengine/CommonRecordingEngine/DvrRtpClipHandler/src/CRtpToFile.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Thu, 17 Dec 2009 09:14:38 +0200
changeset 0 822a42b6c3f1
permissions -rw-r--r--
Revision: 200949 Kit: 200951

/*
* Copyright (c) 2007 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "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:    Implementation of the Common Recording Engine RTP save format class.*
*/




// INCLUDE FILES
#include "CRtpToFile.h"
#include <ipvideo/CRtpMetaHeader.h>
#include <e32math.h>
#include <bsp.h>
#include "videoserviceutilsLogger.h"

// CONSTANTS
const TUint KMaxValidDelta( 500 );     // 0.5 s
const TUint8 KDummyFullQuality( 100 ); // 100%

// ============================ MEMBER FUNCTIONS ===============================

// -----------------------------------------------------------------------------
// CRtpToFile::NewL
// Static two-phased constructor. Leaves object to cleanup stack.
// -----------------------------------------------------------------------------
//
CRtpToFile* CRtpToFile::NewL(
    MRtpFileObserver& aFileObs,
    MRtpFileWriteObserver& aWriteObs )
    {
    CRtpToFile* self = new( ELeave ) CRtpToFile( aFileObs, aWriteObs );
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );
    return self;
    }

// -----------------------------------------------------------------------------
// CRtpToFile::CRtpToFile
// C++ default constructor can NOT contain any code, that might leave.
// -----------------------------------------------------------------------------
//
CRtpToFile::CRtpToFile(
    MRtpFileObserver& aFileObs,
    MRtpFileWriteObserver& aWriteObs )
  : CRtpFileBase(),
    iFileObs( aFileObs ),
    iWriteObs( aWriteObs ),
    iCurrentTime( 0 ),
    iPreviousTime( 0 ),
    iPreviousDelta( 0 ),
    iReferenceTime( 0 ),
    iRecordEndTime( 0 ),
    iSeekArrayReference( 0 ),
    iGroupReUse( KErrNotFound ),
    iAction( MRtpFileWriteObserver::ESaveEnd )
    {
    // None
    }

// -----------------------------------------------------------------------------
// CRtpToFile::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CRtpToFile::ConstructL()
    {
    LOG( "CRtpToFile::ConstructL()" );
    
    CRtpFileBase::ConstructL();
    iCurrentPath = HBufC::NewL( 0 );
    }

// -----------------------------------------------------------------------------
// Destructor
//
CRtpToFile::~CRtpToFile()
// -----------------------------------------------------------------------------
    {
    LOG( "CRtpToFile::~CRtpToFile()" );

    Cancel();
    }

// -----------------------------------------------------------------------------
// CRtpToFile::InitRtpSaveL
// Sets path of RTP file and initiates variables.
// -----------------------------------------------------------------------------
//
void CRtpToFile::InitRtpSaveL(
    const MRtpFileWriteObserver::SRtpRecParams& aParams,
    const MRtpFileWriteObserver::TRtpSaveAction& aAction )
    {
    LOG1( "CRtpToFile::InitRtpSaveL() in, ClipPath: %S", &aParams.iClipPath );
    User::LeaveIfError( ( iMode != EModeNone ) * KErrInUse );
    
    // Mode
    switch ( aAction )
        {
        case MRtpFileWriteObserver::ESaveTimeShift:
            iMode = EModeTimeShift;
            break;

        default:
            iMode = EModeNormal;
            break;
        }

    // File server
    if ( !iFs.Handle() )
        {
        User::LeaveIfError( iFs.Connect() );
        }

    // Create clip
    CreateNewClipL( aParams );

    // Real clip's end time
    iPreviousTime = 0;
    iReferenceTime = iGroupTime * KSiKilo;
    UpdateCurrentTimeL();
    TInt64 duration( aParams.iEndTime.Int64() - 
                     aParams.iStartTime.Int64() ); 
    iRecordEndTime = iCurrentTime.Int64() + duration;
    
    // Prepare variables
    iSeekArrayReference = iGroupTime;
    iStartGroupTime = iGroupTime;

    LOG( "CRtpToFile::InitRtpSaveL() out" );
    }

// -----------------------------------------------------------------------------
// CRtpToFile::ActivateGroupsReuseL
// Starts reuse packet groups for live record when they are played.
// -----------------------------------------------------------------------------
//
void CRtpToFile::ActivateGroupsReuseL()
    {
    LOG2( "CRtpToFile::ActivateGroupsReuseL(), iMode: %d, iGroupReUse: %d",
                                              iMode, iGroupReUse );
    if ( iGroupReUse != KErrNotFound || iMode != EModeTimeShift )
        {
        User::Leave( KErrInUse );
        }

    iGroupReUse = KErrInUse;
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::SwapClipL
// Sets new path of RTP file and initiates variables.
// -----------------------------------------------------------------------------
//
void CRtpToFile::SwapClipL( const MRtpFileWriteObserver::SRtpRecParams& aParams )
    {
    LOG1( "CRtpToFile::SwapClipL(), aClipPath: %S", &aParams.iClipPath );

    User::LeaveIfError( ( iMode != EModeTimeShift ) * KErrGeneral );

    // Update old clip
    WriteSeekHeaderL();
    iGroupReUse = KErrNotFound;
        
    // Open new clip
    CreateNewClipL( aParams );
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::SaveNextRtpGroupL
// Saves one RTP packet group to a specified file.
// -----------------------------------------------------------------------------
//
TInt CRtpToFile::SaveNextGroupL(
    TPtr8& aGroup,
    TUint& aGroupLength,
    const MRtpFileWriteObserver::TRtpSaveAction& aAction )
    {
    // Verify data and mode
    User::LeaveIfError( iMode );
    
    // Group
    iDataPtr.Set( aGroup );
    
    // Set group variables
    AddGroupL();
    GroupTimeL( aGroupLength );
    AddGroupHeaderL(); 

    // Write to file
    iAction = aAction;
    iFile.Write( iThisGroup, iDataPtr, iGroupTotalLen, iStatus );
    SetActive();

    LOG2( "CRtpToFile::SaveNextGroupL(), iThisGroup: %d, iGroupTime: %u",
                                         iThisGroup, iGroupTime );
#ifdef CR_ALL_LOGS
    LogVariables( _L( "SaveNextGroupL()" ) );
#endif // CR_ALL_LOGS
    
    return iThisGroup;
    }

// -----------------------------------------------------------------------------
// CRtpToFile::UpdatePreviousTimeL
// Updates previous time after pause.
// -----------------------------------------------------------------------------
//
void CRtpToFile::UpdatePreviousTimeL()
    {
    UpdateCurrentTimeL();
    iPreviousTime = iCurrentTime.Int64();
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::StopRtpSave
// Stops file saving and finalizes header.
// -----------------------------------------------------------------------------
//
void CRtpToFile::StopRtpSave( const TInt aError )
    {
    Cancel();
    const TRtpFileMode mode( iMode ); 
    
    // If active
    if ( mode != EModeNone )
        {
#ifdef CR_ALL_LOGS
        LogVariables( _L( "StopRtpSave()" ) );
#endif // CR_ALL_LOGS
        iMode = EModeNone;

        // Update clip headers
        if ( mode != EModeTimeShift )
            {
            TRAP_IGNORE( WriteFinalMetaHeaderL( aError ) );
            }
        else
            {
            iLastSeekAddr = KMaxTInt;
            TRAP_IGNORE( WriteSeekHeaderL() );
            }

        // Close file
        iFile.Flush();
        iFile.Close();

        if ( aError == KErrNoMemory && !iGroupsTotalCount )
            {
            // Failed due to insufficient disk space, and couldn't save any
            // packets to clip. Happens when recording is started with disk 
            // space already below threshold, and failed to free any space.
            // Delete the clip completely, otherwise we are just consuming 
            // space below threshold(s).
            LOG( "CRtpToFile::StopRtpSave(), deleting file without packets !" );
            iFs.Delete( *iCurrentPath );
            }
        }
    }

// -----------------------------------------------------------------------------
// CRtpToFile::GetClipPath
// Getter for full path of currently recorded clip.
// -----------------------------------------------------------------------------
//
HBufC* CRtpToFile::ClipPath()
    {
    return iCurrentPath;
    }

// -----------------------------------------------------------------------------
// CRtpFromFile::GetCurrentLength
// Gets the current length of the clip during recording.
// -----------------------------------------------------------------------------
//
TUint CRtpToFile::GetCurrentLength()
    {
    return iGroupTime;
    }
    
// -----------------------------------------------------------------------------
// CRtpFromFile::UpdateRecordEndTime
// Uppdates the current recording end time.
// -----------------------------------------------------------------------------
//
void CRtpToFile::UpdateRecordEndTime( const TTime& aEndTime )
    {
    if ( aEndTime > iCurrentTime )
        {
        iRecordEndTime = aEndTime.Int64();
        }
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::UpdatePlayAttL
// Updates clip's playback count and spot attributes after watching.
// -----------------------------------------------------------------------------
//
void CRtpToFile::UpdatePlayAttL( const TInt aNewSpot )
    {
    CRtpMetaHeader* metaheader = CRtpMetaHeader::NewLC(
                                 iFile, CRtpMetaHeader::EMetaUpdate );
    CRtpMetaHeader::SAttributes att;
    metaheader->ReadAttributesL( att );
    
    // Step playback counter by one
    att.iPlayCount++;
    // Update playback spot
    att.iPlaySpot = ( aNewSpot > 0 && aNewSpot < iLastSeekAddr )? aNewSpot:
                                                                  KErrNone;
    metaheader->WriteAttributesL( att );
    CleanupStack::PopAndDestroy( metaheader );
    LOG2( "CRtpToFile::UpdatePlayAttL(), New playback count: %d, spot: %d", 
                                         att.iPlayCount, att.iPlaySpot );
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::RunL
// -----------------------------------------------------------------------------
//
void CRtpToFile::RunL()
    {
    User::LeaveIfError( iStatus.Int() );
    User::LeaveIfError( iFile.Flush() );
    
    // Start packets re-use?
    if ( iGroupReUse == KErrInUse )
        {
        const TInt point( iFileObs.CurrentFileReadPoint( 0 ) );
        if ( point > ( iSeekHeaderPoint + KSeekHeaderBytes ) )
            {
            iGroupReUse = KErrNone;
            }
        }

    // Stop recording if time shift too close to live
    if ( iGroupReUse > KErrNone && 
         iFileObs.CurrentFileReadPoint( 1 ) < KErrNone )
        {
        iAction = MRtpFileWriteObserver::ESaveEnd;
        LOG( "CRtpToFile::RunL(), Time shift play too close to record !" );
        }
    
    // Stop recording if end time reached
    if ( iCurrentTime.Int64() > iRecordEndTime )
        {
        iAction = MRtpFileWriteObserver::ESaveEnd;
        LOG( "CRtpToFile::RunL(), Record end time reached !" );
        }
    
    iFileObs.RtpGroupSaved( iAction );
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::RunError
// -----------------------------------------------------------------------------
//
TInt CRtpToFile::RunError( TInt aError )
    {
    LOG1( "CRtpToFile::RunError(), RunL Leaved: %d", aError );

    if ( &iWriteObs )
        {
        iWriteObs.WriteStatus( aError );
        }
    
    StopRtpSave( aError );
    return KErrNone;
    }

// -----------------------------------------------------------------------------
// CRtpToFile::DoCancel
// -----------------------------------------------------------------------------
//
void CRtpToFile::DoCancel()
    {
    LOG( "CRtpToFile::DoCancel()" );
    }

// -----------------------------------------------------------------------------
// CRtpToFile::CreateNewClipL
// Opens new clip and creates initial headers.
// -----------------------------------------------------------------------------
//
void CRtpToFile::CreateNewClipL(
    const MRtpFileWriteObserver::SRtpRecParams& aParams )
    {
    // Open file
    iFile.Close();
    User::LeaveIfError( iFile.Replace( iFs, aParams.iClipPath,
                        EFileShareAny | EFileStream | EFileWrite ) );
    // Headers
    WriteInitialMetaHeaderL( aParams );
    WriteSeekHeaderL();
    const TInt firstGroup( iSeekHeaderPoint + KSeekHeaderBytes );
    
    // Variables
    iGroupTime = 0;
    iGroupsTotalCount = 0;
    iFirstSeekAddr = firstGroup;
    iLastSeekAddr = firstGroup;
    iNextGroupPoint = firstGroup;
    delete iCurrentPath; iCurrentPath = NULL;
    iCurrentPath = aParams.iClipPath.AllocL();
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::AddGroupL
// Updates file and packet group header variables for a new group.
// -----------------------------------------------------------------------------
//
void CRtpToFile::AddGroupL()
    {
    // New group
    iThisGroup = iNextGroupPoint;
    
    // Group header
    // Note ! KGroupHeaderBytes size is allocated to incoming group in 
    //        CCRRtpRecordSink::ResetGroupVariables(), but data does not exits
    //        before CRtpToFile::AddGroupHeaderL() method is called.
    iGroupTotalLen = KGroupHeaderBytes + iDataPtr.Length();
    iNextGroupPoint = iThisGroup + iGroupTotalLen;
    const TInt prevGroup( iLastSeekAddr );

    // Time shift handling
    if ( iGroupReUse > KErrNone )
        {
        iGroupReUse--;
        }
    else
        {
        iGroupsTotalCount++;
        iLastSeekAddr = ( iMode != EModeTimeShift )? iThisGroup: 0;
        }
    
    // Start write to the beginning of the clip?
    if ( iGroupReUse == KErrNone )
        {
        iGroupReUse = iGroupsTotalCount;
        iNextGroupPoint = iSeekHeaderPoint + KSeekHeaderBytes;
        LOG2( "CRtpToFile::AddGroupL(), iGroupReUse: %d, iNextGroupPoint: %d",
                                        iGroupReUse, iNextGroupPoint );
        }

    // First group in clip?
    if ( iGroupsTotalCount == 1 )
        {
        iPrevGroupPoint = 0;
        WriteSeekHeaderL();
        iSeekArrayReference = iGroupTime;
        }
    else
        {
        iPrevGroupPoint = prevGroup;
        }
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::GroupTimeL
// Generates group time from group length reported by ring buffer and actual
// network time difference to previous group. Reference time is used to avoid
// running time error caused by network burst.
// -----------------------------------------------------------------------------
//
void CRtpToFile::GroupTimeL( TUint& aGroupLength )
    {
    UpdateCurrentTimeL();
    TUint syncLength( 0 );
    
    // previous time initiated?
    if ( iPreviousTime > 0 )
        {
        const TInt64 delta( iCurrentTime.Int64() - iPreviousTime );
        iReferenceTime+= delta;
        const TInt timeDelta( delta / KSiKilo );
        const TInt burstDelta( Abs( timeDelta - iPreviousDelta ) );
#ifdef CR_ALL_LOGS    
        LOG3( "CRtpToFile::GroupTimeL(), aGroupLength: %u, burstDelta: %d, timeDelta: %d",
                                         aGroupLength, burstDelta, timeDelta );
#endif // CR_ALL_LOGS    
        
        // Use reference time?
        if ( timeDelta > KNormalRecGroupLength && 
             Abs( burstDelta - aGroupLength ) < KMaxValidDelta )
            {
            iPreviousDelta = 0;
            syncLength = iReferenceTime / KSiKilo;
            }
        else
            {
            iPreviousDelta = timeDelta;
            syncLength = aGroupLength;
            }
        }
    else
        {
        // In record start and after pause uses only the reported group length
        iPreviousDelta = 0;
        syncLength = aGroupLength;
        iReferenceTime+= aGroupLength * KSiKilo;
        }

    // Update group time
    iGroupTime += syncLength;
    iPreviousTime = iCurrentTime.Int64();
    
    // Time shift ongoing?
    if ( iMode == EModeTimeShift )
        {
        aGroupLength = syncLength;
        }
    else
        {
        // Update seek array
        aGroupLength = 0;
        if ( ( iGroupTime - iSeekArrayReference ) >= KSeekArrayInterval )
            {
            AppendSeekArrayL( iGroupTime, iThisGroup );
            iSeekArrayReference = iGroupTime;
            }
        }
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::WriteInitialMetaHeaderL
// Writes initial meta data header of clip.
// -----------------------------------------------------------------------------
//
void CRtpToFile::WriteInitialMetaHeaderL(
    const MRtpFileWriteObserver::SRtpRecParams& aParams )
    {
    LOG( "CRtpToFile::WriteInitialMetaHeaderL() in" );

    CRtpMetaHeader* metaheader = CRtpMetaHeader::NewLC(
                                 iFile, CRtpMetaHeader::EMetaWrite );
    // Attributes
    CRtpMetaHeader::SAttributes att;
    att.iOngoing = ETrue;
    att.iCompleted = EFalse;
    att.iProtected = EFalse;
    att.iFailed = EFalse;
    att.iVersion = KCurrentClipVersion;
    att.iQuality = KDummyFullQuality;
    att.iPostRule = aParams.iPostRule;
    att.iParental = aParams.iParental;
    att.iPlayCount = 0;
    att.iPlaySpot = KErrNone;
    metaheader->WriteAttributesL( att );
    LOG1( "CRtpToFile::WriteInitialMetaHeaderL(), iPostRule: %d", att.iPostRule );
    LOG1( "CRtpToFile::WriteInitialMetaHeaderL(), iParental: %d", att.iParental );
    
    // Start date/time
    metaheader->WriteStartTimeL( aParams.iStartTime );
    TName buf( KNullDesC );
#if defined( LIVE_TV_RDEBUG_TRACE ) || defined( LIVE_TV_FILE_TRACE )
    aParams.iStartTime.FormatL( buf, KTimeDateFormat );
    LOG1( "CRtpToFile::WriteInitialMetaHeaderL(), iStartTime: %S", &buf );
#endif // LIVE_TV_RDEBUG_TRACE || LIVE_TV_FILE_TRACE

    // End time
    metaheader->WriteEndTimeL( aParams.iEndTime );

    // Duration
    metaheader->WriteDurationL( 0 );
    
    // Seek array point
    metaheader->WriteSeekArrayPointL( 0 );
    
    // Mime info
    CRtpUtil::GetMimeInfo( buf );
    metaheader->WriteUserIdL( buf );
    LOG1( "CRtpToFile::WriteInitialMetaHeaderL(), Mime: %S", &buf );

    // Device info
    CRtpUtil::GetImeiL( buf );
    metaheader->WriteDeviceInfoL( buf );
    LOG1( "CRtpToFile::WriteInitialMetaHeaderL(), IMEI: %S", &buf );

    // ESG info
    metaheader->WriteEsgDataL( aParams.iService, aParams.iProgram );
    LOG1( "CRtpToFile::WriteInitialMetaHeaderL(), Service: %S", 
                                                 &aParams.iService );
    LOG1( "CRtpToFile::WriteInitialMetaHeaderL(), Program: %S", 
                                                 &aParams.iProgram );
    // SRTP data ( Reserved for future use )
    TBuf8<3> srtp;
    srtp.Num( KErrNotFound ); 
    metaheader->WriteSrtpDataL( srtp );
    
    // SDP file
    metaheader->WriteSdpDataL( aParams.iSdpData );
    LOG1( "CRtpToFile::WriteInitialMetaHeaderL(), SDP length: %d", 
                                                  aParams.iSdpData.Length() );
    metaheader->CommitL();
    iSeekHeaderPoint = metaheader->SeekHeaderPoint();
    CleanupStack::PopAndDestroy( metaheader );

    LOG( "CRtpToFile::WriteInitialMetaHeaderL() out" );
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::WriteFinalMetaHeaderL
// Writes final meta data header of clip.
// -----------------------------------------------------------------------------
//
void CRtpToFile::WriteFinalMetaHeaderL( const TInt aStatus )
    {
    LOG( "CRtpToFile::WriteFinalMetaHeaderL() in" );
    CRtpMetaHeader* metaheader = CRtpMetaHeader::NewLC(
                                 iFile, CRtpMetaHeader::EMetaUpdate );
    // Update duration
    UpdateDurationL( metaheader );

    // Attributes
    CRtpMetaHeader::SAttributes att;
    metaheader->ReadAttributesL( att );
    att.iOngoing = EFalse;
    att.iCompleted = !aStatus;
    att.iFailed = !iGroupsTotalCount;
    metaheader->WriteAttributesL( att );
    LOG1( "CRtpToFile::WriteFinalMetaHeaderL(), Completed: %d", att.iCompleted );
    LOG1( "CRtpToFile::WriteFinalMetaHeaderL(), iFailed  : %d", att.iFailed );

    // End date/time
    metaheader->ReadStartTimeL( iCurrentTime );
    iRecordEndTime = iCurrentTime.Int64() + iGroupTime;
    metaheader->WriteEndTimeL( iRecordEndTime );
#if defined( LIVE_TV_RDEBUG_TRACE ) || defined( LIVE_TV_FILE_TRACE )
    TName buf( KNullDesC ); TTime( iRecordEndTime ).FormatL( buf, KTimeDateFormat );
    LOG1( "CRtpToFile::WriteFinalMetaHeaderL(), endTime: %S", &buf );
#endif // LIVE_TV_RDEBUG_TRACE || LIVE_TV_FILE_TRACE

    // Seek array point
    metaheader->WriteSeekArrayPointL( iNextGroupPoint );
    LOG1( "CRtpToFile::WriteFinalMetaHeaderL(), Seek array: %d", iNextGroupPoint );
    CleanupStack::PopAndDestroy( metaheader );

    // Final seek header
    SaveSeekArrayL();
    WriteSeekHeaderL();
    
    // Set orginal start time as file date
    iFile.SetModified( iCurrentTime );

    LOG( "CRtpToFile::WriteFinalMetaHeaderL() out" );
    }
 
// -----------------------------------------------------------------------------
// CRtpToFile::AddGroupHeaderL
// Adds header of one RTP group.
// Room for group header bytes and packets count comes from CCRRtpRecordSink.
// -----------------------------------------------------------------------------
//
void CRtpToFile::AddGroupHeaderL()
    {
    // Packets count (PTC) is added in CCRRtpRecordSink::SaveGroup()

    // Group time
    HBufC8* bytes = CRtpUtil::MakeBytesLC( iGroupTime );
    iDataPtr.Insert( 0, bytes->Des() );
    CleanupStack::PopAndDestroy( bytes );

    // Previous group point
    bytes = CRtpUtil::MakeBytesLC( iPrevGroupPoint );
    iDataPtr.Insert( 0, bytes->Des() );
    CleanupStack::PopAndDestroy( bytes );

    // Next Group point
    bytes = CRtpUtil::MakeBytesLC( iNextGroupPoint );
    iDataPtr.Insert( 0, bytes->Des() );
    CleanupStack::PopAndDestroy( bytes );

    // Group total size
    bytes = CRtpUtil::MakeBytesLC( iGroupTotalLen );
    iDataPtr.Insert( 0, bytes->Des() );
    CleanupStack::PopAndDestroy( bytes );
    }
 
// -----------------------------------------------------------------------------
// CRtpToFile::UpdateDurationL
// Updates clip's duration.
// -----------------------------------------------------------------------------
//
void CRtpToFile::UpdateDurationL( CRtpMetaHeader* aMetaHeader )
    {
    aMetaHeader->WriteDurationL( TInt( iGroupTime ) );
    LOG1( "CRtpToFile::UpdateDurationL(), new duration: %u", iGroupTime );
    }
    
// -----------------------------------------------------------------------------
// CRtpToFile::UpdateCurrentTimeL
// Gets current time as network time.
// -----------------------------------------------------------------------------
//
void CRtpToFile::UpdateCurrentTimeL()
    {
    iCurrentTime.UniversalTime();
    }

// End of File