upnpsharing/upnpcontentserver/src/upnpcontentmetadatautility.cpp
changeset 0 7f85d04be362
child 38 5360b7ddc251
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/upnpsharing/upnpcontentserver/src/upnpcontentmetadatautility.cpp	Thu Dec 17 08:52:00 2009 +0200
@@ -0,0 +1,727 @@
+/*
+* Copyright (c) 2006-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:      CUpnpContentMetadataUtility class implementation
+ *
+*/
+
+
+
+
+
+
+// INCLUDE FILES
+// System
+#include <e32base.h>
+#include <MCLFContentListingEngine.h>
+#include <ContentListingFactory.h>
+#include <MCLFItemListModel.h>
+#include <CLFContentListing.hrh>
+#include <MCLFItem.h>
+#include <f32file.h>
+
+// upnp stack api
+#include <upnpitem.h>
+#include <upnpcontainer.h>
+#include <upnpstring.h>
+
+// upnpframework / avcontroller helper api
+#include "upnpfileutility.h"
+
+// upnpframework / internal api's
+#include "upnpcommonutils.h"
+#include "upnpmetadatautility.h"
+
+#include "upnpdlnaprofiler.h"
+#include "upnpcdsreselementutility.h"
+
+// homeconnect internal
+#include "upnpcontentmetadatautility.h"
+#include "upnpcustomgrouper.h"
+#include "upnppostfilter.h"
+
+#include "upnpcontentserverdefs.h"
+
+_LIT( KComponentLogfile, "contentserver.txt");
+#include "upnplog.h"
+
+// CONSTANTS
+_LIT( KUPnPFileListSeparator, "\t" );
+const TInt KMediaTypeArrGranularity(1);
+
+using namespace UpnpContentServer;
+
+// ============================ MEMBER FUNCTIONS ============================
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::CUpnpContentMetadataUtility()
+// Default constructor
+// --------------------------------------------------------------------------
+//
+CUpnpContentMetadataUtility::CUpnpContentMetadataUtility()
+    : iRefreshOngoing( ETrue )
+    {
+    }
+
+void CUpnpContentMetadataUtility::ConstructL()
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    // Create Content Listing Engine and a list model
+    iEngine = ContentListingFactory::NewContentListingEngineLC();
+    CleanupStack::Pop();   // iEngine
+    iMusicModel = iEngine->CreateListModelLC( *this );
+    CleanupStack::Pop();    // iMusicModel
+
+    iImageModel = iEngine->CreateListModelLC( *this );
+    CleanupStack::Pop();    // iImageModel
+
+    iVideoModel = iEngine->CreateListModelLC( *this );
+    CleanupStack::Pop();    // iVideoModel
+
+    iCollectionModel = iEngine->CreateListModelLC( *this );
+    CleanupStack::Pop();    // iCollectionModel
+    // Set music media type filter to CLF
+    RArray<TInt> musicArray( KMediaTypeArrGranularity );
+
+    CleanupClosePushL( musicArray );
+    musicArray.AppendL( ECLFMediaTypeMusic );
+    iMusicModel->SetWantedMediaTypesL( musicArray.Array() );
+    CleanupStack::PopAndDestroy( &musicArray );
+
+    // Set image media type filter to CLF
+    RArray<TInt> imageArray( KMediaTypeArrGranularity );
+
+    CleanupClosePushL( imageArray );
+    imageArray.AppendL( ECLFMediaTypeImage );
+    iImageModel->SetWantedMediaTypesL( imageArray.Array() );
+    CleanupStack::PopAndDestroy( &imageArray );
+
+    // Set video media type filter to CLF
+    RArray<TInt> videoArray( KMediaTypeArrGranularity );
+    CleanupClosePushL( videoArray );
+    videoArray.AppendL( ECLFMediaTypeVideo );
+    iVideoModel->SetWantedMediaTypesL( videoArray.Array() );
+    CleanupStack::PopAndDestroy( &videoArray );
+
+    // Set Collection media type filter to CLF
+    RArray<TInt> collectionArray( KMediaTypeArrGranularity );
+
+    CleanupClosePushL( collectionArray );
+    collectionArray.AppendL( ECLFMediaTypeCollection );
+    iCollectionModel->SetWantedMediaTypesL( collectionArray.Array() );
+    CleanupStack::PopAndDestroy( &collectionArray );
+
+    // Group items by collection name
+    iCustomGrouper = CUpnpCustomGrouper::NewL( ECLFFieldIdCollectionName );
+    iCollectionModel->SetCustomGrouper( iCustomGrouper );
+
+    // Start to refresh the music files (HandleOperationEventL
+    // callback comes when finished)
+    iMusicModel->RefreshL();
+    
+    // Create metadata utility
+    iMetaDataUtility = CUPnPMetaDataUtility::NewL();
+
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::NewL()
+// 2 phased constructor
+// --------------------------------------------------------------------------
+//
+CUpnpContentMetadataUtility* CUpnpContentMetadataUtility::NewL()
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    CUpnpContentMetadataUtility* self
+        = new( ELeave ) CUpnpContentMetadataUtility;
+
+    CleanupStack::PushL( self );
+    self->ConstructL();
+    CleanupStack::Pop();
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    return self;
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::~CUpnpContentMetadataUtility()
+// Default destructor
+// --------------------------------------------------------------------------
+//
+CUpnpContentMetadataUtility::~CUpnpContentMetadataUtility()
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    delete iMusicModel;
+    delete iImageModel;
+    delete iVideoModel;
+    delete iCollectionModel;
+    delete iPostFilter;
+    delete iCustomGrouper;
+    delete iEngine;
+    delete iMetaDataUtility;
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::HandleOperationEventL
+// Callback implementation for MCLFOperationObserver
+// --------------------------------------------------------------------------
+//
+void CUpnpContentMetadataUtility::HandleOperationEventL(
+    TCLFOperationEvent aOperationEvent,
+    TInt /*aError*/ )
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    // Waiting is stopped when an event for refresh completion is received
+    if( aOperationEvent == ECLFRefreshComplete )
+
+        {
+        switch ( iRefreshCounter )
+            {
+        case 0 :
+            {
+            iVideoModel->RefreshL();
+            break;
+            }
+        case 1 :
+            {
+            iImageModel->RefreshL();
+            break;
+            }
+        case 2 :
+            {
+            iCollectionModel->RefreshL();
+            break;
+            }
+        case 3 :
+            {
+
+            iRefreshOngoing = EFalse;
+            iRefreshCounter = 0;
+            if ( iHandler )
+                {
+                iHandler->RefreshDoneL();
+                }
+            __LOG("CUpnpContentMetadataUtility::HandleOperationEventL: \
+Refresh done");
+            break;
+
+            }
+        default :
+            {
+            __LOG("CUpnpContentMetadataUtility::HandleOperationEventL: \
+default: error");
+            break;
+            }
+
+            }
+        if ( iRefreshOngoing )
+            {
+            iRefreshCounter++;
+            }
+        }
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::HandleItemChangeL
+// CLF content is changed --> model needs to be refreshed
+// --------------------------------------------------------------------------
+//
+void CUpnpContentMetadataUtility::HandleItemChangeL(
+    const TArray<TCLFItemId>& /*aItemIDArray*/ )
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    // Start to refresh the music files (HandleOperationEventL
+    // callback comes when finished)
+    iRefreshOngoing = ETrue;
+    iMusicModel->RefreshL();
+    iImageModel->RefreshL();
+    iVideoModel->RefreshL();
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::HandleError
+// Method is used to handle errors in changed item event.
+// --------------------------------------------------------------------------
+//
+void CUpnpContentMetadataUtility::HandleError( TInt /*aError*/ )
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    }
+
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::MusicFiles
+// ( other items are commented in header )
+// --------------------------------------------------------------------------
+//
+const MCLFItemListModel& CUpnpContentMetadataUtility::MusicFiles() const
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    return *iMusicModel;
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::ImageFiles
+// ( other items are commented in header )
+// --------------------------------------------------------------------------
+//
+const MCLFItemListModel& CUpnpContentMetadataUtility::ImageFiles() const
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    return *iImageModel;
+    }
+
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::VideoFiles
+// ( other items are commented in header )
+// --------------------------------------------------------------------------
+//
+const MCLFItemListModel& CUpnpContentMetadataUtility::VideoFiles() const
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    return *iVideoModel;
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::Collections
+// ( other items are commented in header )
+// --------------------------------------------------------------------------
+//
+const MCLFItemListModel& CUpnpContentMetadataUtility::Collections() const
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    return *iCollectionModel;
+    }
+
+
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::GetCollectionFileNamesL
+// ( other items are commented in header )
+// --------------------------------------------------------------------------
+//
+void CUpnpContentMetadataUtility::GetCollectionFileNamesL( 
+    CDesCArray& aFileArray,
+    const TDesC& aFiles ) const
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+
+    TInt collectionLength( aFiles.Length() );
+    TInt position( 0 );
+    TInt dataPos( 0 );
+    do
+        {
+        // Put filenames from ":" separeted row to aFileArray
+        TPtrC data( aFiles.Right( collectionLength - position ) );
+        dataPos = data.Find( KUPnPFileListSeparator );
+        if( dataPos > 0 )
+            {
+            ++position; // skip KUPnPFileListSeparator
+            position += dataPos;
+            aFileArray.AppendL( data.Left( dataPos ) );
+            }
+        } while ( dataPos > 0 );
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::CollectionItemsL()
+// ( other items are commented in header )
+// --------------------------------------------------------------------------
+//
+void CUpnpContentMetadataUtility::CollectionItemsL( 
+    const TDesC& aNameOfCollection )
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    __LOG1( "CUpnpContentMetadataUtility: collection: %S",
+                  &aNameOfCollection );
+
+    //clear previous filtering
+    iCollectionModel->SetPostFilter( NULL );
+
+    // Delete old post filter if any
+
+    delete iPostFilter;
+    iPostFilter = NULL;
+
+    // Create and activate a post filter for collection filtering
+    // so that the model will contain only files
+    // from selected collection
+    iPostFilter = CUpnpPostFilter::NewL( ECLFFieldIdCollectionName,
+                                            aNameOfCollection, EFalse );
+    iCollectionModel->SetPostFilter( iPostFilter );
+
+    iCollectionModel->RefreshL( ECLFRefreshPostFilter );
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    }
+
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::CreateItemL
+// Create the item with mandatory fields
+// --------------------------------------------------------------------------
+//
+CUpnpItem* CUpnpContentMetadataUtility::CreateItemL(
+    const MCLFItem& aCLFItem,
+    const TDesC8& aParentId ) const
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+
+    TPtrC fullFileName;
+    TInt err( aCLFItem.GetField( ECLFFieldIdFileNameAndPath,
+                                 fullFileName ));
+    
+    TInt32 filesize( 0 );
+    TInt err1( aCLFItem.GetField( ECLFFieldIdFileSize, filesize ) );    
+ 
+    CUpnpItem* newItem( NULL );
+    
+    if ( !err && !err1 && filesize )
+        {
+        newItem = CreateItemL( fullFileName, aParentId );
+        }
+    else
+        {
+        __LOG8_1( "MCLFItem ECLFFieldIdFileNameAndPath err= %d", err );
+        __LOG8_1( "MCLFItem ECLFFieldIdFileSize err= %d", err1 );
+        }
+    
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    return newItem;
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::CreateItemL
+// Update the basic fields, based on list of filenames
+// --------------------------------------------------------------------------
+//
+CUpnpItem* CUpnpContentMetadataUtility::CreateItemL(
+    const TDesC& aFullFilename,
+    const TDesC8& aParentId
+    ) const
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+
+    CUpnpItem* newItem( NULL );
+    
+    TFileName path( aFullFilename );
+    TBool isProtected( EFalse );
+    TRAPD( err, 
+           isProtected = UPnPFileUtility::IsFileProtectedL( path ) );
+
+    if ( !isProtected && !err )
+        {
+        // title
+        HBufC8* itemName( NULL );
+        iMetaDataUtility->LoadTitleL( path );
+        if( iMetaDataUtility->Title().Length() > 0 )
+            {
+            itemName = UpnpString::FromUnicodeL( 
+                    iMetaDataUtility->Title() );
+            }
+        else
+            {
+            // If does not find the title, using filename instead
+            TParse fileParser;
+            fileParser.Set( path, NULL, NULL );
+            itemName = UpnpString::FromUnicodeL( fileParser.Name() );
+            }
+        
+        if ( itemName )
+            {
+            CleanupStack::PushL( itemName );
+            
+            newItem = CUpnpItem::NewL();
+            CleanupStack::PushL( newItem );            
+            newItem->SetTitleL( *itemName );            
+            newItem->SetObjectClassL( KClassItem );
+            newItem->SetParentIdL( aParentId );
+            CleanupStack::Pop( newItem );
+
+            CleanupStack::PopAndDestroy( itemName );
+            }
+        }
+    else
+        {
+        __LOG8_1( "UPnPFileUtility::IsFileProtectedL err= %d", err );
+        }
+    
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    return newItem;
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::RefreshOngoing() const
+// Used to check if refresh is ongoing.
+// --------------------------------------------------------------------------
+//
+
+TBool CUpnpContentMetadataUtility::RefreshOngoing() const
+    {
+    return iRefreshOngoing;
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::UpdateMetaDataL
+// Updates meta data for the item
+// --------------------------------------------------------------------------
+//
+TBool CUpnpContentMetadataUtility::UpdateMetadataL(
+    const TUpnpMediaType& aMediaType, 
+    CUpnpItem* aItem, 
+    const TDesC& aFileName )
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+
+    // 1st use correct model according to item type
+    MCLFItemListModel* model = NULL;
+    switch( aMediaType )
+        {
+    case EMusicFile :
+        {
+        model = iMusicModel;
+        break;
+        }
+    case EVideoFile :
+        {
+        model = iVideoModel;
+        break;
+        }
+    case EPhotoFile :
+        {
+        model = iImageModel;
+        break;
+        }
+    default:
+        {
+        break;
+        }
+        }
+    // Then find the CLF item and update data from it
+    TBool found = EFalse;
+    TBool end = EFalse;
+    TInt beginLoop = iClfIndex;
+
+    // increment. If passed item count, start from beginning.
+        iClfIndex = 0;
+        beginLoop = -1;
+
+    while ( model->ItemCount() && 
+            !found && 
+            !end )
+        {
+        // Get the item
+        const MCLFItem& myItem = model->Item( iClfIndex );
+        TPtrC fileName;
+        TInt error = myItem.GetField( ECLFFieldIdFileNameAndPath,
+                                      fileName );
+
+        // if there was no error and file name matched
+        if ( !error && aFileName.CompareF( fileName ) == 0 )
+            {
+            found = ETrue;
+
+            // Get the mediatype
+            TInt32 mediaType;
+            TInt errorType = myItem.GetField( ECLFFieldIdMediaType,
+                                              mediaType );
+            // If it is music file, fill the meta data for it
+            if ( !errorType && mediaType == ECLFMediaTypeMusic )
+                {
+                // Get title, artist, album & genre tag info of the item
+                // Test update the class as it is in 1.x
+                aItem->SetObjectClassL( 
+                    KClassAudioMusicTrack );
+
+                TPtrC songArtist;
+                TInt errorArtist( myItem.GetField( ECLFFieldIdArtist,
+                                                   songArtist ) );
+                if ( !errorArtist )
+                    {
+                    CUpnpElement* elArtist = CUpnpElement::NewL( 
+                        KElementArtist );
+                    CleanupStack::PushL( elArtist );
+                    HBufC8* artist = UpnpString::FromUnicodeL( 
+                        songArtist );
+                    CleanupStack::PushL( artist );
+                    elArtist->SetValueL( *artist );
+                    // UPnP stack needs filepath
+                    elArtist->SetFilePathL( aFileName );
+                    CleanupStack::PopAndDestroy( artist );
+                    aItem->AddElementL( elArtist ); // transfer own..
+                    CleanupStack::Pop( elArtist );
+                    }
+                TPtrC songAlbum;
+                TInt errorAlbum( myItem.GetField( ECLFFieldIdAlbum,
+                                                  songAlbum ) );
+                if ( !errorAlbum )
+                    {
+                    CUpnpElement* elAlbum = CUpnpElement::NewL( 
+                        KElementAlbum );
+                    CleanupStack::PushL( elAlbum );
+                    HBufC8* album = UpnpString::FromUnicodeL( songAlbum );
+
+                    CleanupStack::PushL( album );
+                    elAlbum->SetValueL( *album );
+                    // UPnP stack needs filepath
+                    elAlbum->SetFilePathL( aFileName );
+                    CleanupStack::PopAndDestroy( album );
+                    aItem->AddElementL( elAlbum ); // transfer own..
+                    CleanupStack::Pop( elAlbum );
+                    }
+                TPtrC songGenre;
+                TInt errorGenre( myItem.GetField( ECLFFieldIdGenre,
+                                                  songGenre ) );
+                if ( !errorGenre )
+                    {
+                    CUpnpElement* elGenre = CUpnpElement::NewL( 
+                        KElementGenre );
+                    CleanupStack::PushL( elGenre );
+                    HBufC8* genre = UpnpString::FromUnicodeL( songGenre );
+
+                    CleanupStack::PushL( genre );
+                    elGenre->SetValueL( *genre );
+                    // UPnP stack needs filepath
+                    elGenre->SetFilePathL( aFileName );
+                    CleanupStack::PopAndDestroy( genre );
+                    aItem->AddElementL( elGenre ); // transfer own..
+                    CleanupStack::Pop( elGenre );
+
+                    }
+                }
+            else if ( !errorType && mediaType == ECLFMediaTypeImage )
+                {
+                // Just set correct object class
+                aItem->SetObjectClassL( KImageItemObjectClass );
+                }
+            else if ( !errorType && mediaType == ECLFMediaTypeVideo )
+                {
+                // Just set correct object class
+                aItem->SetObjectClassL( KVideoItemObjectClass );
+                }
+
+            // Below this happens to ALL media types
+            TTime dateTime;
+            TInt errorDate( myItem.GetField( ECLFFieldIdFileDate,
+                                             dateTime ) );
+
+        if ( !errorDate )
+                {
+                HBufC* date = NULL;
+                TRAP( errorDate, date = 
+                    UPnPCommonUtils::TTimeToUPnPDateL(dateTime));
+                
+                
+                if(date && errorDate == KErrNone)
+                    {
+                    CleanupStack::PushL( date );
+                    
+                    CUpnpElement* elDate = CUpnpElement::NewL( 
+                    KElementDate );
+                    CleanupStack::PushL( elDate );
+                
+                    
+                    HBufC8* date8 = UpnpString::FromUnicodeL( *date );
+                    CleanupStack::PushL( date8 );
+                    elDate->SetValueL( *date8 );
+              
+                    CleanupStack::PopAndDestroy( date8 );
+                    
+                    elDate->SetFilePathL( aFileName );
+                    aItem->AddElementL( elDate ); // transfer own..
+                
+                    CleanupStack::Pop( elDate );
+                    CleanupStack::Pop( date );
+                    }
+                    
+               
+                if(date)
+                    {
+                    delete date; 
+                    date = NULL;
+                    }
+                }
+
+            }
+        else
+            {
+            // The item was not found
+            if ( iClfIndex != beginLoop )
+                {
+                if ( ++iClfIndex >= model->ItemCount() )
+                    {
+                    iClfIndex = 0;
+                    end = ETrue;
+                    }
+                }
+            else
+                {
+                end = ETrue;
+                __LOG("end=ETRue");
+                }
+            }
+        } // while
+    if( found )
+        {
+        __LOG("CUpnpContentMetadataUtility:: item found");
+        }
+    else
+        {
+        __LOG("CUpnpContentMetadataUtility:: item not found");
+        }
+
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    return found;
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::ClearPostFiltersL
+// ( other items are commented in header )
+// --------------------------------------------------------------------------
+void CUpnpContentMetadataUtility::ClearPostFiltersL()
+    {
+    __LOG8_1( "%s begin.", __PRETTY_FUNCTION__ );
+    //clear previous filtering
+    iCollectionModel->SetPostFilter( NULL );
+
+    // Delete old post filter if any
+
+    delete iPostFilter;
+    iPostFilter = NULL;
+    
+    iCollectionModel->RefreshL( ECLFRefreshPostFilter );
+
+   // Set the default postfilter
+    iPostFilter = CUpnpPostFilter::NewL( ECLFFieldIdCollectionName,
+                                         KNullDesC, ETrue );
+    iCollectionModel->SetPostFilter( iPostFilter );
+
+    iCollectionModel->RefreshL( ECLFRefreshPostFilter );
+    __LOG8_1( "%s end.", __PRETTY_FUNCTION__ );
+    }
+
+// --------------------------------------------------------------------------
+// CUpnpContentMetadataUtility::SetCallback
+// ( other items are commented in header )
+// --------------------------------------------------------------------------
+void CUpnpContentMetadataUtility::SetCallback( 
+    MUpnpMetadataObserver* aObserver )
+    {
+    iHandler = aObserver;
+
+    }
+
+//  End of File