diff --git a/server/src/DatabaseHelper.cpp b/server/src/DatabaseHelper.cpp index 40aea73..1fc408d 100644 --- a/server/src/DatabaseHelper.cpp +++ b/server/src/DatabaseHelper.cpp @@ -532,7 +532,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptrsql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":id", pid}, - variable{":chId", 0}, + variable{":chId", update.channel_id}, variable{":type", permission::SQL_PERM_PLAYLIST}, variable{":permId", permission_data->name}, diff --git a/server/src/VirtualServer.cpp b/server/src/VirtualServer.cpp index 5a542f5..0384701 100644 --- a/server/src/VirtualServer.cpp +++ b/server/src/VirtualServer.cpp @@ -1174,4 +1174,45 @@ void VirtualServer::ensureValidDefaultGroups() { logError(this->serverId, "Using {} ({}) instead!", admin_channel_group->groupId(), admin_channel_group->name()); this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = admin_channel_group->groupId(); } +} + +void VirtualServer::send_text_message(const std::shared_ptr &channel, const std::shared_ptr &client, const std::string &message) { + assert(channel); + assert(client); + + auto client_id = client->getClientId(); + auto channel_id = channel->channelId(); + auto now = chrono::system_clock::now(); + + bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as(); + auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as(); + for(const auto& client : this->getClients()) { + if(client->connectionState() != ConnectionState::CONNECTED) + continue; + + auto type = client->getType(); + if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC) + continue; + + auto own_channel = client->currentChannel == channel; + if(conversation_private && !own_channel) + continue; + + if(type != ClientType::CLIENT_TEAMSPEAK || own_channel) { + if(!own_channel && &*client != client) { + if(flag_password) + continue; /* TODO: Send notification about new message. The client then could request messages via message history */ + + if(!client->calculate_and_get_join_state(channel)) + continue; + } + client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, client, client_id, channel_id, now, message); + } + } + + if(!conversation_private) { + auto conversations = this->conversation_manager(); + auto conversation = conversations->get_or_create(channel->channelId()); + conversation->register_message(client->getClientDatabaseId(), client->getUid(), this->getDisplayName(), now, message); + } } \ No newline at end of file diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index e474d2c..17dc13c 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -270,6 +270,8 @@ namespace ts { std::unique_lock& /* tree lock */ ); + void send_text_message(const std::shared_ptr& /* channel */, const std::shared_ptr& /* sender */, const std::string& /* message */); + inline int voice_encryption_mode() { return this->_voice_encryption_mode; } inline std::shared_ptr conversation_manager() { return this->_conversation_manager; } protected: diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index 98ddc8e..edeee93 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "music/Song.h" #include "../channel/ClientChannelView.h" #include "DataClient.h" @@ -294,6 +295,10 @@ namespace ts { } return disconnect_lock; } + + inline bool playlist_subscribed(const std::shared_ptr& playlist) const { + return this->_subscribed_playlist.lock() == playlist; + } protected: std::weak_ptr _this; sockaddr_storage remote_address; @@ -362,6 +367,7 @@ namespace ts { std::weak_ptr selectedBot; std::weak_ptr subscribed_bot; + std::weak_ptr _subscribed_playlist{}; virtual void tick(const std::chrono::system_clock::time_point &time); //Locked by everything who has something todo with command handling @@ -532,6 +538,7 @@ namespace ts { command_result handleCommandPlaylistList(Command&); command_result handleCommandPlaylistCreate(Command&); command_result handleCommandPlaylistDelete(Command&); + command_result handleCommandPlaylistSetSubscription(Command&); command_result handleCommandPlaylistPermList(Command&); command_result handleCommandPlaylistAddPerm(Command&); @@ -546,6 +553,7 @@ namespace ts { command_result handleCommandPlaylistEdit(Command&); command_result handleCommandPlaylistSongList(Command&); + command_result handleCommandPlaylistSongSetCurrent(Command&); command_result handleCommandPlaylistSongAdd(Command&); command_result handleCommandPlaylistSongReorder(Command&); command_result handleCommandPlaylistSongRemove(Command&); diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index 27cbb74..d9d1c3e 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -225,6 +225,8 @@ command_result ConnectedClient::handleCommand(Command &cmd) { else if (command == "playlistlist") return this->handleCommandPlaylistList(cmd); else if (command == "playlistcreate") return this->handleCommandPlaylistCreate(cmd); else if (command == "playlistdelete") return this->handleCommandPlaylistDelete(cmd); + else if (command == "playlistsetsubscription") return this->handleCommandPlaylistSetSubscription(cmd); + else if (command == "playlistpermlist") return this->handleCommandPlaylistPermList(cmd); else if (command == "playlistaddperm") return this->handleCommandPlaylistAddPerm(cmd); else if (command == "playlistdelperm") return this->handleCommandPlaylistDelPerm(cmd); @@ -235,6 +237,7 @@ command_result ConnectedClient::handleCommand(Command &cmd) { else if (command == "playlistedit") return this->handleCommandPlaylistEdit(cmd); else if (command == "playlistsonglist") return this->handleCommandPlaylistSongList(cmd); + else if (command == "playlistsongsetcurrent") return this->handleCommandPlaylistSongSetCurrent(cmd); else if (command == "playlistsongadd") return this->handleCommandPlaylistSongAdd(cmd); else if (command == "playlistsongreorder" || command == "playlistsongmove") return this->handleCommandPlaylistSongReorder(cmd); else if (command == "playlistsongremove") return this->handleCommandPlaylistSongRemove(cmd); @@ -557,40 +560,7 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) { return command_result{permission::b_client_channel_textmessage_send}; /* You're not allowed to send messages :) */ } - auto message = cmd["msg"].string(); - auto _this = this->_this.lock(); - auto client_id = this->getClientId(); - - auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as(); - for(const auto& client : this->server->getClients()) { - if(client->connectionState() != ConnectionState::CONNECTED) - continue; - - auto type = client->getType(); - if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC) - continue; - - auto own_channel = client->currentChannel == this->currentChannel; - if(conversation_private && !own_channel) - continue; - - if(type != ClientType::CLIENT_TEAMSPEAK || own_channel) { - if(!own_channel && &*client != this) { - if(flag_password) - continue; /* TODO: Send notification about new message. The client then could request messages via message history */ - - if(!client->calculate_and_get_join_state(channel)) - continue; - } - client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, _this, client_id, channel_id, timestamp, message); - } - } - - if(!conversation_private) { - auto conversations = this->server->conversation_manager(); - auto conversation = conversations->get_or_create(channel->channelId()); - conversation->register_message(this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), timestamp, cmd["msg"].string()); - } + this->server->send_text_message(channel, this->ref(), cmd["msg"].string()); } else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_SERVER) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_server_textmessage_send, 1); diff --git a/server/src/client/command_handler/music.cpp b/server/src/client/command_handler/music.cpp index 412e82d..76a93a6 100644 --- a/server/src/client/command_handler/music.cpp +++ b/server/src/client/command_handler/music.cpp @@ -798,6 +798,23 @@ command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd) return command_result{error::ok}; } +command_result ConnectedClient::handleCommandPlaylistSongSetCurrent(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); + if(!playlist) return command_result{error::playlist_invalid_id}; + + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_move_power, permission::i_playlist_song_move_power); perr) + return command_result{perr}; + + if(!playlist->set_current_song(cmd["song_id"])) + return command_result{error::playlist_invalid_song_id}; + + return command_result{error::ok}; +} + command_result ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; @@ -823,10 +840,6 @@ command_result ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) { auto song = playlist->add_song(_this.lock(), cmd["url"], cmd["invoker"], cmd["previous"]); if(!song) return command_result{error::vs_critical}; - Command notify(this->notify_response_command("notifyplaylistsongadd")); - notify["song_id"] = song->id; - this->sendCommand(notify); - return command_result{error::ok}; } @@ -1051,6 +1064,33 @@ command_result ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command return command_result{error::ok}; } +command_result ConnectedClient::handleCommandPlaylistSetSubscription(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + if(!config::music::enabled) return command_result{error::music_disabled}; + + auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); + if(!playlist && cmd["playlist_id"] != 0) return command_result{error::playlist_invalid_id}; + + { + auto old_playlist = this->_subscribed_playlist.lock(); + if(old_playlist) + old_playlist->remove_subscriber(_this.lock()); + } + + if(playlist) { + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr) + return command_result{perr}; + + playlist->add_subscriber(_this.lock()); + this->_subscribed_playlist = playlist; + } + + return command_result{error::ok}; +} + diff --git a/server/src/client/music/MusicClient.cpp b/server/src/client/music/MusicClient.cpp index 854b9d3..07e1eee 100644 --- a/server/src/client/music/MusicClient.cpp +++ b/server/src/client/music/MusicClient.cpp @@ -159,8 +159,7 @@ void MusicClient::initialize_bot() { } void MusicClient::broadcast_text_message(const std::string &message) { - for(const auto& cl : this->server->getClientsByChannel(this->currentChannel)) - cl->sendChannelMessage(_this.lock(), message); + this->server->send_text_message(this->currentChannel, this->ref(), message); } //https://en.wikipedia.org/wiki/Decibel @@ -230,8 +229,10 @@ void MusicClient::handle_event_song_replay_failed() { void MusicClient::replay_next_song() { auto playlist = this->playlist(); if(playlist) { - auto song = playlist->pop_next(); - if(song) + bool await_load; + if(auto song = playlist->current_song(await_load); song) this->replay_song(song); + else if(!await_load) + playlist->next(); } } \ No newline at end of file diff --git a/server/src/client/music/MusicClient.h b/server/src/client/music/MusicClient.h index c91e270..968aa38 100644 --- a/server/src/client/music/MusicClient.h +++ b/server/src/client/music/MusicClient.h @@ -75,6 +75,7 @@ namespace ts { void player_pause(); void forwardSong(); + void resetSong(); void rewindSong(); std::shared_ptr current_player(); diff --git a/server/src/client/music/MusicClientPlayer.cpp b/server/src/client/music/MusicClientPlayer.cpp index c41d237..70177fb 100644 --- a/server/src/client/music/MusicClientPlayer.cpp +++ b/server/src/client/music/MusicClientPlayer.cpp @@ -150,8 +150,8 @@ void MusicClient::tick(const std::chrono::system_clock::time_point &time) { else if(!this->current_song()) { auto playlist = this->playlist(); if(playlist) { /* may bot just got initialized */ - auto song = playlist->pop_next(); - if(song) { + bool await_load; + if(auto song = playlist->current_song(await_load); song) { auto player_state = this->_player_state; this->replay_song(song, [player_state](const shared_ptr<::music::MusicPlayer>& player){ if(player_state == ReplayState::STOPPED) @@ -161,9 +161,11 @@ void MusicClient::tick(const std::chrono::system_clock::time_point &time) { else player->play(); }); + } else if(!await_load) { + this->replay_next_song(); } } - if(!playlist || !this->current_song()){ + if(!playlist || !this->current_song()) { this->changePlayerState(ReplayState::SLEEPING); } } @@ -224,14 +226,46 @@ void MusicClient::player_pause() { void MusicClient::forwardSong() { auto song = this->current_song(); + auto playlist = this->playlist(); this->handle_event_song_ended(); + + /* explicitly wanted a "next" song so start over again */ + if(playlist->properties()[property::PLAYLIST_FLAG_FINISHED].as()) { + playlist->properties()[property::PLAYLIST_FLAG_FINISHED] = false; + this->handle_event_song_ended(); + } if(song == this->current_song()) this->player_reset(true); } -void MusicClient::rewindSong() { +void MusicClient::resetSong() { + auto song = this->current_song(); + + auto playlist = this->playlist(); + if(playlist) { + bool await_load; + if(auto song = playlist->current_song(await_load); song) { + this->replay_song(song); + return; + } + } + this->player_reset(true); +} + +void MusicClient::rewindSong() { + auto song = this->current_song(); + + auto playlist = this->playlist(); + if(playlist) { + playlist->previous(); + + bool await_load; + if(auto song = playlist->current_song(await_load); song) { + this->replay_song(song); + return; + } + } this->player_reset(true); - logError(this->getServerId(), "MusicClient::rewindSong hasn't been implemented yet!"); } void MusicClient::musicEventHandler(const std::weak_ptr& weak_player, ::music::MusicEvent event) { diff --git a/server/src/music/MusicPlaylist.cpp b/server/src/music/MusicPlaylist.cpp index 1a069f6..705edd0 100644 --- a/server/src/music/MusicPlaylist.cpp +++ b/server/src/music/MusicPlaylist.cpp @@ -4,7 +4,6 @@ #include "src/VirtualServer.h" #include "src/client/ConnectedClient.h" #include -#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h" #include "MusicPlayer.h" using namespace ts; @@ -350,10 +349,16 @@ void Playlist::destroy_tree() { } } -std::deque> Playlist::list_songs() { +std::deque > Playlist::list_songs() { + unique_lock list_lock(this->playlist_lock); + return this->_list_songs(list_lock); +} + +std::deque> Playlist::_list_songs(const std::unique_lock& lock) { + assert(lock.owns_lock()); + deque> result; - unique_lock list_lock(this->playlist_lock); auto head = this->playlist_head; while(head) { result.push_back(head->entry); @@ -402,13 +407,19 @@ std::shared_ptr Playlist::add_song(ClientDbId client, const s } auto order_entry = this->playlist_find(list_lock, entry->previous_song_id); this->playlist_insert(list_lock, list_entry, order_entry); + if(order_entry ? order_entry->entry->id : 0 != order || list_entry->modified) { list_entry->modified = false; entry->previous_song_id = list_entry->previous_song ? list_entry->previous_song->entry->id : 0; + list_lock.unlock(); + this->sql_apply_changes(entry); + } else { + list_lock.unlock(); } this->enqueue_load(entry); + this->notify_song_add(entry); this->properties()[property::PLAYLIST_FLAG_FINISHED] = false; return entry; } @@ -422,6 +433,7 @@ bool Playlist::delete_song(ts::SongId id) { list_lock.unlock(); this->sql_remove(song->entry); + this->notify_song_remove(song->entry); return true; } @@ -431,15 +443,18 @@ bool Playlist::reorder_song(ts::SongId song_id, ts::SongId order_id) { auto order = this->playlist_find(list_lock, order_id); if(!song) return false; if(!order && order_id != 0) return false; + if(song->previous_song == order) return true; if(!this->playlist_reorder(list_lock, song, order)) return false; list_lock.unlock(); this->sql_flush_all_changes(); + this->notify_song_reorder(song->entry); return true; } void Playlist::enqueue_load(const std::shared_ptr &entry) { + assert(entry); if(entry->load_future) return; /* song has been already loaded or parsed */ entry->load_start = system_clock::now(); @@ -525,6 +540,7 @@ void Playlist::execute_async_load(const std::shared_ptrurl_loader, entry->url); if(!provider) { entry->load_future->executionFailed("failed to load info provider"); + this->notify_song_loaded(entry); return; } @@ -542,11 +558,15 @@ void Playlist::execute_async_load(const std::shared_ptr(result); } else if(result->type == UrlType::TYPE_STREAM || result->type == UrlType::TYPE_VIDEO) { auto casted = static_pointer_cast(result); + root["length"] = chrono::ceil(casted->length).count(); root["title"] = casted->title; root["description"] = casted->description; - for(const auto& meta : casted->metadata) { - root["metadata"][meta.first] = meta.second; + if(casted->thumbnail) { + if(auto thump = dynamic_pointer_cast<::music::ThumbnailUrl>(casted->thumbnail); thump) + root["thumbnail"] = thump->url(); } + for(const auto& meta : casted->metadata) + root["metadata"][meta.first] = meta.second; } entry->metadata = root.toStyledString(); @@ -580,11 +600,109 @@ void Playlist::execute_async_load(const std::shared_ptrload_future->executionSucceed(result); entry->loaded = true; - if(result->type != UrlType::TYPE_PLAYLIST) /* we've already deleted this entry if this is a playlist entry*/ + /* we've already deleted this entry if this is a playlist entry */ + if(result->type != UrlType::TYPE_PLAYLIST) { this->sql_apply_changes(entry); + this->notify_song_loaded(entry); + } } else { entry->load_future->executionFailed("failed to load info: " + info_future.errorMegssage()); + this->notify_song_loaded(entry); return; } } +} + +void Playlist::add_subscriber(const std::shared_ptr &client) { + std::lock_guard slock{this->subscriber_lock}; + this->subscribers.push_back(client); +} + +void Playlist::remove_subscriber(const std::shared_ptr &tclient) { + std::lock_guard slock{this->subscriber_lock}; + this->subscribers.erase(std::remove_if(this->subscribers.begin(), this->subscribers.end(), [&](const std::weak_ptr& wclient) { + server::ConnectedLockedClient client{wclient.lock()}; + if(!client || client == tclient) return true; + + return false; + }), this->subscribers.end()); +} + +inline bool write_song(const std::shared_ptr &entry, command_builder& builder, size_t index) { + if(!entry) { + builder.put_unchecked(index, "song_id", "0"); + return true; + } + + builder.put_unchecked(index, "song_id", entry->id); + builder.put_unchecked(index, "song_previous_song_id", entry->previous_song_id); + + builder.put_unchecked(index, "song_url", entry->url); + builder.put_unchecked(index, "song_url_loader", entry->url_loader); + builder.put_unchecked(index, "song_loaded", entry->loaded); + + builder.put_unchecked(index, "song_invoker", entry->invoker); + builder.put_unchecked(index, "song_metadata", entry->metadata); + + return true; +} + +void Playlist::broadcast_notify(const ts::command_builder &command) { + std::lock_guard slock{this->subscriber_lock}; + this->subscribers.erase(std::remove_if(this->subscribers.begin(), this->subscribers.end(), [&](const std::weak_ptr& wclient) { + server::ConnectedLockedClient client{wclient.lock()}; + if(!client) return true; + + client->sendCommand(command); + return false; + }), this->subscribers.end()); +} + +bool Playlist::notify_song_add(const std::shared_ptr &entry) { + command_builder result{"notifyplaylistsongadd"}; + + result.put_unchecked(0, "playlist_id", this->playlist_id()); + write_song(entry, result, 0); + + this->broadcast_notify(result); + return true; +} + +bool Playlist::notify_song_remove(const std::shared_ptr &entry) { + command_builder result{"notifyplaylistsongremove"}; + + result.put_unchecked(0, "playlist_id", this->playlist_id()); + result.put_unchecked(0, "song_id", entry->id); + + this->broadcast_notify(result); + return true; +} + +bool Playlist::notify_song_reorder(const std::shared_ptr &entry) { + command_builder result{"notifyplaylistsongreorder"}; + + result.put_unchecked(0, "playlist_id", this->playlist_id()); + result.put_unchecked(0, "song_id", entry->id); + result.put_unchecked(0, "song_previous_song_id", entry->previous_song_id); + + this->broadcast_notify(result); + return true; +} + +bool Playlist::notify_song_loaded(const std::shared_ptr &entry) { + command_builder result{"notifyplaylistsongloaded"}; + + result.put_unchecked(0, "playlist_id", this->playlist_id()); + result.put_unchecked(0, "song_id", entry->id); + + if(entry->load_future && entry->load_future->failed()) { + result.put_unchecked(0, "success", "0"); + result.put_unchecked(0, "load_error_msg", entry->load_future->errorMegssage()); + } else { + result.put_unchecked(0, "success", "1"); + result.put_unchecked(0, "song_metadata", entry->metadata); + } + + this->broadcast_notify(result); + return true; } \ No newline at end of file diff --git a/server/src/music/MusicPlaylist.h b/server/src/music/MusicPlaylist.h index b9b259a..61d9ae8 100644 --- a/server/src/music/MusicPlaylist.h +++ b/server/src/music/MusicPlaylist.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "MusicBotManager.h" #include "PlaylistPermissions.h" @@ -56,7 +57,7 @@ namespace ts { assert(this->entry); this->previous_song = song; - this->entry->previous_song_id = 0; + this->entry->previous_song_id = song ? song->entry->id : 0; this->modified = true; } }; @@ -75,7 +76,7 @@ namespace ts { Playlist(const std::shared_ptr& /* manager */, const std::shared_ptr& /* properties */, std::shared_ptr /* permissions */); virtual ~Playlist(); - void load_songs(); + virtual void load_songs(); inline bool loaded() { return this->_songs_loaded; }; virtual std::deque> list_songs(); @@ -103,6 +104,11 @@ namespace ts { template ::value, int>::type = 0> inline std::shared_ptr ref() { return std::dynamic_pointer_cast(this->_self.lock()); } + + + void add_subscriber(const std::shared_ptr&); + void remove_subscriber(const std::shared_ptr&); + bool is_subscriber(const std::shared_ptr&); protected: virtual void set_self_ref(const std::shared_ptr& /* playlist */); bool is_playlist_owner(ClientDbId database_id) const override { return this->properties()[property::PLAYLIST_OWNER_DBID].as_save() == database_id; } @@ -117,8 +123,13 @@ namespace ts { std::shared_ptr get_server(); ServerId get_server_id(); - std::shared_mutex playlist_lock; - std::shared_ptr playlist_head; + std::shared_mutex playlist_lock{}; + std::shared_ptr playlist_head{}; + + std::mutex subscriber_lock{}; + std::deque> subscribers{}; + + virtual std::deque> _list_songs(const std::unique_lock& /* playlist lock */); /* playlist functions are threadsave */ std::shared_ptr playlist_find(const std::unique_lock& /* playlist lock */, SongId /* song */); @@ -141,6 +152,12 @@ namespace ts { bool sql_apply_changes(const std::shared_ptr& /* entry */); bool sql_flush_all_changes(); + void broadcast_notify(const command_builder& /* command */); + bool notify_song_add(const std::shared_ptr& /* entry */); + bool notify_song_remove(const std::shared_ptr& /* entry */); + bool notify_song_reorder(const std::shared_ptr& /* entry */); + bool notify_song_loaded(const std::shared_ptr& /* entry */); + void enqueue_load(const std::shared_ptr& /* entry */); void execute_async_load(const std::shared_ptr &/* entry */); }; diff --git a/server/src/music/PlayablePlaylist.cpp b/server/src/music/PlayablePlaylist.cpp index e65cf8d..da63e32 100644 --- a/server/src/music/PlayablePlaylist.cpp +++ b/server/src/music/PlayablePlaylist.cpp @@ -14,51 +14,73 @@ PlayablePlaylist::PlayablePlaylist(const std::shared_ptr &handl PlayablePlaylist::~PlayablePlaylist() {} +void PlayablePlaylist::load_songs() { + Playlist::load_songs(); + + unique_lock playlist_lock(this->playlist_lock); + auto song_id = this->properties()[property::PLAYLIST_CURRENT_SONG_ID].as_save(); + auto current_song = this->playlist_find(playlist_lock, song_id); + if(!current_song && song_id != 0) { + logWarning(this->get_server_id(), "[Playlist] Failed to reinitialize current song index for playlist {}. Song {} is missing.", this->playlist_id(), song_id); + this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = 0; + } + if(current_song) { + this->current_loading_entry = current_song->entry; + this->enqueue_load(current_song->entry); + } +} + void PlayablePlaylist::set_self_ref(const std::shared_ptr &ptr) { Playlist::set_self_ref(ptr); } -std::shared_ptr PlayablePlaylist::pop_next() { +void PlayablePlaylist::previous() { unique_lock lock(this->currently_playing_lock); - auto entry = this->loading_entry.lock(); - if(!entry) { - lock.unlock(); - entry = this->pop_next_entry(); - lock.lock(); - this->loading_entry = entry; + auto entry = this->playlist_previous_entry(); + if(entry) this->enqueue_load(entry); + this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = entry ? entry->id : 0; + this->current_loading_entry = entry; +} + +void PlayablePlaylist::next() { + unique_lock lock(this->currently_playing_lock); + + auto entry = this->playlist_next_entry(); + if(entry) this->enqueue_load(entry); + this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = entry ? entry->id : 0; + this->current_loading_entry = entry; +} + +std::shared_ptr PlayablePlaylist::current_song(bool& await_load) { + await_load = false; + + unique_lock lock(this->currently_playing_lock); + auto entry = this->current_loading_entry.lock(); + + auto id = entry ? entry->id : 0; + if(this->properties()[property::PLAYLIST_CURRENT_SONG_ID].as() != id) /* should not happen */ + this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = id; - auto id = entry ? entry->id : 0; - if(this->properties()[property::PLAYLIST_CURRENT_SONG_ID].as() != id) - this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = id; - } if(!entry) return nullptr; - - if(!entry->load_future) { - this->loading_entry.reset(); - return nullptr; /* skip this entry */ - } + if(!entry->load_future) return nullptr; /* skip this entry */ if(entry->load_future->state() == threads::FutureState::WORKING) { if(entry->load_start + seconds(30) < system_clock::now()) { - this->loading_entry.reset(); - logError(this->get_server_id(), "[PlayList] Failed to load song info. Error: {}. Timeout. Removing song", entry->load_future->errorMegssage()); - this->delete_song(entry->id); /* acquired playlist */ + this->delete_song(entry->id); /* delete song because we cant load it */ return PlayableSong::create({ entry->id, entry->url, entry->invoker - }, MusicClient::failedLoader("Failed to parse song info. Info loader timeouted!")); + }, MusicClient::failedLoader("Song failed to load")); } + await_load = true; return nullptr; } - this->loading_entry.reset(); /* reset the loading entry because we're returning it when succeeded, else we skip*/ if(!entry->load_future->succeeded() || !entry->load_future->getValue(nullptr)) { - //TODO log error within channel? - logError(this->get_server_id(), "[PlayList] Failed to load song info. Error: {}. Removing song", entry->load_future->errorMegssage()); this->delete_song(entry->id); /* acquired playlist */ /* return promise */ @@ -66,7 +88,7 @@ std::shared_ptr PlayablePlaylist::pop_next() { entry->id, entry->url, entry->invoker - }, MusicClient::failedLoader("Failed to parse song info: " + entry->load_future->errorMegssage())); + }, MusicClient::failedLoader(entry->load_future->errorMegssage())); } auto result = entry->load_future->getValue(nullptr); @@ -85,19 +107,19 @@ std::shared_ptr PlayablePlaylist::pop_next() { }, MusicClient::providerLoader(entry->url_loader)); } -std::shared_ptr PlayablePlaylist::pop_next_entry() { - if(!this->playlist_head) return nullptr; /* fuzzy check if we're not maybe empty */ - +std::shared_ptr PlayablePlaylist::playlist_next_entry() { + if(!this->playlist_head) return nullptr; /* fuzzy check if we're not empty */ auto replay_mode = this->properties()[property::PLAYLIST_REPLAY_MODE].as(); - auto songs = this->list_songs(); - if(songs.empty()) return nullptr; /* we've nothing to play */ unique_lock playlist_lock(this->playlist_lock); auto current_song = this->playlist_find(playlist_lock, this->currently_playing()); if(replay_mode == ReplayMode::SINGLE_LOOPED) { if(current_song) return current_song->entry; - return songs.front(); + if(this->playlist_head) return this->playlist_head->entry; + + this->properties()[property::PLAYLIST_FLAG_FINISHED] = true; + return nullptr; } std::shared_ptr result; @@ -112,13 +134,17 @@ std::shared_ptr PlayablePlaylist::pop_next_entry() { result = nullptr; } } else if(replay_mode == ReplayMode::LINEAR_LOOPED || !this->properties()[property::PLAYLIST_FLAG_FINISHED]) { - result = songs.front(); + result = this->playlist_head ? this->playlist_head->entry : nullptr; } } else if(replay_mode == ReplayMode::SHUFFLE) { + auto songs = this->_list_songs(playlist_lock); + //TODO may add a already played list? - if(songs.size() == 1) { + if(songs.size() == 0) { this->properties()[property::PLAYLIST_FLAG_FINISHED] = true; result = nullptr; + } else if(songs.size() == 1) { + result = songs.front(); } else { size_t index; while(songs[index = (rand() % songs.size())] == (!current_song ? nullptr : current_song->entry)) { } @@ -127,18 +153,48 @@ std::shared_ptr PlayablePlaylist::pop_next_entry() { } } playlist_lock.unlock(); - - if(current_song && this->properties()[property::PLAYLIST_FLAG_DELETE_PLAYED].as()) + if(current_song && this->properties()[property::PLAYLIST_FLAG_DELETE_PLAYED].as()) { this->delete_song(current_song->entry->id); + } return result; } +std::shared_ptr PlayablePlaylist::playlist_previous_entry() { + if(!this->playlist_head) return nullptr; /* fuzzy check if we're not empty */ + auto replay_mode = this->properties()[property::PLAYLIST_REPLAY_MODE].as(); + + unique_lock playlist_lock(this->playlist_lock); + this->properties()[property::PLAYLIST_FLAG_FINISHED] = false; + + auto current_song = this->playlist_find(playlist_lock, this->currently_playing()); + if(current_song) { + if(replay_mode == ReplayMode::LINEAR || replay_mode == ReplayMode::LINEAR_LOOPED) { + if(current_song->previous_song) return current_song->previous_song->entry; + if(!this->playlist_head) return nullptr; + + if(replay_mode == ReplayMode::LINEAR) + return this->playlist_head->entry; + + auto tail = this->playlist_head; + while(tail->next_song) tail = tail->next_song; + return tail->entry; + } else if(replay_mode == ReplayMode::SINGLE_LOOPED) { + return current_song ? current_song->entry : this->playlist_head ? this->playlist_head->entry : nullptr; + } else { + return current_song ? current_song->entry : nullptr; /* not possible to rewind shuffle mode */ + } + } else { + return this->playlist_head ? this->playlist_head->entry : nullptr; + } +} + bool PlayablePlaylist::set_current_song(SongId song_id) { { unique_lock playlist_lock(this->playlist_lock); auto current_song = this->playlist_find(playlist_lock, song_id); - this->loading_entry = current_song->entry; + if(!current_song && song_id != 0) return false; + this->current_loading_entry = current_song->entry; if(current_song) { this->enqueue_load(current_song->entry); auto id = current_song ? current_song->entry->id : 0; @@ -148,7 +204,7 @@ bool PlayablePlaylist::set_current_song(SongId song_id) { } { auto bot = this->current_bot(); - bot->forwardSong(); /* skip to the song */ + bot->resetSong(); /* reset the song and use the song which is supposed to be the "current" */ } return true; diff --git a/server/src/music/PlayablePlaylist.h b/server/src/music/PlayablePlaylist.h index 10ae540..a282004 100644 --- a/server/src/music/PlayablePlaylist.h +++ b/server/src/music/PlayablePlaylist.h @@ -27,22 +27,27 @@ namespace ts { PlayablePlaylist(const std::shared_ptr& /* manager */, const std::shared_ptr& /* properties */, const std::shared_ptr& /* permissions */); virtual ~PlayablePlaylist(); + void load_songs() override; + inline ReplayMode::value replay_mode() { return this->properties()[property::PLAYLIST_REPLAY_MODE].as(); } inline void set_replay_mode(ReplayMode::value mode) { this->properties()[property::PLAYLIST_REPLAY_MODE] = mode; } inline SongId currently_playing() { return this->properties()[property::PLAYLIST_CURRENT_SONG_ID]; } bool set_current_song(SongId /* song */); - std::shared_ptr pop_next(); + void previous(); + void next(); + std::shared_ptr current_song(bool& await_load); inline std::shared_ptr current_bot() { return this->_current_bot.lock(); } protected: std::mutex currently_playing_lock; - std::weak_ptr loading_entry; + std::weak_ptr current_loading_entry; std::weak_ptr _current_bot; void set_self_ref(const std::shared_ptr &ptr) override; - std::shared_ptr pop_next_entry(); + std::shared_ptr playlist_previous_entry(); + std::shared_ptr playlist_next_entry(); }; } } diff --git a/server/src/music/PlaylistPermissions.cpp b/server/src/music/PlaylistPermissions.cpp index cb815fa..a87dc97 100644 --- a/server/src/music/PlaylistPermissions.cpp +++ b/server/src/music/PlaylistPermissions.cpp @@ -25,5 +25,5 @@ permission::PermissionType PlaylistPermissions::client_has_permissions( return permission::v2::permission_granted( this->permission_manager()->permission_value_flagged(needed_permission), this->calculate_client_specific_permissions(granted_permission, client), - flags & do_no_require_granted) ? permission::ok : needed_permission; + (flags & do_no_require_granted) == 0) ? permission::ok : granted_permission; } \ No newline at end of file diff --git a/shared b/shared index 0466698..0f920b5 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 04666982d937a1f14fe7fff22aa735afe7563525 +Subproject commit 0f920b58bccc90e825dc57ebba133e6834639cf5