www.pudn.com > MP3CORD.rar > playlist.cpp
/*____________________________________________________________________________ FreeAmp - The Free MP3 Player Portions Copyright (C) 1998-1999 EMusic.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. $Id: playlist.cpp,v 1.84 2000/01/16 20:07:41 ijr Exp $ ____________________________________________________________________________*/ // The debugger can't handle symbols more than 255 characters long. // STL often creates symbols longer than that. // When symbols are longer than 255 characters, the warning is disabled. #ifdef WIN32 #pragma warning(disable:4786) #endif #include#include #include #include #include #include using namespace std; #include "config.h" #include "playlist.h" #include "errors.h" #include "mutex.h" #include "thread.h" #include "metadata.h" #include "registrar.h" #include "event.h" #include "eventdata.h" #include "musiccatalog.h" class UndoAdd : public UndoItem { public: UndoAdd(PlaylistManager* plm, const string& url, uint32 index); virtual ~UndoAdd(); virtual void Undo(); virtual void Redo(); private: PlaylistManager* m_plm; string m_url; uint32 m_index; }; class UndoAddMulti : public UndoItem { public: UndoAddMulti(PlaylistManager* plm, vector & urls, uint32 index); virtual ~UndoAddMulti(); virtual void Undo(); virtual void Redo(); private: PlaylistManager* m_plm; vector m_urls; uint32 m_index; }; class UndoRemove : public UndoItem { public: UndoRemove(PlaylistManager* plm, const string& url, uint32 index); virtual ~UndoRemove(); virtual void Undo(); virtual void Redo(); private: PlaylistManager* m_plm; string m_url; uint32 m_index; }; class UndoMove : public UndoItem { public: UndoMove(PlaylistManager* plm, uint32 oldIndex, uint32 newIndex); virtual ~UndoMove(); virtual void Undo(); virtual void Redo(); private: PlaylistManager* m_plm; uint32 m_newIndex, m_oldIndex; }; // Function object used for sorting PlaylistItems in PlaylistManager bool PlaylistItemSort::operator() (PlaylistItem* item1, PlaylistItem* item2) const { bool result = true; assert(item1); assert(item2); if(item1 && item2) { MetaData m1 = item1->GetMetaData(); MetaData m2 = item2->GetMetaData(); switch(m_sortKey) { case kPlaylistSortKey_Artist: { result = m1.Artist() < m2.Artist(); break; } case kPlaylistSortKey_Album: { result = m1.Album() < m2.Album(); break; } case kPlaylistSortKey_Title: { result = m1.Title() < m2.Title(); break; } case kPlaylistSortKey_Year: { result = m1.Year() < m2.Year(); break; } case kPlaylistSortKey_Track: { result = m1.Track() < m2.Track(); break; } case kPlaylistSortKey_Genre: { result = m1.Genre() < m2.Genre(); break; } case kPlaylistSortKey_Time: { result = m1.Time() < m2.Time(); break; } case kPlaylistSortKey_Location: { result = item1->URL() < item2->URL(); break; } default: { cerr << "Whoa! Why are we here?" << endl; break; } } } /*if(m_sortType == kPlaylistSortType_Descending) { result = !result; }*/ return result; } // Function object used for ordering MetaDataFormats in PlaylistManager bool MetaDataSort::operator() (MetaDataFormat* item1, MetaDataFormat* item2) const { bool result = true; assert(item1); assert(item2); if(item1 && item2) { result = item1->Order() < item2->Order(); } return result; } // return an integral random number in the range 0 - (n - 1) static int my_rand(int n) { srand( (unsigned)time( NULL ) ); return rand() % n; } // Implementation of the PlaylistManager Class PlaylistManager::PlaylistManager(FAContext* context) { m_context = context; m_activeList = &m_masterList; m_playlistKey = kPlaylistKey_MasterPlaylist; m_current = kInvalidIndex; m_shuffle = false; m_repeatMode = kPlaylistMode_RepeatNone; m_sortKey = kPlaylistSortKey_Random; m_sortType = kPlaylistSortType_Ascending; m_context->prefs->GetPlaylistShuffle(&m_shuffle); m_context->prefs->GetPlaylistRepeat((int32*)&m_repeatMode); Registrar registrar; registrar.SetSubDir("plugins"); registrar.SetSearchString("*.plf"); registrar.InitializeRegistry(&m_playlistRegistry, context->prefs); registrar.SetSearchString("*.mdf"); registrar.InitializeRegistry(&m_metadataRegistry, context->prefs); registrar.SetSearchString("*.ppp"); registrar.InitializeRegistry(&m_portableRegistry, context->prefs); RegistryItem* module = NULL; MetaDataFormat* mdf = NULL; int32 i = 0; while((module = m_metadataRegistry.GetItem(i++))) { mdf = (MetaDataFormat*) module->InitFunction()(m_context); if(mdf) { m_metadataFormats.push_back(mdf); } } sort(m_metadataFormats.begin(), m_metadataFormats.end(), MetaDataSort()); i = 0; PlaylistFormat* plf = NULL; while((module = m_playlistRegistry.GetItem(i++))) { plf = (PlaylistFormat*) module->InitFunction()(m_context); if(plf) { PlaylistFormatInfo plfi; uint32 index = 0; // error != kError_NoMoreFormats while(IsntError(plf->GetSupportedFormats(&plfi, index++))) { plfi.SetRef(plf); m_playlistFormats.push_back(new PlaylistFormatInfo(plfi)); } } } i = 0; PortableDevice* pd = NULL; while((module = m_portableRegistry.GetItem(i++))) { pd = (PortableDevice*) module->InitFunction()(m_context); if(pd) { DeviceInfo di; uint32 index = 0; // error != kError_NoMoreDevices while(IsntError(pd->GetSupportedDevices(&di, index++))) { di.SetRef(pd); di.SetPluginName(module->Name()); m_portablePlayers.push_back(new DeviceInfo(di)); } } } } PlaylistManager::~PlaylistManager() { uint32 index = 0; uint32 size = 0; PlaylistItem* item = NULL; vector ::iterator i; //uint32 count = 0; size = m_masterList.size(); for(index = 0; index < size; index++) { item = m_masterList[index]; if(item) { // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } } } size = m_externalList.size(); for(index = 0; index < size; index++) { item = m_externalList[index]; if(item) { // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } } } size = m_portableList.size(); for(index = 0; index < size; index++) { item = m_portableList[index]; if(item) { // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } } } size = m_playlistFormats.size(); for(index = 0; index < size; index++) { delete m_playlistFormats[index]->GetRef(); delete m_playlistFormats[index]; } size = m_portablePlayers.size(); for(index = 0; index < size; index++) { delete m_portablePlayers[index]->GetRef(); delete m_portablePlayers[index]; } for(i = m_metadataFormats.begin(); i != m_metadataFormats.end(); i++) delete (*i); m_context->prefs->SetPlaylistShuffle(m_shuffle); m_context->prefs->SetPlaylistRepeat(m_repeatMode); } // Playlist actions Error PlaylistManager::SetCurrentItem(PlaylistItem* item) { return SetCurrentIndex(IndexOf(item)); } const PlaylistItem* PlaylistManager::GetCurrentItem() { PlaylistItem* result = NULL; m_mutex.Acquire(); if(m_masterList.size()) { if(m_shuffle) result = m_shuffleList[m_current]; else result = m_masterList[m_current]; } m_mutex.Release(); return result; } Error PlaylistManager::SetCurrentIndex(uint32 index) { Error result = kError_InvalidParam; m_mutex.Acquire(); index = CheckIndex(index); if(index != kInvalidIndex) { if(m_shuffle) index = InternalIndexOf(&m_shuffleList, ItemAt(index)); InternalSetCurrentIndex(index); result = kError_NoErr; } m_mutex.Release(); return result; } void PlaylistManager::InternalSetCurrentIndex(uint32 index) { m_current = index; if (index != kInvalidIndex) m_context->target->AcceptEvent(new PlaylistCurrentItemInfoEvent(GetCurrentItem(), this)); } uint32 PlaylistManager::GetCurrentIndex() { uint32 result = kInvalidIndex; if(m_masterList.size()) { if(m_shuffle) result = IndexOf(m_shuffleList[m_current]); else result = m_current; } return result; } Error PlaylistManager::GotoNextItem(bool userAction) { m_mutex.Acquire(); Error result = kError_NoErr; uint32 count = m_masterList.size(); uint32 index = m_current; if(count) { if(!(kPlaylistMode_RepeatOne == m_repeatMode) || userAction) { index++; if( (index >= count) && (m_repeatMode == kPlaylistMode_RepeatAll || userAction)) { index = 0; if(m_shuffle) { pointer_to_unary_function lRand = pointer_to_unary_function (my_rand); random_shuffle(m_shuffleList.begin(), m_shuffleList.end(), lRand); } } else if(index >= count) { index = m_current; } } InternalSetCurrentIndex(index); } else { m_current = kInvalidIndex; } m_mutex.Release(); return result; } Error PlaylistManager::GotoPreviousItem(bool userAction) { m_mutex.Acquire(); Error result = kError_NoErr; uint32 count = m_masterList.size(); uint32 index = m_current; if(count) { if(!(kPlaylistMode_RepeatOne == m_repeatMode) || userAction) { if( (index == 0) && (m_repeatMode == kPlaylistMode_RepeatAll || userAction)) { index = count - 1; if(m_shuffle) { pointer_to_unary_function lRand = pointer_to_unary_function (my_rand); random_shuffle(m_shuffleList.begin(), m_shuffleList.end(), lRand); } } else if(index != 0) { index--; } } InternalSetCurrentIndex(index); } else { m_current = kInvalidIndex; } m_mutex.Release(); return result; } bool PlaylistManager::HasAnotherItem() { m_mutex.Acquire(); bool result = false; if(m_repeatMode == kPlaylistMode_RepeatOne || m_repeatMode == kPlaylistMode_RepeatAll) { result = true; } else { result = (m_current < m_masterList.size() - 1); } m_mutex.Release(); return result; } Error PlaylistManager::SetShuffleMode(bool shuffle) { m_mutex.Acquire(); const PlaylistItem* currentItem = GetCurrentItem(); m_shuffle = shuffle; if(shuffle) { pointer_to_unary_function lRand = pointer_to_unary_function (my_rand); random_shuffle(m_shuffleList.begin(), m_shuffleList.end(), lRand); if(currentItem) m_current = InternalIndexOf(&m_shuffleList, currentItem); } else { if(currentItem) m_current = IndexOf(currentItem); } m_context->target->AcceptEvent(new PlaylistShuffleEvent(m_shuffle, this)); m_mutex.Release(); return kError_NoErr; } Error PlaylistManager::SetRepeatMode(RepeatMode mode) { m_repeatMode = mode; m_context->target->AcceptEvent(new PlaylistRepeatEvent(m_repeatMode, this)); return kError_NoErr; } Error PlaylistManager::ToggleRepeatMode() { Error result = kError_NoErr; if(m_repeatMode == kPlaylistMode_RepeatNone) result = SetRepeatMode(kPlaylistMode_RepeatOne); else if(m_repeatMode == kPlaylistMode_RepeatOne) result = SetRepeatMode(kPlaylistMode_RepeatAll); else if(m_repeatMode == kPlaylistMode_RepeatAll) result = SetRepeatMode(kPlaylistMode_RepeatNone); return result; } Error PlaylistManager::ToggleShuffleMode() { return SetShuffleMode(!m_shuffle); } // Functions for adding items to playlist Error PlaylistManager::AddItem(const char* url) { Error result = kError_InvalidParam; assert(url); if(url) { result = ReadPlaylist(url); if(IsError(result)) { result = kError_OutOfMemory; PlaylistItem* item = new PlaylistItem(url); if(item) { result = AddItem(item, true); } } } return result; } Error PlaylistManager::AddItem(const char* url, uint32 index) { Error result = kError_InvalidParam; assert(url); if(url) { result = ReadPlaylist(url); if(IsError(result)) { result = kError_OutOfMemory; PlaylistItem* item = new PlaylistItem(url); if(item) { result = AddItem(item, index, true); } } } return result; } Error PlaylistManager::AddItem(const string& url) { Error result = kError_InvalidParam; result = AddItem(url.c_str()); return result; } Error PlaylistManager::AddItem(const string& url, uint32 index) { Error result = kError_InvalidParam; result = AddItem(url.c_str(), index); return result; } Error PlaylistManager::AddItems(const vector & urls) { Error result = kError_InvalidParam; vector ::const_iterator i; vector list; for(i = urls.begin(); i != urls.end(); i++) { result = ReadPlaylist((*i).c_str()); if(IsError(result)) { result = kError_OutOfMemory; PlaylistItem* item = new PlaylistItem((*i).c_str()); if(item) { list.push_back(item); } } } result = AddItems(&list); return result; } Error PlaylistManager::AddItems(const vector & urls, uint32 index) { Error result = kError_InvalidParam; vector ::const_iterator i; vector list; for(i = urls.begin(); i != urls.end(); i++) { result = ReadPlaylist((*i).c_str()); if(IsError(result)) { result = kError_OutOfMemory; PlaylistItem* item = new PlaylistItem((*i).c_str()); if(item) { list.push_back(item); } } } result = AddItems(&list, index); return result; } Error PlaylistManager::AddItem(PlaylistItem* item, bool queryForMetaData) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(item); if(item) { m_activeList->push_back(item); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { AddItemToShuffleList(item); if(m_current == kInvalidIndex && m_activeList->size()) InternalSetCurrentIndex(0); } if(queryForMetaData) RetrieveMetaData(item); /*UndoAdd* undoItem = new UndoAdd(this, item->URL(), m_activeList->size() - 1); m_undo.AddItem(undoItem);*/ m_context->target->AcceptEvent(new PlaylistItemAddedEvent(item, this)); result = kError_NoErr; } m_mutex.Release(); return result; } Error PlaylistManager::AddItem(PlaylistItem* item, uint32 index, bool queryForMetaData) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(item); if(index > m_activeList->size()) index = m_activeList->size(); //index = CheckIndex(index); if(item && index != kInvalidIndex) { m_activeList->insert(m_activeList->begin() + index,item); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { AddItemToShuffleList(item); if(m_current == kInvalidIndex && m_activeList->size()) InternalSetCurrentIndex(0); else if(index <= m_current) { InternalSetCurrentIndex(m_current + 1); } } if(queryForMetaData) RetrieveMetaData(item); /*UndoAdd* undoItem = new UndoAdd(this, item->URL(), index); m_undo.AddItem(undoItem);*/ m_context->target->AcceptEvent(new PlaylistItemAddedEvent(item, this)); result = kError_NoErr; } m_mutex.Release(); return result; } Error PlaylistManager::AddItems(vector * list, bool queryForMetaData) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(list); if(list) { //uint32 index = m_activeList->size(); m_activeList->insert(m_activeList->end(), list->begin(), list->end()); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { AddItemsToShuffleList(list); if(m_current == kInvalidIndex && m_activeList->size()) InternalSetCurrentIndex(0); } // we need our own vector so user can delete theirs vector * items = new vector ; if(items) { items->insert(items->end(), list->begin(), list->end()); if(queryForMetaData) RetrieveMetaData(items); } /*vector ::iterator i = list->begin(); vector urls; for(; i != list->end(); i++) urls.push_back((*i)->URL()); UndoAddMulti* undoItem = new UndoAddMulti(this, urls, index); m_undo.AddItem(undoItem);*/ //vector ::iterator i = list->begin(); //for(; i != list->end(); i++) //m_context->target->AcceptEvent(new PlaylistItemAddedEvent(*i, this)); m_context->target->AcceptEvent(new PlaylistItemsAddedEvent(list, this)); result = kError_NoErr; } m_mutex.Release(); return result; } Error PlaylistManager::AddItems(vector * list, uint32 index, bool queryForMetaData) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(list); if(index > m_activeList->size()) index = m_activeList->size(); //index = CheckIndex(index); if(list && index != kInvalidIndex) { m_activeList->insert(m_activeList->begin() + index, list->begin(), list->end()); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { AddItemsToShuffleList(list); if(m_current == kInvalidIndex && m_activeList->size()) InternalSetCurrentIndex(0); else if(index <= m_current) { InternalSetCurrentIndex(m_current + list->size()); } } // we need our own vector so user can delete theirs vector * items = new vector ; if(items) { items->insert(items->end(), list->begin(), list->end()); if(queryForMetaData) RetrieveMetaData(items); } /*vector ::iterator i = list->begin(); vector urls; for(; i != list->end(); i++) urls.push_back((*i)->URL()); UndoAddMulti* undoItem = new UndoAddMulti(this, urls, index); m_undo.AddItem(undoItem);*/ //vector ::iterator i = list->begin(); //for(; i != list->end(); i++) // m_context->target->AcceptEvent(new PlaylistItemAddedEvent(*i, this)); m_context->target->AcceptEvent(new PlaylistItemsAddedEvent(list, this)); result = kError_NoErr; } m_mutex.Release(); return result; } // Functions for removing items from playlist Error PlaylistManager::RemoveItem(PlaylistItem* item) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(item); if(item) { result = RemoveItem(IndexOf(item)); } m_mutex.Release(); return result; } Error PlaylistManager::RemoveItem(uint32 index) { Error result = kError_InvalidParam; m_mutex.Acquire(); index = CheckIndex(index); if(index != kInvalidIndex) { const PlaylistItem* currentItem = GetCurrentItem(); PlaylistItem* item = (*m_activeList)[index]; m_activeList->erase(m_activeList->begin() + index); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { int32 shuffleIndex = InternalIndexOf(&m_shuffleList, item); m_shuffleList.erase(m_shuffleList.begin() + shuffleIndex); if(!m_activeList->size()) { m_current = kInvalidIndex; } } /*UndoRemove* undoItem = new UndoRemove(this, item->URL(), index); m_undo.AddItem(undoItem);*/ // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { uint32 newIndex = IndexOf(currentItem); if(newIndex != kInvalidIndex) { InternalSetCurrentIndex(newIndex); } else if(m_current != kInvalidIndex && m_current >= m_activeList->size()) { InternalSetCurrentIndex(m_activeList->size() - 1); } else { InternalSetCurrentIndex(m_current); } } m_context->target->AcceptEvent(new PlaylistItemRemovedEvent(item, index, this)); result = kError_NoErr; } m_mutex.Release(); return result; } Error PlaylistManager::RemoveItems(uint32 index, uint32 count) { Error result = kError_InvalidParam; m_mutex.Acquire(); index = CheckIndex(index); if(index != kInvalidIndex) { uint32 i; const PlaylistItem* currentItem = GetCurrentItem(); /*UndoMultiItem* multiItem= new UndoMultiItem(); for(i = 0; i < count; i++) { PlaylistItem* item = (*m_activeList)[index + i]; UndoRemove* undoItem = new UndoRemove(this, item->URL(), index + i); multiItem->AddItem(undoItem); } m_undo.AddItem(multiItem);*/ for(i = 0; i < count; i++) { PlaylistItem* item = (*m_activeList)[index + i]; if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { int32 shuffleIndex = InternalIndexOf(&m_shuffleList, item); m_shuffleList.erase(m_shuffleList.begin() + shuffleIndex); if(!m_activeList->size()) { m_current = kInvalidIndex; } } // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } m_context->target->AcceptEvent(new PlaylistItemRemovedEvent(item, index + i, this)); } m_activeList->erase(m_activeList->begin() + index, m_activeList->begin() + index + count); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { uint32 newIndex = IndexOf(currentItem); if(newIndex != kInvalidIndex) { InternalSetCurrentIndex(newIndex); } else if(m_current != kInvalidIndex && m_current >= m_activeList->size()) { InternalSetCurrentIndex(m_activeList->size() - 1); } else { InternalSetCurrentIndex(m_current); } } result = kError_NoErr; } m_mutex.Release(); return result; } Error PlaylistManager::RemoveItems(vector * items) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(items); if(items) { uint32 index = 0; uint32 size = 0; PlaylistItem* item = NULL; uint32 oldIndex; const PlaylistItem* currentItem = GetCurrentItem(); size = items->size(); /*vector ::iterator i = items->begin(); UndoMultiItem* multiItem= new UndoMultiItem(); for(; i != items->end(); i++) { UndoRemove* undoItem = new UndoRemove(this, (*i)->URL(), IndexOf(*i)); multiItem->AddItem(undoItem); } m_undo.AddItem(multiItem);*/ for(index = 0; index < size; index++) { item = (*items)[index]; if(item) { oldIndex = IndexOf(item); m_activeList->erase(m_activeList->begin() + oldIndex); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { int32 shuffleIndex = InternalIndexOf(&m_shuffleList, item); m_shuffleList.erase(m_shuffleList.begin() + shuffleIndex); } // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } m_context->target->AcceptEvent(new PlaylistItemRemovedEvent(item, oldIndex, this)); result = kError_NoErr; } } if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { uint32 newIndex = IndexOf(currentItem); if(newIndex != kInvalidIndex) { InternalSetCurrentIndex(newIndex); } else if(m_current != kInvalidIndex && m_current >= m_activeList->size()) { InternalSetCurrentIndex(m_activeList->size() - 1); } else { InternalSetCurrentIndex(m_current); } } } m_mutex.Release(); return result; } Error PlaylistManager::RemoveAll() { Error result = kError_InvalidParam; uint32 index = m_activeList->size() - 1; //uint32 size = m_activeList->size(); PlaylistItem* item = NULL; m_mutex.Acquire(); vector ::reverse_iterator i; for(i = m_activeList->rbegin(); i != m_activeList->rend(); i++, index--) { item = (*i); if(item) { /*UndoRemove* undoItem = new UndoRemove(this, item->URL(), IndexOf(item)); m_undo.AddItem(undoItem);*/ // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } m_context->target->AcceptEvent(new PlaylistItemRemovedEvent(item, index, this)); } } m_activeList->clear(); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { m_shuffleList.clear(); m_current = kInvalidIndex; } result = kError_NoErr; m_mutex.Release(); return result; } // Functions for moving items around Error PlaylistManager::SwapItems(PlaylistItem* item1, PlaylistItem* item2) { return SwapItems(IndexOf(item1), IndexOf(item2)); } Error PlaylistManager::SwapItems(uint32 index1, uint32 index2) { Error result = kError_InvalidParam; m_mutex.Acquire(); index1 = CheckIndex(index1); index2 = CheckIndex(index2); if(index1 != kInvalidIndex && index2 != kInvalidIndex) { PlaylistItem* temp; /*UndoMove* undoItem1 = new UndoMove(this, index2, index1); m_undo.AddItem(undoItem1); UndoMove* undoItem2 = new UndoMove(this, index1, index2); m_undo.AddItem(undoItem2);*/ temp = (*m_activeList)[index1]; (*m_activeList)[index1] = (*m_activeList)[index2]; (*m_activeList)[index2] = temp; m_context->target->AcceptEvent( new PlaylistItemMovedEvent(index1, index2, (*m_activeList)[index2], this)); // we add 1 to index2 bc it was shifted down by the above move m_context->target->AcceptEvent( new PlaylistItemMovedEvent(index2 + 1, index1, (*m_activeList)[index1], this)); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { if(m_current == index1) InternalSetCurrentIndex(index2); else if(m_current == index2) InternalSetCurrentIndex(index1); } result = kError_NoErr; } m_mutex.Release(); return result; } Error PlaylistManager::MoveItem(PlaylistItem* item, uint32 index) { return MoveItem(IndexOf(item), index); } Error PlaylistManager::MoveItem(uint32 oldIndex, uint32 newIndex) { Error result = kError_InvalidParam; m_mutex.Acquire(); oldIndex = CheckIndex(oldIndex); newIndex = CheckIndex(newIndex); if(oldIndex != kInvalidIndex && newIndex != kInvalidIndex) { PlaylistItem* item = (*m_activeList)[oldIndex]; /*UndoMove* undoItem = new UndoMove(this, newIndex, oldIndex); m_undo.AddItem(undoItem);*/ m_activeList->erase(m_activeList->begin() + oldIndex); if(newIndex > m_activeList->size()) newIndex = m_activeList->size(); m_activeList->insert(m_activeList->begin() + newIndex,item); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) { if(oldIndex == m_current) InternalSetCurrentIndex(newIndex); else if(newIndex == m_current) { if(oldIndex > m_current) InternalSetCurrentIndex(m_current + 1); else InternalSetCurrentIndex(m_current - 1); } } m_context->target->AcceptEvent( new PlaylistItemMovedEvent(oldIndex, newIndex, item, this)); result = kError_NoErr; } m_mutex.Release(); return result; } Error PlaylistManager::MoveItems(vector * items, uint32 index) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(items); index = CheckIndex(index); const PlaylistItem* currentItem = GetCurrentItem(); vector oldIndices; if(items) { uint32 size = 0; uint32 i = 0; PlaylistItem* item = NULL; size = items->size(); for(i = 0; i < size; i++) { item = (*items)[i]; if(item) { uint32 oldIndex = IndexOf(item); if(oldIndex < index) index--; oldIndices.push_back(oldIndex); } } for(i = 0; i < size; i++) { item = (*items)[i]; if(item) { m_activeList->erase(m_activeList->begin() + IndexOf(item)); } } if(index >= m_activeList->size()) index = m_activeList->size(); m_activeList->insert(m_activeList->begin() + index, items->begin(), items->end()); if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) InternalSetCurrentIndex(IndexOf(currentItem)); /*UndoMultiItem* multiItem= new UndoMultiItem(); for(i = 0; i < size; i++) { UndoMove* undoItem = new UndoMove(this, index + i, oldIndices[i]); multiItem.AddItem(undoItem); } m_undo.AddItem(multiItem);*/ for(i = 0; i < size; i++) { item = (*items)[i]; if(item) { m_context->target->AcceptEvent( new PlaylistItemMovedEvent(oldIndices[i], index + i, item, this)); } } result = kError_NoErr; } m_mutex.Release(); return result; } Error PlaylistManager::MoveItems(vector * items, uint32 index) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(items); if(items) { vector pl_items; vector ::iterator i; for(i = items->begin(); i != items->end(); i++) { pl_items.push_back(ItemAt(*i)); } result = MoveItems(&pl_items, index); } m_mutex.Release(); return result; } // Functions for updating // This function searches the items in the playlist // and updates the metadata if the tracks are the // same (matched based on URL) Error PlaylistManager::UpdateTrackMetaData(PlaylistItem* updatedTrack) { Error result = kError_InvalidParam; m_mutex.Acquire(); vector ::iterator i = m_activeList->begin(); MetaData metadata = updatedTrack->GetMetaData(); for(; i != m_activeList->end(); i++) { if((*i)->URL() == updatedTrack->URL()) { (*i)->SetMetaData(&metadata); m_context->target->AcceptEvent(new PlaylistItemUpdatedEvent(*i, this)); } } m_mutex.Release(); return result; } // Functions for sorting Error PlaylistManager::Sort(PlaylistSortKey key, PlaylistSortType type) { Error result = kError_InvalidParam; m_mutex.Acquire(); const PlaylistItem* currentItem = GetCurrentItem(); if(currentItem && key >= kPlaylistSortKey_FirstKey && key < kPlaylistSortKey_LastKey) { if(type == kPlaylistSortType_Ascending) stable_sort(m_activeList->begin(), m_activeList->end(), PlaylistItemSort(key)); else stable_sort(m_activeList->begin(), m_activeList->end(), not2(PlaylistItemSort(key))); m_sortKey = key; m_sortType = type; result = kError_NoErr; } else if(currentItem && key == kPlaylistSortKey_Random) { pointer_to_unary_function lRand = pointer_to_unary_function (my_rand); random_shuffle(m_activeList->begin(), m_activeList->end(), lRand); m_sortKey = key; m_sortType = type; result = kError_NoErr; } if(IsntError(result)) { if(kPlaylistKey_MasterPlaylist == GetActivePlaylist()) InternalSetCurrentIndex(IndexOf(currentItem)); m_context->target->AcceptEvent(new PlaylistSortedEvent(key, this)); //m_undo.Clear(); } m_mutex.Release(); return result; } PlaylistSortKey PlaylistManager::GetPlaylistSortKey() const { return m_sortKey; } PlaylistSortType PlaylistManager::GetPlaylistSortType() const { return m_sortType; } // Which playlist are we dealing with for purposes of editing: // 1) Master Playlist - list of songs to play // 2) Secondary Playlist - a playlist that we want to edit // - External playlist // - Portable playlist Error PlaylistManager::SetActivePlaylist(PlaylistKey key) { Error result = kError_InvalidParam; m_mutex.Acquire(); if(key >= kPlaylistKey_FirstKey && key < kPlaylistKey_LastKey) { m_playlistKey = key; result = kError_NoErr; switch(key) { case kPlaylistKey_MasterPlaylist: { m_activeList = &m_masterList; break; } case kPlaylistKey_ExternalPlaylist: { m_activeList = &m_externalList; break; } case kPlaylistKey_PortablePlaylist: { //m_activeList = &m_portableList; m_activeList = &m_externalList; break; } default: { result = kError_InvalidParam; } } } m_mutex.Release(); return result; } PlaylistKey PlaylistManager::GetActivePlaylist() const { return m_playlistKey; } Error PlaylistManager::SetExternalPlaylist(char* url) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(url); if(url) { // first delete old playlist uint32 index = 0; uint32 numItems = 0; PlaylistItem* item = NULL; numItems = m_externalList.size(); for(index = 0; index < numItems; index++) { item = m_externalList[index]; if(item) { // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } } } m_externalList.clear(); result = ReadPlaylist(url, &m_externalList); vector * items = new vector ; items->insert(items->end(), m_externalList.begin(), m_externalList.end()); RetrieveMetaData(items); } m_mutex.Release(); return result; } Error PlaylistManager::GetExternalPlaylist(char* url, uint32* length) { Error result = kError_InvalidParam; m_mutex.Acquire(); assert(url); assert(length); if(url && length) { if(*length >= m_externalPlaylist.size() + 1) { strcpy(url, m_externalPlaylist.c_str()); result = kError_NoErr; } else { result = kError_BufferTooSmall; } *length = m_externalPlaylist.size() + 1; } m_mutex.Release(); return result; } Error PlaylistManager::SetPortablePlaylist(DeviceInfo* device, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(device); if(device) { result = ReadPortablePlaylist(device, NULL, function, cookie); m_portableDevice = *device; } return result; } Error PlaylistManager::GetPortablePlaylist(DeviceInfo* device) { Error result = kError_InvalidParam; assert(device); if(device) { result = kError_NoErr; *device = m_portableDevice; } return result; } // External playlist support bool PlaylistManager::IsSupportedPlaylistFormat(const char *extension) { bool retvalue = false; uint32 numFormats = m_playlistFormats.size(); for(uint32 index = 0; index < numFormats; index++) { PlaylistFormatInfo* format = m_playlistFormats[index]; if(!strcasecmp(extension, format->GetExtension())) { retvalue = true; break; } } return retvalue; } Error PlaylistManager::GetSupportedPlaylistFormats(PlaylistFormatInfo* format, uint32 index) { Error result = kError_InvalidParam; assert(format); if(format) { result = kError_NoMoreFormats; uint32 numFormats = m_playlistFormats.size(); if(index < numFormats) { result = kError_NoErr; *format = *m_playlistFormats[index]; } } return result; } Error PlaylistManager::ReadPlaylist(const char* url, vector * items, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(url); if(url) { // find a suitable plugin result = kError_FormatNotSupported; char* extension = strrchr(url, '.'); if(extension) { extension++; uint32 numFormats = m_playlistFormats.size(); for(uint32 index = 0; index < numFormats; index++) { PlaylistFormatInfo* format = m_playlistFormats[index]; if(!strcasecmp(extension, format->GetExtension())) { bool addToActiveList = false; if(!items) { items = new vector ; addToActiveList = true; } result = format->GetRef()->ReadPlaylist(url, items, function, cookie); if(addToActiveList) { AddItems(items); delete items; } break; } } } } return result; } Error PlaylistManager::WritePlaylist(const char* url, PlaylistFormatInfo* format, vector * items, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(url); assert(format); if(url && format) { if(!items) { items = m_activeList; } result = format->GetRef()->WritePlaylist(url, format, items, function, cookie); } return result; } Error PlaylistManager::WritePlaylist(const char* url, vector * items, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(url); if(url) { // find a suitable plugin result = kError_FormatNotSupported; char* extension = strrchr(url, '.'); if(extension) { extension++; uint32 numFormats = m_playlistFormats.size(); for(uint32 index = 0; index < numFormats; index++) { PlaylistFormatInfo* format = m_playlistFormats[index]; if(!strcasecmp(extension, format->GetExtension())) { if(!items) { items = m_activeList; } result = format->GetRef()->WritePlaylist(url, format, items, function, cookie); break; } } } } return result; } // Portable player communication Error PlaylistManager::GetSupportedPortables(DeviceInfo* device, uint32 index) { Error result = kError_InvalidParam; assert(device); if(device) { result = kError_NoMoreDevices; uint32 numDevices = m_portablePlayers.size(); if(index < numDevices) { result = kError_NoErr; *device = *m_portablePlayers[index]; } } return result; } bool PlaylistManager::IsPortableAvailable(DeviceInfo* device) { bool result = false; assert(device); if(device) { result = device->GetRef()->IsDeviceAvailable(device); } return result; } Error PlaylistManager::GetDeviceInfo(DeviceInfo* device) { Error result = kError_InvalidParam; assert(device); if(device) { result = device->GetRef()->GetDeviceInfo(device); } return result; } Error PlaylistManager::InitializeDevice(DeviceInfo* device, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(device); if(device) { result = device->GetRef()->InitializeDevice(device, function, cookie); } return result; } Error PlaylistManager::ReadPortablePlaylist(DeviceInfo* device, vector * items, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(device); if(device) { // first delete old playlist if(!items) { uint32 index = 0; uint32 numItems = 0; PlaylistItem* item = NULL; numItems = m_externalList.size(); for(index = 0; index < numItems; index++) { item = m_externalList[index]; if(item) { // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } } } } bool addToActiveList = false; if(!items) { items = new vector ; addToActiveList = true; } result = device->GetRef()->ReadPlaylist(device, items, function, cookie); if(addToActiveList) { AddItems(items); delete items; } } return result; } #if 0 Error PlaylistManager::ReadPortablePlaylist(DeviceInfo* device, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(device); if(device) { // first delete old playlist uint32 index = 0; uint32 numItems = 0; PlaylistItem* item = NULL; numItems = m_portableList.size(); for(index = 0; index < numItems; index++) { item = m_portableList[index]; if(item) { // if the metadata thread is still accessing this item // we don't wanna delete the item out from under it. // instead we set a flag and let the metadata thread // clean up when it returns. if(item->GetState() == kPlaylistItemState_RetrievingMetaData) { item->SetState(kPlaylistItemState_Delete); } else { delete item; } } } m_portableList.clear(); result = device->GetRef()->ReadPlaylist(device, &m_portableList, function, cookie); vector ::iterator i = m_portableList.begin(); for(; i != m_portableList.end(); i++) m_context->target->AcceptEvent(new PlaylistItemAddedEvent(*i, this)); } return result; } #endif Error PlaylistManager::SyncPortablePlaylist(DeviceInfo* device, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(device); if(device) { result = device->GetRef()->WritePlaylist(device, &m_externalList, function, cookie); } return result; } Error PlaylistManager::DownloadItemFromPortable(DeviceInfo* device, PlaylistItem* item, const char* url, PLMCallBackFunction function, void* cookie) { Error result = kError_InvalidParam; assert(device); assert(item); assert(url); if(device && item && url) { result = device->GetRef()->DownloadSong(device, item, url, function, cookie); } return result; } // Utility Functions bool PlaylistManager::IsEmpty() { bool result; m_mutex.Acquire(); result = m_activeList->empty(); m_mutex.Release(); return result; } uint32 PlaylistManager::CountItems() { uint32 result; m_mutex.Acquire(); result = m_activeList->size(); m_mutex.Release(); return result; } PlaylistItem* PlaylistManager::ItemAt(uint32 index) { PlaylistItem* result = NULL; m_mutex.Acquire(); index = CheckIndex(index); if(index != kInvalidIndex) { result = (*m_activeList)[index]; } m_mutex.Release(); return result; } uint32 PlaylistManager::IndexOf(const PlaylistItem* item) { return InternalIndexOf(m_activeList, item); } uint32 PlaylistManager::InternalIndexOf(vector * list, const PlaylistItem* item) { uint32 result = kInvalidIndex; uint32 index = 0; uint32 size = 0; assert(list); assert(item); if(list && item) { vector ::iterator i = list->begin(); size = list->size(); for(index = 0; i != list->end();i++, index++) { if(item == (*i)) { result = index; break; } } } return result; } bool PlaylistManager::HasItem(const PlaylistItem* item) { return (IndexOf(item) != kInvalidIndex); } bool PlaylistManager::CanUndo() { m_mutex.Acquire(); bool result = m_undo.CanUndo(); m_mutex.Release(); return result; } bool PlaylistManager::CanRedo() { m_mutex.Acquire(); bool result = m_undo.CanRedo(); m_mutex.Release(); return result; } void PlaylistManager::Undo() { m_mutex.Acquire(); m_undo.Undo(); m_mutex.Release(); } void PlaylistManager::Redo() { m_mutex.Acquire(); m_undo.Redo(); m_mutex.Release(); } // Internal functions inline uint32 PlaylistManager::CheckIndex(uint32 index) { // If we're dealing with a bogus index then set it to -1. if(index >= CountItems()) { index = kInvalidIndex; } return index; } void PlaylistManager::AddItemToShuffleList(PlaylistItem* item) { m_shuffleList.push_back(item); } void PlaylistManager::AddItemsToShuffleList(vector * list) { pointer_to_unary_function lRand = pointer_to_unary_function (my_rand); random_shuffle(list->begin(), list->end(), lRand); m_shuffleList.insert(m_shuffleList.end(), list->begin(), list->end()); } void PlaylistManager:: MetaDataThreadFunction(vector * list) { assert(list); if(list) { uint32 index = 0; uint32 numItems = 0; PlaylistItem* item = NULL; numItems = list->size(); for(index = 0; index < numItems; index++) { item = (*list)[index]; if(item) { MetaData metadata; MetaData* dbData = NULL; // is there any metadata stored for this item in the music db? dbData = m_context->catalog->ReadMetaDataFromDatabase(item->URL().c_str()); if(dbData) { metadata = *dbData; delete dbData; } else // if not look to see if the file contains metadata { metadata = item->GetMetaData(); MetaDataFormat* mdf = NULL; uint32 numFormats; numFormats = m_metadataFormats.size(); for(uint32 i = 0; i < numFormats; i++) { mdf = m_metadataFormats[i]; if(mdf) { mdf->ReadMetaData(item->URL().c_str(), &metadata); } } } // check to see if the song length is unknown // and if so calculate it. if(metadata.Time() == 0) { } m_mutex.Acquire(); if(item->GetState() == kPlaylistItemState_Delete) { delete item; } else { item->SetMetaData(&metadata); item->SetState(kPlaylistItemState_Normal); m_context->target->AcceptEvent(new PlaylistItemUpdatedEvent(item, this)); } m_mutex.Release(); } } delete list; } } typedef struct MetaDataThreadStruct{ PlaylistManager* plm; vector * items; Thread* thread; } MetaDataThreadStruct; void PlaylistManager:: metadata_thread_function(void* arg) { MetaDataThreadStruct* mts = (MetaDataThreadStruct*)arg; mts->plm->MetaDataThreadFunction(mts->items); delete mts->thread; delete mts; } void PlaylistManager::RetrieveMetaData(PlaylistItem* item) { assert(item); if(item) { vector * items = new vector ; if(items) { items->push_back(item); RetrieveMetaData(items); } } } void PlaylistManager::RetrieveMetaData(vector * list) { uint32 index = 0; uint32 size = 0; PlaylistItem* item = NULL; assert(list); if(list) { size = list->size(); for(index = 0; index < size; index++) { item = (*list)[index]; if(item && item->GetState() == kPlaylistItemState_Normal) { item->SetState(kPlaylistItemState_RetrievingMetaData); } } Thread* thread = Thread::CreateThread(); if(thread) { MetaDataThreadStruct* mts = new MetaDataThreadStruct; mts->plm = this; mts->items = list; mts->thread = thread; thread->Create(metadata_thread_function, mts); } } } UndoAdd::UndoAdd(PlaylistManager* plm, const string& url, uint32 index): m_plm(plm), m_url(url), m_index(index) { } UndoAdd::~UndoAdd() { } void UndoAdd::Undo() { m_plm->RemoveItem(m_index); } void UndoAdd::Redo() { m_plm->AddItem(m_url, m_index); } UndoAddMulti::UndoAddMulti(PlaylistManager* plm, vector & urls, uint32 index): m_plm(plm), m_urls(urls), m_index(index) { } UndoAddMulti::~UndoAddMulti() { } void UndoAddMulti::Undo() { m_plm->RemoveItems(m_index, m_urls.size()); } void UndoAddMulti::Redo() { m_plm->AddItems(m_urls, m_index); } UndoRemove::UndoRemove(PlaylistManager* plm, const string& url, uint32 index): m_plm(plm), m_url(url), m_index(index) { } UndoRemove::~UndoRemove() { } void UndoRemove::Undo() { m_plm->AddItem(m_url, m_index); } void UndoRemove::Redo() { m_plm->RemoveItem(m_index); } UndoMove::UndoMove(PlaylistManager* plm, uint32 newIndex, uint32 oldIndex): m_plm(plm), m_newIndex(newIndex), m_oldIndex(oldIndex) { } UndoMove::~UndoMove() { } void UndoMove::Undo() { m_plm->MoveItem(m_newIndex, m_oldIndex); } void UndoMove::Redo() { m_plm->MoveItem(m_oldIndex, m_newIndex); }