mpxplugins/serviceplugins/collectionplugins/mpxsqlitedbplugin/src/mpxdbplaylistsongs.cpp
changeset 0 ff3acec5bc43
child 11 13afc0e517bd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mpxplugins/serviceplugins/collectionplugins/mpxsqlitedbplugin/src/mpxdbplaylistsongs.cpp	Thu Dec 17 08:45:05 2009 +0200
@@ -0,0 +1,832 @@
+/*
+* 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 "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:  Responsible for interaction with the PlaylistSongs and
+*                PlaylistSongInfo tables.
+*
+*/
+
+
+// INCLUDE FILES
+#include <f32file.h>
+#include <mpxlog.h>
+#include <mpxmedia.h>
+#include <mpxmediaarray.h>
+#include <mpxmediageneraldefs.h>
+#include "mpxdbcommonutil.h"
+
+#include "mpxcollectiondbdef.h"
+#include "mpxdbutil.h"
+#include "mpxdbpluginqueries.h"
+#include "mpxdbmanager.h"
+#include "mpxdbplaylistsongs.h"
+
+// ============================ MEMBER FUNCTIONS ==============================
+
+// ----------------------------------------------------------------------------
+// Two-phased constructor.
+// ----------------------------------------------------------------------------
+//
+CMPXDbPlaylistSongs* CMPXDbPlaylistSongs::NewL(
+    CMPXDbManager& aDbManager)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::NewL");
+
+    CMPXDbPlaylistSongs* self = CMPXDbPlaylistSongs::NewLC(aDbManager);
+    CleanupStack::Pop(self);
+    return self;
+    }
+
+// ----------------------------------------------------------------------------
+// Two-phased constructor.
+// ----------------------------------------------------------------------------
+//
+CMPXDbPlaylistSongs* CMPXDbPlaylistSongs::NewLC(
+    CMPXDbManager& aDbManager)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::NewLC");
+
+    CMPXDbPlaylistSongs* self = new (ELeave) CMPXDbPlaylistSongs(aDbManager);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    return self;
+    }
+
+// ----------------------------------------------------------------------------
+// Destructor
+// ----------------------------------------------------------------------------
+//
+CMPXDbPlaylistSongs::~CMPXDbPlaylistSongs()
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::~CMPXDbPlaylistSongs");
+    }
+
+// ----------------------------------------------------------------------------
+// Constructor
+// ----------------------------------------------------------------------------
+//
+CMPXDbPlaylistSongs::CMPXDbPlaylistSongs(
+    CMPXDbManager& aDbManager) :
+    CMPXDbTable(aDbManager)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::CMPXDbPlaylistSongs");
+    }
+
+// ----------------------------------------------------------------------------
+// Second phase constructor.
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::ConstructL()
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::ConstructL");
+    BaseConstructL();
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::AddSongsL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::AddSongsL(
+    TUint32 aPlaylistId,
+    const CMPXMediaArray& aMediaArray,
+    TInt aDriveId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::AddSongsL");
+
+    TInt count(aMediaArray.Count());
+    for (TInt index = 0; index < count; ++index)
+        {
+        AddSongL(aPlaylistId, *(aMediaArray[index]), aDriveId);
+        }
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::AddSongL
+// ----------------------------------------------------------------------------
+//
+TUint32 CMPXDbPlaylistSongs::AddSongL(
+    TUint32 aPlaylistId,
+    const CMPXMedia& aMedia,
+    TInt aDriveId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::AddSongL");
+    return AddSongL(aPlaylistId, CountL(aPlaylistId), aMedia, aDriveId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::AddSongL
+// ----------------------------------------------------------------------------
+//
+TUint32 CMPXDbPlaylistSongs::AddSongL(
+    TUint32 aPlaylistId,
+    TInt aOrdinal,
+    const CMPXMedia& aMedia,
+    TInt aDriveId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::AddSongL");
+
+    // the UniqueId field is AUTOINCREMENT and its value is going to be generated
+    // automatically by the database - no need to supply it here
+    TUint32 songId((aMedia.ValueTObjectL<TMPXItemId>(KMPXMediaGeneralId)).iId2);
+
+    // insert PlaylistSongs record
+    iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongsInsert, aPlaylistId, songId, aOrdinal);
+
+    // if the song is not already in the PlaylistSongInfo table - add it
+    if (!SongInfoExistsL(aDriveId, songId))
+        {
+        // add a new song info record
+        HBufC* uri = MPXDbCommonUtil::ProcessSingleQuotesLC(aMedia.ValueText(KMPXMediaGeneralUri));
+        HBufC* title = MPXDbCommonUtil::ProcessSingleQuotesLC(aMedia.ValueText(KMPXMediaGeneralTitle));
+
+        TUint32 dbFlag(0);
+        if (aMedia.IsSupported(KMPXMediaGeneralFlags))
+            {
+            dbFlag = (aMedia.ValueTObjectL<TUint>(KMPXMediaGeneralFlags));
+            dbFlag = dbFlag & (~KMPXMediaGeneralFlagsDriveInfo); // clear drive info
+            }
+
+        // add the URI without the drive letter
+        TPtrC uriPtr(uri->Mid(KMCPathStartPos));
+        iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongInfoInsert, songId, &uriPtr, title,
+            MPXDbCommonUtil::GetVolIdMatchDriveIdL(iDbManager.Fs(), TDriveUnit(*uri)), dbFlag);
+
+        CleanupStack::PopAndDestroy(title);
+        CleanupStack::PopAndDestroy(uri);
+        }
+
+    return songId;
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::UpdateSongL
+// ----------------------------------------------------------------------------
+//
+TBool CMPXDbPlaylistSongs::UpdateSongL(
+    TUint32 aSongId,
+    const CMPXMedia& aMedia,
+    TBool aResetFlags,
+    TBool& aUpdated)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::UpdateSongL");
+
+    aUpdated = EFalse;
+    TBool visibleChange(EFalse);
+
+    // get the current record
+    RSqlStatement recordset(iDbManager.ExecuteSelectQueryL(KQueryPlaylistSongInfoGet, aSongId));
+    CleanupClosePushL(recordset);
+
+    if (recordset.Next() == KSqlAtRow)
+        {
+        const TArray<TMPXAttribute> attributes = aMedia.Attributes();
+
+        // stores the current song ID, which may change during execution
+        TUint32 songId(aSongId);
+
+        TInt attrCount(attributes.Count());
+        for (TInt i = 0; i < attrCount; ++i)
+            {
+            TInt contentId(attributes[i].ContentId());
+            TUint attributeId(attributes[i].AttributeId());
+
+            switch(contentId)
+                {
+                case KMPXMediaIdGeneral:
+                    {
+                    if (attributeId & EMPXMediaGeneralTitle)
+                        {
+                        // existing title
+                        TPtrC title(aMedia.ValueText(KMPXMediaGeneralTitle).Left(KMCMaxTextLen));
+
+                        // compare with the old title
+                        if (title.Compare(MPXDbCommonUtil::GetColumnTextL(recordset, EPlaylistSongInfoTitle)) != 0)
+                            {
+                            HBufC* titleProc = MPXDbCommonUtil::ProcessSingleQuotesLC(title);
+
+                            // title has changed - update on all drives
+                            iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongInfoUpdateTitle,
+                                titleProc, songId);
+
+                            CleanupStack::PopAndDestroy(titleProc);
+
+                            visibleChange = ETrue;
+                            aUpdated = ETrue;
+                            MPX_DEBUG2("    Title[%S]", &title);
+                            }
+                        }
+
+                    if (attributeId & EMPXMediaGeneralUri)
+                        {
+                        const TDesC& uriOrig(aMedia.ValueText(KMPXMediaGeneralUri));
+                        songId = MPXDbCommonUtil::GenerateUniqueIdL(iDbManager.Fs(), EMPXCollection, uriOrig, EFalse);
+
+                        if (aSongId != songId)
+                            {
+                            // URI of the song has been changed. This changes the Id of the song and both the
+                            // PlaylistSongs and PlaylistSongInfo tables should be updated accordingly.
+
+                            HBufC* uri = MPXDbCommonUtil::ProcessSingleQuotesLC(uriOrig);
+
+                            // use the URI without the drive letter
+                            TPtrC uriPtr(uri->Mid(KMCPathStartPos));
+
+                            // update the PlaylistSongInfo table on all drives
+                            iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongInfoUpdate, songId,
+                                &uriPtr, MPXDbCommonUtil::GetVolIdMatchDriveIdL(iDbManager.Fs(), TDriveUnit(*uri)), aSongId);
+
+                            // update the PlaylistSongs table on all drives
+                            iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongsUpdate, songId,
+                                aSongId);
+
+                            aUpdated = ETrue;
+                            MPX_DEBUG3("    CurrentSongId[0x%x] changed to [0x%x]", aSongId, songId);
+
+                            CleanupStack::PopAndDestroy(uri);
+                            }
+                        }
+
+                    if (attributeId & EMPXMediaGeneralFlags)
+                        {
+                        TUint flag(aMedia.ValueTObjectL<TUint>(KMPXMediaGeneralFlags));
+                        TUint32 curFlag(recordset.ColumnInt64(EPlaylistSongInfoDbFlag));
+
+                        if (flag & KMPXMediaGeneralFlagsSetOrUnsetBit)
+                            {
+                            if (aResetFlags)
+                                {
+                                visibleChange = ETrue;
+                                curFlag = flag;
+                                }
+                            else
+                                {
+                                // Set flag, visible change is true only if the flag status is changing
+                                visibleChange = visibleChange || ((curFlag^flag) & 0x7FFFFFFF);
+                                curFlag |= flag;
+                                }
+                            }
+                        else
+                            {
+                            // Clear flag, visible change is true only if the flag status is changing
+                            visibleChange = visibleChange || (((curFlag^0xFFFFFFFF)^flag) & 0x7FFFFFFF);
+                            curFlag &= (~flag);
+                            }
+
+                        if (visibleChange)
+                            {
+                            // update the flags on all drives
+                            iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongInfoUpdateFlags,
+                                curFlag, songId);
+                            aUpdated = ETrue;
+                            }
+
+                        MPX_DEBUG2("    GeneralFlags[%b]", curFlag);
+                        }
+                    } // end case
+                } // end switch
+            } // end for
+        }
+
+    CleanupStack::PopAndDestroy(&recordset);
+    return visibleChange;
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::UpdateSongsL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::UpdateSongsL(
+    TUint32 aPlaylistId,
+    TUint32 aNewPlaylistId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::UpdateSongsL");
+
+    // update the PlaylistSongs table on all drives
+    iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongsUpdatePlaylistId,
+        aNewPlaylistId, aPlaylistId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::ReorderSongL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::ReorderSongL(
+    const TMPXItemId& aPlaylistId,
+    const TMPXItemId& aSongId,
+    TUint aOriginalOrdinal,
+    TUint aNewOrdinal)
+    {
+    MPX_DEBUG1("-->CMPXDbPlaylistSongs::ReorderSongL");
+    MPX_DEBUG5("    playlist[0x%x, 0x%x], song[0x%x, 0x%x]",
+               aPlaylistId.iId1, aPlaylistId.iId2, aSongId.iId1, aSongId.iId2);
+
+    //
+    // Move the song up. The rest of the songs in between the old ordinal and new ordinal
+    // need to be moved down, i.e. their ordinals need to be incremented
+    //
+    if (aOriginalOrdinal > aNewOrdinal)
+        {
+        iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongsMoveSongUp,
+            aPlaylistId.iId2, aNewOrdinal, aOriginalOrdinal);
+        }
+
+    //
+    // Move the song down. The rest of the songs in between the old ordinal and new ordinal
+    // need to be moved up, i.e. their ordinals need to be decremented
+    //
+    else if (aOriginalOrdinal < aNewOrdinal)
+        {
+        iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongsMoveSongDown,
+            aPlaylistId.iId2, aNewOrdinal, aOriginalOrdinal);
+        }
+
+    //
+    // Change the ordinal of the song itself. If the ordinal is unchanged, no update is
+    // required
+    //
+    if (aOriginalOrdinal != aNewOrdinal)
+        {
+        iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongsUpdateSongOrdinal,
+            aNewOrdinal, aSongId.iId1);
+        }
+
+    MPX_DEBUG1("<--CMPXDbPlaylistSongs::ReorderSongL");
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::DeleteSongL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::DeleteSongL(
+    TUint32 aSongId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::DeleteSongL");
+
+    // delete from the PlaylistSongs table on all drives
+    iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongsDelete, aSongId);
+
+    // delete from the PlaylistSongInfo table on all drives
+    iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongInfoDelete, aSongId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::DeleteSongL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::DeleteSongL(
+    TUint32 aPlaylistId,
+    TUint32 aSongId,
+    TInt aOrdinal,
+    TInt aDriveId)
+    {
+    MPX_FUNC("CMPXDbPlaylist::DeleteSongL");
+
+    // get the number of instances for the song in the playlist
+    TInt count(SongInstanceCountL(aPlaylistId, aSongId));
+
+    if (1 == count)
+        {
+        // just one instance with this ID in the playlist
+        // delete it regardless of the ordinal
+        iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongsDeleteSong, aPlaylistId, aSongId);
+
+        // check how many instances of this song are left for all playlists
+        if (!SongCountL(aSongId))
+            {
+            // delete the PlaylistSongInfo record
+            iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongInfoDelete, aSongId);
+            }
+        }
+    else if (count > 1)
+        {
+        // multiple songs with this id in the playlist
+        // try to delete the one with the specified ordinal
+        iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongsDeleteSongOrdinal, aPlaylistId,
+            aSongId, aOrdinal);
+        }
+    else
+        {
+        // no such song in the playlist
+        User::Leave(KErrCorrupt);
+        }
+    // adjust song ordinals for the songs after the deleted song
+    iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongsUpdateSongOrdinalAfterDelete,
+        aPlaylistId, aOrdinal);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::DeleteSongsL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::DeleteSongsL(
+    TUint32 aPlaylistId,
+    TInt aDriveId)
+    {
+    MPX_FUNC("CMPXDbPlaylist::DeleteSongsL");
+
+    // delete the records from the PlaylistSongs table
+    iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongsDeletePlaylist, aPlaylistId);
+
+    // delete the unused records from the PlaylistSongInfo table
+    CleanupSongInfoL(aDriveId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::DeleteSongsForCategoryL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::DeleteSongsForCategoryL(
+    TMPXGeneralCategory aCategory,
+    TUint32 aCategoryId,
+    TInt aDriveId)
+    {
+    MPX_FUNC("CMPXDbPlaylist::DeleteSongsForCategoryL");
+
+    // get the category field name in the Music table
+    TPtrC category = MPXDbUtil::MusicFieldNameForCategoryL(aCategory);
+
+    iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongsDeleteForCategory,
+        &category, aCategoryId);
+
+    // delete the unused records from the PlaylistSongInfo table
+    CleanupSongInfoL(aDriveId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::DeleteSongsForArtistAndAlbumL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::DeleteSongsForArtistAndAlbumL(
+    TUint32 aArtistId,
+    TUint32 aAlbumId,
+    TInt aDriveId)
+    {
+    MPX_FUNC("CMPXDbPlaylist::DeleteSongsForArtistAndAlbumL");
+
+    // delete the songs in the PlaylisSongs table
+    iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongsDeleteForArtistAlbum,
+        aArtistId, aAlbumId);
+
+    // delete the unused records from the PlaylistSongInfo table
+    CleanupSongInfoL(aDriveId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::DeleteAllSongsL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::DeleteAllSongsL()
+    {
+    MPX_FUNC("CMPXDbPlaylist::DeleteAllSongsL");
+
+    // delete all records from the PlaylistSongs table, all drives
+    iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongsDeleteAll);
+
+    // delete all records from the PlaylistSongInfo table, all drives
+    iDbManager.ExecuteQueryL(KDbManagerAllDrives, KQueryPlaylistSongInfoDeleteAll);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::GetSongsL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::GetSongsL(
+    TUint32 aPlaylistId,
+    CMPXMediaArray& aMediaArray)
+    {
+    MPX_DEBUG2("-->CMPXDbPlaylistSongs::GetSongsL(playlist 0x%x)", aPlaylistId);
+
+    RArray<TMPXAttribute> attributes;
+    CleanupClosePushL( attributes );
+
+    attributes.AppendL(KMPXMediaGeneralType);
+    attributes.AppendL(KMPXMediaGeneralCategory);
+    attributes.AppendL(KMPXMediaGeneralId);
+    
+    // cannot execute a joined query to the music table
+    // because the song records in the music table may be on a different drive
+    ExecuteMediaQueryL(attributes.Array(), aMediaArray, KQueryPlaylistSongsGetSongs, aPlaylistId);
+
+    CleanupStack::PopAndDestroy(&attributes);
+
+    MPX_DEBUG1("<--CMPXDbPlaylistSongs::GetSongsL");
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::GetSongsL
+// ----------------------------------------------------------------------------
+//
+TBool CMPXDbPlaylistSongs::GetSongsL(
+    TUint32 aPlaylistId,
+    const TArray<TMPXAttribute>& aAttrs,
+    CMPXMediaArray& aMediaArray)
+    {
+    MPX_DEBUG2("-->CMPXDbPlaylistSongs::GetSongsL(playlist 0x%x)", aPlaylistId);
+    TBool valid(EFalse);
+    
+    RSqlStatement recordset(iDbManager.ExecuteSelectQueryL(KQueryPlaylistSongsGetSongsInfo,
+    		aPlaylistId));
+    CleanupClosePushL(recordset);
+
+    TInt err(KErrNone);
+    while ((err = recordset.Next()) == KSqlAtRow)
+        {
+        CMPXMedia* media = CMPXMedia::NewL();
+        CleanupStack::PushL(media);
+
+        UpdateMediaL(recordset, aAttrs, *media);
+
+        aMediaArray.AppendL(*media);
+        CleanupStack::PopAndDestroy(media);
+        }
+    CleanupStack::PopAndDestroy(&recordset);
+
+    if (err!= KSqlAtEnd)
+        {
+        User::Leave(KErrCorrupt);
+        }
+    else
+    	{
+    	valid = ETrue;
+    	}
+
+    MPX_DEBUG1("<--CMPXDbPlaylistSongs::GetSongsL");
+    return valid;
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::GetSongL
+// ----------------------------------------------------------------------------
+//
+TBool CMPXDbPlaylistSongs::GetSongL(
+    TUint32 aPlaylistId,
+    TUint32 aSongId,
+    const TArray<TMPXAttribute>& aAttrs,
+    CMPXMedia& aMedia)
+    {
+    MPX_FUNC("CMPXDbPlaylist::GetSongL");
+
+    // get the song
+    TBool valid(EFalse);
+    RSqlStatement recordset(iDbManager.ExecuteSelectQueryL(KQueryPlaylistSongsGetSong,
+        aPlaylistId, aSongId));
+    CleanupClosePushL(recordset);
+
+    if (recordset.Next() == KSqlAtRow)
+        {
+        // convert to media
+        UpdateMediaL(recordset, aAttrs, aMedia);
+        valid = ETrue;
+        }
+
+    CleanupStack::PopAndDestroy(&recordset);
+
+    return valid;
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::GetSongL
+// ----------------------------------------------------------------------------
+//
+TBool CMPXDbPlaylistSongs::GetSongL(
+    const TMPXItemId& aSongId,
+    const TArray<TMPXAttribute>& aAttrs,
+    CMPXMedia*& aMedia)
+    {
+    MPX_FUNC("CMPXDbPlaylist::GetSongL");
+
+    // get the song
+    TBool valid(EFalse);
+    RSqlStatement recordset(iDbManager.ExecuteSelectQueryL(KQueryPlaylistSongsGetSongInfo,
+        aSongId.iId1, aSongId.iId2));
+    CleanupClosePushL(recordset);
+
+    if (recordset.Next() == KSqlAtRow)
+        {
+        // convert to media
+        aMedia = CMPXMedia::NewL();
+        CleanupStack::PushL(aMedia);
+        UpdateMediaL(recordset, aAttrs, *aMedia);
+        CleanupStack::Pop(aMedia);
+        valid = ETrue;
+        }
+
+    CleanupStack::PopAndDestroy(&recordset);
+
+    return valid;
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::CountL
+// ----------------------------------------------------------------------------
+//
+TInt CMPXDbPlaylistSongs::CountL(
+    TUint32 aPlaylistId)
+    {
+    MPX_FUNC("CMPXDbPlaylist::CountL");
+    return ExecuteSumQueryL(KQueryPlaylistSongsCount, aPlaylistId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::UpdateMediaL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::UpdateMediaL(
+    RSqlStatement& aRecord,
+    const TArray<TMPXAttribute>& aAttrs,
+    CMPXMedia& aMedia)
+    {
+    MPX_FUNC("CMPXDbPlaylist::UpdateMediaL");
+
+    aMedia.SetTObjectValueL<TMPXGeneralType>(KMPXMediaGeneralType, EMPXItem);
+    aMedia.SetTObjectValueL<TMPXGeneralCategory>(KMPXMediaGeneralCategory, EMPXSong);
+    aMedia.SetTObjectValueL<TMPXItemId>(KMPXMediaGeneralId, TMPXItemId(
+        aRecord.ColumnInt64(EPlaylistSongsUniqueId),
+        aRecord.ColumnInt64(EPlaylistSongsSongId)));
+
+/*
+    aMedia.SetTObjectValueL<TMPXItemId>(KMPXMediaGeneralId,
+        aRecord.ColumnInt64(EPlaylistSongsSongId));
+*/
+    TInt count(aAttrs.Count());
+    for (TInt index = 0; index < count; ++index)
+        {
+        TInt contentId(aAttrs[index].ContentId());
+        TUint attributeId(aAttrs[index].AttributeId());
+
+        if (contentId == KMPXMediaIdGeneral)
+            {
+            if (attributeId & EMPXMediaGeneralCollectionId)
+                {
+                aMedia.SetTObjectValueL<TMPXItemId>(KMPXMediaGeneralCollectionId,
+                    aRecord.ColumnInt64(EPlaylistSongsPlaylistId));
+                }
+            if (attributeId & EMPXMediaGeneralTitle)
+                {
+                TPtrC title(MPXDbCommonUtil::GetColumnTextL(aRecord, EPlaylistSongsTitle));
+                aMedia.SetTextValueL(KMPXMediaGeneralTitle, title);
+                MPX_DEBUG2("    Title[%S]", &title);
+                }
+            if (attributeId & EMPXMediaGeneralUri)
+                {
+                TUint volId(aRecord.ColumnInt64(EPlaylistSongsVolumeId));
+                HBufC* uri = MPXDbCommonUtil::CreateFullPathL(
+                    MPXDbCommonUtil::GetDriveIdMatchVolIdL(iDbManager.Fs(), volId),
+                    MPXDbCommonUtil::GetColumnTextL(aRecord, EPlaylistSongsUri));
+                CleanupStack::PushL(uri);
+
+                aMedia.SetTextValueL(KMPXMediaGeneralUri, *uri);
+
+                MPX_DEBUG2("    URI[%S]", uri);
+                CleanupStack::PopAndDestroy(uri);
+                }
+            if (attributeId & EMPXMediaGeneralFlags)
+                {
+                // assuming song details shouldn't be available for this song
+                TUint32 dbFlags(aRecord.ColumnInt64(EPlaylistSongsDbFlag) |
+                                KMPXMediaGeneralFlagsIsMissingDetails);
+                TUint32 volId(aRecord.ColumnInt64(EPlaylistSongsVolumeId));
+                TDriveUnit driveUnit(EDriveC);
+                if (aMedia.IsSupported(KMPXMediaGeneralUri))
+                    {
+                    // ignore the return value
+                    MPXDbCommonUtil::GetDriveL(aMedia.ValueText(KMPXMediaGeneralUri), driveUnit);
+                    }
+                else
+                    {
+                    driveUnit = MPXDbCommonUtil::GetDriveIdMatchVolIdL(iDbManager.Fs(), volId);
+                    }
+
+                // the volume the playlist song is located on may be removed
+                if (~dbFlags & KMPXMediaGeneralFlagsIsInvalid)
+                    {
+                    if (((volId != 0) && (MPXDbCommonUtil::GetDriveIdMatchVolIdL(iDbManager.Fs(), volId) == KErrNotFound)) ||
+                        ((volId == 0) && (MPXDbCommonUtil::GetVolIdMatchDriveIdL(iDbManager.Fs(), driveUnit) == 0)))
+                        {
+                        dbFlags |= KMPXMediaGeneralFlagsIsInvalid;
+                        }
+                    }
+
+                TInt driveId(driveUnit & KMPXMediaGeneralFlagsDriveInfo);  // 5 bits
+                aMedia.SetTObjectValueL<TUint>(KMPXMediaGeneralFlags, dbFlags | driveId);
+                MPX_DEBUG2("    GeneralFlags[%b]", dbFlags | driveId);
+                }
+            }
+        }
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::SongInstanceCountL
+// ----------------------------------------------------------------------------
+//
+TInt CMPXDbPlaylistSongs::SongInstanceCountL(
+    TUint32 aPlaylistId,
+    TUint32 aSongId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::SongInstanceCountL");
+    return ExecuteSumQueryL(KQueryPlaylistSongsPlaylistSongCount, aPlaylistId, aSongId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::SongCountL
+// ----------------------------------------------------------------------------
+//
+TInt CMPXDbPlaylistSongs::SongCountL(
+    TUint32 aSongId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::SongCountL");
+    return ExecuteSumQueryL(KQueryPlaylistSongsSongCount, aSongId);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::SongInfoExistsL
+// The song info record must be in the same database as the corresponding
+// PlaylistSongs record(s), otherwise when adding a duplicate of a song on a
+// different drive this method will return true and the song info record won't
+// be created.
+// ----------------------------------------------------------------------------
+//
+TBool CMPXDbPlaylistSongs::SongInfoExistsL(
+    TInt aDriveId,
+    TUint32 aSongId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::SongInfoExistsL");
+
+    RSqlStatement recordset(
+        iDbManager.ExecuteSelectQueryL(aDriveId, KQueryPlaylistSongInfoExists, aSongId));
+    TBool exists(recordset.Next() == KSqlAtRow);
+    recordset.Close();
+    return exists;
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::CleanupSongInfoL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::CleanupSongInfoL(
+    TInt aDriveId)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::CleanupSongInfoL");
+    iDbManager.ExecuteQueryL(aDriveId, KQueryPlaylistSongInfoCleanup);
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::CreateTableL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::CreateTableL(
+    RSqlDatabase& aDatabase,
+    TBool /* aCorruptTable */)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::CreateTableL");
+
+    // create the PlaylistSongs table
+    User::LeaveIfError(aDatabase.Exec(KPlaylistSongsCreateTable));
+
+    // create the PlaylistSongInfo table
+    User::LeaveIfError(aDatabase.Exec(KPlaylistSongInfoCreateTable));
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::DropTableL
+// ----------------------------------------------------------------------------
+//
+void CMPXDbPlaylistSongs::DropTableL(
+    RSqlDatabase& aDatabase)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::DropTableL");
+
+    // drop the PlaylistSongs table
+    User::LeaveIfError(aDatabase.Exec(KPlaylistSongsDropTable));
+
+    // drop the PlaylistSongInfo table
+    User::LeaveIfError(aDatabase.Exec(KPlaylistSongInfoDropTable));
+    }
+
+// ----------------------------------------------------------------------------
+// CMPXDbPlaylistSongs::CheckTableL
+// ----------------------------------------------------------------------------
+//
+TBool CMPXDbPlaylistSongs::CheckTableL(
+    RSqlDatabase& aDatabase)
+    {
+    MPX_FUNC("CMPXDbPlaylistSongs::CheckTableL");
+
+    // check the PlaylistSongs table
+    TBool check = DoCheckTable(aDatabase, KPlaylistSongsCheckTable);
+
+    // check the PlaylistSongInfo table
+    return check && DoCheckTable(aDatabase, KPlaylistSongInfoCheckTable);
+    }
+
+// End of File