CPI_Playlist.c
上传用户:tuheem
上传日期:2007-05-01
资源大小:21889k
文件大小:62k
源码类别:

多媒体编程

开发平台:

Visual C++

  1. ////////////////////////////////////////////////////////////////////////////////
  2. #include "stdafx.h"
  3. #include "globals.h"
  4. #include "CPI_Playlist.h"
  5. #include "CPI_PlaylistItem.h"
  6. #include "CPI_PlaylistItem_Internal.h"
  7. #include "CPI_Player.h"
  8. #define CPC_TRACKSTACK_BUFFER_QUANTISATION 32
  9. typedef int (__cdecl *wp_SortFN)(const void *elem1, const void *elem2);
  10. int __cdecl exp_CompareStrings(const void *elem1, const void *elem2);
  11. DWORD WINAPI CPI_PlaylistWorkerThreadEP(void* pCookie);
  12. ////////////////////////////////////////////////////////////////////////////////
  13. //
  14. typedef struct _CPs_PlaylistWorkerThreadInfo
  15. {
  16.     DWORD m_dwHostThreadID;
  17.     DWORD m_dwCurrentBatchID;
  18. } CPs_PlaylistWorkerThreadInfo;
  19. //
  20. //
  21. typedef struct _CPs_Playlist
  22. {
  23.     CP_HPLAYLISTITEM m_hFirst;
  24.     CP_HPLAYLISTITEM m_hLast;
  25.     CP_HPLAYLISTITEM m_hCurrent;
  26.     CP_HPLAYLISTITEM* m_pTrackStack;
  27.     unsigned int m_iTrackStackSize;
  28.     unsigned int m_iTrackStackBufferSize;
  29.     unsigned int m_iTrackStackCursor;
  30.     HANDLE m_hWorkerThread;
  31.     DWORD m_dwWorkerThreadID;
  32.     CPs_PlaylistWorkerThreadInfo m_WorkerThreadInfo;
  33.     BOOL m_bSyncLoadNextFile;
  34.     BOOL m_bAutoActivateInitial;
  35. } CPs_Playlist;
  36. //
  37. //
  38. typedef enum _CPe_PlayListFileType
  39. {
  40.     pftUnknown,
  41.     pftPLS,
  42.     pftM3U
  43. } CPe_PlayListFileType;
  44. //
  45. typedef struct _CPs_FilenameLLItem
  46. {
  47.     char* m_pcFilename;
  48.     void* m_pNextItem;
  49. } CPs_FilenameLLItem;
  50. //
  51. //
  52. #define CPC_PLAYLISTWORKER_NOTIFYCHUNKSIZE 32
  53. typedef struct _CPs_NotifyChunk
  54. {
  55.     int m_iNumberInChunk;
  56.     CP_HPLAYLISTITEM m_aryItems[CPC_PLAYLISTWORKER_NOTIFYCHUNKSIZE];
  57.     DWORD m_aryBatchIDs[CPC_PLAYLISTWORKER_NOTIFYCHUNKSIZE];
  58. } CPs_NotifyChunk;
  59. //
  60. ////////////////////////////////////////////////////////////////////////////////
  61. ////////////////////////////////////////////////////////////////////////////////
  62. //
  63. //
  64. //
  65. CP_HPLAYLIST CPL_CreatePlaylist()
  66. {
  67.     CPs_Playlist* pNewPlaylist = (CPs_Playlist*)malloc(sizeof(CPs_Playlist));
  68.     pNewPlaylist->m_hFirst = NULL;
  69.     pNewPlaylist->m_hLast = NULL;
  70.     pNewPlaylist->m_hCurrent = NULL;
  71.     pNewPlaylist->m_pTrackStack = NULL;
  72.     pNewPlaylist->m_iTrackStackSize = 0;
  73.     pNewPlaylist->m_iTrackStackBufferSize = 0;
  74.     pNewPlaylist->m_iTrackStackCursor = 0;
  75.     pNewPlaylist->m_bSyncLoadNextFile = FALSE;
  76.     pNewPlaylist->m_bAutoActivateInitial = FALSE;
  77.     pNewPlaylist->m_WorkerThreadInfo.m_dwHostThreadID = GetCurrentThreadId();
  78.     pNewPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID = 0;
  79.     // Create worker thread
  80.     pNewPlaylist->m_hWorkerThread = CreateThread(NULL, 0, CPI_PlaylistWorkerThreadEP, &pNewPlaylist->m_WorkerThreadInfo, 0, &(pNewPlaylist->m_dwWorkerThreadID) );
  81.     CP_ASSERT(pNewPlaylist->m_hWorkerThread);
  82.     return pNewPlaylist;
  83. }
  84. //
  85. //
  86. //
  87. void CPL_DestroyPlaylist(CP_HPLAYLIST hPlaylist)
  88. {
  89.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  90.     CP_CHECKOBJECT(pPlaylist);
  91.     // Stop the worker thread from processing any more pending ID3 reads
  92.     pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID++;
  93.     // Request worker thread to shutdown
  94.     PostThreadMessage(pPlaylist->m_dwWorkerThreadID, CPPLWT_TERMINATE, 0, 0);
  95.     // Clean up list
  96.     CPL_Empty(hPlaylist);
  97.     // Delete an unattached active item
  98.     if(pPlaylist->m_hCurrent && CPLII_DECODEHANDLE(pPlaylist->m_hCurrent)->m_bDestroyOnDeactivate)
  99.         CPLII_DestroyItem(pPlaylist->m_hCurrent);
  100.     // Wait for shutdown to actually happen
  101.     WaitForSingleObject(pPlaylist->m_hWorkerThread, INFINITE);
  102.     CloseHandle(pPlaylist->m_hWorkerThread);
  103.     // Remove any read ID3s from our message queue
  104.     {
  105.         MSG msg;
  106.         while(PeekMessage(&msg, NULL, CPPLNM_TAGREAD, CPPLNM_TAGREAD, PM_REMOVE) )
  107.         {
  108.             CPs_NotifyChunk* pChunk = (CPs_NotifyChunk*)msg.wParam;
  109.             int iChunkItemIDX;
  110.             // Add all of the items in the chunk
  111.             for(iChunkItemIDX = 0; iChunkItemIDX < pChunk->m_iNumberInChunk; iChunkItemIDX++)
  112.                 CPLII_DestroyItem(pChunk->m_aryItems[iChunkItemIDX]);
  113.             free(pChunk);
  114.         }
  115.     }
  116.     // Clean up object
  117.     free(pPlaylist);
  118. }
  119. //
  120. //
  121. //
  122. void CPL_UnlinkItem(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  123. {
  124.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  125.     CPs_PlaylistItem* pItemToUnlink = CPLII_DECODEHANDLE(hItem);
  126.     CP_CHECKOBJECT(pPlaylist);
  127.     // Remove item from list
  128.     if(pItemToUnlink->m_hPrev)
  129.         CPLII_DECODEHANDLE(pItemToUnlink->m_hPrev)->m_hNext = pItemToUnlink->m_hNext;
  130.     else
  131.     {
  132.         CP_ASSERT(pPlaylist->m_hFirst == hItem);
  133.         pPlaylist->m_hFirst = pItemToUnlink->m_hNext;
  134.     }
  135.     if(pItemToUnlink->m_hNext)
  136.         CPLII_DECODEHANDLE(pItemToUnlink->m_hNext)->m_hPrev = pItemToUnlink->m_hPrev;
  137.     else
  138.     {
  139.         CP_ASSERT(pPlaylist->m_hLast == hItem);
  140.         pPlaylist->m_hLast = pItemToUnlink->m_hPrev;
  141.     }
  142. }
  143. //
  144. //
  145. //
  146. void CPL_Empty(CP_HPLAYLIST hPlaylist)
  147. {
  148.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  149.     CP_HPLAYLISTITEM hCursor, hNext;
  150.     CP_CHECKOBJECT(pPlaylist);
  151.     // Stop the worker thread from processing any more pending ID3 reads
  152.     pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID++;
  153.     // Unlink the active item
  154.     if(pPlaylist->m_hCurrent)
  155.     {
  156.         // This is the active item - clear it's next and prev entries and mark it
  157.         // so that it's destroyed when activation next changes
  158.         CPs_PlaylistItem* pActiveItem = CPLII_DECODEHANDLE(pPlaylist->m_hCurrent);
  159.         if(pActiveItem->m_bDestroyOnDeactivate == FALSE)
  160.         {
  161.             CPL_UnlinkItem(hPlaylist, pPlaylist->m_hCurrent);
  162.             pActiveItem->m_hNext = NULL;
  163.             pActiveItem->m_hPrev = NULL;
  164.             pActiveItem->m_bDestroyOnDeactivate = TRUE;
  165.             CPL_cb_OnPlaylistActivationChange(pPlaylist->m_hCurrent, FALSE);
  166.             pActiveItem->m_iCookie = CPC_INVALIDITEM;
  167.         }
  168.     }
  169.     // Callback
  170.     CPL_cb_OnPlaylistEmpty();
  171.     // Clean up items
  172.     hCursor = pPlaylist->m_hFirst;
  173.     while(hCursor)
  174.     {
  175.         hNext = CPLI_Next(hCursor);
  176.         CPLII_DestroyItem(hCursor);
  177.         hCursor = hNext;
  178.     }
  179.     // Reset state
  180.     pPlaylist->m_hFirst = NULL;
  181.     pPlaylist->m_hLast = NULL;
  182.     // Clean up the trackstack
  183.     if(pPlaylist->m_pTrackStack)
  184.         free(pPlaylist->m_pTrackStack);
  185.     pPlaylist->m_pTrackStack = NULL;
  186.     pPlaylist->m_iTrackStackSize = 0;
  187.     pPlaylist->m_iTrackStackBufferSize = 0;
  188.     pPlaylist->m_iTrackStackCursor = 0;
  189. }
  190. //
  191. //
  192. //
  193. void CPL_AddSingleFile_pt2(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hNewFile, const DWORD dwBatchID)
  194. {
  195.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  196.     const char* pcPath = CPLI_GetPath(hNewFile);
  197.     CP_CHECKOBJECT(pPlaylist);
  198.     // Batch has changed since this message was sent
  199.     if(dwBatchID != pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID)
  200.     {
  201.         CPLII_DestroyItem(hNewFile);
  202.         return;
  203.     }
  204.     // If items are only allowed once - look for another instance of this item
  205.     // and skip this add if it is found
  206.     if(options.allow_file_once_in_playlist)
  207.     {
  208.         CP_HPLAYLISTITEM hCursor;
  209.         hCursor = pPlaylist->m_hFirst;
  210.         while(hCursor)
  211.         {
  212.             // Is this item in the list already?
  213.             if(stricmp(CPLII_DECODEHANDLE(hCursor)->m_pcPath, pcPath) == 0)
  214.             {
  215.                 CPLII_DestroyItem(hNewFile);
  216.                 return;
  217.             }
  218.             hCursor = CPLI_Next(hCursor);
  219.         }
  220.     }
  221.     // Add item to the list
  222.     CPLII_DECODEHANDLE(hNewFile)->m_hPrev = pPlaylist->m_hLast;
  223.     if(pPlaylist->m_hLast)
  224.         CPLII_DECODEHANDLE(pPlaylist->m_hLast)->m_hNext = hNewFile;
  225.     pPlaylist->m_hLast = hNewFile;
  226.     if(pPlaylist->m_hFirst == NULL)
  227.         pPlaylist->m_hFirst = hNewFile;
  228.     // If there is no track name (ID3 read off or failed) - create one from the path
  229.     if(CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName == NULL)
  230.     {
  231.         int iNumChars;
  232.         int iCharIDX;
  233.         int iLastSlashIDX = CPC_INVALIDCHAR;
  234.         int iLastDotIDX = CPC_INVALIDCHAR;
  235.         int iLastCharIDX = CPC_INVALIDCHAR;
  236. if(strnicmp(pcPath,CIC_HTTPHEADER,strlen(CIC_HTTPHEADER)) == 0)
  237.             iLastCharIDX = strlen(pcPath);
  238. else
  239.         for(iCharIDX=0; pcPath[iCharIDX]; iCharIDX++)
  240.         {
  241.             if(pcPath[iCharIDX] == '\')
  242.                 iLastSlashIDX = iCharIDX;
  243.             if(pcPath[iCharIDX] == '.')
  244.                 iLastDotIDX = iCharIDX;
  245.             iLastCharIDX = iCharIDX;
  246.         }
  247.         // Correct indices
  248.         if(iLastSlashIDX == CPC_INVALIDCHAR)
  249.             iLastSlashIDX = 0;
  250.         else
  251.             iLastSlashIDX++; // We want the char after the last slash
  252.         if(iLastDotIDX == CPC_INVALIDCHAR || iLastDotIDX < iLastSlashIDX)
  253.             iLastDotIDX = iLastCharIDX;
  254.         else
  255.             iLastDotIDX--; // We want the string up to the char before the last dot
  256.         // Create title buffer
  257.         iNumChars = (iLastDotIDX-iLastSlashIDX)+1;
  258.         CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName = (char*)malloc(iNumChars+1);
  259.         memcpy(CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName, pcPath+iLastSlashIDX, iNumChars);
  260.         CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName[iNumChars] = '';
  261.     }
  262.     // Add to track stack
  263.     CPL_Stack_Append(hPlaylist, hNewFile);
  264.     // Callback
  265.     CPL_cb_OnPlaylistAppend(hNewFile);
  266. }
  267. //
  268. //
  269. //
  270. void CPL_AddSingleFile(CP_HPLAYLIST hPlaylist, const char* pcPath, const char* pcTitle)
  271. {
  272.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  273.     CP_HPLAYLISTITEM hNewFile;
  274.     CP_CHECKOBJECT(pPlaylist);
  275.     hNewFile = CPLII_CreateItem(pcPath);
  276.     // There was a title passed - setup the item accordingly
  277.     if(pcTitle && pcTitle[0])
  278.         STR_AllocSetString(&CPLII_DECODEHANDLE(hNewFile)->m_pcTrackName, pcTitle, FALSE);
  279.     // Defer this add to the worker thread if we are reading tags
  280.     if(options.read_id3_tag && options.read_id3_tag_in_background && !pPlaylist->m_bSyncLoadNextFile)
  281.     {
  282.         while(!PostThreadMessage(pPlaylist->m_dwWorkerThreadID, CPPLWT_READTAG, (WPARAM)pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID, (LPARAM)hNewFile))
  283.         { 
  284. Sleep(50); 
  285. }       
  286. if(pPlaylist->m_bAutoActivateInitial && stricmp(pcPath, options.initial_file) == 0)
  287.             PostThreadMessage(pPlaylist->m_dwWorkerThreadID, CPPLWT_SETACTIVE, (WPARAM)pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID, (LPARAM)hNewFile);
  288.     }
  289.     else
  290.     {
  291.         pPlaylist->m_bSyncLoadNextFile = FALSE;
  292.         if(options.read_id3_tag)
  293.             CPLI_ReadTag(hNewFile);
  294.         // If we didn't get a track length from the tag - work it out
  295.         if(CPLI_GetTrackLength(hNewFile) == 0
  296.                 && options.work_out_track_lengths)
  297.         {
  298.             CPLI_CalculateLength(hNewFile);
  299.         }
  300.         CPL_AddSingleFile_pt2(hPlaylist, hNewFile, pPlaylist->m_WorkerThreadInfo.m_dwCurrentBatchID);
  301.     }
  302. }
  303. //
  304. //
  305. //
  306. void CPL_HandleAsyncNotify(CP_HPLAYLIST hPlaylist, WPARAM wParam, LPARAM lParam)
  307. {
  308.     CPs_NotifyChunk* pChunk = (CPs_NotifyChunk*)wParam;
  309.     int iChunkItemIDX;
  310.     // Add all of the items in the chunk
  311.     CLV_BeginBatch(globals.m_hPlaylistViewControl);
  312.     for(iChunkItemIDX = 0; iChunkItemIDX < pChunk->m_iNumberInChunk; iChunkItemIDX++)
  313.         CPL_AddSingleFile_pt2(globals.m_hPlaylist, pChunk->m_aryItems[iChunkItemIDX], pChunk->m_aryBatchIDs[iChunkItemIDX]);
  314.     CLV_EndBatch(globals.m_hPlaylistViewControl);
  315.     // Cleanup
  316.     free(pChunk);
  317. }
  318. //
  319. //
  320. //
  321. void CPL_RemoveDuplicates(CP_HPLAYLIST hPlaylist)
  322. {
  323.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  324.     CP_HPLAYLISTITEM hCursor;
  325.     CP_CHECKOBJECT(pPlaylist);
  326.     // Scan the playlist removing duplicates
  327.     hCursor = pPlaylist->m_hFirst;
  328.     while(hCursor)
  329.     {
  330.         CP_HPLAYLISTITEM hCursor_Scan;
  331.         // Look for duplicates after this item (as all items will be scanned
  332.         // in this way there is no need to look for duplicates before this item)
  333.         hCursor_Scan = CPLI_Next(hCursor);
  334.         while(hCursor_Scan)
  335.         {
  336.             // Is this a duplicate
  337.             if(stricmp( CPLII_DECODEHANDLE(hCursor_Scan)->m_pcPath,
  338.                         CPLII_DECODEHANDLE(hCursor)->m_pcPath) == 0)
  339.             {
  340.                 CPL_RemoveItem(hPlaylist, hCursor_Scan);
  341.                 // Items before the current are already unique - stop scanning
  342.                 break;
  343.             }
  344.             hCursor_Scan = CPLI_Next(hCursor_Scan);
  345.         }
  346.         hCursor = CPLI_Next(hCursor);
  347.     }
  348. }
  349. //
  350. //
  351. //
  352. void CPL_PlayActiveItem(CP_HPLAYLIST hPlaylist, const BOOL bStopFirst)
  353. {
  354.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  355.     CP_CHECKOBJECT(pPlaylist);
  356.     // Stop
  357.     if(bStopFirst == TRUE)
  358.         CPI_Player__Stop(globals.m_hPlayer);
  359.     // Start playing
  360.     if(pPlaylist->m_hCurrent)
  361.     {
  362.         CPI_Player__OpenFile(globals.m_hPlayer, CPLI_GetPath(pPlaylist->m_hCurrent));
  363.         CPI_Player__Play(globals.m_hPlayer);
  364.     }
  365. }
  366. //
  367. //
  368. //
  369. void CPL_RemoveItem(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  370. {
  371.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  372.     CP_CHECKOBJECT(pPlaylist);
  373.     // Callback
  374.     CPL_cb_OnPlaylistItemDelete(hItem);
  375.     CPL_UnlinkItem(hPlaylist, hItem);
  376.     // Remove item from track stack
  377.     CPL_Stack_Remove(hPlaylist, hItem);
  378.     if(hItem == pPlaylist->m_hCurrent)
  379.     {
  380.         // This is the active item - clear it's next and prev entries and mark it
  381.         // so that it's destroyed when activation next changes
  382.         CPs_PlaylistItem* pActiveItem = CPLII_DECODEHANDLE(hItem);
  383.         pActiveItem->m_hNext = NULL;
  384.         pActiveItem->m_hPrev = NULL;
  385.         pActiveItem->m_bDestroyOnDeactivate = TRUE;
  386.         CPL_cb_OnPlaylistActivationChange(hItem, FALSE);
  387.         pActiveItem->m_iCookie = CPC_INVALIDITEM;
  388.     }
  389.     else
  390.     {
  391.         // Cleanup
  392.         CPLII_DestroyItem(hItem);
  393.     }
  394. }
  395. //
  396. //
  397. //
  398. void CPL_SetActiveItem(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  399. {
  400.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  401.     CP_CHECKOBJECT(pPlaylist);
  402.     if(pPlaylist->m_hCurrent == hItem)
  403.         return;
  404.     // Unset any previous activation state
  405.     if(pPlaylist->m_hCurrent)
  406.     {
  407.         if(CPLII_DECODEHANDLE(pPlaylist->m_hCurrent)->m_bDestroyOnDeactivate)
  408.             CPLII_DestroyItem(pPlaylist->m_hCurrent);
  409.         else
  410.             CPL_cb_OnPlaylistActivationChange(pPlaylist->m_hCurrent, FALSE);
  411.     }
  412.     pPlaylist->m_hCurrent = hItem;
  413.     // Set new activation state
  414.     if(pPlaylist->m_hCurrent)
  415.     {
  416.         CPL_cb_OnPlaylistActivationChange(pPlaylist->m_hCurrent, TRUE);
  417.         if(options.read_id3_tag_of_selected == TRUE)
  418.             CPLI_ReadTag(hItem);
  419.     }
  420.     else
  421.         CPL_cb_OnPlaylistActivationEmpty();
  422.     // Update track stack
  423.     CPL_Stack_SetCursor(hPlaylist, pPlaylist->m_hCurrent);
  424.     // Setup the initial file buffer (for remember last played)
  425.     if(pPlaylist->m_hCurrent)
  426.         strncpy(options.initial_file, CPLI_GetPath(hItem), sizeof(options.initial_file));
  427. }
  428. //
  429. //
  430. //
  431. void CPL_PlayItem(CP_HPLAYLIST hPlaylist, const BOOL bStopFirst, const CPe_PlayMode enPlayMode)
  432. {
  433.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  434.     CP_HPLAYLISTITEM hItemToPlay = NULL;
  435.     CP_CHECKOBJECT(pPlaylist);
  436.     // Decide on what to play
  437.     switch(enPlayMode)
  438.     {
  439.     case pmCurrentItem:
  440.         if(pPlaylist->m_hCurrent)
  441.         {
  442.             // If the current item is no longer in the list and we are stopping - play the first item
  443.             if(bStopFirst == TRUE || CPLII_DECODEHANDLE(pPlaylist->m_hCurrent)->m_bDestroyOnDeactivate)
  444.             {
  445.                 if(pPlaylist->m_iTrackStackSize > 0)
  446.                 {
  447.                     if(pPlaylist->m_iTrackStackCursor < pPlaylist->m_iTrackStackSize)
  448.                         hItemToPlay = pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackCursor];
  449.                     else
  450.                         hItemToPlay = pPlaylist->m_pTrackStack[0];
  451.                 }
  452.             }
  453.             else
  454.                 hItemToPlay = pPlaylist->m_hCurrent;
  455.         }
  456.         else
  457.         {
  458.             if(pPlaylist->m_iTrackStackCursor < pPlaylist->m_iTrackStackSize)
  459.             {
  460.                 hItemToPlay = pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackCursor];
  461.             }
  462.             else
  463.             {
  464.                 if(options.shuffle_play)
  465.                     CPL_Stack_Shuffle(globals.m_hPlaylist, FALSE);
  466.                 if(pPlaylist->m_iTrackStackSize > 0)
  467.                     hItemToPlay = pPlaylist->m_pTrackStack[0];
  468.             }
  469.         }
  470.         break;
  471.     case pmNextItem:
  472.         // If the currently playing track is not the one at the head of the stack - play the head of the stack
  473.         if(pPlaylist->m_hCurrent
  474.                 && pPlaylist->m_iTrackStackCursor < pPlaylist->m_iTrackStackSize
  475.                 && pPlaylist->m_hCurrent != pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackCursor])
  476.         {
  477.             hItemToPlay = pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackCursor];
  478.         }
  479.         // Play the next item from the track stack
  480.         if(hItemToPlay == NULL)
  481.         {
  482.             if(pPlaylist->m_iTrackStackCursor < pPlaylist->m_iTrackStackSize)
  483.                 pPlaylist->m_iTrackStackCursor++;
  484.             if(pPlaylist->m_iTrackStackCursor < pPlaylist->m_iTrackStackSize)
  485.                 hItemToPlay = pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackCursor];
  486.         }
  487.         if(hItemToPlay == NULL && options.repeat_playlist == TRUE)
  488.         {
  489.             if(options.shuffle_play)
  490.                 CPL_Stack_Shuffle(globals.m_hPlaylist, FALSE);
  491.             if(pPlaylist->m_iTrackStackSize > 0)
  492.                 hItemToPlay = pPlaylist->m_pTrackStack[0];
  493.         }
  494.         break;
  495.     case pmPrevItem:
  496.         // Play the prev item in the track stack
  497.         if(pPlaylist->m_iTrackStackCursor > 0)
  498.             hItemToPlay = pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackCursor-1];
  499.         else
  500.         {
  501.             if(options.repeat_playlist == TRUE)
  502.             {
  503.                 if(pPlaylist->m_iTrackStackSize > 0)
  504.                     hItemToPlay = pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackSize-1];
  505.             }
  506.             else if(pPlaylist->m_iTrackStackSize > 0)
  507.                 hItemToPlay = pPlaylist->m_pTrackStack[0];
  508.         }
  509.         break;
  510.     default:
  511.         CP_FAIL(UnknownPlayMode);
  512.     }
  513.     CPL_SetActiveItem(hPlaylist, hItemToPlay);
  514.     CPL_PlayActiveItem(hPlaylist, bStopFirst);
  515. }
  516. //
  517. //
  518. //
  519. CP_HPLAYLISTITEM CPL_GetFirstItem(CP_HPLAYLIST hPlaylist)
  520. {
  521.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  522.     CP_CHECKOBJECT(pPlaylist);
  523.     return pPlaylist->m_hFirst;
  524. }
  525. //
  526. //
  527. //
  528. CP_HPLAYLISTITEM CPL_GetLastItem(CP_HPLAYLIST hPlaylist)
  529. {
  530.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  531.     CP_CHECKOBJECT(pPlaylist);
  532.     return pPlaylist->m_hLast;
  533. }
  534. //
  535. //
  536. //
  537. CP_HPLAYLISTITEM CPL_GetActiveItem(CP_HPLAYLIST hPlaylist)
  538. {
  539.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  540.     CP_CHECKOBJECT(pPlaylist);
  541.     return pPlaylist->m_hCurrent;
  542. }
  543. //
  544. //
  545. //
  546. CP_HPLAYLISTITEM CPL_FindPlaylistItem(CP_HPLAYLIST hPlaylist, const char* pcPath)
  547. {
  548.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  549.     CP_HPLAYLISTITEM hCursor;
  550.     CP_CHECKOBJECT(pPlaylist);
  551.     for(hCursor = pPlaylist->m_hFirst; hCursor; hCursor = CPLI_Next(hCursor))
  552.     {
  553.         CP_TRACE1("Looked at "%s"", CPLII_DECODEHANDLE(hCursor)->m_pcPath);
  554.         if(stricmp(CPLII_DECODEHANDLE(hCursor)->m_pcPath, pcPath) == 0)
  555.             return hCursor;
  556.     }
  557.     return NULL;
  558. }
  559. //
  560. //
  561. //
  562. void WriteFile_Text(HANDLE hFile, const char* pcLine, const BOOL bAppendCR)
  563. {
  564.     DWORD dwBytesWritten;
  565.     int iLineLen = strlen(pcLine);
  566.     WriteFile(hFile, pcLine, iLineLen, &dwBytesWritten, NULL);
  567.     if(bAppendCR)
  568.         WriteFile(hFile, "rn", 2, &dwBytesWritten, NULL);
  569. }
  570. //
  571. //
  572. //
  573. CPe_PlayListFileType CPL_GetFileType(const char* pcPath)
  574. {
  575.     // Determine format from file extension
  576.     const char* pcExtension = NULL;
  577.     int iCharIDX;
  578.     // Find the extension (the chars after the last dot)
  579.     for(iCharIDX = 0; pcPath[iCharIDX]; iCharIDX++)
  580.     {
  581.         if(pcPath[iCharIDX] == '.')
  582.             pcExtension = pcPath + iCharIDX + 1;
  583.         else if(pcPath[iCharIDX] == '\')
  584.             pcExtension = NULL;
  585.     }
  586.     // No extension - we don't know what format to use!
  587.     if(pcExtension == NULL)
  588.         return pftUnknown;
  589.     if(stricmp(pcExtension, "pls") == 0)
  590.         return pftPLS;
  591.     else if(stricmp(pcExtension, "m3u") == 0)
  592.         return pftM3U;
  593.     return pftUnknown;
  594. }
  595. //
  596. //
  597. //
  598. unsigned int CPL_GetPathVolumeBytes(const char* pcPath)
  599. {
  600.     // We understand volumes in the format of C: or \SYSTEMNAMESHAREPOINT so look for
  601.     // these
  602.     if(pcPath[1] == ':')
  603.         return 3;
  604.     else if(pcPath[0] == '\' && pcPath[1] == '\')
  605.     {
  606.         int iCharIDX;
  607.         int iNumSlashesFound;
  608.         // UNCs format is \SERVERSharePointpath
  609.         // Find the second slash (skipping the double slash at the start)
  610.         iNumSlashesFound = 0;
  611.         for(iCharIDX = 2; pcPath[iCharIDX]; iCharIDX++)
  612.         {
  613.             if(pcPath[iCharIDX] == '\')
  614.                 iNumSlashesFound++;
  615.             // We've found the second slash - build the prefix
  616.             if(iNumSlashesFound == 2)
  617.                 return iCharIDX+1;
  618.         }
  619.     }
  620.     else if(strnicmp(pcPath, CIC_HTTPHEADER, sizeof(CIC_HTTPHEADER)-1) == 0)
  621.         return sizeof(CIC_HTTPHEADER);
  622.     // There is no volume information
  623.     return 0;
  624. }
  625. //
  626. //
  627. //
  628. unsigned int CPL_GetPathDirectoryBytes(const char* pcPath, const unsigned int iVolumeBytes)
  629. {
  630.     unsigned int iCharIDX;
  631.     unsigned int iLastSlashIDX;
  632.     // Find the last slash and trim everything before it
  633.     // - if there is no directory stub then empty this string
  634.     iLastSlashIDX = 0;
  635.     for(iCharIDX = iVolumeBytes; pcPath[iCharIDX]; iCharIDX++)
  636.     {
  637.         if(pcPath[iCharIDX] == '\')
  638.             iLastSlashIDX = iCharIDX + 1;
  639.     }
  640.     return iLastSlashIDX;
  641. }
  642. //
  643. //
  644. //
  645. void CPL_ExportPlaylist(CP_HPLAYLIST hPlaylist, const char* pcOutputName)
  646. {
  647.     HANDLE hOutputFile;
  648.     CPe_PlayListFileType enFileType;
  649.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  650.     const unsigned int iPlaylist_VolumeBytes = CPL_GetPathVolumeBytes(pcOutputName);
  651.     const unsigned int iPlaylist_DirectoryBytes = CPL_GetPathDirectoryBytes(pcOutputName, iPlaylist_VolumeBytes);
  652.     CP_CHECKOBJECT(pPlaylist);
  653.     // Check for known file types
  654.     enFileType = CPL_GetFileType(pcOutputName);
  655.     if(enFileType == pftUnknown)
  656.         return;
  657.     // Open the file
  658.     hOutputFile = CreateFile(pcOutputName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  659.     if(hOutputFile == INVALID_HANDLE_VALUE)
  660.     {
  661.         MessageBox(windows.wnd_main, "Could not open file", "Error", MB_ICONERROR);
  662.         return;
  663.     }
  664.     // Go through all playlist items - outputting them to the file
  665.     {
  666.         CP_HPLAYLISTITEM hCursor;
  667.         int iFileNumber;
  668.         // If this is a PLS file then write some header info
  669.         if(enFileType == pftPLS)
  670.         {
  671.             int iNumberOfEntries =0;
  672.             char cNumEntriesLine[32];
  673.             WriteFile_Text(hOutputFile, "[PlayList]", TRUE);
  674.             // Count the number of playlist items
  675.             for(hCursor = pPlaylist->m_hFirst; hCursor; hCursor = CPLI_Next(hCursor))
  676.                 iNumberOfEntries++;
  677.             sprintf(cNumEntriesLine, "NumberOfEntries=%d", iNumberOfEntries);
  678.             WriteFile_Text(hOutputFile, cNumEntriesLine, TRUE);
  679.         }
  680.         iFileNumber = 0;
  681.         for(hCursor = pPlaylist->m_hFirst; hCursor; hCursor = CPLI_Next(hCursor), iFileNumber++)
  682.         {
  683.             char cRelPath[MAX_PATH];
  684.             const char* _pcFilename = CPLI_GetPath(hCursor);
  685.             // We prefer relative paths in our playlist files - only works if playlist
  686.             // and target are on the same volume
  687.             if(strnicmp(_pcFilename, pcOutputName, iPlaylist_VolumeBytes) == 0)
  688.             {
  689.                 // - so strip off the directory stubs that the file and playlist may have in common
  690.                 const char* pcLastCommonSplitPoint = _pcFilename;
  691.                 unsigned int iCharIDX;
  692.                 for(iCharIDX = 0; _pcFilename[iCharIDX] && iCharIDX < iPlaylist_DirectoryBytes; iCharIDX++)
  693.                 {
  694.                     if(tolower(_pcFilename[iCharIDX]) != tolower(pcOutputName[iCharIDX]))
  695.                         break;
  696.                     if(_pcFilename[iCharIDX] == '\')
  697.                         pcLastCommonSplitPoint = _pcFilename+iCharIDX+1;
  698.                 }
  699.                 // - add a .. for every slash left in the playlist's path
  700.                 cRelPath[0] = '';
  701.                 for(; iCharIDX < iPlaylist_DirectoryBytes; iCharIDX++)
  702.                 {
  703.                     if(pcOutputName[iCharIDX] == '\')
  704.                         strcat(cRelPath, "..\");
  705.                 }
  706.                 strcat(cRelPath, pcLastCommonSplitPoint);
  707.             }
  708.             else
  709.                 strcpy(cRelPath, _pcFilename);
  710.             // PLS files have the format FileXXX=pathname - we want to write the stuff up to (and including)
  711.             // the equals sign
  712.             if(enFileType == pftPLS)
  713.             {
  714.                 char cPlsFileHeader[32];
  715.                 sprintf(cPlsFileHeader, "File%d=", iFileNumber + 1);
  716.                 WriteFile_Text(hOutputFile, cPlsFileHeader, FALSE);
  717.             }
  718.             // Write the filename
  719.             WriteFile_Text(hOutputFile, cRelPath, TRUE);
  720.         }
  721.     }
  722.     CloseHandle(hOutputFile);
  723. }
  724. //
  725. //
  726. //
  727. void CPL_AddPrefixedFile( CP_HPLAYLIST hPlaylist,
  728.                           const char* pcFilename, const char* pcTitle,
  729.                           const char* pcPlaylistFile,
  730.                           const unsigned int iPlaylist_VolumeBytes,
  731.                           const unsigned int iPlaylist_DirBytes)
  732. {
  733.     const unsigned int iFile_VolumeBytes = CPL_GetPathVolumeBytes(pcFilename);
  734.     // If the file has volume information - add it as it is
  735.     if(iFile_VolumeBytes)
  736.         CPL_AddSingleFile(hPlaylist, pcFilename, pcTitle);
  737.     // If the filename has a leading  then add it prepended by the playlist's volume
  738.     else if(pcFilename[0] == '\')
  739.     {
  740.         char cFullPath[MAX_PATH];
  741.         memcpy(cFullPath, pcPlaylistFile, iPlaylist_VolumeBytes);
  742.         strcpy(cFullPath + iPlaylist_VolumeBytes, pcFilename + 1);
  743.         CPL_AddSingleFile(hPlaylist, cFullPath, pcTitle);
  744.     }
  745.     // Add the filename prepended by the playlist's directory
  746.     else
  747.     {
  748.         char cFullPath[MAX_PATH];
  749.         memcpy(cFullPath, pcPlaylistFile, iPlaylist_DirBytes);
  750.         strcpy(cFullPath + iPlaylist_DirBytes, pcFilename);
  751.         CPL_AddSingleFile(hPlaylist, cFullPath, pcTitle);
  752.     }
  753. }
  754. //
  755. //
  756. //
  757. void CPL_AddFile(CP_HPLAYLIST hPlaylist, const char* pcFilename)
  758. {
  759.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  760.     CPe_PlayListFileType enFileType;
  761.     unsigned int iPlaylist_VolumeBytes;
  762.     unsigned int iPlaylist_DirectoryBytes;
  763.     CP_CHECKOBJECT(pPlaylist);
  764.     // Check for known file types
  765.     enFileType = CPL_GetFileType(pcFilename);
  766.     if(enFileType == pftUnknown)
  767.     {
  768.         // This doesn't seem to be a playlist file - add it as a playlist item
  769.         CPL_AddSingleFile(hPlaylist, pcFilename, NULL);
  770.         return;
  771.     }
  772.     // Get playlist file information
  773.     iPlaylist_VolumeBytes = CPL_GetPathVolumeBytes(pcFilename);
  774.     iPlaylist_DirectoryBytes = CPL_GetPathDirectoryBytes(pcFilename, iPlaylist_VolumeBytes);
  775.     // Load the playlist files
  776.     CPL_cb_LockWindowUpdates(TRUE);
  777.     if(enFileType == pftPLS)
  778.     {
  779.         int iNumFiles, iFileIDX;
  780.         iNumFiles = GetPrivateProfileInt("playlist", "NumberOfEntries", 0, pcFilename);
  781.         for(iFileIDX = 0; iFileIDX < iNumFiles; iFileIDX++)
  782.         {
  783.             DWORD dwNumCharsRead;
  784.             char cPlsFileHeader[32];
  785.             char cBuffer[MAX_PATH];
  786.             char cTitle[1024];
  787.             sprintf(cPlsFileHeader, "File%d", iFileIDX + 1);
  788.             // Get the path - leave room for a drive
  789.             dwNumCharsRead = GetPrivateProfileString("playlist", cPlsFileHeader, NULL, cBuffer, MAX_PATH, pcFilename);
  790.             if(dwNumCharsRead == 0)
  791.                 continue;
  792.             sprintf(cPlsFileHeader, "Title%d", iFileIDX + 1);
  793.             dwNumCharsRead = GetPrivateProfileString("playlist", cPlsFileHeader, NULL, cTitle, 1024, pcFilename);
  794.             if(dwNumCharsRead == 0)
  795.                 CPL_AddPrefixedFile(hPlaylist, cBuffer, NULL, pcFilename, iPlaylist_VolumeBytes, iPlaylist_DirectoryBytes);
  796.             else
  797.                 CPL_AddPrefixedFile(hPlaylist, cBuffer, cTitle, pcFilename, iPlaylist_VolumeBytes, iPlaylist_DirectoryBytes);
  798.         }
  799.     }
  800.     else
  801.     {
  802.         // Open file and load it all into memory
  803.         HANDLE hFile;
  804.         hFile = CreateFile(pcFilename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  805.         if(hFile != INVALID_HANDLE_VALUE)
  806.         {
  807.             const DWORD dwFileSize = GetFileSize(hFile, NULL);
  808.             // We will only load playlists that are smaller than 256K
  809.             if(dwFileSize < 0x40000)
  810.             {
  811.                 // The plan is to load the entire file into a memblock and then split it into lines
  812.                 // and scan off the whitepace and add the items to the list
  813.                 char *pcPlaylistBuffer = (char*)malloc(dwFileSize+1);
  814.                 DWORD dwBytesRead;
  815.                 unsigned int iLastLineStartIDX, iCharIDX;
  816.                 ReadFile(hFile, pcPlaylistBuffer, dwFileSize, &dwBytesRead, NULL);
  817.                 // Read in the file line by line
  818.                 iLastLineStartIDX = 0;
  819.                 for(iCharIDX = 0; iCharIDX < dwFileSize+1; iCharIDX++)
  820.                 {
  821.                     if( (pcPlaylistBuffer[iCharIDX] == 'r'
  822.                             || pcPlaylistBuffer[iCharIDX] == 'n'
  823.                             || iCharIDX == dwFileSize)
  824.                             && iLastLineStartIDX < iCharIDX)
  825.                     {
  826.                         char cBuffer[512];
  827.                         // Is there a file on this line (strip whitespace from start)
  828.                         if(sscanf(pcPlaylistBuffer + iLastLineStartIDX, " %512[^rn]", cBuffer) == 1)
  829.                         {
  830.                             // Something has been read - ignore lines starting with #
  831.                             if(cBuffer[0] != '#')
  832.                                 CPL_AddPrefixedFile(hPlaylist, cBuffer, NULL, pcFilename, iPlaylist_VolumeBytes, iPlaylist_DirectoryBytes);
  833.                         }
  834.                         // Set the line start for the next line
  835.                         if(pcPlaylistBuffer[iCharIDX + 1] == 'n')
  836.                             iCharIDX++;
  837.                         iLastLineStartIDX = iCharIDX + 1;
  838.                     }
  839.                 }
  840.                 free(pcPlaylistBuffer);
  841.             }
  842.             CloseHandle(hFile);
  843.         }
  844.     }
  845.     if(options.shuffle_play)
  846.         PostThreadMessage(pPlaylist->m_dwWorkerThreadID, CPPLWT_SYNCSHUFFLE, 0, 0);
  847.     CPL_cb_LockWindowUpdates(FALSE);
  848. }
  849. //
  850. //
  851. //
  852. int __cdecl cpl_sort_Path(const void *e1, const void *e2)
  853. {
  854.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  855.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  856.     return stricmp(pElem1->m_pcPath, pElem2->m_pcPath);
  857. }
  858. //
  859. //
  860. //
  861. int __cdecl cpl_sort_Filename(const void *e1, const void *e2)
  862. {
  863.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  864.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  865.     return stricmp(pElem1->m_pcPath, pElem2->m_pcPath);
  866. }
  867. //
  868. //
  869. //
  870. int __cdecl cpl_sort_TrackNum(const void *e1, const void *e2)
  871. {
  872.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  873.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  874.     if(pElem1->m_cTrackNum == pElem2->m_cTrackNum)
  875.         return cpl_sort_Path(e1, e2);
  876.     else if((char)pElem1->m_cTrackNum < (char)pElem2->m_cTrackNum)
  877.         return -1;
  878.     return 1;
  879. }
  880. //
  881. //
  882. //
  883. int __cdecl cpl_sort_Length(const void *e1, const void *e2)
  884. {
  885.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  886.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  887.     if(pElem1->m_iTrackLength == pElem2->m_iTrackLength)
  888.         return 0;
  889.     else if(pElem1->m_iTrackLength < pElem2->m_iTrackLength)
  890.         return -1;
  891.     return 1;
  892. }
  893. //
  894. //
  895. //
  896. int __cdecl cpl_sort_TrackStackPos(const void *e1, const void *e2)
  897. {
  898.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  899.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  900.     if(pElem1->m_iTrackStackPos == pElem2->m_iTrackStackPos)
  901.         return 0;
  902.     if(pElem1->m_iTrackStackPos == CIC_TRACKSTACK_UNSTACKED)
  903.         return 1;
  904.     if(pElem2->m_iTrackStackPos == CIC_TRACKSTACK_UNSTACKED)
  905.         return -1;
  906.     if(pElem1->m_iTrackStackPos > pElem2->m_iTrackStackPos)
  907.         return 1;
  908.     else
  909.         return -1;
  910. }
  911. //
  912. //
  913. //
  914. int __cdecl cpl_sort_TrackName(const void *e1, const void *e2)
  915. {
  916.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  917.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  918.     return stricmp(pElem1->m_pcTrackName, pElem2->m_pcTrackName);
  919. }
  920. //
  921. //
  922. //
  923. int __cdecl cpl_sort_Album(const void *e1, const void *e2)
  924. {
  925.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  926.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  927.     int iStringCompare;
  928.     if(pElem1->m_pcAlbum == NULL && pElem2->m_pcAlbum == NULL)
  929.         return cpl_sort_TrackNum(e1, e2);
  930.     else if(pElem1->m_pcAlbum == NULL)
  931.         return -1;
  932.     else if(pElem2->m_pcAlbum == NULL)
  933.         return 1;
  934.     // Sort by artist - but fall back to track name
  935.     iStringCompare = stricmp(pElem1->m_pcAlbum, pElem2->m_pcAlbum);
  936.     if(iStringCompare != 0)
  937.         return iStringCompare;
  938.     return cpl_sort_TrackNum(e1, e2);
  939. }
  940. //
  941. //
  942. //
  943. int __cdecl cpl_sort_Artist(const void *e1, const void *e2)
  944. {
  945.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  946.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  947.     int iStringCompare;
  948.     if(pElem1->m_pcArtist == NULL && pElem2->m_pcArtist == NULL)
  949.         return cpl_sort_Album(e1, e2);
  950.     else if(pElem1->m_pcArtist == NULL)
  951.         return -1;
  952.     else if(pElem2->m_pcArtist == NULL)
  953.         return 1;
  954.     // Sort by artist - but fall back to track name
  955.     iStringCompare = stricmp(pElem1->m_pcArtist, pElem2->m_pcArtist);
  956.     if(iStringCompare != 0)
  957.         return iStringCompare;
  958.     return cpl_sort_Album(e1, e2);
  959. }
  960. //
  961. //
  962. //
  963. int __cdecl cpl_sort_Year(const void *e1, const void *e2)
  964. {
  965.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  966.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  967.     int iStringCompare;
  968.     if(pElem1->m_pcYear == NULL && pElem2->m_pcYear == NULL)
  969.         return cpl_sort_Artist(e1, e2);
  970.     else if(pElem1->m_pcYear == NULL)
  971.         return -1;
  972.     else if(pElem2->m_pcYear == NULL)
  973.         return 1;
  974.     // Sort by artist - but fall back to track name
  975.     iStringCompare = stricmp(pElem1->m_pcYear, pElem2->m_pcYear);
  976.     if(iStringCompare != 0)
  977.         return iStringCompare;
  978.     return cpl_sort_Artist(e1, e2);
  979. }
  980. //
  981. //
  982. //
  983. int __cdecl cpl_sort_Genre(const void *e1, const void *e2)
  984. {
  985.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  986.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  987.     int iStringCompare;
  988.     if(CPLI_GetGenre((CP_HPLAYLISTITEM)pElem1) == NULL && CPLI_GetGenre((CP_HPLAYLISTITEM)pElem2) == NULL)
  989.         return cpl_sort_Artist(e1, e2);
  990.     else if(CPLI_GetGenre((CP_HPLAYLISTITEM)pElem1) == NULL)
  991.         return -1;
  992.     else if(CPLI_GetGenre((CP_HPLAYLISTITEM)pElem2) == NULL)
  993.         return 1;
  994.     // Sort by artist - but fall back to track name
  995.     iStringCompare = stricmp(CPLI_GetGenre((CP_HPLAYLISTITEM)pElem1), CPLI_GetGenre((CP_HPLAYLISTITEM)pElem2));
  996.     if(iStringCompare != 0)
  997.         return iStringCompare;
  998.     return cpl_sort_Artist(e1, e2);
  999. }
  1000. //
  1001. //
  1002. //
  1003. int __cdecl cpl_sort_Comment(const void *e1, const void *e2)
  1004. {
  1005.     const CPs_PlaylistItem* pElem1 = *(const CPs_PlaylistItem**)e1;
  1006.     const CPs_PlaylistItem* pElem2 = *(const CPs_PlaylistItem**)e2;
  1007.     int iStringCompare;
  1008.     if(pElem1->m_pcComment == NULL && pElem2->m_pcComment == NULL)
  1009.         return cpl_sort_Artist(e1, e2);
  1010.     else if(pElem1->m_pcComment == NULL)
  1011.         return -1;
  1012.     else if(pElem2->m_pcComment == NULL)
  1013.         return 1;
  1014.     // Sort by artist - but fall back to track name
  1015.     iStringCompare = stricmp(pElem1->m_pcComment, pElem2->m_pcComment);
  1016.     if(iStringCompare != 0)
  1017.         return iStringCompare;
  1018.     return cpl_sort_Artist(e1, e2);
  1019. }
  1020. //
  1021. //
  1022. //
  1023. int __cdecl cpl_sort_Random(const void *e1, const void *e2)
  1024. {
  1025.     const unsigned short r1 = rand();
  1026.     const unsigned short r2 = rand();
  1027.     if(r1 < r2)
  1028.         return -1;
  1029.     else if(r1 == r2)
  1030.         return 0;
  1031.     else
  1032.         return 1;
  1033. }
  1034. //
  1035. //
  1036. //
  1037. void CPL_SortList(CP_HPLAYLIST hPlaylist, const CPe_PlayItemSortElement enElement, const BOOL bDesc)
  1038. {
  1039.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1040.     CPs_PlaylistItem** pFlatArray;
  1041.     unsigned int iNumItems;
  1042.     wp_SortFN pfnSort;
  1043.     CP_CHECKOBJECT(pPlaylist);
  1044.     // Skip if list is empty
  1045.     if(pPlaylist->m_hFirst == NULL)
  1046.         return;
  1047.     // Decide sort function
  1048.     {
  1049.         switch(enElement)
  1050.         {
  1051.         case piseTrackStackPos:
  1052.             pfnSort = cpl_sort_TrackStackPos;
  1053.             break;
  1054.         case piseTrackName:
  1055.             pfnSort = cpl_sort_TrackName;
  1056.             break;
  1057.         case piseArtist:
  1058.             pfnSort = cpl_sort_Artist;
  1059.             break;
  1060.         case piseAlbum:
  1061.             pfnSort = cpl_sort_Album;
  1062.             break;
  1063.         case piseYear:
  1064.             pfnSort = cpl_sort_Year;
  1065.             break;
  1066.         case piseTrackNum:
  1067.             pfnSort = cpl_sort_TrackNum;
  1068.             break;
  1069.         case piseGenre:
  1070.             pfnSort = cpl_sort_Genre;
  1071.             break;
  1072.         case piseComment:
  1073.             pfnSort = cpl_sort_Comment;
  1074.             break;
  1075.         case piseFilename:
  1076.             pfnSort = cpl_sort_Filename;
  1077.             break;
  1078.         case pisePath:
  1079.             pfnSort = cpl_sort_Path;
  1080.             break;
  1081.         case piseLength:
  1082.             pfnSort = cpl_sort_Length;
  1083.             break;
  1084.         default:
  1085.             CP_FAIL(UnknownSortOrder);
  1086.         }
  1087.     }
  1088.     // Count items
  1089.     {
  1090.         CP_HPLAYLISTITEM hCursor;
  1091.         iNumItems = 0;
  1092.         for(hCursor = pPlaylist->m_hFirst; hCursor; hCursor = CPLI_Next(hCursor))
  1093.             iNumItems++;
  1094.     }
  1095.     // Build flat array
  1096.     {
  1097.         CP_HPLAYLISTITEM hCursor;
  1098.         int iItemIDX;
  1099.         pFlatArray = (CPs_PlaylistItem**)malloc(sizeof(CPs_PlaylistItem*) * iNumItems);
  1100.         iItemIDX = 0;
  1101.         for(hCursor = pPlaylist->m_hFirst; hCursor; hCursor = CPLI_Next(hCursor), iItemIDX++)
  1102.             pFlatArray[iItemIDX] = CPLII_DECODEHANDLE(hCursor);
  1103.     }
  1104.     // Qsort it
  1105.     qsort(pFlatArray, iNumItems, sizeof(CPs_PlaylistItem*), pfnSort);
  1106.     // Relink list
  1107.     {
  1108.         int iFirstItem, iTermItem, iInc;
  1109.         CP_HPLAYLISTITEM* phCursor_Referrer = &(pPlaylist->m_hFirst);
  1110.         CP_HPLAYLISTITEM hCursor_Prev = NULL;
  1111.         CP_HPLAYLISTITEM hLastAssignment = NULL;
  1112.         int iItemIDX;
  1113.         // Work out how to traverse the flat array
  1114.         if(bDesc == FALSE)
  1115.         {
  1116.             iFirstItem = 0;
  1117.             iTermItem = iNumItems;
  1118.             iInc = 1;
  1119.         }
  1120.         else
  1121.         {
  1122.             iFirstItem = iNumItems-1;
  1123.             iTermItem = -1;
  1124.             iInc = -1;
  1125.         }
  1126.         // Traverse the array
  1127.         for(iItemIDX = iFirstItem; iItemIDX != iTermItem; iItemIDX+=iInc)
  1128.         {
  1129.             *phCursor_Referrer = pFlatArray[iItemIDX];
  1130.             pFlatArray[iItemIDX]->m_hPrev = hCursor_Prev;
  1131.             phCursor_Referrer = &(CPLII_DECODEHANDLE(*phCursor_Referrer)->m_hNext);
  1132.             hCursor_Prev = pFlatArray[iItemIDX];
  1133.             hLastAssignment = hCursor_Prev;
  1134.         }
  1135.         pPlaylist->m_hLast = hLastAssignment;
  1136.         CPLII_DECODEHANDLE(pPlaylist->m_hLast)->m_hNext = NULL;
  1137.     }
  1138.     free(pFlatArray);
  1139.     CPL_cb_SetWindowToReflectList();
  1140. }
  1141. //
  1142. //
  1143. //
  1144. void CPL_InsertItemBefore(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem_Anchor, CP_HPLAYLISTITEM hItem_ToMove)
  1145. {
  1146.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1147.     CPs_PlaylistItem* pAnchor = CPLII_DECODEHANDLE(hItem_Anchor);
  1148.     CP_CHECKOBJECT(pPlaylist);
  1149.     CPL_UnlinkItem(hPlaylist, hItem_ToMove);
  1150.     if(pAnchor->m_hPrev)
  1151.     {
  1152.         CPLII_DECODEHANDLE(pAnchor->m_hPrev)->m_hNext = hItem_ToMove;
  1153.         CPLII_DECODEHANDLE(hItem_ToMove)->m_hPrev = pAnchor->m_hPrev;
  1154.     }
  1155.     else
  1156.     {
  1157.         pPlaylist->m_hFirst = hItem_ToMove;
  1158.         CPLII_DECODEHANDLE(hItem_ToMove)->m_hPrev = NULL;
  1159.     }
  1160.     pAnchor->m_hPrev = hItem_ToMove;
  1161.     CPLII_DECODEHANDLE(hItem_ToMove)->m_hNext = hItem_Anchor;
  1162. }
  1163. //
  1164. //
  1165. //
  1166. void CPL_InsertItemAfter(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem_Anchor, CP_HPLAYLISTITEM hItem_ToMove)
  1167. {
  1168.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1169.     CPs_PlaylistItem* pAnchor = CPLII_DECODEHANDLE(hItem_Anchor);
  1170.     CP_CHECKOBJECT(pPlaylist);
  1171.     CPL_UnlinkItem(hPlaylist, hItem_ToMove);
  1172.     if(pAnchor->m_hNext)
  1173.     {
  1174.         CPLII_DECODEHANDLE(pAnchor->m_hNext)->m_hPrev = hItem_ToMove;
  1175.         CPLII_DECODEHANDLE(hItem_ToMove)->m_hNext = pAnchor->m_hNext;
  1176.     }
  1177.     else
  1178.     {
  1179.         pPlaylist->m_hLast = hItem_ToMove;
  1180.         CPLII_DECODEHANDLE(hItem_ToMove)->m_hNext = NULL;
  1181.     }
  1182.     pAnchor->m_hNext = hItem_ToMove;
  1183.     CPLII_DECODEHANDLE(hItem_ToMove)->m_hPrev = hItem_Anchor;
  1184. }
  1185. //
  1186. //
  1187. //
  1188. void SortLList(CPs_FilenameLLItem* pFirst)
  1189. {
  1190.     char** ppStrings;
  1191.     int iNumStrings;
  1192.     int iStringIDX;
  1193.     CPs_FilenameLLItem* pCursor;
  1194.     if(!pFirst)
  1195.         return;
  1196.     // Count the number of strings in the list
  1197.     iNumStrings = 0;
  1198.     for(pCursor = pFirst; pCursor; pCursor = (CPs_FilenameLLItem*)pCursor->m_pNextItem)
  1199.         iNumStrings++;
  1200.     // Allocate string buffer and assign strings to it
  1201.     ppStrings = (char**)malloc(sizeof(char*) * iNumStrings);
  1202.     iStringIDX = 0;
  1203.     for(pCursor = pFirst; pCursor; pCursor = (CPs_FilenameLLItem*)pCursor->m_pNextItem)
  1204.     {
  1205.         ppStrings[iStringIDX] = pCursor->m_pcFilename;
  1206.         iStringIDX++;
  1207.     }
  1208.     // Sort filelist
  1209.     qsort(ppStrings, iNumStrings, sizeof(char*), exp_CompareStrings);
  1210.     // Assign list to the now sorted string list
  1211.     iStringIDX = 0;
  1212.     for(pCursor = pFirst; pCursor; pCursor = (CPs_FilenameLLItem*)pCursor->m_pNextItem)
  1213.     {
  1214.         pCursor->m_pcFilename = ppStrings[iStringIDX];
  1215.         iStringIDX++;
  1216.     }
  1217.     free(ppStrings);
  1218. }
  1219. //
  1220. //
  1221. //
  1222. void CPL_AddDirectory_Recurse(CP_HPLAYLIST hPlaylist, const char *pDir)
  1223. {
  1224.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1225.     CPs_FilenameLLItem* m_pFirstFile = NULL;
  1226.     CPs_FilenameLLItem* m_pFirstDir = NULL;
  1227.     CPs_FilenameLLItem* pCursor;
  1228.     CPs_FilenameLLItem* pNextItem;
  1229.     char cFullPath[MAX_PATH];
  1230.     char cWildCard[MAX_PATH];
  1231.     const int iDirStrLen = strlen(pDir);
  1232.     WIN32_FIND_DATA finddata;
  1233.     HANDLE hFileFind;
  1234.     CP_CHECKOBJECT(pPlaylist);
  1235.     // Build a full path to the directory
  1236.     strcpy(cFullPath, pDir);
  1237.     if(cFullPath[iDirStrLen-1] == '\' && iDirStrLen > 1)
  1238.         cFullPath[iDirStrLen-1] = '';
  1239.     // Check that this is a correct path
  1240.     if(cFullPath[0] == '' || path_is_directory(cFullPath) == FALSE)
  1241.     {
  1242.         MessageBox(NULL, "Not a valid directory.", cFullPath, MB_ICONERROR);
  1243.         return;
  1244.     }
  1245.     //Scan directory building a list of filenames
  1246.     if(strcmp(cFullPath, "\") == 0)
  1247.         strcpy(cWildCard, "\*.*");
  1248.     else
  1249.     {
  1250.         strcpy(cWildCard, cFullPath);
  1251.         strcat(cWildCard, "\*.*");
  1252.     }
  1253.     strcat(cFullPath, "\");
  1254.     hFileFind = FindFirstFile(cWildCard, &finddata);
  1255.     if(hFileFind == INVALID_HANDLE_VALUE)
  1256.     {
  1257.         MessageBox(NULL, "Could not perform scan", cFullPath, MB_ICONERROR);
  1258.         return;
  1259.     }
  1260.     do
  1261.     {
  1262.         char pcFullPath[MAX_PATH];
  1263.         // Skip dots
  1264.         if(finddata.cFileName[0] == '.')
  1265.             continue;
  1266.         strcpy(pcFullPath, cFullPath);
  1267.         strcat(pcFullPath, finddata.cFileName);
  1268.         // Add to linked list
  1269.         if(finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  1270.         {
  1271.             // Add to dirs list
  1272.             CPs_FilenameLLItem* pNewItem = (CPs_FilenameLLItem*)malloc(sizeof(CPs_FilenameLLItem));
  1273.             pNewItem->m_pNextItem = m_pFirstDir;
  1274.             STR_AllocSetString(&pNewItem->m_pcFilename, pcFullPath, FALSE);
  1275.             m_pFirstDir = pNewItem;
  1276.         }
  1277.         else
  1278.         {
  1279.             // Add to files list
  1280.             CPs_FilenameLLItem* pNewItem = (CPs_FilenameLLItem*)malloc(sizeof(CPs_FilenameLLItem));
  1281.             pNewItem->m_pNextItem = m_pFirstFile;
  1282.             STR_AllocSetString(&pNewItem->m_pcFilename, pcFullPath, FALSE);
  1283.             m_pFirstFile = pNewItem;
  1284.         }
  1285.     } while(FindNextFile(hFileFind, &finddata) != 0);
  1286.     FindClose(hFileFind);
  1287.     SortLList(m_pFirstDir);
  1288.     SortLList(m_pFirstFile);
  1289.     // Add files first - then directories
  1290.     for(pCursor = m_pFirstFile; pCursor; pCursor = (CPs_FilenameLLItem*)pCursor->m_pNextItem)
  1291.         CPL_AddFile(globals.m_hPlaylist, pCursor->m_pcFilename);
  1292.     for(pCursor = m_pFirstDir; pCursor; pCursor = (CPs_FilenameLLItem*)pCursor->m_pNextItem)
  1293.         CPL_AddDirectory_Recurse(hPlaylist, pCursor->m_pcFilename);
  1294.     // Cleanup
  1295.     for(pCursor = m_pFirstFile; pCursor; pCursor = pNextItem)
  1296.     {
  1297.         pNextItem = (CPs_FilenameLLItem*)pCursor->m_pNextItem;
  1298.         free(pCursor->m_pcFilename);
  1299.         free(pCursor);
  1300.     }
  1301.     for(pCursor = m_pFirstDir; pCursor; pCursor = pNextItem)
  1302.     {
  1303.         pNextItem = (CPs_FilenameLLItem*)pCursor->m_pNextItem;
  1304.         free(pCursor->m_pcFilename);
  1305.         free(pCursor);
  1306.     }
  1307. }
  1308. //
  1309. //
  1310. //
  1311. void CPL_AddDroppedFiles(CP_HPLAYLIST hPlaylist, HDROP hDrop)
  1312. {
  1313.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1314.     int iNumFiles, iFileIDX;
  1315.     char** ppFiles;
  1316.     CP_CHECKOBJECT(pPlaylist);
  1317.     // Read all the files into an array of strings
  1318.     iNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
  1319.     ppFiles = (char**)malloc(iNumFiles * sizeof(char*));
  1320.     for(iFileIDX = 0; iFileIDX < iNumFiles; iFileIDX++)
  1321.     {
  1322.         const int iBufferSize = DragQueryFile(hDrop, iFileIDX, NULL, 0)+1;
  1323.         ppFiles[iFileIDX] = (char*)malloc(iBufferSize * sizeof(char));
  1324.         DragQueryFile(hDrop, iFileIDX, ppFiles[iFileIDX], iBufferSize);
  1325.     }
  1326.     DragFinish(hDrop);
  1327.     // Sort filelist
  1328.     qsort(ppFiles, iNumFiles, sizeof(char*), exp_CompareStrings);
  1329.     // Add to playlist
  1330.     CLV_BeginBatch(globals.m_hPlaylistViewControl);
  1331.     for(iFileIDX = 0; iFileIDX < iNumFiles; iFileIDX++)
  1332.     {
  1333.         if(path_is_directory(ppFiles[iFileIDX]) == TRUE)
  1334.         {
  1335.             CPL_AddDirectory_Recurse(globals.m_hPlaylist, ppFiles[iFileIDX]);
  1336.             strcpy(options.last_used_directory, ppFiles[iFileIDX]);
  1337.         }
  1338.         else
  1339.             CPL_AddFile(globals.m_hPlaylist, ppFiles[iFileIDX]);
  1340.     }
  1341.     CLV_EndBatch(globals.m_hPlaylistViewControl);
  1342.     // Free string array
  1343.     for(iFileIDX = 0; iFileIDX < iNumFiles; iFileIDX++)
  1344.         free(ppFiles[iFileIDX]);
  1345.     free(ppFiles);
  1346.     // Shuffle playlist
  1347.     if(options.shuffle_play)
  1348.         PostThreadMessage(pPlaylist->m_dwWorkerThreadID, CPPLWT_SYNCSHUFFLE, 0, 0);
  1349. }
  1350. //
  1351. //
  1352. //
  1353. void CPL_Stack_Append(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  1354. {
  1355.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1356.     int iItemNumber;
  1357.     CP_CHECKOBJECT(pPlaylist);
  1358.     // Ensure buffer is big enough
  1359.     if( (pPlaylist->m_iTrackStackSize+1) >= pPlaylist->m_iTrackStackBufferSize)
  1360.     {
  1361.         pPlaylist->m_iTrackStackBufferSize += CPC_TRACKSTACK_BUFFER_QUANTISATION;
  1362.         pPlaylist->m_pTrackStack = realloc(pPlaylist->m_pTrackStack, pPlaylist->m_iTrackStackBufferSize * sizeof(CP_HPLAYLISTITEM));
  1363.     }
  1364.     // Ensure cursor is rational
  1365.     if(pPlaylist->m_iTrackStackCursor > pPlaylist->m_iTrackStackSize)
  1366.         pPlaylist->m_iTrackStackCursor = pPlaylist->m_iTrackStackSize;
  1367.     // Add item
  1368.     pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackSize] = hItem;
  1369.     pPlaylist->m_iTrackStackSize++;
  1370.     // Number item
  1371.     iItemNumber = (pPlaylist->m_iTrackStackSize-1) - pPlaylist->m_iTrackStackCursor;
  1372.     CPLI_SetTrackStackPos(hItem, iItemNumber);
  1373.     CPL_cb_TrackStackChanged();
  1374. }
  1375. //
  1376. //
  1377. //
  1378. void CPL_Stack_Renumber(CP_HPLAYLIST hPlaylist)
  1379. {
  1380.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1381.     unsigned int iStackIDX;
  1382.     CP_CHECKOBJECT(pPlaylist);
  1383.     for(iStackIDX = 0; iStackIDX < pPlaylist->m_iTrackStackSize; iStackIDX++)
  1384.         CPLI_SetTrackStackPos(pPlaylist->m_pTrackStack[iStackIDX], iStackIDX - pPlaylist->m_iTrackStackCursor);
  1385.     CPL_cb_TrackStackChanged();
  1386. }
  1387. //
  1388. //
  1389. //
  1390. void CPL_Stack_Remove(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  1391. {
  1392.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1393.     unsigned int iStackIDX;
  1394.     BOOL bFoundItem;
  1395.     CP_CHECKOBJECT(pPlaylist);
  1396.     CPLI_SetTrackStackPos(hItem, CIC_TRACKSTACK_UNSTACKED);
  1397.     // Search stack for item - remove it if we find it and renumber all items from there onwards
  1398.     bFoundItem = FALSE;
  1399.     for(iStackIDX = 0; iStackIDX < pPlaylist->m_iTrackStackSize; iStackIDX++)
  1400.     {
  1401.         if(bFoundItem == FALSE && pPlaylist->m_pTrackStack[iStackIDX] == hItem)
  1402.         {
  1403.             pPlaylist->m_iTrackStackSize--;
  1404.             if(iStackIDX < pPlaylist->m_iTrackStackCursor)
  1405.                 pPlaylist->m_iTrackStackCursor--;
  1406.             bFoundItem = TRUE;
  1407.         }
  1408.         if(bFoundItem == TRUE && iStackIDX < pPlaylist->m_iTrackStackSize)
  1409.             pPlaylist->m_pTrackStack[iStackIDX] = pPlaylist->m_pTrackStack[iStackIDX+1];
  1410.     }
  1411.     CPL_Stack_Renumber(hPlaylist);
  1412.     if(bFoundItem == TRUE)
  1413.     {
  1414.         // If the trackstack is now empty - free the buffers
  1415.         if(pPlaylist->m_iTrackStackSize == 0)
  1416.         {
  1417.             pPlaylist->m_iTrackStackBufferSize = 0;
  1418.             free(pPlaylist->m_pTrackStack);
  1419.             pPlaylist->m_pTrackStack = NULL;
  1420.         }
  1421.         CPL_cb_TrackStackChanged();
  1422.     }
  1423. }
  1424. //
  1425. //
  1426. //
  1427. void CPL_Stack_SetCursor(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  1428. {
  1429.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1430.     unsigned int iStackIDX;
  1431.     CP_CHECKOBJECT(pPlaylist);
  1432.     // Search for the item and set the cursor to this item - if it isn't in the stack then
  1433.     // add this item to the end (and move the cursor there)
  1434.     for(iStackIDX = 0; iStackIDX < pPlaylist->m_iTrackStackSize; iStackIDX++)
  1435.     {
  1436.         if(pPlaylist->m_pTrackStack[iStackIDX] == hItem)
  1437.         {
  1438.             pPlaylist->m_iTrackStackCursor = iStackIDX;
  1439.             break;
  1440.         }
  1441.     }
  1442.     CPL_Stack_Renumber(hPlaylist);
  1443. }
  1444. //
  1445. //
  1446. //
  1447. void CPL_Stack_Clear(CP_HPLAYLIST hPlaylist)
  1448. {
  1449.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1450.     unsigned int iStackIDX;
  1451.     CP_CHECKOBJECT(pPlaylist);
  1452.     // Mark all items in stack as unstacked
  1453.     for(iStackIDX = 0; iStackIDX < pPlaylist->m_iTrackStackSize; iStackIDX++)
  1454.         CPLI_SetTrackStackPos(pPlaylist->m_pTrackStack[iStackIDX], CIC_TRACKSTACK_UNSTACKED);
  1455.     // Clear the stack buffer
  1456.     pPlaylist->m_iTrackStackSize = 0;
  1457.     pPlaylist->m_iTrackStackBufferSize = 0;
  1458.     pPlaylist->m_iTrackStackCursor = 0;
  1459.     free(pPlaylist->m_pTrackStack);
  1460.     pPlaylist->m_pTrackStack = NULL;
  1461.     CPL_cb_TrackStackChanged();
  1462. }
  1463. //
  1464. //
  1465. //
  1466. void CPL_Stack_RestackAll(CP_HPLAYLIST hPlaylist)
  1467. {
  1468.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1469.     CP_HPLAYLISTITEM hCursor;
  1470.     CP_CHECKOBJECT(pPlaylist);
  1471.     pPlaylist->m_iTrackStackSize = 0;
  1472.     pPlaylist->m_iTrackStackCursor = 0;
  1473.     for(hCursor = pPlaylist->m_hFirst; hCursor; hCursor = CPLI_Next(hCursor))
  1474.         CPL_Stack_Append(hPlaylist, hCursor);
  1475.     if(pPlaylist->m_hCurrent)
  1476.         CPL_Stack_SetCursor(hPlaylist, pPlaylist->m_hCurrent);
  1477. }
  1478. //
  1479. //
  1480. //
  1481. CPe_ItemStackState CPL_Stack_GetItemState(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  1482. {
  1483.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1484.     unsigned int iStackIDX;
  1485.     CP_CHECKOBJECT(pPlaylist);
  1486.     // Mark all items in stack as unstacked
  1487.     for(iStackIDX = 0; iStackIDX < pPlaylist->m_iTrackStackSize; iStackIDX++)
  1488.     {
  1489.         if(pPlaylist->m_pTrackStack[iStackIDX] == hItem)
  1490.         {
  1491.             if(iStackIDX < pPlaylist->m_iTrackStackCursor)
  1492.                 return issPlayed;
  1493.             else if(iStackIDX == pPlaylist->m_iTrackStackCursor)
  1494.                 return issStacked_Top;
  1495.             else
  1496.                 return issStacked;
  1497.         }
  1498.     }
  1499.     return issUnstacked;
  1500. }
  1501. //
  1502. //
  1503. //
  1504. void CPL_Stack_Shuffle(CP_HPLAYLIST hPlaylist, const BOOL bForceCurrentToHead)
  1505. {
  1506.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1507.     unsigned int iStackIDX;
  1508.     CP_CHECKOBJECT(pPlaylist);
  1509.     if(pPlaylist->m_iTrackStackSize == 0)
  1510.         return;
  1511.     // Qsort stack
  1512.     qsort(pPlaylist->m_pTrackStack, pPlaylist->m_iTrackStackSize, sizeof(CP_HPLAYLISTITEM*), cpl_sort_Random);
  1513.     pPlaylist->m_iTrackStackCursor = 0;
  1514.     if(pPlaylist->m_hCurrent)
  1515.     {
  1516.         // If there is a playing track - ensure that it is at position 0
  1517.         if(bForceCurrentToHead == TRUE)
  1518.         {
  1519.             for(iStackIDX = 0; iStackIDX < pPlaylist->m_iTrackStackSize; iStackIDX++)
  1520.             {
  1521.                 if(pPlaylist->m_pTrackStack[iStackIDX] == pPlaylist->m_hCurrent)
  1522.                 {
  1523.                     pPlaylist->m_pTrackStack[iStackIDX] = pPlaylist->m_pTrackStack[0];
  1524.                     pPlaylist->m_pTrackStack[0] = pPlaylist->m_hCurrent;
  1525.                     break;
  1526.                 }
  1527.             }
  1528.         }
  1529.         else
  1530.         {
  1531.             // If the current song is still at the head - swap it with the last song (to prevent a double play)
  1532.             if(pPlaylist->m_pTrackStack[0] == pPlaylist->m_hCurrent)
  1533.             {
  1534.                 pPlaylist->m_pTrackStack[0] = pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackSize-1];
  1535.                 pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackSize-1] = pPlaylist->m_hCurrent;
  1536.             }
  1537.         }
  1538.     }
  1539.     CPL_Stack_Renumber(hPlaylist);
  1540. }
  1541. //
  1542. //
  1543. //
  1544. void CPL_Stack_ClipFromCurrent(CP_HPLAYLIST hPlaylist)
  1545. {
  1546.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1547.     unsigned int iStackIDX;
  1548.     CP_CHECKOBJECT(pPlaylist);
  1549.     if(pPlaylist->m_iTrackStackSize == 0 || pPlaylist->m_iTrackStackCursor >= pPlaylist->m_iTrackStackSize)
  1550.         return;
  1551.     // Mark all items in stack as unstacked
  1552.     for(iStackIDX = pPlaylist->m_iTrackStackCursor+1; iStackIDX < pPlaylist->m_iTrackStackSize; iStackIDX++)
  1553.         CPLI_SetTrackStackPos(pPlaylist->m_pTrackStack[iStackIDX], CIC_TRACKSTACK_UNSTACKED);
  1554.     pPlaylist->m_iTrackStackSize = pPlaylist->m_iTrackStackCursor+1;
  1555.     CPL_cb_TrackStackChanged();
  1556. }
  1557. //
  1558. //
  1559. //
  1560. void CPL_Stack_ClipFromItem(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  1561. {
  1562.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1563.     unsigned int iStackIDX;
  1564.     BOOL bItemFound;
  1565.     unsigned int iFoundItemIDX;
  1566.     CP_CHECKOBJECT(pPlaylist);
  1567.     if(pPlaylist->m_iTrackStackSize == 0)
  1568.         return;
  1569.     // Mark items in stack after item as unstacked
  1570.     bItemFound = FALSE;
  1571.     iFoundItemIDX = CPC_INVALIDITEM;
  1572.     for(iStackIDX = pPlaylist->m_iTrackStackCursor; iStackIDX < pPlaylist->m_iTrackStackSize; iStackIDX++)
  1573.     {
  1574.         if(bItemFound == FALSE && pPlaylist->m_pTrackStack[iStackIDX] == hItem)
  1575.         {
  1576.             bItemFound = TRUE;
  1577.             iFoundItemIDX = iStackIDX;
  1578.         }
  1579.         else if(bItemFound)
  1580.             CPLI_SetTrackStackPos(pPlaylist->m_pTrackStack[iStackIDX], CIC_TRACKSTACK_UNSTACKED);
  1581.     }
  1582.     if(bItemFound)
  1583.     {
  1584.         pPlaylist->m_iTrackStackSize = iFoundItemIDX+1;
  1585.         CPL_cb_TrackStackChanged();
  1586.     }
  1587. }
  1588. //
  1589. //
  1590. //
  1591. void CPL_Stack_PlayNext(CP_HPLAYLIST hPlaylist, CP_HPLAYLISTITEM hItem)
  1592. {
  1593.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1594.     unsigned int iStackIDX;
  1595.     CP_CHECKOBJECT(pPlaylist);
  1596.     CPL_Stack_Remove(hPlaylist, hItem);
  1597.     // Rationalise cursor
  1598.     if(pPlaylist->m_iTrackStackCursor >= pPlaylist->m_iTrackStackSize)
  1599.     {
  1600.         CPL_Stack_Append(hPlaylist, hItem);
  1601.         return;
  1602.     }
  1603.     // Simple case
  1604.     if(pPlaylist->m_iTrackStackBufferSize == 0)
  1605.     {
  1606.         CPL_Stack_Append(hPlaylist, hItem);
  1607.         return;
  1608.     }
  1609.     // Ensure buffer is big enough
  1610.     if( (pPlaylist->m_iTrackStackSize+1) >= pPlaylist->m_iTrackStackBufferSize)
  1611.     {
  1612.         pPlaylist->m_iTrackStackBufferSize += CPC_TRACKSTACK_BUFFER_QUANTISATION;
  1613.         pPlaylist->m_pTrackStack = realloc(pPlaylist->m_pTrackStack, pPlaylist->m_iTrackStackBufferSize * sizeof(CP_HPLAYLISTITEM));
  1614.     }
  1615.     // Shunt all items up one
  1616.     for(iStackIDX = pPlaylist->m_iTrackStackSize; iStackIDX > (pPlaylist->m_iTrackStackCursor+1); iStackIDX--)
  1617.     {
  1618.         pPlaylist->m_pTrackStack[iStackIDX] = pPlaylist->m_pTrackStack[iStackIDX-1];
  1619.         CPLI_SetTrackStackPos(pPlaylist->m_pTrackStack[iStackIDX], iStackIDX-pPlaylist->m_iTrackStackCursor);
  1620.     }
  1621.     // Add item
  1622.     pPlaylist->m_pTrackStack[pPlaylist->m_iTrackStackCursor+1] = hItem;
  1623.     CPLI_SetTrackStackPos(pPlaylist->m_pTrackStack[iStackIDX], 1);
  1624.     pPlaylist->m_iTrackStackSize++;
  1625.     CPL_cb_TrackStackChanged();
  1626. }
  1627. //
  1628. //
  1629. //
  1630. DWORD WINAPI CPI_PlaylistWorkerThreadEP(void* pCookie)
  1631. {
  1632.     CPs_PlaylistWorkerThreadInfo* pThreadInfo = (CPs_PlaylistWorkerThreadInfo*)pCookie;
  1633.     MSG msg;
  1634.     CPs_NotifyChunk* pPendingChunk;
  1635. BOOL bRet;
  1636.     SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
  1637.     pPendingChunk = NULL;
  1638.  
  1639.     while((bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
  1640.     {
  1641. if (bRet == -1)
  1642.     {
  1643.         // handle the error and possibly exit
  1644. return 0;
  1645. }
  1646.         if(msg.message == CPPLWT_TERMINATE)
  1647.         {
  1648.             break;
  1649.         }
  1650.         else if(msg.message == CPPLWT_READTAG)
  1651.         {
  1652.             MSG msgPeek;
  1653.             CP_HPLAYLISTITEM hNewFile = (CP_HPLAYLISTITEM)msg.lParam;
  1654.             if(pThreadInfo->m_dwCurrentBatchID == (DWORD)msg.wParam)
  1655.             {
  1656.                 CPLI_ReadTag(hNewFile);
  1657.                 // If we didn't get a track length from the tag - work it out
  1658.                 if(CPLI_GetTrackLength(hNewFile) == 0
  1659.                         && options.work_out_track_lengths)
  1660.                 {
  1661.                     CPLI_CalculateLength(hNewFile);
  1662.                 }
  1663.                 // Allocate chunk
  1664.                 if(!pPendingChunk)
  1665.                 {
  1666.                     pPendingChunk = (CPs_NotifyChunk*)malloc(sizeof(CPs_NotifyChunk));
  1667.                     pPendingChunk->m_iNumberInChunk = 0;
  1668.                 }
  1669.                 // Add to current chunk
  1670.                 pPendingChunk->m_aryItems[pPendingChunk->m_iNumberInChunk] = hNewFile;
  1671.                 pPendingChunk->m_aryBatchIDs[pPendingChunk->m_iNumberInChunk] = (DWORD)msg.wParam;
  1672.                 pPendingChunk->m_iNumberInChunk++;
  1673.             }
  1674.             else
  1675.                 CPLII_DestroyItem(hNewFile);
  1676.             // Send the current chunk if its full or there are no more pending readtag messages
  1677.             if(pPendingChunk)
  1678.             {
  1679.                 if(pPendingChunk->m_iNumberInChunk == CPC_PLAYLISTWORKER_NOTIFYCHUNKSIZE
  1680.                         || PeekMessage(&msgPeek, NULL, CPPLWT_READTAG, CPPLWT_READTAG, PM_NOREMOVE) == FALSE)
  1681.                 {
  1682.                     PostThreadMessage(pThreadInfo->m_dwHostThreadID, CPPLNM_TAGREAD, (WPARAM)pPendingChunk, 0L);
  1683.                     pPendingChunk = NULL;
  1684.                 }
  1685.             }
  1686.         }
  1687.         else if(msg.message == CPPLWT_SYNCSHUFFLE)
  1688.         {
  1689.             PostThreadMessage(pThreadInfo->m_dwHostThreadID, CPPLNM_SYNCSHUFFLE, 0L, 0L);
  1690.         }
  1691.         else if(msg.message == CPPLWT_SETACTIVE)
  1692.         {
  1693.             CP_HPLAYLISTITEM hFile = (CP_HPLAYLISTITEM)msg.lParam;
  1694.             // Send the current chunk if there is one
  1695.             if(pPendingChunk)
  1696.             {
  1697.                 PostThreadMessage(pThreadInfo->m_dwHostThreadID, CPPLNM_TAGREAD, (WPARAM)pPendingChunk, 0L);
  1698.                 pPendingChunk = NULL;
  1699.             }
  1700.             // Send the setactivate
  1701.             if(pThreadInfo->m_dwCurrentBatchID == (DWORD)msg.wParam)
  1702.                 PostThreadMessage(pThreadInfo->m_dwHostThreadID, CPPLNM_SYNCSETACTIVE, (WPARAM)hFile, 0L);
  1703.         }
  1704.     }
  1705.     // Clean up any pending chunk
  1706.     if(pPendingChunk)
  1707.     {
  1708.         int iChunkItemIDX;
  1709.         for(iChunkItemIDX = 0; iChunkItemIDX < pPendingChunk->m_iNumberInChunk; iChunkItemIDX++)
  1710.             CPLII_DestroyItem(pPendingChunk->m_aryItems[iChunkItemIDX]);
  1711.         free(pPendingChunk);
  1712.     }
  1713.     CP_TRACE0("Playlist worker thread terminating");
  1714.     return 0;
  1715. }
  1716. //
  1717. //
  1718. //
  1719. void CPL_SyncLoadNextFile(CP_HPLAYLIST hPlaylist)
  1720. {
  1721.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1722.     CP_CHECKOBJECT(pPlaylist);
  1723.     pPlaylist->m_bSyncLoadNextFile = TRUE;
  1724. }
  1725. //
  1726. //
  1727. //
  1728. void CPL_SetAutoActivateInitial(CP_HPLAYLIST hPlaylist, const BOOL bAutoActivateInitial)
  1729. {
  1730.     CPs_Playlist* pPlaylist = (CPs_Playlist*)hPlaylist;
  1731.     CP_CHECKOBJECT(pPlaylist);
  1732.     pPlaylist->m_bAutoActivateInitial = TRUE;
  1733. }
  1734. //
  1735. //
  1736. //