From 415ef601dfd3c244a31c1e9d60eb1689da854e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20A=C3=9Fmus?= Date: Mon, 4 Jun 2007 01:00:03 +0000 Subject: [PATCH] * probably fixed the build, did I forget some files before? * implemented full undo/redo for any playlist operations git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21317 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/mediaplayer/Jamfile | 17 +- .../playlist/CopyPLItemsCommand.cpp | 116 +++++ .../mediaplayer/playlist/CopyPLItemsCommand.h | 39 ++ .../playlist/ImportPLItemsCommand.cpp | 156 ++++++ .../playlist/ImportPLItemsCommand.h | 41 ++ .../mediaplayer/{ => playlist}/ListViews.cpp | 0 .../mediaplayer/{ => playlist}/ListViews.h | 0 .../playlist/MovePLItemsCommand.cpp | 181 +++++++ .../mediaplayer/playlist/MovePLItemsCommand.h | 40 ++ .../mediaplayer/{ => playlist}/Playlist.cpp | 2 +- .../mediaplayer/{ => playlist}/Playlist.h | 2 +- .../{ => playlist}/PlaylistListView.cpp | 90 +--- .../{ => playlist}/PlaylistListView.h | 6 +- .../{ => playlist}/PlaylistObserver.cpp | 0 .../{ => playlist}/PlaylistObserver.h | 0 .../{ => playlist}/PlaylistWindow.cpp | 3 +- .../{ => playlist}/PlaylistWindow.h | 0 .../playlist/RemovePLItemsCommand.cpp | 125 +++++ .../playlist/RemovePLItemsCommand.h | 38 ++ src/apps/mediaplayer/support/AutoLocker.h | 151 ++++++ src/apps/mediaplayer/support/RWLocker.cpp | 471 ++++++++++++++++++ src/apps/mediaplayer/support/RWLocker.h | 137 +++++ 22 files changed, 1532 insertions(+), 83 deletions(-) create mode 100644 src/apps/mediaplayer/playlist/CopyPLItemsCommand.cpp create mode 100644 src/apps/mediaplayer/playlist/CopyPLItemsCommand.h create mode 100644 src/apps/mediaplayer/playlist/ImportPLItemsCommand.cpp create mode 100644 src/apps/mediaplayer/playlist/ImportPLItemsCommand.h rename src/apps/mediaplayer/{ => playlist}/ListViews.cpp (100%) rename src/apps/mediaplayer/{ => playlist}/ListViews.h (100%) create mode 100644 src/apps/mediaplayer/playlist/MovePLItemsCommand.cpp create mode 100644 src/apps/mediaplayer/playlist/MovePLItemsCommand.h rename src/apps/mediaplayer/{ => playlist}/Playlist.cpp (98%) rename src/apps/mediaplayer/{ => playlist}/Playlist.h (97%) rename src/apps/mediaplayer/{ => playlist}/PlaylistListView.cpp (83%) rename src/apps/mediaplayer/{ => playlist}/PlaylistListView.h (93%) rename src/apps/mediaplayer/{ => playlist}/PlaylistObserver.cpp (100%) rename src/apps/mediaplayer/{ => playlist}/PlaylistObserver.h (100%) rename src/apps/mediaplayer/{ => playlist}/PlaylistWindow.cpp (97%) rename src/apps/mediaplayer/{ => playlist}/PlaylistWindow.h (100%) create mode 100644 src/apps/mediaplayer/playlist/RemovePLItemsCommand.cpp create mode 100644 src/apps/mediaplayer/playlist/RemovePLItemsCommand.h create mode 100644 src/apps/mediaplayer/support/AutoLocker.h create mode 100644 src/apps/mediaplayer/support/RWLocker.cpp create mode 100644 src/apps/mediaplayer/support/RWLocker.h diff --git a/src/apps/mediaplayer/Jamfile b/src/apps/mediaplayer/Jamfile index 0a5f20efe8..8d0f312661 100644 --- a/src/apps/mediaplayer/Jamfile +++ b/src/apps/mediaplayer/Jamfile @@ -6,6 +6,7 @@ AddSubDirSupportedPlatforms libbe_test ; # source directories local sourceDirs = + playlist supplier support ; @@ -16,6 +17,17 @@ for sourceDir in $(sourceDirs) { } Application MediaPlayer : + # playlist + CopyPLItemsCommand.cpp + ImportPLItemsCommand.cpp + ListViews.cpp + MovePLItemsCommand.cpp + Playlist.cpp + PlaylistListView.cpp + PlaylistObserver.cpp + PlaylistWindow.cpp + RemovePLItemsCommand.cpp + # supplier AudioSupplier.cpp MediaTrackAudioSupplier.cpp @@ -37,13 +49,8 @@ Application MediaPlayer : ControllerView.cpp DrawingTidbits.cpp InfoWin.cpp - ListViews.cpp MainApp.cpp MainWin.cpp - Playlist.cpp - PlaylistListView.cpp - PlaylistObserver.cpp - PlaylistWindow.cpp SoundOutput.cpp TransportButton.cpp TransportControlGroup.cpp diff --git a/src/apps/mediaplayer/playlist/CopyPLItemsCommand.cpp b/src/apps/mediaplayer/playlist/CopyPLItemsCommand.cpp new file mode 100644 index 0000000000..f3ca795742 --- /dev/null +++ b/src/apps/mediaplayer/playlist/CopyPLItemsCommand.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "CopyPLItemsCommand.h" + +#include +#include + +#include + +#include "Playlist.h" + + +using std::nothrow; + + +CopyPLItemsCommand::CopyPLItemsCommand(Playlist* playlist, + const int32* indices, int32 count, int32 toIndex) + : Command() + , fPlaylist(playlist) + , fRefs(count > 0 ? new (nothrow) entry_ref[count] : NULL) + , fToIndex(toIndex) + , fCount(count) +{ + if (!indices || !fPlaylist || !fRefs) { + // indicate a bad object state + delete[] fRefs; + fRefs = NULL; + return; + } + + // init original entries and + for (int32 i = 0; i < fCount; i++) { + if (fPlaylist->GetRefAt(indices[i], &fRefs[i]) < B_OK) { + delete[] fRefs; + fRefs = NULL; + return; + } + } +} + + +CopyPLItemsCommand::~CopyPLItemsCommand() +{ + delete[] fRefs; +} + + +status_t +CopyPLItemsCommand::InitCheck() +{ + if (!fPlaylist || !fRefs) + return B_NO_INIT; + return B_OK; +} + + +status_t +CopyPLItemsCommand::Perform() +{ + BAutolock _(fPlaylist); + + status_t ret = B_OK; + + // add refs to playlist at the insertion index + int32 index = fToIndex; + for (int32 i = 0; i < fCount; i++) { + if (!fPlaylist->AddRef(fRefs[i], index++)) { + ret = B_NO_MEMORY; + break; + } + } + if (ret < B_OK) + return ret; + + return B_OK; +} + + +status_t +CopyPLItemsCommand::Undo() +{ + BAutolock _(fPlaylist); + + // remember currently playling ref in case we copy items over it + entry_ref currentRef; + bool adjustCurrentRef = fPlaylist->GetRefAt(fPlaylist->CurrentRefIndex(), + ¤tRef) == B_OK; + + // remove refs from playlist + int32 index = fToIndex; + for (int32 i = 0; i < fCount; i++) { + fPlaylist->RemoveRef(index++, false); + } + + // take care about currently played ref + if (adjustCurrentRef) + fPlaylist->SetCurrentRefIndex(fPlaylist->IndexOf(currentRef)); + + return B_OK; +} + + +void +CopyPLItemsCommand::GetName(BString& name) +{ + if (fCount > 1) + name << "Copy Entries"; + else + name << "Copy Entry"; +} diff --git a/src/apps/mediaplayer/playlist/CopyPLItemsCommand.h b/src/apps/mediaplayer/playlist/CopyPLItemsCommand.h new file mode 100644 index 0000000000..e943c916ed --- /dev/null +++ b/src/apps/mediaplayer/playlist/CopyPLItemsCommand.h @@ -0,0 +1,39 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef COPY_PL_ITEMS_COMMAND_H +#define COPY_PL_ITEMS_COMMAND_H + + +#include "Command.h" + +class Playlist; + +class CopyPLItemsCommand : public Command { + public: + CopyPLItemsCommand( + Playlist* playlist, + const int32* indices, + int32 count, + int32 toIndex); + virtual ~CopyPLItemsCommand(); + + virtual status_t InitCheck(); + + virtual status_t Perform(); + virtual status_t Undo(); + + virtual void GetName(BString& name); + + private: + Playlist* fPlaylist; + entry_ref* fRefs; + int32 fToIndex; + int32 fCount; +}; + +#endif // COPY_PL_ITEMS_COMMAND_H diff --git a/src/apps/mediaplayer/playlist/ImportPLItemsCommand.cpp b/src/apps/mediaplayer/playlist/ImportPLItemsCommand.cpp new file mode 100644 index 0000000000..c1e7a26103 --- /dev/null +++ b/src/apps/mediaplayer/playlist/ImportPLItemsCommand.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "ImportPLItemsCommand.h" + +#include +#include + +#include + +#include "Playlist.h" + + +using std::nothrow; + + +ImportPLItemsCommand::ImportPLItemsCommand(Playlist* playlist, + const BMessage* refsMessage, int32 toIndex) + : Command() + , fPlaylist(playlist) + + , fOldRefs(NULL) + , fOldCount(0) + + , fNewRefs(NULL) + , fNewCount(0) + + , fToIndex(toIndex) +{ + if (!fPlaylist) + return; + + Playlist temp; + temp.AppendRefs(refsMessage); + + fNewCount = temp.CountItems(); + if (fNewCount <= 0) + return; + + fNewRefs = new (nothrow) entry_ref[fNewCount]; + if (!fNewRefs) + return; + + // init new entries + for (int32 i = 0; i < fNewCount; i++) { + if (temp.GetRefAt(i, &fNewRefs[i]) < B_OK) { + delete[] fNewRefs; + fNewRefs = NULL; + return; + } + } + + if (fToIndex < 0) { + fOldCount = fPlaylist->CountItems(); + if (fOldCount > 0) { + fOldRefs = new (nothrow) entry_ref[fOldCount]; + if (!fOldRefs) { + // indicate bad object init + delete[] fNewRefs; + fNewRefs = NULL; + return; + } + } + } + + for (int32 i = 0; i < fOldCount; i++) { + if (fPlaylist->GetRefAt(i, &fOldRefs[i]) < B_OK) { + // indicate bad object init + delete[] fNewRefs; + fNewRefs = NULL; + return; + } + } +} + + +ImportPLItemsCommand::~ImportPLItemsCommand() +{ + delete[] fOldRefs; + delete[] fNewRefs; +} + + +status_t +ImportPLItemsCommand::InitCheck() +{ + if (!fPlaylist || !fNewRefs) + return B_NO_INIT; + return B_OK; +} + + +status_t +ImportPLItemsCommand::Perform() +{ + BAutolock _(fPlaylist); + + int32 index = fToIndex; + if (fToIndex < 0) { + fPlaylist->MakeEmpty(); + index = 0; + } + + bool startPlaying = fPlaylist->CountItems() == 0; + + // add refs to playlist at the insertion index + for (int32 i = 0; i < fNewCount; i++) { + if (!fPlaylist->AddRef(fNewRefs[i], index++)) + return B_NO_MEMORY; + } + + if (startPlaying) { + // open first file + fPlaylist->SetCurrentRefIndex(0); + } + + return B_OK; +} + + +status_t +ImportPLItemsCommand::Undo() +{ + BAutolock _(fPlaylist); + + if (fToIndex < 0) { + // remove new refs from playlist and restore old refs + fPlaylist->MakeEmpty(); + for (int32 i = 0; i < fOldCount; i++) { + if (!fPlaylist->AddRef(fOldRefs[i], i)) + return B_NO_MEMORY; + } + } else { + // remove refs from playlist + for (int32 i = 0; i < fNewCount; i++) { + fPlaylist->RemoveRef(fToIndex); + } + } + + return B_OK; +} + + +void +ImportPLItemsCommand::GetName(BString& name) +{ + if (fNewCount > 1) + name << "Import Entries"; + else + name << "Import Entry"; +} diff --git a/src/apps/mediaplayer/playlist/ImportPLItemsCommand.h b/src/apps/mediaplayer/playlist/ImportPLItemsCommand.h new file mode 100644 index 0000000000..6f15b62922 --- /dev/null +++ b/src/apps/mediaplayer/playlist/ImportPLItemsCommand.h @@ -0,0 +1,41 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef IMPORT_PL_ITEMS_COMMAND_H +#define IMPORT_PL_ITEMS_COMMAND_H + + +#include "Command.h" + +class BMessage; +class Playlist; + +class ImportPLItemsCommand : public Command { + public: + ImportPLItemsCommand( + Playlist* playlist, + const BMessage* refsMessage, + int32 toIndex); + virtual ~ImportPLItemsCommand(); + + virtual status_t InitCheck(); + + virtual status_t Perform(); + virtual status_t Undo(); + + virtual void GetName(BString& name); + + private: + Playlist* fPlaylist; + entry_ref* fOldRefs; + int32 fOldCount; + entry_ref* fNewRefs; + int32 fNewCount; + int32 fToIndex; +}; + +#endif // IMPORT_PL_ITEMS_COMMAND_H diff --git a/src/apps/mediaplayer/ListViews.cpp b/src/apps/mediaplayer/playlist/ListViews.cpp similarity index 100% rename from src/apps/mediaplayer/ListViews.cpp rename to src/apps/mediaplayer/playlist/ListViews.cpp diff --git a/src/apps/mediaplayer/ListViews.h b/src/apps/mediaplayer/playlist/ListViews.h similarity index 100% rename from src/apps/mediaplayer/ListViews.h rename to src/apps/mediaplayer/playlist/ListViews.h diff --git a/src/apps/mediaplayer/playlist/MovePLItemsCommand.cpp b/src/apps/mediaplayer/playlist/MovePLItemsCommand.cpp new file mode 100644 index 0000000000..284d8124a8 --- /dev/null +++ b/src/apps/mediaplayer/playlist/MovePLItemsCommand.cpp @@ -0,0 +1,181 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "MovePLItemsCommand.h" + +#include +#include + +#include + +#include "Playlist.h" + + +using std::nothrow; + + +MovePLItemsCommand::MovePLItemsCommand(Playlist* playlist, + const int32* indices, int32 count, int32 toIndex) + : Command() + , fPlaylist(playlist) + , fRefs(count > 0 ? new (nothrow) entry_ref[count] : NULL) + , fIndices(count > 0 ? new (nothrow) int32[count] : NULL) + , fToIndex(toIndex) + , fCount(count) +{ + if (!indices || !fPlaylist || !fRefs || !fIndices) { + // indicate a bad object state + delete[] fRefs; + fRefs = NULL; + return; + } + + memcpy(fIndices, indices, fCount * sizeof(int32)); + + // init original entry indices and + // adjust toIndex compensating for items that + // are removed before that index + int32 itemsBeforeIndex = 0; + for (int32 i = 0; i < fCount; i++) { + if (fPlaylist->GetRefAt(fIndices[i], &fRefs[i]) < B_OK) { + delete[] fRefs; + fRefs = NULL; + return; + } + if (fIndices[i] < fToIndex) + itemsBeforeIndex++; + } + fToIndex -= itemsBeforeIndex; +} + + +MovePLItemsCommand::~MovePLItemsCommand() +{ + delete[] fRefs; + delete[] fIndices; +} + + +status_t +MovePLItemsCommand::InitCheck() +{ + if (!fPlaylist || !fRefs || !fIndices) + return B_NO_INIT; + + // analyse the move, don't return B_OK in case + // the container state does not change... + + int32 index = fIndices[0]; + // NOTE: fIndices == NULL if fCount < 1 + + if (index != fToIndex) { + // a change is guaranteed + return B_OK; + } + + // the insertion index is the same as the index of the first + // moved item, a change only occures if the indices of the + // moved items is not contiguous + bool isContiguous = true; + for (int32 i = 1; i < fCount; i++) { + if (fIndices[i] != index + 1) { + isContiguous = false; + break; + } + index = fIndices[i]; + } + if (isContiguous) { + // the container state will not change because of the move + return B_ERROR; + } + + return B_OK; +} + + +status_t +MovePLItemsCommand::Perform() +{ + BAutolock _(fPlaylist); + + status_t ret = B_OK; + + // remember currently playling ref in case we move it + entry_ref currentRef; + bool adjustCurrentRef = fPlaylist->GetRefAt(fPlaylist->CurrentRefIndex(), + ¤tRef) == B_OK; + + // remove refs from playlist + for (int32 i = 0; i < fCount; i++) { + // "- i" to account for the items already removed + fPlaylist->RemoveRef(fIndices[i] - i, false); + } + + // add refs to playlist at the insertion index + int32 index = fToIndex; + for (int32 i = 0; i < fCount; i++) { + if (!fPlaylist->AddRef(fRefs[i], index++)) { + ret = B_NO_MEMORY; + break; + } + } + if (ret < B_OK) + return ret; + + // take care about currently played ref + if (adjustCurrentRef) + fPlaylist->SetCurrentRefIndex(fPlaylist->IndexOf(currentRef)); + + return B_OK; +} + + +status_t +MovePLItemsCommand::Undo() +{ + BAutolock _(fPlaylist); + + status_t ret = B_OK; + + // remember currently playling ref in case we move it + entry_ref currentRef; + bool adjustCurrentRef = fPlaylist->GetRefAt(fPlaylist->CurrentRefIndex(), + ¤tRef) == B_OK; + + // remove refs from playlist + int32 index = fToIndex; + for (int32 i = 0; i < fCount; i++) { + fPlaylist->RemoveRef(index++, false); + } + + // add ref to playlist at remembered indices + for (int32 i = 0; i < fCount; i++) { + if (!fPlaylist->AddRef(fRefs[i], fIndices[i])) { + ret = B_NO_MEMORY; + break; + } + } + if (ret < B_OK) + return ret; + + // take care about currently played ref + if (adjustCurrentRef) + fPlaylist->SetCurrentRefIndex(fPlaylist->IndexOf(currentRef)); + + return B_OK; +} + + +void +MovePLItemsCommand::GetName(BString& name) +{ + if (fCount > 1) + name << "Move Entries"; + else + name << "Move Entry"; +} diff --git a/src/apps/mediaplayer/playlist/MovePLItemsCommand.h b/src/apps/mediaplayer/playlist/MovePLItemsCommand.h new file mode 100644 index 0000000000..ce3f460180 --- /dev/null +++ b/src/apps/mediaplayer/playlist/MovePLItemsCommand.h @@ -0,0 +1,40 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef MOVE_PL_ITEMS_COMMAND_H +#define MOVE_PL_ITEMS_COMMAND_H + + +#include "Command.h" + +class Playlist; + +class MovePLItemsCommand : public Command { + public: + MovePLItemsCommand( + Playlist* playlist, + const int32* indices, + int32 count, + int32 toIndex); + virtual ~MovePLItemsCommand(); + + virtual status_t InitCheck(); + + virtual status_t Perform(); + virtual status_t Undo(); + + virtual void GetName(BString& name); + + private: + Playlist* fPlaylist; + entry_ref* fRefs; + int32* fIndices; + int32 fToIndex; + int32 fCount; +}; + +#endif // MOVE_PL_ITEMS_COMMAND_H diff --git a/src/apps/mediaplayer/Playlist.cpp b/src/apps/mediaplayer/playlist/Playlist.cpp similarity index 98% rename from src/apps/mediaplayer/Playlist.cpp rename to src/apps/mediaplayer/playlist/Playlist.cpp index 1552beb588..2e59f6fe02 100644 --- a/src/apps/mediaplayer/Playlist.cpp +++ b/src/apps/mediaplayer/playlist/Playlist.cpp @@ -260,7 +260,7 @@ Playlist::RemoveListener(Listener* listener) void -Playlist::AppendRefs(BMessage* refsReceivedMessage, int32 appendIndex) +Playlist::AppendRefs(const BMessage* refsReceivedMessage, int32 appendIndex) { // the playlist ist replaced by the refs in the message // or the refs are appended at the appendIndex diff --git a/src/apps/mediaplayer/Playlist.h b/src/apps/mediaplayer/playlist/Playlist.h similarity index 97% rename from src/apps/mediaplayer/Playlist.h rename to src/apps/mediaplayer/playlist/Playlist.h index 5ca176629b..2c4522d61e 100644 --- a/src/apps/mediaplayer/Playlist.h +++ b/src/apps/mediaplayer/playlist/Playlist.h @@ -74,7 +74,7 @@ public: void RemoveListener(Listener* listener); // support functions - void AppendRefs(BMessage* refsReceivedMessage, + void AppendRefs(const BMessage* refsReceivedMessage, int32 appendIndex = -1); static void AppendToPlaylistRecursive(const entry_ref& ref, Playlist* playlist); diff --git a/src/apps/mediaplayer/PlaylistListView.cpp b/src/apps/mediaplayer/playlist/PlaylistListView.cpp similarity index 83% rename from src/apps/mediaplayer/PlaylistListView.cpp rename to src/apps/mediaplayer/playlist/PlaylistListView.cpp index 7aa618a53d..621cf1dfae 100644 --- a/src/apps/mediaplayer/PlaylistListView.cpp +++ b/src/apps/mediaplayer/playlist/PlaylistListView.cpp @@ -16,12 +16,17 @@ #include #include +#include "CommandStack.h" #include "Controller.h" #include "ControllerObserver.h" +#include "CopyPLItemsCommand.h" +#include "ImportPLItemsCommand.h" #include "ListViews.h" +#include "MovePLItemsCommand.h" #include "PlaybackState.h" #include "Playlist.h" #include "PlaylistObserver.h" +#include "RemovePLItemsCommand.h" using std::nothrow; @@ -169,7 +174,7 @@ PlaylistItem::Draw(BView* owner, BRect frame, const font_height& fh, PlaylistListView::PlaylistListView(BRect frame, Playlist* playlist, - Controller* controller) + Controller* controller, CommandStack* stack) : SimpleListView(frame, "playlist listview", NULL) , fPlaylist(playlist) @@ -179,6 +184,8 @@ PlaylistListView::PlaylistListView(BRect frame, Playlist* playlist, , fControllerObserver(new ControllerObserver(this, OBSERVE_PLAYBACK_STATE_CHANGES)) + , fCommandStack(stack) + , fCurrentPlaylistIndex(-1) , fPlaybackState(PLAYBACK_STATE_STOPPED) @@ -329,85 +336,24 @@ PlaylistListView::KeyDown(const char* bytes, int32 numBytes) void PlaylistListView::MoveItems(BList& indices, int32 toIndex) { - if (!fPlaylist->Lock()) - return; - - entry_ref currentRef; - bool adjustCurrentRef = fPlaylist->GetRefAt(fPlaylist->CurrentRefIndex(), - ¤tRef) == B_OK; - - int32 count = indices.CountItems(); - entry_ref refs[count]; - for (int32 i = 0; i < count; i++) { - int32 index = (int32)indices.ItemAtFast(i) - i; - // "-i" to account for items already removed in the - // target list - if (index < 0) { - // asynchronous message is out of date - return; - } - refs[i] = fPlaylist->RemoveRef(index, false); - if (index < toIndex) - toIndex --; - } - - for (int32 i = 0; i < count; i++) { - fPlaylist->AddRef(refs[i], toIndex++); - } - - if (adjustCurrentRef) - fPlaylist->SetCurrentRefIndex(fPlaylist->IndexOf(currentRef)); - - fPlaylist->Unlock(); + fCommandStack->Perform(new (nothrow) MovePLItemsCommand(fPlaylist, + (int32*)indices.Items(), indices.CountItems(), toIndex)); } void PlaylistListView::CopyItems(BList& indices, int32 toIndex) { - if (!fPlaylist->Lock()) - return; - - int32 count = indices.CountItems(); - entry_ref refs[count]; - for (int32 i = 0; i < count; i++) { - int32 index = (int32)indices.ItemAtFast(i); - if (index < 0) { - // asynchronous message is out of date - return; - } - if (fPlaylist->GetRefAt(index, &refs[i]) < B_OK) - return; - } - - for (int32 i = 0; i < count; i++) { - fPlaylist->AddRef(refs[i], toIndex++); - } - - fPlaylist->Unlock(); + fCommandStack->Perform(new (nothrow) CopyPLItemsCommand(fPlaylist, + (int32*)indices.Items(), indices.CountItems(), toIndex)); } void PlaylistListView::RemoveItemList(BList& indices) { - if (!fPlaylist->Lock()) - return; - - int32 count = indices.CountItems(); - int32 lastRemovedIndex = -1; - for (int32 i = 0; i < count; i++) { - lastRemovedIndex = (int32)indices.ItemAtFast(i) - i; - // "-i" to account for items already removed in the - // target list - fPlaylist->RemoveRef(lastRemovedIndex); - } - - // in case we removed the currently playing file - if (fPlaylist->CurrentRefIndex() == -1) - fPlaylist->SetCurrentRefIndex(lastRemovedIndex); - - fPlaylist->Unlock(); + fCommandStack->Perform(new (nothrow) RemovePLItemsCommand(fPlaylist, + (int32*)indices.Items(), indices.CountItems())); } @@ -424,12 +370,8 @@ PlaylistListView::DrawListItem(BView* owner, int32 index, BRect frame) const void PlaylistListView::RefsReceived(BMessage* message, int32 appendIndex) { - if (!fPlaylist->Lock()) - return; - - fPlaylist->AppendRefs(message, appendIndex); - - fPlaylist->Unlock(); + fCommandStack->Perform(new (nothrow) ImportPLItemsCommand(fPlaylist, + message, appendIndex)); } diff --git a/src/apps/mediaplayer/PlaylistListView.h b/src/apps/mediaplayer/playlist/PlaylistListView.h similarity index 93% rename from src/apps/mediaplayer/PlaylistListView.h rename to src/apps/mediaplayer/playlist/PlaylistListView.h index 87e182c45a..0a4b44ed0c 100644 --- a/src/apps/mediaplayer/PlaylistListView.h +++ b/src/apps/mediaplayer/playlist/PlaylistListView.h @@ -10,6 +10,7 @@ #include "ListViews.h" +class CommandStack; class Controller; class ControllerObserver; class Playlist; @@ -20,7 +21,8 @@ class PlaylistListView : public SimpleListView { public: PlaylistListView(BRect frame, Playlist* playlist, - Controller* controller); + Controller* controller, + CommandStack* stack); virtual ~PlaylistListView(); // BView interface @@ -56,6 +58,8 @@ class PlaylistListView : public SimpleListView { Controller* fController; ControllerObserver* fControllerObserver; + CommandStack* fCommandStack; + int32 fCurrentPlaylistIndex; uint32 fPlaybackState; diff --git a/src/apps/mediaplayer/PlaylistObserver.cpp b/src/apps/mediaplayer/playlist/PlaylistObserver.cpp similarity index 100% rename from src/apps/mediaplayer/PlaylistObserver.cpp rename to src/apps/mediaplayer/playlist/PlaylistObserver.cpp diff --git a/src/apps/mediaplayer/PlaylistObserver.h b/src/apps/mediaplayer/playlist/PlaylistObserver.h similarity index 100% rename from src/apps/mediaplayer/PlaylistObserver.h rename to src/apps/mediaplayer/playlist/PlaylistObserver.h diff --git a/src/apps/mediaplayer/PlaylistWindow.cpp b/src/apps/mediaplayer/playlist/PlaylistWindow.cpp similarity index 97% rename from src/apps/mediaplayer/PlaylistWindow.cpp rename to src/apps/mediaplayer/playlist/PlaylistWindow.cpp index 9e6b5c2527..330689953e 100644 --- a/src/apps/mediaplayer/PlaylistWindow.cpp +++ b/src/apps/mediaplayer/playlist/PlaylistWindow.cpp @@ -33,7 +33,8 @@ PlaylistWindow::PlaylistWindow(BRect frame, Playlist* playlist, _CreateMenu(frame); frame.right -= B_V_SCROLL_BAR_WIDTH; - fListView = new PlaylistListView(frame, playlist, controller); + fListView = new PlaylistListView(frame, playlist, controller, + fCommandStack); BScrollView* scrollView = new BScrollView("playlist scrollview", fListView, B_FOLLOW_ALL, 0, false, true, B_NO_BORDER); diff --git a/src/apps/mediaplayer/PlaylistWindow.h b/src/apps/mediaplayer/playlist/PlaylistWindow.h similarity index 100% rename from src/apps/mediaplayer/PlaylistWindow.h rename to src/apps/mediaplayer/playlist/PlaylistWindow.h diff --git a/src/apps/mediaplayer/playlist/RemovePLItemsCommand.cpp b/src/apps/mediaplayer/playlist/RemovePLItemsCommand.cpp new file mode 100644 index 0000000000..46228068c1 --- /dev/null +++ b/src/apps/mediaplayer/playlist/RemovePLItemsCommand.cpp @@ -0,0 +1,125 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "RemovePLItemsCommand.h" + +#include +#include + +#include + +#include "Playlist.h" + + +using std::nothrow; + + +RemovePLItemsCommand::RemovePLItemsCommand(Playlist* playlist, + const int32* indices, int32 count) + : Command() + , fPlaylist(playlist) + , fRefs(count > 0 ? new (nothrow) entry_ref[count] : NULL) + , fIndices(count > 0 ? new (nothrow) int32[count] : NULL) + , fCount(count) +{ + if (!indices || !fPlaylist || !fRefs || !fIndices) { + // indicate a bad object state + delete[] fRefs; + fRefs = NULL; + return; + } + + memcpy(fIndices, indices, fCount * sizeof(int32)); + + // init original entry indices + for (int32 i = 0; i < fCount; i++) { + if (fPlaylist->GetRefAt(fIndices[i], &fRefs[i]) < B_OK) { + delete[] fRefs; + fRefs = NULL; + return; + } + } +} + + +RemovePLItemsCommand::~RemovePLItemsCommand() +{ + delete[] fRefs; + delete[] fIndices; +} + + +status_t +RemovePLItemsCommand::InitCheck() +{ + if (!fPlaylist || !fRefs || !fIndices) + return B_NO_INIT; + return B_OK; +} + + +status_t +RemovePLItemsCommand::Perform() +{ + BAutolock _(fPlaylist); + + int32 lastRemovedIndex = -1; + + // remove refs from playlist + for (int32 i = 0; i < fCount; i++) { + // "- i" to account for the items already removed + lastRemovedIndex = fIndices[i] - i; + fPlaylist->RemoveRef(lastRemovedIndex); + } + + // in case we removed the currently playing file + if (fPlaylist->CurrentRefIndex() == -1) + fPlaylist->SetCurrentRefIndex(lastRemovedIndex); + + return B_OK; +} + + +status_t +RemovePLItemsCommand::Undo() +{ + BAutolock _(fPlaylist); + + status_t ret = B_OK; + + // remember currently playling ref in case we move it + entry_ref currentRef; + bool adjustCurrentRef = fPlaylist->GetRefAt(fPlaylist->CurrentRefIndex(), + ¤tRef) == B_OK; + + // add refs to playlist at remembered indices + for (int32 i = 0; i < fCount; i++) { + if (!fPlaylist->AddRef(fRefs[i], fIndices[i])) { + ret = B_NO_MEMORY; + break; + } + } + if (ret < B_OK) + return ret; + + // take care about currently played ref + if (adjustCurrentRef) + fPlaylist->SetCurrentRefIndex(fPlaylist->IndexOf(currentRef)); + + return B_OK; +} + + +void +RemovePLItemsCommand::GetName(BString& name) +{ + if (fCount > 1) + name << "Remove Entries"; + else + name << "Remove Entry"; +} diff --git a/src/apps/mediaplayer/playlist/RemovePLItemsCommand.h b/src/apps/mediaplayer/playlist/RemovePLItemsCommand.h new file mode 100644 index 0000000000..6f310ef774 --- /dev/null +++ b/src/apps/mediaplayer/playlist/RemovePLItemsCommand.h @@ -0,0 +1,38 @@ +/* + * Copyright 2007, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef REMOVE_PL_ITEMS_COMMAND_H +#define REMOVE_PL_ITEMS_COMMAND_H + + +#include "Command.h" + +class Playlist; + +class RemovePLItemsCommand : public Command { + public: + RemovePLItemsCommand( + Playlist* playlist, + const int32* indices, + int32 count); + virtual ~RemovePLItemsCommand(); + + virtual status_t InitCheck(); + + virtual status_t Perform(); + virtual status_t Undo(); + + virtual void GetName(BString& name); + + private: + Playlist* fPlaylist; + entry_ref* fRefs; + int32* fIndices; + int32 fCount; +}; + +#endif // REMOVE_PL_ITEMS_COMMAND_H diff --git a/src/apps/mediaplayer/support/AutoLocker.h b/src/apps/mediaplayer/support/AutoLocker.h new file mode 100644 index 0000000000..ea568ebc61 --- /dev/null +++ b/src/apps/mediaplayer/support/AutoLocker.h @@ -0,0 +1,151 @@ +/* + * Copyright 2004-2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * IngoWeinhold + */ + +/** Scope-based automatic deletion of objects/arrays. + * ObjectDeleter - deletes an object + * ArrayDeleter - deletes an array + * MemoryDeleter - free()s malloc()ed memory + */ + +#ifndef AUTO_LOCKER_H +#define AUTO_LOCKER_H + +#include + +// locking + +// AutoLockerStandardLocking +template +class AutoLockerStandardLocking { +public: + inline bool Lock(Lockable *lockable) + { + return lockable->Lock(); + } + + inline void Unlock(Lockable *lockable) + { + lockable->Unlock(); + } +}; + +// AutoLockerReadLocking +template +class AutoLockerReadLocking { +public: + inline bool Lock(Lockable *lockable) + { + return lockable->ReadLock(); + } + + inline void Unlock(Lockable *lockable) + { + lockable->ReadUnlock(); + } +}; + +// AutoLockerWriteLocking +template +class AutoLockerWriteLocking { +public: + inline bool Lock(Lockable *lockable) + { + return lockable->WriteLock(); + } + + inline void Unlock(Lockable *lockable) + { + lockable->WriteUnlock(); + } +}; + +// AutoLocker +template > +class AutoLocker { +private: + typedef AutoLocker ThisClass; +public: + inline AutoLocker(Lockable *lockable, bool alreadyLocked = false) + : fLockable(lockable), + fLocked(fLockable && alreadyLocked) + { + if (!fLocked) + _Lock(); + } + + inline AutoLocker(Lockable &lockable, bool alreadyLocked = false) + : fLockable(&lockable), + fLocked(fLockable && alreadyLocked) + { + if (!fLocked) + _Lock(); + } + + inline ~AutoLocker() + { + Unlock(); + } + + inline void SetTo(Lockable *lockable, bool alreadyLocked) + { + Unlock(); + fLockable = lockable; + fLocked = alreadyLocked; + if (!fLocked) + _Lock(); + } + + inline void SetTo(Lockable &lockable, bool alreadyLocked) + { + SetTo(&lockable, alreadyLocked); + } + + inline void Unset() + { + Unlock(); + } + + inline AutoLocker &operator=(Lockable *lockable) + { + SetTo(lockable); + return *this; + } + + inline AutoLocker &operator=(Lockable &lockable) + { + SetTo(&lockable); + return *this; + } + + inline bool IsLocked() const { return fLocked; } + + inline void Unlock() + { + if (fLockable && fLocked) { + fLocking.Unlock(fLockable); + fLocked = false; + } + } + + inline operator bool() const { return fLocked; } + +private: + inline void _Lock() + { + if (fLockable) + fLocked = fLocking.Lock(fLockable); + } + +private: + Lockable *fLockable; + bool fLocked; + Locking fLocking; +}; + +#endif // AUTO_LOCKER_H diff --git a/src/apps/mediaplayer/support/RWLocker.cpp b/src/apps/mediaplayer/support/RWLocker.cpp new file mode 100644 index 0000000000..6fb06d24b5 --- /dev/null +++ b/src/apps/mediaplayer/support/RWLocker.cpp @@ -0,0 +1,471 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * IngoWeinhold + */ + +#include "RWLocker.h" + +#include + +// info about a read lock owner +struct RWLocker::ReadLockInfo { + thread_id reader; + int32 count; +}; + + +// constructor +RWLocker::RWLocker() + : fLock(), + fMutex(), + fQueue(), + fReaderCount(0), + fWriterCount(0), + fReadLockInfos(8), + fWriter(B_ERROR), + fWriterWriterCount(0), + fWriterReaderCount(0) +{ + _Init(NULL); +} + +// constructor +RWLocker::RWLocker(const char* name) + : fLock(name), + fMutex(), + fQueue(), + fReaderCount(0), + fWriterCount(0), + fReadLockInfos(8), + fWriter(B_ERROR), + fWriterWriterCount(0), + fWriterReaderCount(0) +{ + _Init(name); +} + +// destructor +RWLocker::~RWLocker() +{ + fLock.Lock(); + delete_sem(fMutex.semaphore); + delete_sem(fQueue.semaphore); + for (int32 i = 0; ReadLockInfo* info = _ReadLockInfoAt(i); i++) + delete info; +} + +// ReadLock +bool +RWLocker::ReadLock() +{ + status_t error = _ReadLock(B_INFINITE_TIMEOUT); + return (error == B_OK); +} + +// ReadLockWithTimeout +status_t +RWLocker::ReadLockWithTimeout(bigtime_t timeout) +{ + bigtime_t absoluteTimeout = system_time() + timeout; + // take care of overflow + if (timeout > 0 && absoluteTimeout < 0) + absoluteTimeout = B_INFINITE_TIMEOUT; + return _ReadLock(absoluteTimeout); +} + +// ReadUnlock +void +RWLocker::ReadUnlock() +{ + if (fLock.Lock()) { + thread_id thread = find_thread(NULL); + if (thread == fWriter) { + // We (also) have a write lock. + if (fWriterReaderCount > 0) + fWriterReaderCount--; + // else: error: unmatched ReadUnlock() + } else { + int32 index = _IndexOf(thread); + if (ReadLockInfo* info = _ReadLockInfoAt(index)) { + fReaderCount--; + if (--info->count == 0) { + // The outer read lock bracket for the thread has been + // reached. Dispose the info. + _DeleteReadLockInfo(index); + } + if (fReaderCount == 0) { + // The last reader needs to unlock the mutex. + _ReleaseBenaphore(fMutex); + } + } // else: error: caller has no read lock + } + fLock.Unlock(); + } // else: we are probably going to be destroyed +} + +// IsReadLocked +// +// Returns whether or not the calling thread owns a read lock or even a +// write lock. +bool +RWLocker::IsReadLocked() const +{ + bool result = false; + if (fLock.Lock()) { + thread_id thread = find_thread(NULL); + result = (thread == fWriter || _IndexOf(thread) >= 0); + fLock.Unlock(); + } + return result; +} + +// WriteLock +bool +RWLocker::WriteLock() +{ + status_t error = _WriteLock(B_INFINITE_TIMEOUT); + return (error == B_OK); +} + +// WriteLockWithTimeout +status_t +RWLocker::WriteLockWithTimeout(bigtime_t timeout) +{ + bigtime_t absoluteTimeout = system_time() + timeout; + // take care of overflow + if (timeout > 0 && absoluteTimeout < 0) + absoluteTimeout = B_INFINITE_TIMEOUT; + return _WriteLock(absoluteTimeout); +} + +// WriteUnlock +void +RWLocker::WriteUnlock() +{ + if (fLock.Lock()) { + thread_id thread = find_thread(NULL); + if (thread == fWriter) { + fWriterCount--; + if (--fWriterWriterCount == 0) { + // The outer write lock bracket for the thread has been + // reached. + fWriter = B_ERROR; + if (fWriterReaderCount > 0) { + // We still own read locks. + _NewReadLockInfo(thread, fWriterReaderCount); + // A reader that expects to be the first reader may wait + // at the mutex semaphore. We need to wake it up. + if (fReaderCount > 0) + _ReleaseBenaphore(fMutex); + fReaderCount += fWriterReaderCount; + fWriterReaderCount = 0; + } else { + // We don't own any read locks. So we have to release the + // mutex benaphore. + _ReleaseBenaphore(fMutex); + } + } + } // else: error: unmatched WriteUnlock() + fLock.Unlock(); + } // else: We're probably going to die. +} + +// IsWriteLocked +// +// Returns whether or not the calling thread owns a write lock. +bool +RWLocker::IsWriteLocked() const +{ + return (fWriter == find_thread(NULL)); +} + +// _Init +void +RWLocker::_Init(const char* name) +{ + // init the mutex benaphore + BString mutexName(name); + mutexName += "_RWLocker_mutex"; + fMutex.semaphore = create_sem(0, mutexName.String()); + fMutex.counter = 0; + // init the queueing benaphore + BString queueName(name); + queueName += "_RWLocker_queue"; + fQueue.semaphore = create_sem(0, queueName.String()); + fQueue.counter = 0; +} + +// _ReadLock +// +// /timeout/ -- absolute timeout +status_t +RWLocker::_ReadLock(bigtime_t timeout) +{ + status_t error = B_OK; + thread_id thread = find_thread(NULL); + bool locked = false; + if (fLock.Lock()) { + // Check, if we already own a read (or write) lock. In this case we + // can skip the usual locking procedure. + if (thread == fWriter) { + // We already own a write lock. + fWriterReaderCount++; + locked = true; + } else if (ReadLockInfo* info = _ReadLockInfoAt(_IndexOf(thread))) { + // We already own a read lock. + info->count++; + fReaderCount++; + locked = true; + } + fLock.Unlock(); + } else // failed to lock the data + error = B_ERROR; + // Usual locking, i.e. we do not already own a read or write lock. + if (error == B_OK && !locked) { + error = _AcquireBenaphore(fQueue, timeout); + if (error == B_OK) { + if (fLock.Lock()) { + bool firstReader = false; + if (++fReaderCount == 1) { + // We are the first reader. + _NewReadLockInfo(thread); + firstReader = true; + } else + _NewReadLockInfo(thread); + fLock.Unlock(); + // The first reader needs to lock the mutex. + if (firstReader) { + error = _AcquireBenaphore(fMutex, timeout); + switch (error) { + case B_OK: + // fine + break; + case B_TIMED_OUT: { + // clean up + if (fLock.Lock()) { + _DeleteReadLockInfo(_IndexOf(thread)); + fReaderCount--; + fLock.Unlock(); + } + break; + } + default: + // Probably we are going to be destroyed. + break; + } + } + // Let the next candidate enter the game. + _ReleaseBenaphore(fQueue); + } else { + // We couldn't lock the data, which can only happen, if + // we're going to be destroyed. + error = B_ERROR; + } + } + } + return error; +} + +// _WriteLock +// +// /timeout/ -- absolute timeout +status_t +RWLocker::_WriteLock(bigtime_t timeout) +{ + status_t error = B_ERROR; + if (fLock.Lock()) { + bool infiniteTimeout = (timeout == B_INFINITE_TIMEOUT); + bool locked = false; + int32 readerCount = 0; + thread_id thread = find_thread(NULL); + int32 index = _IndexOf(thread); + if (ReadLockInfo* info = _ReadLockInfoAt(index)) { + // We already own a read lock. + if (fWriterCount > 0) { + // There are writers before us. + if (infiniteTimeout) { + // Timeout is infinite and there are writers before us. + // Unregister the read locks and lock as usual. + readerCount = info->count; + fWriterCount++; + fReaderCount -= readerCount; + _DeleteReadLockInfo(index); + error = B_OK; + } else { + // The timeout is finite and there are readers before us: + // let the write lock request fail. + error = B_WOULD_BLOCK; + } + } else if (info->count == fReaderCount) { + // No writers before us. + // We are the only read lock owners. Just move the read lock + // info data to the special writer fields and then we are done. + // Note: At this point we may overtake readers that already + // have acquired the queueing benaphore, but have not yet + // locked the data. But that doesn't harm. + fWriter = thread; + fWriterCount++; + fWriterWriterCount = 1; + fWriterReaderCount = info->count; + fReaderCount -= fWriterReaderCount; + _DeleteReadLockInfo(index); + locked = true; + error = B_OK; + } else { + // No writers before us, but other readers. + // Note, we're quite restrictive here. If there are only + // readers before us, we could reinstall our readers, if + // our request times out. Unfortunately it is not easy + // to ensure, that no writer overtakes us between unlocking + // the data and acquiring the queuing benaphore. + if (infiniteTimeout) { + // Unregister the readers and lock as usual. + readerCount = info->count; + fWriterCount++; + fReaderCount -= readerCount; + _DeleteReadLockInfo(index); + error = B_OK; + } else + error = B_WOULD_BLOCK; + } + } else { + // We don't own a read lock. + if (fWriter == thread) { + // ... but a write lock. + fWriterCount++; + fWriterWriterCount++; + locked = true; + error = B_OK; + } else { + // We own neither read nor write locks. + // Lock as usual. + fWriterCount++; + error = B_OK; + } + } + fLock.Unlock(); + // Usual locking... + // First step: acquire the queueing benaphore. + if (!locked && error == B_OK) { + error = _AcquireBenaphore(fQueue, timeout); + switch (error) { + case B_OK: + break; + case B_TIMED_OUT: { + // clean up + if (fLock.Lock()) { + fWriterCount--; + fLock.Unlock(); + } // else: failed to lock the data: we're probably going + // to die. + break; + } + default: + // Probably we're going to die. + break; + } + } + // Second step: acquire the mutex benaphore. + if (!locked && error == B_OK) { + error = _AcquireBenaphore(fMutex, timeout); + switch (error) { + case B_OK: { + // Yeah, we made it. Set the special writer fields. + fWriter = thread; + fWriterWriterCount = 1; + fWriterReaderCount = readerCount; + break; + } + case B_TIMED_OUT: { + // clean up + if (fLock.Lock()) { + fWriterCount--; + fLock.Unlock(); + } // else: failed to lock the data: we're probably going + // to die. + break; + } + default: + // Probably we're going to die. + break; + } + // Whatever happened, we have to release the queueing benaphore. + _ReleaseBenaphore(fQueue); + } + } else // failed to lock the data + error = B_ERROR; + return error; +} + +// _AddReadLockInfo +int32 +RWLocker::_AddReadLockInfo(ReadLockInfo* info) +{ + int32 index = fReadLockInfos.CountItems(); + fReadLockInfos.AddItem(info, index); + return index; +} + +// _NewReadLockInfo +// +// Create a new read lock info for the supplied thread and add it to the +// list. Returns the index of the info. +int32 +RWLocker::_NewReadLockInfo(thread_id thread, int32 count) +{ + ReadLockInfo* info = new ReadLockInfo; + info->reader = thread; + info->count = count; + return _AddReadLockInfo(info); +} + +// _DeleteReadLockInfo +void +RWLocker::_DeleteReadLockInfo(int32 index) +{ + if (ReadLockInfo* info = (ReadLockInfo*)fReadLockInfos.RemoveItem(index)) + delete info; +} + +// _ReadLockInfoAt +RWLocker::ReadLockInfo* +RWLocker::_ReadLockInfoAt(int32 index) const +{ + return (ReadLockInfo*)fReadLockInfos.ItemAt(index); +} + +// _IndexOf +int32 +RWLocker::_IndexOf(thread_id thread) const +{ + int32 count = fReadLockInfos.CountItems(); + for (int32 i = 0; i < count; i++) { + if (_ReadLockInfoAt(i)->reader == thread) + return i; + } + return -1; +} + +// _AcquireBenaphore +status_t +RWLocker::_AcquireBenaphore(Benaphore& benaphore, bigtime_t timeout) +{ + status_t error = B_OK; + if (atomic_add(&benaphore.counter, 1) > 0) { + error = acquire_sem_etc(benaphore.semaphore, 1, B_ABSOLUTE_TIMEOUT, + timeout); + } + return error; +} + +// _ReleaseBenaphore +void +RWLocker::_ReleaseBenaphore(Benaphore& benaphore) +{ + if (atomic_add(&benaphore.counter, -1) > 1) + release_sem(benaphore.semaphore); +} + diff --git a/src/apps/mediaplayer/support/RWLocker.h b/src/apps/mediaplayer/support/RWLocker.h new file mode 100644 index 0000000000..e94cff3147 --- /dev/null +++ b/src/apps/mediaplayer/support/RWLocker.h @@ -0,0 +1,137 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * IngoWeinhold + */ + +// This class provides a reader/writer locking mechanism: +// * A writer needs an exclusive lock. +// * For a reader a non-exclusive lock to be shared with other readers is +// sufficient. +// * The ownership of a lock is bound to the thread that requested the lock; +// the same thread has to call Unlock() later. +// * Nested locking is supported: a number of XXXLock() calls needs to be +// bracketed by the same number of XXXUnlock() calls. +// * The lock acquiration strategy is fair: a lock applicant needs to wait +// only for those threads that already own a lock or requested one before +// the current thread. No one can overtake. E.g. if a thread owns a read +// lock, another one is waiting for a write lock, then a third one +// requesting a read lock has to wait until the write locker is done. +// This does not hold for threads that already own a lock (nested locking). +// A read lock owner is immediately granted another read lock and a write +// lock owner another write or a read lock. +// * A write lock owner is allowed to request a read lock and a read lock +// owner a write lock. While the first case is not problematic, the +// second one needs some further explanation: A read lock owner requesting +// a write lock temporarily looses its read lock(s) until the write lock +// is granted. Otherwise two read lock owning threads trying to get +// write locks at the same time would dead lock each other. The only +// problem with this solution is, that the write lock acquiration must +// not fail, because in that case the thread could not be given back +// its read lock(s), since another thread may have been given a write lock +// in the mean time. Fortunately locking can fail only either, if the +// locker has been deleted, or, if a timeout occured. Therefore +// WriteLockWithTimeout() immediatlely returns with a B_WOULD_BLOCK error +// code, if the caller already owns a read lock (but no write lock) and +// another thread already owns or has requested a read or write lock. +// * Calls to read and write locking methods may interleave arbitrarily, +// e.g.: ReadLock(); WriteLock(); ReadUnlock(); WriteUnlock(); +// +// Important note: Read/WriteLock() can fail only, if the locker has been +// deleted. However, it is NOT save to invoke any method on a deleted +// locker object. +// +// Implementation details: +// A locker needs three semaphores (a BLocker and two semaphores): one +// to protect the lockers data, one as a reader/writer mutex (to be +// acquired by each writer and the first reader) and one for queueing +// waiting readers and writers. The simplified locking/unlocking +// algorithm is the following: +// +// writer reader +// queue.acquire() queue.acquire() +// mutex.acquire() if (first reader) mutex.acquire() +// queue.release() queue.release() +// ... ... +// mutex.release() if (last reader) mutex.release() +// +// One thread at maximum waits at the mutex, the others at the queueing +// semaphore. Unfortunately features as nested locking and timeouts make +// things more difficult. Therefore readers as well as writers need to check +// whether they already own a lock before acquiring the queueing semaphore. +// The data for the readers are stored in a list of ReadLockInfo structures; +// the writer data are stored in some special fields. /fReaderCount/ and +// /fWriterCount/ contain the total count of unbalanced Read/WriteLock() +// calls, /fWriterReaderCount/ and /fWriterWriterCount/ only from those of +// the current write lock owner (/fWriter/). To be a bit more precise: +// /fWriterReaderCount/ is not contained in /fReaderCount/, but +// /fWriterWriterCount/ is contained in /fWriterCount/. Therefore +// /fReaderCount/ can be considered to be the count of true reader's read +// locks. + +#ifndef RW_LOCKER_H +#define RW_LOCKER_H + +#include +#include + +#include "AutoLocker.h" + +class RWLocker { + public: + RWLocker(); + RWLocker(const char* name); + virtual ~RWLocker(); + + bool ReadLock(); + status_t ReadLockWithTimeout(bigtime_t timeout); + void ReadUnlock(); + bool IsReadLocked() const; + + bool WriteLock(); + status_t WriteLockWithTimeout(bigtime_t timeout); + void WriteUnlock(); + bool IsWriteLocked() const; + + private: + struct ReadLockInfo; + struct Benaphore { + sem_id semaphore; + int32 counter; + }; + + private: + void _Init(const char* name); + status_t _ReadLock(bigtime_t timeout); + status_t _WriteLock(bigtime_t timeout); + + int32 _AddReadLockInfo(ReadLockInfo* info); + int32 _NewReadLockInfo(thread_id thread, + int32 count = 1); + void _DeleteReadLockInfo(int32 index); + ReadLockInfo* _ReadLockInfoAt(int32 index) const; + int32 _IndexOf(thread_id thread) const; + + static status_t _AcquireBenaphore(Benaphore& benaphore, + bigtime_t timeout); + static void _ReleaseBenaphore(Benaphore& benaphore); + + private: + mutable BLocker fLock; // data lock + Benaphore fMutex; // critical code mutex + Benaphore fQueue; // queueing semaphore + int32 fReaderCount; // total count... + int32 fWriterCount; // total count... + BList fReadLockInfos; + thread_id fWriter; // current write lock owner + int32 fWriterWriterCount; // write lock owner count + int32 fWriterReaderCount; // writer read lock owner + // count +}; + +typedef AutoLocker > AutoReadLocker; +typedef AutoLocker > AutoWriteLocker; + +#endif // RW_LOCKER_H