diff -r 000000000000 -r ba25891c3a9e ncdengine/provider/server/src/ncdnodemetadataimpl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ncdengine/provider/server/src/ncdnodemetadataimpl.cpp Thu Dec 17 08:51:10 2009 +0200 @@ -0,0 +1,2639 @@ +/* +* Copyright (c) 2006-2008 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: Implements CNcdNodeMetaData class +* +*/ + + +#include "ncdnodemetadataimpl.h" +#include "ncdnodeidentifier.h" +#include "ncdnodedisclaimer.h" +#include "ncdnodeiconimpl.h" +#include "ncdnodescreenshotimpl.h" +#include "ncdnodeskinimpl.h" +#include "ncdnodeupgradeimpl.h" +#include "ncdnodedependencyimpl.h" +#include "ncdnodeuricontentimpl.h" +#include "ncdnodecontentinfoimpl.h" +#include "ncdnodepreviewimpl.h" +#include "ncdpurchaseoptionimpl.h" +#include "ncdpurchasedetails.h" +#include "ncdpurchasehistorydbimpl.h" +#include "ncdnodedownloadimpl.h" +#include "ncdnodeinstallimpl.h" +#include "ncdnodeuserdataimpl.h" +#include "catalogssession.h" +#include "catalogsbasemessage.h" +#include "ncdnodefunctionids.h" +#include "catalogsconstants.h" +#include "ncd_pp_dataentity.h" +#include "ncd_pp_dataentitycontent.h" +#include "ncd_cp_query.h" +#include "ncd_pp_download.h" +#include "ncd_pp_purchaseoption.h" +#include "ncdprotocoltypes.h" +#include "catalogsutils.h" +#include "ncdutils.h" +#include "ncdkeyvaluepair.h" +#include "ncdpanics.h" +#include "ncdserversubscribablecontent.h" +#include "ncdpurchasehistoryutils.h" +#include "ncderrors.h" + +#include "catalogsdebug.h" + + +CNcdNodeMetaData::CNcdNodeMetaData( + NcdNodeClassIds::TNcdNodeClassId aClassId, + CNcdNodeManager& aNodeManager ) +: CCatalogsCommunicable(), + iClassId( aClassId ), + iNodeManager( aNodeManager ) + { + DLTRACEIN(("Meta class id: %d", aClassId)); + } + + +void CNcdNodeMetaData::ConstructL( const CNcdNodeIdentifier& aIdentifier ) + { + DLTRACEIN(("")); + + // These two values has to be set. So, this metadata can be identified. + iIdentifier = CNcdNodeIdentifier::NewL( aIdentifier ); + + // The user data is part of the metadata. So, the ui may set its own info + // for the node by using this object. + iUserData = CNcdNodeUserData::NewL( *iIdentifier, iNodeManager ); + + iTimeStamp = KNullDesC().AllocL(); + iName = KNullDesC().AllocL(); + iDescription = KNullDesC().AllocL(); + iLayoutType = KNullDesC().AllocL(); + + // This internalization is not related to purchase history so it's done + // separately + TRAP_IGNORE( InternalizeInstallFromContentInfoL() ); + + + TRAPD( phError, + { + // Get purchase details + CNcdPurchaseDetails* details = PurchaseDetailsLC(); + + InternalizeContentInfoL( *details ); + + // Try to internalize URI content from purchase history + InternalizeUriContentL( *details ); + + InternalizeDependencyL( *details ); + // Try to internalize node download from purchase history. + InternalizeDownloadL( *details ); + + // Try to internalize node install from purchase history + InternalizeInstallL( *details ); + + // Try to internalize node icon from purchase history + // (does not read icon data to memory) + InternalizeIconL( *details ); + + CleanupStack::PopAndDestroy( details ); + }); + + if( phError != KErrNone && + phError != KErrNotFound && + phError != KNcdErrorNoPurchaseInformation ) + { + DLERROR(( "phError: %d", phError )); + User::Leave( phError ); + } + + DLTRACEOUT(("")); + } + + +CNcdNodeMetaData::~CNcdNodeMetaData() + { + DLTRACEIN(("")); + + delete iIdentifier; + iIdentifier = NULL; + + delete iTimeStamp; + iTimeStamp = NULL; + + delete iName; + iName = NULL; + + delete iDescription; + iDescription = NULL; + + delete iLayoutType; + iLayoutType = NULL; + + // Notice that CCatalogsCommunicable classes cannot be destroyed by + // calling delete! Instead call Close to them + + if ( iUserData ) + { + DLINFO(("Closing node user data")); + iUserData->Close(); + iUserData = NULL; + } + + if ( iDisclaimer != NULL ) + { + iDisclaimer->Close(); + iDisclaimer = NULL; + } + + if ( iIcon != NULL ) + { + iIcon->Close(); + iIcon = NULL; + } + + if ( iScreenshot != NULL ) + { + iScreenshot->Close(); + iScreenshot = NULL; + } + + if ( iSkin != NULL ) + { + iSkin->Close(); + iSkin = NULL; + } + + if( iUriContent != NULL ) + { + iUriContent->Close(); + iUriContent = NULL; + } + + if( iContentInfo != NULL ) + { + iContentInfo->Close(); + iContentInfo = NULL; + } + + if( iPreview != NULL ) + { + iPreview->Close(); + iPreview = NULL; + } + + if ( iUpgrade != NULL ) + { + iUpgrade->Close(); + iUpgrade = NULL; + } + + if ( iDependency != NULL ) + { + iDependency->Close(); + iDependency = NULL; + } + + if ( iDownload ) + { + DLINFO(("Closing node download")); + iDownload->Close(); + iDownload = NULL; + } + + if ( iInstall ) + { + DLINFO(("Closing node install")); + iInstall->Close(); + iInstall = NULL; + } + + if ( iMoreInfo != NULL ) + { + iMoreInfo->Close(); + iMoreInfo = NULL; + } + + iDetails.ResetAndDestroy(); + + delete iSubscribableContent; + iSubscribableContent = NULL; + + ResetAndCloseArray( iPurchaseOptions ); + + DLTRACEOUT(("")); + } + + +CNcdNodeManager& CNcdNodeMetaData::NodeManager() const + { + return iNodeManager; + } + + +const CNcdNodeIdentifier& CNcdNodeMetaData::Identifier() const + { + return *iIdentifier; + } + + +NcdNodeClassIds::TNcdNodeClassId CNcdNodeMetaData::ClassId() const + { + return iClassId; + } + +const TDesC& CNcdNodeMetaData::TimeStamp() const + { + DASSERT( iTimeStamp ); + return *iTimeStamp; + } + + +const TDesC& CNcdNodeMetaData::NodeName() const + { + DASSERT( iName ); + return *iName; + } + +void CNcdNodeMetaData::SetNodeNameL( const TDesC& aName ) + { + HBufC* newName = aName.AllocL(); + delete iName; + iName = newName; + } + +const TDesC& CNcdNodeMetaData::Description() const + { + DASSERT( iDescription ); + return *iDescription; + } + +void CNcdNodeMetaData::SetDescriptionL( + const TDesC& aDescription ) + { + HBufC* newDescription = aDescription.AllocL(); + delete iDescription; + iDescription = newDescription; + } + +const TDesC& CNcdNodeMetaData::LayoutType() const + { + DASSERT( iLayoutType ); + return *iLayoutType; + } + +const CNcdNodeDisclaimer& CNcdNodeMetaData::DisclaimerL() const + { + if ( iDisclaimer == NULL ) + { + User::Leave( KErrNotFound ); + } + + return *iDisclaimer; + } + +void CNcdNodeMetaData::SetDisclaimer( CNcdNodeDisclaimer* aDisclaimer ) + { + if ( iDisclaimer != NULL ) + { + iDisclaimer->Close(); + } + iDisclaimer = aDisclaimer; + } + + +const CNcdNodeDisclaimer& CNcdNodeMetaData::MoreInfoL() const + { + if ( iMoreInfo == NULL ) + { + User::Leave( KErrNotFound ); + } + + return *iMoreInfo; + } + +CNcdNodeIcon& CNcdNodeMetaData::IconL() const + { + if ( iIcon == NULL ) + { + User::Leave( KErrNotFound ); + } + + return *iIcon; + } + + +void CNcdNodeMetaData::SetIcon( CNcdNodeIcon* aIcon ) + { + if ( iIcon ) + { + iIcon->Close(); + } + iIcon = aIcon; + } + + +const CNcdNodeScreenshot& CNcdNodeMetaData::ScreenshotL() const + { + if ( iScreenshot == NULL ) + { + User::Leave( KErrNotFound ); + } + + return *iScreenshot; + } + + +const CNcdNodeSkin& CNcdNodeMetaData::SkinL() const + { + DASSERT( iSkin ); + return *iSkin; + } + + +CNcdNodePreview& CNcdNodeMetaData::PreviewL() const + { + if ( iPreview == NULL ) + { + User::Leave( KErrNotFound ); + } + + return *iPreview; + } + + +const CNcdNodeUpgrade& CNcdNodeMetaData::UpgradeL() const + { + if ( iUpgrade == NULL ) + { + User::Leave( KErrNotFound ); + } + return *iUpgrade; + } + + +const CNcdNodeDependency& CNcdNodeMetaData::DependencyL() const + { + if ( iDependency == NULL ) + { + User::Leave( KErrNotFound ); + } + return *iDependency; + } + + +const CNcdNodeContentInfo& CNcdNodeMetaData::ContentInfoL() const + { + if ( iContentInfo == NULL ) + { + User::Leave( KErrNotFound ); + } + return *iContentInfo; + } + + +const CNcdServerSubscribableContent* + CNcdNodeMetaData::SubscribableContent() const + { + return iSubscribableContent; + } + + +const RPointerArray& CNcdNodeMetaData::PurchaseOptions() const + { + return iPurchaseOptions; + } + +CNcdPurchaseOptionImpl& CNcdNodeMetaData::PurchaseOptionByIdL( + const TDesC& aPurchaseOptionId ) const + { + DLTRACEIN(("")); + for ( TInt i = 0; i < iPurchaseOptions.Count(); i++ ) + { + if ( iPurchaseOptions[i]->Id() == aPurchaseOptionId ) + { + return *iPurchaseOptions[i]; + } + } + + User::Leave( KErrNotFound ); + CNcdPurchaseOptionImpl* foo( NULL ); + return *foo; + } + +TBool CNcdNodeMetaData::AlwaysVisible() const + { + return iAlwaysVisible; + } + + +void CNcdNodeMetaData::SetAlwaysVisible( TBool aValue ) + { + iAlwaysVisible = aValue; + } + + +void CNcdNodeMetaData::InternalizeL( MNcdPreminetProtocolDataEntity& aData ) + { + DLTRACEIN(("")); + + // First create the new values + HBufC* tmpTimeStamp = aData.Timestamp().AllocLC(); + HBufC* tmpName = aData.Name().AllocLC(); + HBufC* tmpDescription = aData.Description().AllocLC(); + HBufC* tmpLayoutType = aData.LayoutType().AllocLC(); + + + DLTRACE(( _L("MetaData timestamp: %S"), tmpTimeStamp )); + DLTRACE(( _L("MetaData name: %S"), tmpName )); + DLTRACE(( _L("MetaData description: %S"), tmpDescription )); + + + delete iLayoutType; + iLayoutType = tmpLayoutType; + CleanupStack::Pop( tmpLayoutType ); + + delete iDescription; + iDescription = tmpDescription; + CleanupStack::Pop( tmpDescription ); + + delete iName; + iName = tmpName; + CleanupStack::Pop( tmpName ); + + delete iTimeStamp; + iTimeStamp = tmpTimeStamp; + CleanupStack::Pop( tmpTimeStamp ); + + if ( aData.Disclaimer() ) + { + // New disclaimer info should be set + + if ( iDisclaimer == NULL ) + { + // Create disclaimer because it did not exist before. + iDisclaimer = CNcdNodeDisclaimer::NewL(); + } + // Update disclaimer info. + iDisclaimer->InternalizeL( *aData.Disclaimer() ); + } + else if ( iDisclaimer ) + { + // Because new data does not contain disclaimer. + // Close old one and set the value NULL. + // Notice that CCatalogsCommunicalbe classes should be Closed + // instead of deleting. + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iDisclaimer->SetAsObsolete( ETrue ); + iDisclaimer->Close(); + iDisclaimer = NULL; + } + + if ( aData.Icon() ) + { + // New icon info should be set. + + if ( iIcon == NULL ) + { + iIcon = CNcdNodeIcon::NewL( iNodeManager, *this ); + } + iIcon->InternalizeL( aData ); + } + else if ( iIcon != NULL ) + { + // Notice that icons should not be deleted because + // all the CCatalogsCommunicable classes should be Closed instead. + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iIcon->SetAsObsolete( ETrue ); + iIcon->Close(); + iIcon = NULL; + } + + if ( aData.ScreenshotCount() > 0 ) + { + // New screenshot info should be set. + + if ( iScreenshot == NULL ) + { + iScreenshot = CNcdNodeScreenshot::NewL( iNodeManager, *this ); + } + iScreenshot->InternalizeL( aData ); + } + else if ( iScreenshot != NULL ) + { + // Notice that screenshots should not be deleted because + // all the CCatalogsCommunicable classes should be Closed instead. + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iScreenshot->SetAsObsolete( ETrue ); + iScreenshot->Close(); + iScreenshot = NULL; + } + + if ( aData.Skin() ) + { + // New skin info should be set. + + if ( iSkin == NULL ) + { + iSkin = CNcdNodeSkin::NewL(); + } + iSkin->InternalizeL( aData ); + } + else if ( iSkin != NULL ) + { + // Notice that skins should not be deleted because + // all the CCatalogsCommunicable classes should be Closed instead. + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iSkin->SetAsObsolete( ETrue ); + iSkin->Close(); + iSkin = NULL; + } + + if ( aData.DownloadableContent() ) + { + // New content info should be set. + + if ( iContentInfo == NULL ) + { + iContentInfo = CNcdNodeContentInfo::NewL(); + } + iContentInfo->InternalizeL( aData ); + InternalizeInstallFromContentInfoL(); + } + else if ( iContentInfo != NULL ) + { + DLINFO(("No downloadable content")); + // Notice that content info should not be deleted because + // all the CCatalogsCommunicable classes should be Closed instead. + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iContentInfo->SetAsObsolete( ETrue ); + iContentInfo->Close(); + iContentInfo = NULL; + } + + if ( aData.PreviewCount() > 0 ) + { + if ( iPreview == NULL ) + { + iPreview = CNcdNodePreview::NewL( *this, iNodeManager ); + } + iPreview->InternalizeL( aData ); + } + else if ( iPreview != NULL ) + { + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iPreview->SetAsObsolete( ETrue ); + iPreview->Close(); + iPreview = NULL; + } + + // Create upgrade object if necessary + if ( aData.DownloadableContent() != NULL ) + { + if ( iUpgrade == NULL ) + { + iUpgrade = CNcdNodeUpgrade::NewL( *this ); + } + TPtrC version( KNullDesC ); + if ( iContentInfo ) + { + version.Set( iContentInfo->Version() ); + } + TRAPD( upgradeErr, iUpgrade->InternalizeL( aData, version ) ); + TBool upgradeExists = HandleContentUpgradeL(); + + // HandleContentUpgradeL checks CNcdNodeContentInfo and + // CNcdNodeInstall for the need of upgrade interfaces + if ( upgradeErr == KErrNotFound && + !upgradeExists ) + { + DLTRACE(("No upgrade")); + // The given data did not contain any information about upgrade. + // So, delete the created upgrade object. + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iUpgrade->SetAsObsolete( ETrue ); + iUpgrade->Close(); + iUpgrade = NULL; + } + + LeaveIfNotErrorL( upgradeErr, KErrNotFound ); + } + else if ( iUpgrade != NULL ) + { + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iUpgrade->SetAsObsolete( ETrue ); + iUpgrade->Close(); + iUpgrade = NULL; + } + + + // Create dependency object if necessary + if ( aData.DownloadableContent() != NULL ) + { + if ( iDependency == NULL ) + { + iDependency = CNcdNodeDependency::NewL( *this ); + } + TRAPD( dependencyErr, iDependency->InternalizeL( aData ) ); + if ( dependencyErr == KErrNotFound ) + { + DLTRACE(("No dependency, deleting the object")); + // The given data did not contain any information about dependency. + // So, delete the created object. + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iDependency->SetAsObsolete( ETrue ); + iDependency->Close(); + iDependency = NULL; + } + else if ( dependencyErr != KErrNone ) + { + // Some error occurred. So let this leave. + User::Leave( dependencyErr ); + } + } + else if ( iDependency != NULL ) + { + DLTRACE(("Removing old dependency")); + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iDependency->SetAsObsolete( ETrue ); + iDependency->Close(); + iDependency = NULL; + } + + + + // Subscribable content + const MNcdPreminetProtocolDataEntityContent* subscribableContent = + aData.SubscribableContent(); + + if ( subscribableContent != NULL ) + { + DLINFO(( "Subscribable content element found from protocol-object" )); + + if ( iSubscribableContent == NULL ) + { + iSubscribableContent = CNcdServerSubscribableContent::NewL(); + } + iSubscribableContent->InternalizeL( *subscribableContent ); + } + + + if ( aData.MoreInfo() ) + { + if ( iMoreInfo == NULL ) + { + iMoreInfo = CNcdNodeDisclaimer::NewL(); + } + iMoreInfo->InternalizeL( *aData.MoreInfo() ); + } + else if ( iMoreInfo ) + { + // Because this object may still be left hanging for proxy object if it is used by UI, + // set the object obsolete. So, UI will know this if it is trying to internalize + // the hanging object. + iMoreInfo->SetAsObsolete( ETrue ); + iMoreInfo->Close(); + iMoreInfo = NULL; + } + + iDetails.ResetAndDestroy(); + + for ( TInt i = 0 ; i < aData.DetailCount() ; i++ ) + { + DLTRACE(( _L("Detail id=%S, value=%S"), + &aData.DetailL( i ).Id(), &aData.DetailL( i ).Value())); + if( aData.DetailL( i ).Id() == KNullDesC ) + { + DLTRACE(("Empty id -> not adding detail!")); + continue; + } + CNcdKeyValuePair* detail = CNcdKeyValuePair::NewLC( + aData.DetailL( i ).Id(), aData.DetailL( i ).Value() ); + iDetails.AppendL( detail ); + CleanupStack::Pop( detail ); + } + + + // Create or reinternalize purchase options : + + const TInt KPurchaseOptionCount( aData.PurchaseOptionCount() ); + + DLINFO(( "Amount of purchaseoptions found from protocol-object: %d", + KPurchaseOptionCount )); + + TInt purchaseOptionIndex( 0 ); + while ( purchaseOptionIndex < KPurchaseOptionCount ) + { + const MNcdPreminetProtocolPurchaseOption& tmpOption = + aData.PurchaseOptionL( purchaseOptionIndex ); + InternalizePurchaseOptionL( tmpOption ); + ++purchaseOptionIndex; + } + + // Remove purchase options that were removed from the server + RemoveNotUpdatedPurchaseOptions(); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::InternalizeContentInfoL( + const MNcdPurchaseDetails& aDetails ) + { + DLTRACEIN(("")); + + if ( !iContentInfo ) + { + iContentInfo = CNcdNodeContentInfo::NewL(); + } + + iContentInfo->InternalizeL( aDetails ); + } + + +void CNcdNodeMetaData::InternalizeUriContentL( + const MNcdPurchaseDetails& aDetails ) + { + DLTRACEIN(("")); + + if ( !iUriContent ) + { + iUriContent = CNcdNodeUriContent::NewL(); + if ( !iUriContent->InternalizeL( aDetails ) ) + { + iUriContent->Close(); + iUriContent = NULL; + } + } + else + { + iUriContent->InternalizeL( aDetails ); + } + + DLTRACEOUT(("")); + } + + +CNcdNodeDownload& CNcdNodeMetaData::DownloadL() + { + if( iDownload == NULL ) + { + User::Leave( KErrNotFound ); + } + + return *iDownload; + } + + +void CNcdNodeMetaData::InternalizeDownloadL( const MNcdPurchaseDetails& aDetails ) + { + DLTRACEIN(("")); + + if ( !iDownload ) + { + iDownload = CNcdNodeDownload::NewL(); + + // Delete node download if the internalization failed + if ( !iDownload->InternalizeL( aDetails ) ) + { + iDownload->Close(); + iDownload = NULL; + } + } + else + { + iDownload->InternalizeL( aDetails ); + } + DLTRACEOUT(("")); + } + + +CNcdNodeInstall& CNcdNodeMetaData::InstallL() + { + if( iInstall == NULL ) + { + User::Leave( KErrNotFound ); + } + + return *iInstall; + } + + +void CNcdNodeMetaData::InternalizeInstallL( const MNcdPurchaseDetails& aDetails ) + { + DLTRACEIN(("")); + + if ( !iInstall ) + { + iInstall = CNcdNodeInstall::NewL( *this ); + if ( !iInstall->InternalizeL( aDetails ) ) + { + iInstall->Close(); + iInstall = NULL; + } + } + else + { + iInstall->InternalizeL( aDetails ); + } + + // Get version of bought content from purchase history so that + // we can compare it with content info in HandleContentUpgradeL + TRAPD( err, TCatalogsVersion::ConvertL( + iBoughtContentVersion, aDetails.Version() ) ); + + LeaveIfNotErrorL( err, KErrArgument, KErrGeneral ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::InternalizeInstallFromContentInfoL() + { + DLTRACEIN(("")); + if ( iContentInfo && iContentInfo->Uid() != TUid::Null() ) + { + TBool create = !iInstall; + if ( create ) + { + DLTRACE(("No install, creating")); + iInstall = CNcdNodeInstall::NewL( *this ); + } + + // Only delete install if it was created in this method + if ( !iInstall->InternalizeContentInfoL() && create ) + { + DLTRACE(("App is not installed, deleting install")); + iInstall->Close(); + iInstall = NULL; + } + } + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::InternalizeDependencyL( + const MNcdPurchaseDetails& aDetails ) + { + DLTRACEIN(("")); + if ( !iDependency ) + { + iDependency = CNcdNodeDependency::NewL( *this ); + if ( !iDependency->InternalizeFromPurchaseDetailsL( aDetails ) ) + { + iDependency->Close(); + iDependency = NULL; + } + } + else + { + iDependency->InternalizeFromPurchaseDetailsL( aDetails ); + } + DLTRACEOUT(("dependency internalized from purchasehistory")); + } + +void CNcdNodeMetaData::InternalizeIconL( const MNcdPurchaseDetails& aDetails ) + { + DLTRACEIN(("")); + if( aDetails.HasIcon() ) + { + DLTRACE(("Purchase details have icon")); + // Only use icon from PH if there is no icon previously + // (icon may change on server whilst the icon in PH is the one that + // was available during purchase ). + if( !iIcon ) + { + iIcon = CNcdNodeIcon::NewL( iNodeManager, *this, ETrue ); + } + } + } + +CNcdPurchaseDetails* CNcdNodeMetaData::PurchaseDetailsLC( TBool aLoadIcon ) const + { + DLTRACEIN(("")); + + return NcdPurchaseHistoryUtils::PurchaseDetailsLC( + iNodeManager.PurchaseHistory(), + iIdentifier->ClientUid(), + *iIdentifier, + aLoadIcon ); + } + + +TBool CNcdNodeMetaData::HandleContentUpgradeL() + { + DLTRACEIN(("")); + DLNODEID(( Identifier() )); + if ( iContentInfo ) + { + DLTRACE(("Content info exists, check if content upgrades something")); + + TCatalogsVersion version; + TRAPD( err, TCatalogsVersion::ConvertL( + version, iContentInfo->Version() ) ); + + LeaveIfNotErrorL( err, KErrArgument, KErrGeneral ); + + // First compare version number to purchase history because it's highest priority + if ( version == iBoughtContentVersion ) + { + if ( iUpgrade ) + { + // Reset content upgrade status + iUpgrade->SetContentUpgradesL( + EFalse, + TUid::Null(), + KNullDesC ); + } + DLTRACEOUT(("Version in contentinfo matches bought version, no upgrade")); + return EFalse; + } + + + // ContentCount() ensures that there's actually something installed, otherwise + // we would end up checking against the same contentinfo if the content is + // a SIS app and some version of it is already installed + if ( iInstall && iInstall->ContentCount() ) + { + + TCatalogsVersion installVersion( iInstall->ContentVersion() ); + DLTRACE(("Version from install: %d.%d.%d", + installVersion.iMajor, installVersion.iMinor, installVersion.iBuild )); + + // IsAllContentInstalledL returns true if all content files are installed + if ( installVersion != TCatalogsVersion() && + version > installVersion && + iInstall->IsAllContentInstalledL() ) + { + + if ( !iUpgrade ) + { + DLTRACE(("Creating upgrade")); + iUpgrade = CNcdNodeUpgrade::NewL( *this ); + } + + // content is an upgrade + iUpgrade->SetContentUpgradesL( + ETrue, + iContentInfo->Uid(), + iContentInfo->Version() ); + return ETrue; + } + } + // If content is an application, check if it upgrades. "Else if" is necessary + // because otherwise content version check could be overridden by application version + // check + else if ( iContentInfo->Uid() != TUid::Null() ) + { + TNcdApplicationStatus contentVersion( + ENcdApplicationNotInstalled ); + + TRAPD( err, + contentVersion = CNcdProviderUtils::IsApplicationInstalledL( + iContentInfo->Uid(), + iContentInfo->Version() ) ); + + // Ignore errors in version conversion + LeaveIfNotErrorL( err, KErrArgument, KErrGeneral ); + + if ( contentVersion == ENcdApplicationOlderVersionInstalled ) + { + if ( !iUpgrade ) + { + DLTRACE(("Creating upgrade")); + iUpgrade = CNcdNodeUpgrade::NewL( *this ); + } + + // Set upgrade data which will be available through the API + iUpgrade->SetContentUpgradesL( + ETrue, + iContentInfo->Uid(), + iContentInfo->Version() ); + + DLTRACEOUT(("Content upgrades")); + return ETrue; + } + } + } + + if ( iUpgrade ) + { + // Reset content upgrade status + iUpgrade->SetContentUpgradesL( + EFalse, + TUid::Null(), + KNullDesC ); + } + return EFalse; + } + + +void CNcdNodeMetaData::SetDeleteSoon( TBool aDeleteSoon ) + { + iDeleteSoon = aDeleteSoon; + } + + +TBool CNcdNodeMetaData::DeleteSoon() const + { + return iDeleteSoon; + } + + +void CNcdNodeMetaData::ExternalizeL( RWriteStream& aStream ) + { + DLTRACEIN(("")); + + // Set all the membervariable values to the stream. So, + // that the stream may be used later to create a new + // object. + + // First insert data that node manager will use to + // create this class object + DLTRACE(("Meta extern class id: %d", iClassId)); + aStream.WriteInt32L( iClassId ); + + // Write the data that will be used when internalize function + // is called. + + ExternalizeDesL( TimeStamp(), aStream ); + ExternalizeDesL( NodeName(), aStream ); + ExternalizeDesL( Description(), aStream ); + ExternalizeDesL( LayoutType(), aStream ); + + aStream.WriteInt8L( iAlwaysVisible ); + + if ( iDisclaimer ) + { + aStream.WriteInt32L( 1 ); + iDisclaimer->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + if ( iIcon ) + { + aStream.WriteInt32L( 1 ); + iIcon->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + if ( iScreenshot ) + { + aStream.WriteInt32L( 1 ); + iScreenshot->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + if ( iSkin ) + { + aStream.WriteInt32L( 1 ); + iSkin->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + if ( iPreview ) + { + aStream.WriteInt32L( 1 ); + iPreview->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + if ( iContentInfo ) + { + aStream.WriteInt32L( 1 ); + iContentInfo->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + if ( iUpgrade ) + { + aStream.WriteInt32L( 1 ); + iUpgrade->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + if ( iDependency ) + { + aStream.WriteInt32L( 1 ); + iDependency->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + // Subscribable content + if ( iSubscribableContent ) + { + aStream.WriteInt32L( ETrue ); + iSubscribableContent->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( EFalse ); + } + + + // Purchase options + + TInt purchaseOptionCount( iPurchaseOptions.Count() ); + DLINFO(("Externalizing purchase options: %d", purchaseOptionCount )); + aStream.WriteInt32L( purchaseOptionCount ); + + TInt purchaseOptionIndex( 0 ); + while ( purchaseOptionIndex < purchaseOptionCount ) + { + iPurchaseOptions[purchaseOptionIndex]->ExternalizeL( aStream ); + ++purchaseOptionIndex; + } + + if ( iMoreInfo ) + { + aStream.WriteInt32L( 1 ); + iMoreInfo->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( 0 ); + } + + aStream.WriteInt32L( iDetails.Count() ); + for( TInt i = 0 ; i < iDetails.Count() ; i++ ) + { + iDetails[i]->ExternalizeL( aStream ); + } + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::InternalizeL( RReadStream& aStream ) + { + DLTRACEIN(("")); + + // NOTICE that this internalize function supposes that + // classid, namespace and metadataid info, that are + // inserted during externalization, are already read from + // the stream before calling this function. + + InternalizeDesL( iTimeStamp, aStream ); + InternalizeDesL( iName, aStream ); + InternalizeDesL( iDescription, aStream ); + InternalizeDesL( iLayoutType, aStream ); + + iAlwaysVisible = aStream.ReadInt8L(); + + TInt32 disclaimerExists = aStream.ReadInt32L(); + if ( disclaimerExists ) + { + if ( iDisclaimer == NULL ) + { + iDisclaimer = CNcdNodeDisclaimer::NewL(); + } + iDisclaimer->InternalizeL( aStream ); + } + else if ( iDisclaimer ) + { + // Because icon should not exist, Close it. + iDisclaimer->Close(); + iDisclaimer = NULL; + } + + TInt32 iconExists = aStream.ReadInt32L(); + if ( iconExists ) + { + if ( iIcon == NULL ) + { + iIcon = CNcdNodeIcon::NewL( iNodeManager, *this ); + } + iIcon->InternalizeL( aStream ); + } + else if ( iIcon ) + { + // Because icon should not exist, delete it. + iIcon->Close(); + iIcon = NULL; + } + + TInt32 screenshotExists = aStream.ReadInt32L(); + if ( screenshotExists ) + { + if ( iScreenshot == NULL ) + { + iScreenshot = CNcdNodeScreenshot::NewL( iNodeManager, *this ); + } + iScreenshot->InternalizeL( aStream ); + } + else if ( iScreenshot ) + { + // Because screenshot should not exist, delete it. + iScreenshot->Close(); + iScreenshot = NULL; + } + + TInt32 skinExists = aStream.ReadInt32L(); + if ( skinExists ) + { + if ( iSkin == NULL ) + { + iSkin = CNcdNodeSkin::NewL(); + } + iSkin->InternalizeL( aStream ); + } + else if ( iSkin ) + { + // Because icon should not exist, Close it. + iSkin->Close(); + iSkin = NULL; + } + + TInt32 previewExists = aStream.ReadInt32L(); + if ( previewExists ) + { + if ( iPreview == NULL ) + { + iPreview = CNcdNodePreview::NewL( *this, iNodeManager ); + } + iPreview->InternalizeL( aStream ); + } + else if ( iPreview ) + { + // Because preview should not exist, Close it. + iPreview->Close(); + iPreview = NULL; + } + + DLTRACE(("Internalizing content info")); + TInt32 contentInfoExists = aStream.ReadInt32L(); + if ( contentInfoExists ) + { + if ( iContentInfo == NULL ) + { + iContentInfo = CNcdNodeContentInfo::NewL(); + } + iContentInfo->InternalizeL( aStream ); + InternalizeInstallFromContentInfoL(); + } + else if ( iContentInfo ) + { + // Because content info should not exist, Close it. + iContentInfo->Close(); + iContentInfo = NULL; + } + + DLTRACE(("Internalizing upgrade for metadata:")); + DLNODEID(( Identifier() )); + TInt32 upgradeExists = aStream.ReadInt32L(); + if ( upgradeExists ) + { + if ( iUpgrade == NULL ) + { + iUpgrade = CNcdNodeUpgrade::NewL( *this ); + } + iUpgrade->InternalizeL( aStream ); + + // This requires that both CNcdNodeContentInfo & + // CNcdNodeInstall are up-to-date + HandleContentUpgradeL(); + } + else + { + if ( iUpgrade ) + { + // Because upgrade should not exist, Close it. + iUpgrade->Close(); + iUpgrade = NULL; + } + // Upgrade situation may have changed due to software + // uninstallations so we update to current situation + // HandleContentUpgradeL creates iUpgrade if necessary + HandleContentUpgradeL(); + } + + TInt32 dependencyExists = aStream.ReadInt32L(); + if ( dependencyExists ) + { + if ( iDependency == NULL ) + { + iDependency = CNcdNodeDependency::NewL( *this ); + } + iDependency->InternalizeL( aStream ); + } + else if ( iDependency ) + { + // Because preview should not exist, Close it. + iDependency->Close(); + iDependency = NULL; + } + + + // Subscribable content + TBool subscribable = aStream.ReadInt32L(); + if ( subscribable ) + { + if ( iSubscribableContent == NULL ) + { + iSubscribableContent = CNcdServerSubscribableContent::NewL(); + } + iSubscribableContent->InternalizeL( aStream ); + } + + + // Create or reinternalize purchase options : + + const TInt KPurchaseOptionCount( aStream.ReadInt32L() ); + + DLINFO(( "Amount of purchaseoptions found from the stream: %d", + KPurchaseOptionCount )); + + TInt purchaseOptionIndex( 0 ); + while ( purchaseOptionIndex < KPurchaseOptionCount ) + { + InternalizePurchaseOptionL( aStream ); + ++purchaseOptionIndex; + } + + // Remove purchase options that were removed from the server + RemoveNotUpdatedPurchaseOptions(); + + + TInt32 moreInfoExists = aStream.ReadInt32L(); + if ( moreInfoExists ) + { + if ( iMoreInfo == NULL ) + { + iMoreInfo = CNcdNodeDisclaimer::NewL(); + } + iMoreInfo->InternalizeL( aStream ); + } + else if ( iMoreInfo ) + { + // Because icon should not exist, Close it. + iMoreInfo->Close(); + iMoreInfo = NULL; + } + + iDetails.ResetAndDestroy(); + TInt32 detailCount = aStream.ReadInt32L(); + for( TInt i = 0 ; i < detailCount ; i++ ) + { + CNcdKeyValuePair* detail = CNcdKeyValuePair::NewLC( aStream ); + iDetails.AppendL( detail ); + CleanupStack::Pop( detail ); + } + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::ReceiveMessage( MCatalogsBaseMessage* aMessage, + TInt aFunctionNumber ) + { + DLTRACEIN(("")); + + DASSERT( aMessage ); + + // Now, we can be sure that rest of the time iMessage exists. + // This member variable is set for the CounterPartLost function. + iMessage = aMessage; + + TInt trapError( KErrNone ); + + switch( aFunctionNumber ) + { + case NcdNodeFunctionIds::ENcdInternalize: + TRAP( trapError, InternalizeRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdRelease: + ReleaseRequest( *aMessage ); + break; + + case NcdNodeFunctionIds::ENcdUserDataHandle: + TRAP( trapError, UserDataHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdDisclaimerHandle: + TRAP( trapError, DisclaimerHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdIconHandle: + TRAP( trapError, IconHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdScreenshotHandle: + TRAP( trapError, ScreenshotHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdSkinHandle: + TRAP( trapError, SkinHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdUriContentHandle: + TRAP( trapError, UriContentHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdPreviewHandle: + TRAP( trapError, PreviewHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdContentInfoHandle: + TRAP( trapError, ContentInfoHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdUpgradeHandle: + TRAP( trapError, UpgradeHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdDependencyHandle: + TRAP( trapError, DependencyHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdInternalizePurchaseHistory: + TRAP( trapError, InternalizePurchaseHistoryRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdInternalizePurchaseMeans: + TRAP( trapError, InternalizePurchaseMeansRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdPurchaseOptionIds: + TRAP( trapError, PurchaseOptionIdsRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdInstallHandle: + TRAP( trapError, InstallHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdDownloadHandle: + TRAP( trapError, DownloadHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdMoreInfoHandle: + TRAP( trapError, MoreInfoHandleRequestL( *aMessage ) ); + break; + + case NcdNodeFunctionIds::ENcdIsPurchaseSupported: + TRAP( trapError, IsPurchaseSupportedRequestL( *aMessage ) ); + break; + + default: + DLERROR(("Unidentified function request")); + DASSERT( EFalse ); + break; + } + + if ( trapError != KErrNone ) + { + // Because something went wrong the complete has not been + // yet called for the message. + // So, inform the client about the error. + aMessage->CompleteAndRelease( trapError ); + } + + // Because the message should not be used after this, set it NULL. + // So, CounterPartLost function will know that no messages are + // waiting the response at the moment. + iMessage = NULL; + + if ( aFunctionNumber == NcdNodeFunctionIds::ENcdRelease ) + { + // Because release was called for this object it may be time to + // delete this object. Inform manager about the release so it may + // close this object and clear the cache if needed. + // Notice that if the manager closes this object then this object will + // be deleted. It is safe to do here because no memeber variables are + // needed here after the call. + NodeManager().MetaDataReleased( *this ); + } + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::CounterPartLost( const MCatalogsSession& aSession ) + { + // This function may be called whenever -- when the message is waiting + // response or when the message does not exist. + // iMessage may be NULL here, because in the end of the + // ReceiveMessage it is set to NULL. The life time of the message + // ends shortly after CompleteAndRelease is called. + if ( iMessage != NULL ) + { + iMessage->CounterPartLost( aSession ); + } + } + +void CNcdNodeMetaData::InternalizeRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + CBufBase* buf = CBufFlat::NewL( KBufExpandSize ); + CleanupStack::PushL( buf ); + + RBufWriteStream stream( *buf ); + CleanupClosePushL( stream ); + + + // Include all the necessary node data to the stream + ExternalizeDataForRequestL( stream ); + + + // Commits data to the stream when closing. + CleanupStack::PopAndDestroy( &stream ); + + + if ( buf->Size() > 0 ) + { + DLINFO(( "Completing the message, buf len: %d", buf->Ptr(0).Length() )); + } + // If this leaves, ReceiveMessge will complete the message. + aMessage.CompleteAndReleaseL( buf->Ptr( 0 ), KErrNone ); + + + DLINFO(("Deleting the buf")); + CleanupStack::PopAndDestroy( buf ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::ExternalizeDataForRequestL( RWriteStream& aStream ) const + { + DLTRACEIN(("")); + + DLINFO(("Class ID: %d", ClassId() )); + // Metadata existed. So, insert info that meta data was found. + aStream.WriteInt32L( ClassId() ); + + iIdentifier->ExternalizeL( aStream ); + ExternalizeDesL( NodeName(), aStream ); + ExternalizeDesL( Description(), aStream ); + ExternalizeDesL( LayoutType(), aStream ); + + aStream.WriteInt8L( iAlwaysVisible ); + + aStream.WriteInt32L( iDetails.Count() ); + for( TInt i = 0 ; i < iDetails.Count() ; i++ ) + { + iDetails[i]->ExternalizeL( aStream ); + } + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::InternalizePurchaseMeansRequestL( + MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + // Parse the input message and create a descriptor array containing + // the purchase option ids which should be externalized for the request. + CDesCArray* purchaseOptionIds = new( ELeave ) CDesCArrayFlat( 5 ); + CleanupStack::PushL( purchaseOptionIds ); + HBufC8* input = HBufC8::NewLC( aMessage.InputLength() ); + TPtr8 inputPtr = input->Des(); + aMessage.ReadInput( inputPtr ); + RDesReadStream inputStream( *input ); + CleanupClosePushL( inputStream ); + + TInt poCount = inputStream.ReadInt32L(); + for ( TInt i = 0; i < poCount; i++ ) + { + HBufC* tmpId( NULL ); + InternalizeDesL( tmpId, inputStream ); + CleanupStack::PushL( tmpId ); + purchaseOptionIds->AppendL( *tmpId ); + CleanupStack::PopAndDestroy( tmpId ); + } + + CleanupStack::PopAndDestroy( &inputStream ); + CleanupStack::PopAndDestroy( input ); + + CBufBase* buf = CBufFlat::NewL( KBufExpandSize ); + CleanupStack::PushL( buf ); + + RBufWriteStream stream( *buf ); + CleanupClosePushL( stream ); + + // Include all the necessary purchase data to the stream + MCatalogsSession& session = aMessage.Session(); + ExternalizePurchaseMeansForRequestL( *purchaseOptionIds, stream, session ); + + // Commits data to the stream when closing. + CleanupStack::PopAndDestroy( &stream ); + + + if ( buf->Size() > 0 ) + { + DLINFO(( "Completing the message, buf len: %d", buf->Ptr(0).Length() )); + } + // If this leaves, ReceiveMessge will complete the message. + aMessage.CompleteAndReleaseL( buf->Ptr( 0 ), KErrNone ); + + + DLINFO(("Deleting the buf")); + CleanupStack::PopAndDestroy( buf ); + CleanupStack::PopAndDestroy( purchaseOptionIds ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::ExternalizePurchaseMeansForRequestL( + const CDesCArray& aPurchaseOptionIds, + RWriteStream& aStream, + MCatalogsSession& aSession ) const + { + + // Subscribable content + if ( iSubscribableContent != NULL ) + { + aStream.WriteInt32L( ETrue ); + iSubscribableContent->ExternalizeL( aStream ); + } + else + { + aStream.WriteInt32L( EFalse ); + } + + + // this is the same as handle amount + TInt purchaseOptionAmount( aPurchaseOptionIds.Count() ); + aStream.WriteInt32L( purchaseOptionAmount ); + + for ( TInt i = 0; i < purchaseOptionAmount; i++ ) + { + CNcdPurchaseOptionImpl& tmpPurchaseOption = PurchaseOptionByIdL( + aPurchaseOptionIds[i] ); + TInt tmpHandle( aSession.AddObjectL( &tmpPurchaseOption ) ); + + TRAPD( addError, aStream.WriteInt32L( tmpHandle ) ); + if ( addError != KErrNone ) + { + // Should all other added objects be removed from + // the session also? + aSession.RemoveObject( tmpHandle ); + User::Leave( addError ); + } + } + } + + +void CNcdNodeMetaData::InternalizePurchaseHistoryRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + CBufBase* buf = CBufFlat::NewL( KBufExpandSize ); + CleanupStack::PushL( buf ); + + RBufWriteStream stream( *buf ); + CleanupClosePushL( stream ); + + + // Include all the necessary data to the stream + ExternalizePurchaseHistoryForRequestL( stream ); + + + // Commits data to the stream when closing. + CleanupStack::PopAndDestroy( &stream ); + + if ( buf->Size() > 0 ) + { + DLINFO(( "Completing the message, buf len: %d", buf->Ptr(0).Length() )); + } + + // If this leaves ReceiveMessage function will complete the message. + aMessage.CompleteAndReleaseL( buf->Ptr( 0 ), KErrNone ); + + + DLINFO(("Deleting the buf")); + CleanupStack::PopAndDestroy( buf ); + + DLTRACEOUT(("")); + } + +void CNcdNodeMetaData::ExternalizePurchaseHistoryForRequestL( RWriteStream& aStream ) const + { + DLTRACEIN(("")); + + // Insert node class id just in case somebody wants to check that + // the data is of the right type + aStream.WriteInt32L( ClassId() ); + + // Get up to date purchase info straight from purchase history + CNcdPurchaseDetails* purchaseDetails = NULL; + + TRAPD( error, + { + purchaseDetails = PurchaseDetailsLC(); + CleanupStack::Pop( purchaseDetails ); + }); + + // there's no problem in pushing a NULL pointer as long as it's + // pushed with PushL or CleanupDeletePushL + CleanupStack::PushL( purchaseDetails ); + + if ( error == KNcdErrorNoPurchaseInformation + || ( purchaseDetails && + purchaseDetails->PurchaseOptionId() == KNullDesC ) ) + { + // If no info is found it means that the node is + // not purchased. + // Also, purchase option id is checked here. In some situations, + // dummy purchase details may be inserted into the purchase history + // from outside the engine. So, then the purchase option id is most likely + // not set correctly. + aStream.WriteInt32L( EFalse ); + CleanupStack::PopAndDestroy( purchaseDetails ); + return; + } + else if ( error == KErrNone ) + { + aStream.WriteInt32L( ETrue ); + } + else + { + User::Leave( error ); + } + + + const TDesC& purchasedOptionId = purchaseDetails->PurchaseOptionId(); + ExternalizeDesL( purchasedOptionId, aStream ); + + TTime timeOfPurchase = purchaseDetails->PurchaseTime(); + const TInt64& integerTimeOfPurchase = timeOfPurchase.Int64(); + // Store framework provides the necessary implementation for + // the operator<< to externalise the 64-bit integer + aStream << integerTimeOfPurchase; + + const TDesC& finalPrice = purchaseDetails->FinalPrice(); + ExternalizeDesL( finalPrice, aStream ); + + + CleanupStack::PopAndDestroy( purchaseDetails ); + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::PurchaseOptionIdsRequestL( + MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + CBufBase* buf = CBufFlat::NewL( KBufExpandSize ); + CleanupStack::PushL( buf ); + + RBufWriteStream stream( *buf ); + CleanupClosePushL( stream ); + + stream.WriteInt32L( iPurchaseOptions.Count() ); + for ( TInt i = 0; i < iPurchaseOptions.Count(); i++ ) + { + ExternalizeDesL( iPurchaseOptions[i]->Id(), stream ); + } + + CleanupStack::PopAndDestroy( &stream ); + + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( buf->Ptr( 0 ), KErrNone ); + CleanupStack::PopAndDestroy( buf ); + } + + +void CNcdNodeMetaData::ReleaseRequest( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + // Decrease the reference count for this object. + // When the reference count reaches zero, this object will be destroyed + // and removed from the session. + MCatalogsSession& requestSession( aMessage.Session() ); + TInt handle( aMessage.Handle() ); + aMessage.CompleteAndRelease( KErrNone ); + requestSession.RemoveObject( handle ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::IconIdRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + // If this leaves, ReceiveMessage will complete the message. + + if ( ! iIcon ) + { + User::Leave( KErrNotFound ); + } + + aMessage.CompleteAndReleaseL( iIcon->IconId(), KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::IconDataRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + // If this leaves, ReceiveMessage will complete the message. + + if ( iIcon == NULL ) + { + User::Leave( KErrNotFound ); + } + + HBufC8* icon = iIcon->IconDataL(); + CleanupStack::PushL( icon ); + + aMessage.CompleteAndReleaseL( *icon, KErrNone ); + + CleanupStack::PopAndDestroy( icon ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::UserDataHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iUserData == NULL ) + { + DLINFO(("User data NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node + MCatalogsSession& requestSession( aMessage.Session() ); + + // Add the node again to the session and get the new handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iUserData ) ); + + DLINFO(("User data handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::DisclaimerHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iDisclaimer == NULL ) + { + DLINFO(("Disclaimer NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node. + MCatalogsSession& requestSession( aMessage.Session() ); + + // Add the icon to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iDisclaimer ) ); + + DLINFO(("Disclaimer handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::IconHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iIcon == NULL ) + { + DLINFO(("Icon NULL")); + User::Leave( KErrNotFound ); + } + + if ( !iIcon->IconDataReady() ) + { + DLINFO(("Icon data not ready")); + User::Leave( KErrNotFound ); + } + + if ( iIcon->IconId() == KNullDesC ) + { + DLINFO(("Icon id not set")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node. + MCatalogsSession& requestSession( aMessage.Session() ); + + // Add the icon to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iIcon ) ); + + DLINFO(("Icon handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::ScreenshotHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iScreenshot == NULL ) + { + DLINFO(("Screenshot NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node. + MCatalogsSession& requestSession( aMessage.Session() ); + + // Add the screenshot to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iScreenshot ) ); + + DLINFO(("Screenshot handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::SkinHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iSkin == NULL ) + { + DLINFO(("Skin NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node. + MCatalogsSession& requestSession( aMessage.Session() ); + + // Add the skin to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iSkin ) ); + + DLINFO(("Skin handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::UriContentHandleRequestL( + MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iUriContent == NULL ) + { + DLINFO(("UriContent NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node. + MCatalogsSession& requestSession( aMessage.Session() ); + + + // Add the uri content to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iUriContent ) ); + + DLINFO(("Uri content handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::ContentInfoHandleRequestL( + MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iContentInfo == NULL ) + { + DLINFO(("ContentInfo NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node. + MCatalogsSession& requestSession( aMessage.Session() ); + + + // Add the content info to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iContentInfo ) ); + + DLINFO(("Content info handle: %d", handle )); + + // Send the information to the client side. + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::PreviewHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iPreview == NULL ) + { + DLINFO(("Preview NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the preview. + MCatalogsSession& requestSession( aMessage.Session() ); + + + // Add the preview to the session and get the handle. + // If the preview already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iPreview ) ); + + DLINFO(("Preview handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::UpgradeHandleRequestL( MCatalogsBaseMessage& aMessage ) + { + DLTRACEIN(("")); + DLNODEID(( Identifier() )); + HandleContentUpgradeL(); + // Upgrade interface should be provided in proxy side only if the upgrade + // exists in server side and it has some identifiers or content that can be used to + // load node upgrades from web. + if( !iUpgrade + || ( iUpgrade->AllUpgradesInstalledL() && !iUpgrade->ContentUpgrades() ) ) + { + DLINFO(("Upgrade NULL or no node targets available")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the preview. + MCatalogsSession& requestSession( aMessage.Session() ); + + + // Add the preview to the session and get the handle. + // If the object already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iUpgrade ) ); + + DLINFO(("Upgrade handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::DependencyHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + // Dependency interface should be provided in proxy side only if the dependency + // exists in server side and it has some identifiers or content that can be used to + // load dependencies from web. + // NOTE: interface will be visible even if all of the dependencies are installed + if( !iDependency ) + { + DLINFO(("Dependency NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the preview. + MCatalogsSession& requestSession( aMessage.Session() ); + + + // Add the preview to the session and get the handle. + // If the preview already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iDependency ) ); + + DLINFO(("Dependency handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::DownloadHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iDownload == NULL ) + { + DLINFO(("Node download NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node + MCatalogsSession& requestSession( aMessage.Session() ); + + // Add the download to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iDownload ) ); + + DLINFO(("Download handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + + +void CNcdNodeMetaData::InstallHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iInstall == NULL ) + { + DLINFO(("Node install NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node + MCatalogsSession& requestSession( aMessage.Session() ); + + // Add the install to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iInstall ) ); + + DLINFO(("Install handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + +void CNcdNodeMetaData::MoreInfoHandleRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + + if( iMoreInfo == NULL ) + { + DLINFO(("More info NULL")); + User::Leave( KErrNotFound ); + } + + // Get the session that will contain the handle of the node. + MCatalogsSession& requestSession( aMessage.Session() ); + + // Add the more info to the session and get the handle. + // If the node already existed in the session we will still + // get a new handle to the same object. + TInt32 handle( requestSession.AddObjectL( iMoreInfo ) ); + + DLINFO(("More info handle: %d", handle )); + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( handle, KErrNone ); + + DLTRACEOUT(("")); + } + +void CNcdNodeMetaData::IsPurchaseSupportedRequestL( MCatalogsBaseMessage& aMessage ) const + { + DLTRACEIN(("")); + TBool isPurchaseSupported = EFalse; + if( iPurchaseOptions.Count() > 0 ) + { + isPurchaseSupported = ETrue; + } + + // Send the information to the client side + // If this leaves, ReceiveMessage will complete the message. + aMessage.CompleteAndReleaseL( isPurchaseSupported, KErrNone ); + } + +void CNcdNodeMetaData::InternalizePurchaseOptionL( + const MNcdPreminetProtocolPurchaseOption& aData ) + { + DLTRACEIN(("")); + CNcdPurchaseOptionImpl& option( + CreateOrGetPurchaseOptionL( aData.Id() ) ); + + TRAPD( error, option.InternalizeL( aData ) ); + if ( error != KErrNone ) + { + // If internalization fails, let's remove the option so + // no incomplete data is left to be used + + // If an error occurs in the removal we don't want to + // pass that forward. Let's pass the original error. + TRAP_IGNORE( RemovePurchaseOptionL( aData.Id() ) ); + + User::Leave( error ); + } + + // Set the option recently updated so we can later in + // RemoveRecentlyUpdatedPurchaseOptions() differentiate + // options that should be removed because they were not + // received from the server anymore. + option.SetRecentlyUpdated( ETrue ); + + // Updates content URI if necessary + HandleContentUriUpdateL( option ); + DLTRACEOUT(("Purchase option internalized successfully")); + } + + +CNcdPurchaseOptionImpl& CNcdNodeMetaData::CreateOrGetPurchaseOptionL( + const TDesC& aPurchaseOptionId ) + { + DLTRACEIN(("")); + CNcdPurchaseOptionImpl* option( NULL ); + TRAPD( error, option = &PurchaseOptionByIdL( aPurchaseOptionId ) ); + if ( error != KErrNone && error != KErrNotFound ) + { + User::Leave( error ); + } + else if ( error == KErrNotFound ) + { + // Not found, have to create + option = CNcdPurchaseOptionImpl::NewL( *this ); + // Set option id already here so if something goes wrong + // after appending the option into array, the option can be + // identified and removed. + option->SetIdL( aPurchaseOptionId ); + + error = KErrNone; + error = iPurchaseOptions.Append( option ); + if ( error != KErrNone ) + { + DLERROR(("Appending purchase option failed with %d", error )); + // When CObject base object is created its reference count + // is set to one. Therefore close is said once. + option->Close(); + User::Leave( error ); + } + } + return *option; + } + +void CNcdNodeMetaData::RemovePurchaseOptionL( + const TDesC& aPurchaseOptionId ) + { + DLTRACEIN(("")); + const TInt KAmountOfOptions( iPurchaseOptions.Count() ); + for ( TInt i = 0; i < KAmountOfOptions; i++ ) + { + if ( iPurchaseOptions[i]->Id() == aPurchaseOptionId ) + { + // Meta data has one reference count to all objects + // so that for one Close is said to the option. + iPurchaseOptions[i]->Close(); + iPurchaseOptions.Remove( i ); + return; + } + } + User::Leave( KErrNotFound ); + } + +void CNcdNodeMetaData::RemoveNotUpdatedPurchaseOptions() + { + DLTRACEIN(("")); + TInt optionsIndexer( iPurchaseOptions.Count() - 1 ); + while ( optionsIndexer > -1 ) + { + if ( !iPurchaseOptions[optionsIndexer]->RecentlyUpdated() ) + { + DLINFO(( "Removing purchase option that is removed from server." )); + // Meta data has one reference count to all objects + // so that for one Close is said to the option. + iPurchaseOptions[optionsIndexer]->Close(); + iPurchaseOptions.Remove( optionsIndexer ); + } + else + { + DLINFO(( "Resettting recently updated info of po." )); + // Resetting the flag for later use + iPurchaseOptions[optionsIndexer]-> + SetRecentlyUpdated( EFalse ); + } + --optionsIndexer; + } + DLTRACEOUT(("")); + } + +// --------------------------------------------------------------------------- +// Internalization from a stream is a little harder now than +// than internalizing from the protocol object. It is done +// so that the po is first internalized into temp option and then +// needed info is taken from it for example to identify the option +// among the already existing options. If the option is already +// found the temp option is externalized to stream and then +// internalized into the already existing option. If no option +// with same id is found then created temp option is appended to +// array. Usually when internalizing from a stream it means that +// we are internalizing from the database and we don't have the +// object created yet. So the latter case is the usual case. + +// One drawback with this implementation is that we can use only +// little of the functions which are used when internalizing from +// the protocol. + +// One other way to implement this could be +// just to move the purchase option id first in the +// internalize/externalize so that the option id could be retrieved +// easily without no temp externalization/internalization. +// This would need some changes and would bind the order of +// externalization/internalization of variables so it is not done +// now. Also if current implementation is kept, the externalization +// internalization could be replaced with assignment opertators in +// purchase option and its composite classes. +// --------------------------------------------------------------------------- +// +void CNcdNodeMetaData::InternalizePurchaseOptionL( + RReadStream& aStream ) + { + DLTRACEIN(("")); + + // Temp option for reading option id + CNcdPurchaseOptionImpl* tmpOption = + CNcdPurchaseOptionImpl::NewLC( *this ); + tmpOption->InternalizeL( aStream ); + const TDesC& poId( tmpOption->Id() ); + + // Search for the option requires the purchase option id so it is the + // only reason why the temp option is needed always in this function. + CNcdPurchaseOptionImpl* option( NULL ); + TRAPD( error, option = &PurchaseOptionByIdL( poId ) ); + if ( error != KErrNone && error != KErrNotFound ) + { + User::Leave( error ); + } + + CleanupStack::Pop( tmpOption ); + + if ( option == NULL ) + { + DLTRACE(("")); + // Purchase option not found, append the temp option into + // array of purchase options + option = tmpOption; + + error = KErrNone; + error = iPurchaseOptions.Append( option ); + if ( error != KErrNone ) + { + DLERROR(("Appending purchase option failed with %d", error )); + // When CObject base object is created its reference count + // is set to one. Therefore close is said once. + option->Close(); + User::Leave( error ); + } + } + else + { + DLTRACE(("")); + CleanupClosePushL( *tmpOption ); + // We externalize the content of a option into a stream. + // This is done to copy the content from the temp option + // into the option already found from the array. + + // This could be replaced with an assignment operation + // in CNcdPurchaseOptionImpl and in all of its composite + // classes. Because at the moment this is a rare situation + // (never happens at the moment) we don't use + // any effort into it at the moment. + // + CBufBase* buf = CBufFlat::NewL( KBufExpandSize ); + CleanupStack::PushL( buf ); + + RBufWriteStream writeStream( *buf ); + CleanupClosePushL( writeStream ); + DLINFO(( "Externalizing option in copy" )); + tmpOption->ExternalizeL( writeStream ); + // Commits data to the stream when closing. + CleanupStack::PopAndDestroy( &writeStream ); + + // Now we internalize the option already found in the + // array from the stream. + RDesReadStream readStream( buf->Ptr( 0 ) ); + CleanupClosePushL( readStream ); + DLINFO(( "Internalizing option in copy" )); + TRAPD( error, option->InternalizeL( readStream ) ); + if ( error != KErrNone ) + { + // If internalization fails, let's remove the option so + // no incomplete data is left to be used + + // If an error occurs in the removal we don't want to + // pass that forward. Let's pass the original error. + TRAP_IGNORE( RemovePurchaseOptionL( poId ) ); + + User::Leave( error ); + } + // Closes the stream + CleanupStack::PopAndDestroy( &readStream ); + + CleanupStack::PopAndDestroy( buf ); + CleanupStack::PopAndDestroy( tmpOption ); + } + + // Set the option recently updated so we can later in + // RemoveRecentlyUpdatedPurchaseOptions() differentiate + // options that should be removed because they were not + // received from the server anymore. + option->SetRecentlyUpdated( ETrue ); + } + + +// --------------------------------------------------------------------------- +// Updates URI content interface if necessary +// --------------------------------------------------------------------------- +// +void CNcdNodeMetaData::HandleContentUriUpdateL( + const CNcdPurchaseOptionImpl& aOption ) + { + DLTRACEIN(("")); + if ( !iUriContent || + !aOption.IsFree() || + aOption.DownloadInfoCount() == 0 ) + { + DLTRACEOUT(("No bought URI content or not free content so nothing to do")); + return; + } + + TInt count = aOption.DownloadInfoCount(); + TInt index = KErrNotFound; + + DLINFO(("Going through %d download infos", count)); + for ( TInt i = 0; i < count; ++i ) + { + if ( aOption.DownloadInfo( i ).ContentUsage() == + MNcdPurchaseDownloadInfo::EConsumable ) + { + DLINFO(("Download in index %d is consumable!", i)); + index = i; + // Only one uri is supported so break immediately as we + // come across it + break; + } + } + + if ( index == KErrNotFound || + aOption.DownloadInfo( index ).ContentUri() == iUriContent->Uri() ) + { + DLTRACEOUT(("Option doesn't contain URI content or URI has not changed")); + return; + } + + + DLTRACE(("New URI differs from the old one. Updating...")); + DLINFO(( _L("New URI: %S"), + &aOption.DownloadInfo( index ).ContentUri() )); + + CNcdPurchaseDetails* details = PurchaseDetailsLC(); + + // Ensure that ids match, otherwise + // we would be updating wrong purchase option + if ( details->PurchaseOptionId() == aOption.Id() ) + { + // Find the correct download info for updating + TArray dlInfo ( details->DownloadInfoL() ); + + TInt oldIndex = KErrNotFound; + for ( TInt i = 0; i < dlInfo.Count(); ++i ) + { + if ( dlInfo[i]->ContentUsage() == + MNcdPurchaseDownloadInfo::EConsumable ) + { + DLINFO(("Download in index %d is consumable!", i)); + oldIndex = i; + // Only one uri is supported so break immediately as we + // come across it + break; + } + } + + NCD_ASSERT_ALWAYS( oldIndex != KErrNotFound, ENcdPanicNoData ); + + CNcdPurchaseDownloadInfo* modDownload = + static_cast( dlInfo[oldIndex] ); + + DLTRACE(("Updating the URI to download info")); + modDownload->SetContentUriL( + aOption.DownloadInfo( index ).ContentUri() ); + + DLTRACE(("Saving purchase")); + iNodeManager.PurchaseHistory().SavePurchaseL( *details ); + DLTRACE(("Purchase updated")); + + iUriContent->InternalizeL( *details ); + DLTRACE(("URI content internalized")); + } + + CleanupStack::PopAndDestroy( details ); + + DLTRACEOUT(("All is well")); + } +