Changed xml to protocol buffers

This commit is contained in:
Andreas 2013-01-03 21:40:47 +01:00
parent a752f704f6
commit fe0c1b5206
14 changed files with 546 additions and 687 deletions

View File

@ -423,6 +423,7 @@ add_subdirectory(tools/ultimate_lyrics_parser)
add_subdirectory(ext/libclementine-common)
add_subdirectory(ext/libclementine-tagreader)
add_subdirectory(ext/clementine-tagreader)
add_subdirectory(ext/libclementine-remote)
option(WITH_DEBIAN OFF)
if(WITH_DEBIAN)

View File

@ -0,0 +1,16 @@
include_directories(${PROTOBUF_INCLUDE_DIRS})
set(MESSAGES
remotecontrolmessages.proto
)
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
add_library(libclementine-remote STATIC
${PROTO_SOURCES}
)
target_link_libraries(libclementine-remote
libclementine-common
)

View File

@ -0,0 +1,73 @@
package pb.remote;
enum MsgType {
// Messages generally send from client to server
CONNECT = 0;
DISCONNECT = 1;
REQUEST_PLAYLISTS = 2;
REQUEST_PLAYLIST_SONGS = 3;
CHANGE_SONG = 4;
SET_VOLUME = 5;
// Messages send by both
PLAY = 20;
PLAYPAUSE = 21;
PAUSE = 22;
STOP = 23;
NEXT = 24;
PREV = 25;
// Messages send from server to client
INFOS = 40;
CURRENT_METAINFOS = 41;
PLAYLISTS = 42;
PLAYLIST_SONGS = 43;
KEEP_ALIVE = 44;
}
enum EngineState {
Empty = 0;
Idle = 1;
Playing = 2;
Paused = 3;
}
message Message {
required MsgType msgType = 1;
optional EngineState state = 2;
optional ClementineInfos infos = 3;
optional SongMetadata currentSong = 4;
optional int32 volume = 5;
repeated Playlist playlists = 6;
}
message ClementineInfos {
optional string version = 1;
}
message SongMetadata {
optional int32 id = 1;
optional int32 index = 2;
optional string title = 3;
optional string album = 4;
optional string artist = 5;
optional string albumartist = 6;
optional int32 track = 7;
optional int32 disc = 8;
optional string pretty_year = 9;
optional string genre = 10;
optional int32 playcount = 11;
optional string pretty_length = 12;
optional string art = 13;
}
message Playlist {
optional int32 id = 1;
optional string name = 2;
optional int32 item_count = 3;
optional bool active = 4;
repeated SongMetadata songs = 10;
}

View File

@ -62,6 +62,8 @@ endif(HAVE_BREAKPAD)
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-common)
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-tagreader)
include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-tagreader)
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-remote)
include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-remote)
cmake_policy(SET CMP0011 NEW)
include(../cmake/ParseArguments.cmake)
@ -214,9 +216,8 @@ set(SOURCES
networkremote/networkremotehelper.cpp
networkremote/networkremote.cpp
networkremote/incomingxmlparser.cpp
networkremote/outgoingxmlcreator.cpp
networkremote/remotexmltags.cpp
networkremote/incomingdataparser.cpp
networkremote/outgoingdatacreator.cpp
playlist/dynamicplaylistcontrols.cpp
playlist/playlist.cpp
@ -493,9 +494,8 @@ set(HEADERS
networkremote/networkremotehelper.h
networkremote/networkremote.h
networkremote/incomingxmlparser.h
networkremote/outgoingxmlcreator.h
networkremote/remotexmltags.h
networkremote/incomingdataparser.h
networkremote/outgoingdatacreator.h
playlist/dynamicplaylistcontrols.h
playlist/playlist.h
@ -1140,6 +1140,7 @@ add_dependencies(clementine_lib pot)
target_link_libraries(clementine_lib
libclementine-common
libclementine-tagreader
libclementine-remote
${SHA2_LIBRARIES}
${TAGLIB_LIBRARIES}
${MYGPOQT_LIBRARIES}

View File

@ -0,0 +1,130 @@
/* This file is part of Clementine.
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "incomingdataparser.h"
#include "core/logging.h"
#include "engines/enginebase.h"
#include "playlist/playlistmanager.h"
IncomingDataParser::IncomingDataParser(Application* app)
:app_(app)
{
// Connect all the signals
// due the player is in a different thread, we cannot access these functions directly
connect(this, SIGNAL(Play()),
app_->player(), SLOT(Play()));
connect(this, SIGNAL(PlayPause()),
app_->player(), SLOT(PlayPause()));
connect(this, SIGNAL(Pause()),
app_->player(), SLOT(Pause()));
connect(this, SIGNAL(Stop()),
app_->player(), SLOT(Stop()));
connect(this, SIGNAL(Next()),
app_->player(), SLOT(Next()));
connect(this, SIGNAL(Previous()),
app_->player(), SLOT(Previous()));
connect(this, SIGNAL(SetVolume(int)),
app_->player(), SLOT(SetVolume(int)));
connect(this, SIGNAL(PlayAt(int,Engine::TrackChangeFlags,bool)),
app_->player(), SLOT(PlayAt(int,Engine::TrackChangeFlags,bool)));
connect(this, SIGNAL(SetActivePlaylist(int)),
app_->playlist_manager(), SLOT(SetActivePlaylist(int)));
}
IncomingDataParser::~IncomingDataParser() {
}
bool IncomingDataParser::close_connection() {
return close_connection_;
}
void IncomingDataParser::Parse(const QByteArray& b64_data) {
close_connection_ = false;
QByteArray pb_data = QByteArray::fromBase64(b64_data);
// Parse the incoming data
pb::remote::Message msg;
if (!msg.ParseFromArray(pb_data.constData(), pb_data.size())) {
qLog(Info) << "Couldn't parse data";
return;
}
// Now check what's to do
switch (msg.msgtype()) {
case pb::remote::CONNECT: emit SendClementineInfos();
emit SendFirstData();
break;
case pb::remote::DISCONNECT: close_connection_ = true;
break;
case pb::remote::REQUEST_PLAYLISTS: emit SendAllPlaylists();
break;
case pb::remote::REQUEST_PLAYLIST_SONGS: GetPlaylistSongs(&msg);
break;
case pb::remote::SET_VOLUME: emit SetVolume(msg.volume());
break;
case pb::remote::PLAY: emit Play();
break;
case pb::remote::PLAYPAUSE: emit PlayPause();
break;
case pb::remote::PAUSE: emit Pause();
break;
case pb::remote::STOP: emit Stop();
break;
case pb::remote::NEXT: emit Next();
break;
case pb::remote::PREV: emit Previous();
break;
case pb::remote::CHANGE_SONG: ChangeSong(&msg);
break;
default: break;
}
}
void IncomingDataParser::GetPlaylistSongs(pb::remote::Message* msg) {
// Check if we got a playlist
if (msg->playlists_size() == 0)
{
return;
}
// Get the first entry and send the songs
pb::remote::Playlist playlist = msg->playlists(0);
emit SendPlaylistSongs(playlist.id());
}
void IncomingDataParser::ChangeSong(pb::remote::Message* msg) {
// Check if we got a song
if (msg->playlists_size() == 0) {
return;
}
// Get the first entry and check if there is a song
pb::remote::Playlist playlist = msg->playlists(0);
if (playlist.songs_size() == 0) {
return;
}
pb::remote::SongMetadata song = playlist.songs(0);
// Check if we need to change the playlist
if (playlist.id() != app_->playlist_manager()->active_id()) {
emit SetActivePlaylist(playlist.id());
}
// Play the selected song
emit PlayAt(song.index(), Engine::Manual, false);
}

View File

@ -1,18 +1,17 @@
#ifndef INCOMINGXMLPARSER_H
#define PARSEINCOMINGXML_H
#include <QDomDocument>
#ifndef INCOMINGDATAPARSER_H
#define INCOMINGDATAPARSER_H
#include "core/player.h"
#include "core/application.h"
#include "remotecontrolmessages.pb.h"
class IncomingXmlParser : public QObject {
class IncomingDataParser : public QObject {
Q_OBJECT
public:
IncomingXmlParser(Application* app);
~IncomingXmlParser();
IncomingDataParser(Application* app);
~IncomingDataParser();
void Parse(QString* xml_data);
void Parse(const QByteArray& pb_data);
bool close_connection();
signals:
@ -35,9 +34,8 @@ private:
Application* app_;
bool close_connection_;
void ChangeVolume(QDomNode& child);
void GetPlaylistSongs(QDomNode& child);
void ChangeSong(QDomNode& child);
void GetPlaylistSongs(pb::remote::Message* msg);
void ChangeSong(pb::remote::Message* msg);
};
#endif // PARSEINCOMINGXML_H
#endif // INCOMINGDATAPARSER_H

View File

@ -1,172 +0,0 @@
/* This file is part of Clementine.
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "incomingxmlparser.h"
#include "remotexmltags.h"
#include "core/logging.h"
#include "engines/enginebase.h"
#include "playlist/playlistmanager.h"
IncomingXmlParser::IncomingXmlParser(Application* app)
:app_(app)
{
// Connect all the signals
// due the player is in a different thread, we cannot access these functions directly
connect(this, SIGNAL(Play()),
app_->player(), SLOT(Play()));
connect(this, SIGNAL(PlayPause()),
app_->player(), SLOT(PlayPause()));
connect(this, SIGNAL(Pause()),
app_->player(), SLOT(Pause()));
connect(this, SIGNAL(Stop()),
app_->player(), SLOT(Stop()));
connect(this, SIGNAL(Next()),
app_->player(), SLOT(Next()));
connect(this, SIGNAL(Previous()),
app_->player(), SLOT(Previous()));
connect(this, SIGNAL(SetVolume(int)),
app_->player(), SLOT(SetVolume(int)));
connect(this, SIGNAL(PlayAt(int,Engine::TrackChangeFlags,bool)),
app_->player(), SLOT(PlayAt(int,Engine::TrackChangeFlags,bool)));
connect(this, SIGNAL(SetActivePlaylist(int)),
app_->playlist_manager(), SLOT(SetActivePlaylist(int)));
}
IncomingXmlParser::~IncomingXmlParser() {
}
bool IncomingXmlParser::close_connection() {
return close_connection_;
}
void IncomingXmlParser::Parse(QString *xml_data) {
close_connection_ = false;
// Load the xml data
QDomDocument doc;
doc.setContent(*xml_data);
QDomElement root = doc.documentElement();
if (root.tagName() != RemoteXmlTags::ROOT) {
qLog(Info) << "Received invalid XML data";
qLog(Debug) << xml_data;
return;
}
// The first child must be "action". It tells us what to do now
QDomNode child = root.firstChild();
if (child.toElement().tagName() != RemoteXmlTags::ACTION) {
qLog(Info) << "First node is not action! (" <<
child.toElement().tagName() << ")";
return;
}
// Now check what's to do
QString action = child.toElement().text();
qLog(Debug) << "Action = " << action;
if (action == RemoteXmlTags::CLIENT_CONNTECTED) {
emit SendClementineInfos();
emit SendFirstData()/* This file is part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/;
} else if (action == RemoteXmlTags::CLIENT_DISCONNECT) {
close_connection_ = true;
} else if (action == RemoteXmlTags::REQUEST_PLAYLISTS) {
emit SendAllPlaylists();
} else if (action == RemoteXmlTags::REQUEST_PLAYLIST_SONGS) {
GetPlaylistSongs(child);
} else if (action == RemoteXmlTags::SET_VOLUME) {
ChangeVolume(child);
} else if (action == RemoteXmlTags::PLAY) {
emit Play();
} else if (action == RemoteXmlTags::PLAYPAUSE) {
emit PlayPause();
} else if (action == RemoteXmlTags::PAUSE) {
emit Pause();
} else if (action == RemoteXmlTags::STOP) {
emit Stop();
} else if (action == RemoteXmlTags::NEXT) {
emit Next();
} else if (action == RemoteXmlTags::PREV) {
emit Previous();
} else if (action == RemoteXmlTags::CHANGE_SONG) {
ChangeSong(child);
}
}
void IncomingXmlParser::GetPlaylistSongs(QDomNode &child) {
QDomNode c = child;
// Get the Playlist Tag
while (!c.isNull() && c.toElement().tagName() != RemoteXmlTags::PLAYLIST) {
c = c.nextSibling();
}
// If the node was found, send a signal to the outgoingXmlCreator to create the songlist
if (!c.isNull()) {
QDomElement p = c.toElement();
qLog(Debug) << "Element c = " << p.tagName() <<
" Attribute: " << c.toElement().attribute(RemoteXmlTags::ID);
int id = c.toElement().attribute(RemoteXmlTags::ID).toInt();
emit SendPlaylistSongs(id);
}
}
void IncomingXmlParser::ChangeVolume(QDomNode& child) {
QDomNode c = child;
// Get the Volume Tag
while (!c.isNull() && c.toElement().tagName() != RemoteXmlTags::VOLUME) {
c = c.nextSibling();
}
// If we found it, the change the volume
if (!c.isNull()) {
emit SetVolume(c.toElement().text().toInt());
}
}
void IncomingXmlParser::ChangeSong(QDomNode& child) {
QDomNode c = child;
// Get the Volume Tag
while (!c.isNull() && c.toElement().tagName() != RemoteXmlTags::SONG) {
c = c.nextSibling();
}
// If we found it, the change the volume
if (!c.isNull()) {
// first check if we need to change the active Playlist
int selectedPlaylist = c.toElement().attribute(RemoteXmlTags::PLAYLIST).toInt();
int selectedSong = c.toElement().attribute(RemoteXmlTags::INDEX).toInt();
if (selectedPlaylist != app_->playlist_manager()->active_id()) {
emit SetActivePlaylist(selectedPlaylist);
}
emit PlayAt(selectedSong, Engine::Manual, false);
}
}

View File

@ -21,6 +21,7 @@
#include "networkremote.h"
#include <QDataStream>
#include <QSettings>
const char* NetworkRemote::kSettingsGroup = "NetworkRemote";
@ -34,8 +35,8 @@ NetworkRemote::NetworkRemote(Application* app)
NetworkRemote::~NetworkRemote() {
server_->close();
delete incoming_xml_parser_;
delete outgoing_xml_creator_;
delete incoming_data_parser_;
delete outgoing_data_creator_;
}
void NetworkRemote::ReadSettings() {
@ -52,18 +53,18 @@ void NetworkRemote::ReadSettings() {
void NetworkRemote::SetupServer() {
server_ = new QTcpServer();
incoming_xml_parser_ = new IncomingXmlParser(app_);
outgoing_xml_creator_ = new OutgoingXmlCreator(app_);
incoming_data_parser_ = new IncomingDataParser(app_);
outgoing_data_creator_ = new OutgoingDataCreator(app_);
connect(app_->current_art_loader(),
SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)),
outgoing_xml_creator_,
outgoing_data_creator_,
SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&)));
}
void NetworkRemote::StartServer() {
if (!app_) {
qLog(Error) << "Start Server called before having an application!";
qLog(Error) << "Start Server called without having an application!";
return;
}
// Check if user desires to start a network remote server
@ -91,9 +92,7 @@ void NetworkRemote::StopServer() {
}
void NetworkRemote::ReloadSettings() {
if (server_->isListening()) {
server_->close();
}
StopServer();
StartServer();
}
@ -101,29 +100,27 @@ void NetworkRemote::AcceptConnection() {
if (!clients_) {
// Create a new QList with clients
clients_ = new QList<QTcpSocket*>();
outgoing_xml_creator_->SetClients(clients_);
outgoing_data_creator_->SetClients(clients_);
// Setting up the signals, but only once
connect(incoming_xml_parser_, SIGNAL(SendClementineInfos()),
outgoing_xml_creator_, SLOT(SendClementineInfos()));
connect(incoming_xml_parser_, SIGNAL(SendFirstData()),
outgoing_xml_creator_, SLOT(SendFirstData()));
connect(incoming_xml_parser_, SIGNAL(SendAllPlaylists()),
outgoing_xml_creator_, SLOT(SendAllPlaylists()));
connect(incoming_xml_parser_, SIGNAL(SendPlaylistSongs(int)),
outgoing_xml_creator_, SLOT(SendPlaylistSongs(int)));
connect(incoming_data_parser_, SIGNAL(SendClementineInfos()),
outgoing_data_creator_, SLOT(SendClementineInfos()));
connect(incoming_data_parser_, SIGNAL(SendFirstData()),
outgoing_data_creator_, SLOT(SendFirstData()));
connect(incoming_data_parser_, SIGNAL(SendAllPlaylists()),
outgoing_data_creator_, SLOT(SendAllPlaylists()));
connect(incoming_data_parser_, SIGNAL(SendPlaylistSongs(int)),
outgoing_data_creator_, SLOT(SendPlaylistSongs(int)));
connect(app_->playlist_manager(), SIGNAL(ActiveChanged(Playlist*)),
outgoing_xml_creator_, SLOT(ActiveChanged(Playlist*)));
outgoing_data_creator_, SLOT(ActiveChanged(Playlist*)));
connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)),
outgoing_xml_creator_, SLOT(ActiveChanged(Playlist*)));
outgoing_data_creator_, SLOT(PlaylistChanged(Playlist*)));
connect(app_->player(), SIGNAL(VolumeChanged(int)), outgoing_xml_creator_,
connect(app_->player(), SIGNAL(VolumeChanged(int)), outgoing_data_creator_,
SLOT(VolumeChanged(int)));
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)),
outgoing_xml_creator_, SLOT(StateChanged(Engine::State)));
qLog(Info) << "Signals connected!";
outgoing_data_creator_, SLOT(StateChanged(Engine::State)));
}
QTcpSocket* client = server_->nextPendingConnection();
@ -134,16 +131,14 @@ void NetworkRemote::AcceptConnection() {
}
void NetworkRemote::IncomingData() {
QByteArray buf;
QTcpSocket* client = static_cast<QTcpSocket*>(QObject::sender());
buf = client->read(client->bytesAvailable());
// Now read all the data from the socket
QByteArray data;
data = client->readAll();
incoming_data_parser_->Parse(data);
QString strbuf(buf);
incoming_xml_parser_->Parse(&strbuf);
qLog(Debug) << "Data = " << buf << "Size = " << buf.size();
if (incoming_xml_parser_->close_connection()) {
if (incoming_data_parser_->close_connection()) {
client->close();
}
}

View File

@ -7,8 +7,8 @@
#include "core/player.h"
#include "core/application.h"
#include "incomingxmlparser.h"
#include "outgoingxmlcreator.h"
#include "incomingdataparser.h"
#include "outgoingdatacreator.h"
class NetworkRemote : public QThread {
Q_OBJECT
@ -29,8 +29,8 @@ public slots:
private:
QTcpServer* server_;
QList<QTcpSocket*>* clients_;
IncomingXmlParser* incoming_xml_parser_;
OutgoingXmlCreator* outgoing_xml_creator_;
IncomingDataParser* incoming_data_parser_;
OutgoingDataCreator* outgoing_data_creator_;
int port_;
bool use_remote_;
Application* app_;

View File

@ -0,0 +1,268 @@
/* This file is part of Clementine.
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "outgoingdatacreator.h"
#include "core/logging.h"
OutgoingDataCreator::OutgoingDataCreator(Application* app)
: app_(app),
clients_(NULL)
{
// Create Keep Alive Timer
keep_alive_timer_ = new QTimer(this);
connect(keep_alive_timer_, SIGNAL(timeout()), this, SLOT(SendKeepAlive()));
keep_alive_timeout_ = 10000;
}
OutgoingDataCreator::~OutgoingDataCreator() {
}
void OutgoingDataCreator::SetClients(QList<QTcpSocket*>* clients) {
clients_ = clients;
// After we got some clients, start the keep alive timer
// Default: every 10 seconds
keep_alive_timer_->start(keep_alive_timeout_);
}
void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) {
// Check if we have clients to send data to
if (!clients_ || clients_->size() == 0) {
return;
}
QTcpSocket* sock;
foreach(sock, *clients_) {
// Check if the client is still active
if (sock->state() == QTcpSocket::ConnectedState) {
std::string data = msg->SerializeAsString();
QByteArray b64_data = QByteArray::fromRawData(data.data(), data.length());
sock->write(b64_data.toBase64());
sock->write("\n");
sock->flush();
} else {
clients_->removeAt(clients_->indexOf(sock));
}
}
}
void OutgoingDataCreator::SendClementineInfos() {
// Create the general message and set the message type
pb::remote::Message msg;
msg.set_msgtype(pb::remote::INFOS);
// Now add the message specific data
SetEngineState(&msg);
QString version = QString("%1 %2").arg(QCoreApplication::applicationName(),
QCoreApplication::applicationVersion());
pb::remote::ClementineInfos *infos = msg.mutable_infos();
infos->set_version(version.toAscii());
SendDataToClients(&msg);
}
void OutgoingDataCreator::SetEngineState(pb::remote::Message *msg) {
switch(app_->player()->GetState()) {
case Engine::Idle: msg->set_state(pb::remote::Idle);
break;
case Engine::Empty: msg->set_state(pb::remote::Empty);
break;
case Engine::Playing: msg->set_state(pb::remote::Playing);
break;
case Engine::Paused: msg->set_state(pb::remote::Paused);
break;
}
}
void OutgoingDataCreator::SendAllPlaylists() {
// Get all Playlists
QList<Playlist*> playlists = app_->playlist_manager()->GetAllPlaylists();
QListIterator<Playlist*> i(playlists);
int active_playlist = app_->playlist_manager()->active_id();
// Create message
pb::remote::Message msg;
msg.set_msgtype(pb::remote::PLAYLISTS);
while(i.hasNext()) {
// Get the next Playlist
Playlist* p = i.next();
QString playlist_name = app_->playlist_manager()->GetPlaylistName(p->id());
// Create a new playlist
pb::remote::Playlist* playlist = msg.add_playlists();
playlist->set_name(playlist_name.toStdString());
playlist->set_id(p->id());
playlist->set_item_count(p->GetAllSongs().size());
playlist->set_active((p->id() == active_playlist));
}
SendDataToClients(&msg);
}
void OutgoingDataCreator::ActiveChanged(Playlist *) {
// When a playlist was changed, send the new list
SendAllPlaylists();
}
void OutgoingDataCreator::SendFirstData() {
// First Send the current song
PlaylistItemPtr item = app_->player()->GetCurrentItem();
if (!item) {
qLog(Info) << "No current item found!";
}
CurrentSongChanged(current_song_, current_uri_, current_image_);
// then the current volume
VolumeChanged(app_->player()->GetVolume());
}
void OutgoingDataCreator::CurrentSongChanged(const Song& song, const QString& uri, const QImage& img) {
current_song_ = song;
current_uri_ = uri;
current_image_ = img;
if (clients_) {
// Create the message
pb::remote::Message msg;
msg.set_msgtype(pb::remote::CURRENT_METAINFOS);
// If there is no song, create an empty node, otherwise fill it with data
int i = app_->playlist_manager()->active()->current_row();
CreateSong(msg.mutable_currentsong(), &current_song_, &uri, i);
SendDataToClients(&msg);
}
}
void OutgoingDataCreator::CreateSong(pb::remote::SongMetadata* song_metadata,
Song* song, const QString* artUri, int index) {
if (song->is_valid()) {
song_metadata->set_id(song->id());
song_metadata->set_index(index);
song_metadata->set_title( DataCommaSizeFromQString(song->PrettyTitle()));
song_metadata->set_artist(DataCommaSizeFromQString(song->artist()));
song_metadata->set_album( DataCommaSizeFromQString(song->album()));
song_metadata->set_albumartist(DataCommaSizeFromQString(song->albumartist()));
song_metadata->set_pretty_length(DataCommaSizeFromQString(song->PrettyLength()));
song_metadata->set_genre(DataCommaSizeFromQString(song->genre()));
song_metadata->set_pretty_year(DataCommaSizeFromQString(song->PrettyYear()));
song_metadata->set_track(song->track());
song_metadata->set_disc(song->disc());
song_metadata->set_playcount(song->playcount());
// Append coverart
if (!artUri->isEmpty()) {
QImage orig(QUrl(*artUri).toLocalFile());
QImage small;
// Check if we resize the image
if (orig.width() > 1000) {
small = orig.scaled(1000, 1000, Qt::KeepAspectRatio);
} else {
small = orig;
}
// Read the image in a buffer and compress it
QByteArray data;
QBuffer buf(&data);
buf.open(QIODevice::WriteOnly);
small.save(&buf, "JPG");
// Append the Data in the protocol buffer
song_metadata->set_art(data.toBase64());
buf.close();
}
}
}
void OutgoingDataCreator::VolumeChanged(int volume) {
// Create the message
pb::remote::Message msg;
msg.set_msgtype(pb::remote::SET_VOLUME);
msg.set_volume(volume);
SendDataToClients(&msg);
}
void OutgoingDataCreator::SendPlaylistSongs(int id) {
// Get the PlaylistQByteArray(data.data(), data.size()
Playlist* playlist = app_->playlist_manager()->playlist(id);
if(!playlist) {
qLog(Info) << "Could not find playlist with id = " << id;
return;
}
SongList song_list = playlist->GetAllSongs();
QListIterator<Song> i(song_list);
// Create the message and the playlist
pb::remote::Message msg;
msg.set_msgtype(pb::remote::PLAYLIST_SONGS);
// Create a new playlist
pb::remote::Playlist* pb_playlist = msg.add_playlists();
pb_playlist->set_id(id);
pb_playlist->set_item_count(playlist->GetAllSongs().size());
// Send all songs
int index = 0;
while(i.hasNext()) {
Song song = i.next();
QString art = song.art_automatic();
pb::remote::SongMetadata* pb_song = pb_playlist->add_songs();
CreateSong(pb_song, &song, &art, index);
++index;
}
SendDataToClients(&msg);
}
void OutgoingDataCreator::PlaylistChanged(Playlist* playlist) {
// If a playlist changed, then send the new songs to the client
SendPlaylistSongs(playlist->id());
}
void OutgoingDataCreator::StateChanged(Engine::State state) {
// Send state only if it changed
// When selecting next song, StateChanged is emitted, but we already know
// that we are playing
if (state == last_state_) {
return;
}
last_state_ = state;
pb::remote::Message msg;
switch (state) {
case Engine::Playing: msg.set_msgtype(pb::remote::PLAY);
break;
case Engine::Paused: msg.set_msgtype(pb::remote::PAUSE);
break;
case Engine::Empty: msg.set_msgtype(pb::remote::STOP); // Empty is called when player stopped
break;
default: msg.set_msgtype(pb::remote::STOP);
break;
};
SendDataToClients(&msg);
}
void OutgoingDataCreator::SendKeepAlive() {
pb::remote::Message msg;
msg.set_msgtype(pb::remote::KEEP_ALIVE);
SendDataToClients(&msg);
}

View File

@ -1,7 +1,6 @@
#ifndef OUTGOINGXMLCREATOR_H
#define OUTGOINGXMLCREATOR_H
#ifndef OUTGOINGDATACREATOR_H
#define OUTGOINGDATACREATOR_H
#include <QDomDocument>
#include <QTcpSocket>
#include <QImage>
#include <QList>
@ -13,12 +12,13 @@
#include "engines/engine_fwd.h"
#include "playlist/playlist.h"
#include "playlist/playlistmanager.h"
#include "remotecontrolmessages.pb.h"
class OutgoingXmlCreator : public QObject {
class OutgoingDataCreator : public QObject {
Q_OBJECT
public:
OutgoingXmlCreator(Application* app);
~OutgoingXmlCreator();
OutgoingDataCreator(Application* app);
~OutgoingDataCreator();
void SetClients(QList<QTcpSocket*>* clients);
@ -27,6 +27,7 @@ public slots:
void SendAllPlaylists();
void SendFirstData();
void SendPlaylistSongs(int id);
void PlaylistChanged(Playlist*);
void VolumeChanged(int volume);
void ActiveChanged(Playlist*);
void CurrentSongChanged(const Song& song, const QString& uri, const QImage& img);
@ -43,10 +44,9 @@ private:
QTimer* keep_alive_timer_;
int keep_alive_timeout_;
void SendDataToClients(QByteArray data);
void CreateXmlHeader(QDomDocument* doc, QString action);
QDomElement CreateSong(QDomDocument* doc, Song* song, const QString* art_uri, int index);
QDomElement CreateSongTag(QDomDocument* doc, QString tag, QString text);
void SendDataToClients(pb::remote::Message* msg);
void SetEngineState(pb::remote::Message* msg);
void CreateSong(pb::remote::SongMetadata* song_metadata, Song* song, const QString* art_uri, int index);
};
#endif // OUTGOINGXMLCREATOR_H
#endif // OUTGOINGDATACREATOR_H

View File

@ -1,309 +0,0 @@
/* This file is part of Clementine.
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "outgoingxmlcreator.h"
#include "remotexmltags.h"
#include "core/logging.h"
OutgoingXmlCreator::OutgoingXmlCreator(Application* app)
: app_(app),
clients_(NULL)
{
// Create Keep Alive Timer
keep_alive_timer_ = new QTimer(this);
connect(keep_alive_timer_, SIGNAL(timeout()), this, SLOT(SendKeepAlive()));
keep_alive_timeout_ = 10000;
}
OutgoingXmlCreator::~OutgoingXmlCreator() {
}
void OutgoingXmlCreator::SetClients(QList<QTcpSocket*>* clients) {
clients_ = clients;
// After we got some clients, start the keep alive timer
// Default: every 10 seconds
keep_alive_timer_->start(keep_alive_timeout_);
}
void OutgoingXmlCreator::SendDataToClients(QByteArray data) {
// Check if we have clients to send data to
if (!clients_) {
return;
}
QTcpSocket* sock;
foreach(sock, *clients_) {
// Check if the client is still active
if (sock->state() == QTcpSocket::ConnectedState) {
sock->write(data);
} else {
clients_->removeAt(clients_->indexOf(sock));
}
}
}
void OutgoingXmlCreator::SendClementineInfos() {
// Create the xml and header
QDomDocument doc;
CreateXmlHeader(&doc, RemoteXmlTags::SEND_INFOS);
QDomElement root = doc.documentElement();
QDomElement info = doc.createElement(RemoteXmlTags::INFOS);
QDomElement tag_version = doc.createElement(RemoteXmlTags::VERSION);
QDomText version_text = doc.createTextNode(QString("%1 %2").arg(
QCoreApplication::applicationName(),
QCoreApplication::applicationVersion()));
tag_version.appendChild(version_text);
info.appendChild(tag_version);
root.appendChild(info);
SendDataToClients(doc.toByteArray());
}
void OutgoingXmlCreator::SendAllPlaylists() {
// Get all Playlists
QList<Playlist*> playlists = app_->playlist_manager()->GetAllPlaylists();
QListIterator<Playlist*> i(playlists);
int active_playlist = app_->playlist_manager()->active_id();
// Create the xml and header
QDomDocument doc;
CreateXmlHeader(&doc, RemoteXmlTags::SEND_PLAYLISTS);
QDomElement root = doc.documentElement();
while(i.hasNext()) {
Playlist* p = i.next();
// Append the id
QDomElement node_playlist = doc.createElement(RemoteXmlTags::PLAYLIST);
node_playlist.setAttribute(RemoteXmlTags::ID, p->id());
node_playlist.setAttribute(RemoteXmlTags::CURRENT_ROW, p->current_row());
// get the name of the playlist
QDomElement playlist_elem = doc.createElement(RemoteXmlTags::PLAYLIST_NAME);
QString playlist_name = app_->playlist_manager()->GetPlaylistName(p->id());
QDomText playlist_text = doc.createTextNode(playlist_name);
// Add it to the playlist node
playlist_elem.appendChild(playlist_text);
node_playlist.appendChild(playlist_elem);
// get the item count
QDomElement playlist_count = doc.createElement(RemoteXmlTags::PLAYLIST_ITEMS);
QDomText item_count = doc.createTextNode(QString::number(p->GetAllSongs().size()));
playlist_count.appendChild(item_count);
node_playlist.appendChild(playlist_count);
// Check if the Playlist is active
QDomElement playlist_active_elem = doc.createElement(RemoteXmlTags::PLAYLIST_ACTIVE);
QDomText text_active = doc.createTextNode( QString::number( (p->id() == active_playlist) ) );
playlist_active_elem.appendChild(text_active);
node_playlist.appendChild(playlist_active_elem);
// Append it to the root node
root.appendChild(node_playlist);
}
SendDataToClients(doc.toByteArray());
}
void OutgoingXmlCreator::ActiveChanged(Playlist *) {
// When a playlist was changed, send the new list
SendAllPlaylists();
}
void OutgoingXmlCreator::SendFirstData() {
// First Send the current song
PlaylistItemPtr item = app_->player()->GetCurrentItem();
if (!item) {
qLog(Info) << "No current item found!";
}
CurrentSongChanged(current_song_, current_uri_, current_image_);
// then the current volume
VolumeChanged(app_->player()->GetVolume());
}
void OutgoingXmlCreator::CurrentSongChanged(const Song& song, const QString& uri, const QImage& img) {
current_song_ = song;
current_uri_ = uri;
current_image_ = img;
if (clients_) {
// Create the xml and header
QDomDocument doc;
CreateXmlHeader(&doc, RemoteXmlTags::SEND_METAINFOS);
QDomElement root = doc.documentElement();
// If there is no song, create an empty node, otherwise fill it with data
int i = app_->playlist_manager()->active()->current_row();
root.appendChild(CreateSong(&doc, &current_song_, &uri, i));
SendDataToClients(doc.toByteArray());
}
}
QDomElement OutgoingXmlCreator::CreateSong(QDomDocument* doc, Song* song, const QString* artUri, int index) {
QDomElement nodeSong = doc->createElement(RemoteXmlTags::SONG);
if (song->is_valid()) {
QString pretty_length = song->PrettyLength();
QString pretty_year = song->PrettyYear();
nodeSong.setAttribute(RemoteXmlTags::ID, song->id());
nodeSong.setAttribute(RemoteXmlTags::INDEX, index);
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_TITLE, song->PrettyTitle() ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_ARTIST, song->artist() ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_ALBUM, song->album() ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_ALBUMARTIST, song->albumartist() ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_LENGTH, pretty_length ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_GENRE, song->genre() ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_YEAR, pretty_year ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_TRACK, QString::number(song->track()) ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_DISC, QString::number(song->disc()) ));
nodeSong.appendChild(CreateSongTag(doc, RemoteXmlTags::SONG_PLAYCOUNT, QString::number(song->playcount()) ));
// Append coverart
if (!artUri->isEmpty()) {
QImage orig(QUrl(*artUri).toLocalFile());
QImage small;
// Check if we resize the image
if (orig.width() > 1000) {
small = orig.scaled(1000, 1000, Qt::KeepAspectRatio);
} else {
small = orig;
}
// Read the image in a buffer and compress it
QByteArray data;
QBuffer buf(&data);
buf.open(QIODevice::WriteOnly);
small.save(&buf, "JPG");
// Append the Data in the xml file
QDomElement art = doc->createElement(RemoteXmlTags::SONG_ART);
// Art must be sent in Base64 encoding, because the raw data escapes QString and
// not all data is transfered
QDomText art_content = doc->createTextNode(data.toBase64());
art.appendChild(art_content);
nodeSong.appendChild(art);
buf.close();
}
}
return nodeSong;
}
QDomElement OutgoingXmlCreator::CreateSongTag(QDomDocument* doc, QString tag, QString text) {
QDomElement elem = doc->createElement(tag);
QDomText elem_text = doc->createTextNode(text);
elem.appendChild(elem_text);
return elem;
}
void OutgoingXmlCreator::VolumeChanged(int volume) {
// Create the xml and header
QDomDocument doc;
CreateXmlHeader(&doc, RemoteXmlTags::VOLUME);
QDomElement root = doc.documentElement();
QDomElement volume_elem = doc.createElement(RemoteXmlTags::VOLUME);
QDomText volume_text = doc.createTextNode(QString::number(volume));
volume_elem.appendChild(volume_text);
root.appendChild(volume_elem);
SendDataToClients(doc.toByteArray());
}
void OutgoingXmlCreator::SendPlaylistSongs(int id) {
// Get the Playlist
Playlist* playlist = app_->playlist_manager()->playlist(id);
if(!playlist) {
qLog(Info) << "Could not find playlist with id = " << id;
return;
}
SongList song_list = playlist->GetAllSongs();
QListIterator<Song> i(song_list);
// Create the xml and header
QDomDocument doc;
CreateXmlHeader(&doc, RemoteXmlTags::SEND_PLAYLIST_SONGS);
QDomElement root = doc.documentElement();
QDomElement playlist_elem = doc.createElement(RemoteXmlTags::PLAYLIST);
playlist_elem.setAttribute(RemoteXmlTags::ID, id);
// Send all songs
int index = 0;
while(i.hasNext()) {
Song song = i.next();
QString art = song.art_automatic();
playlist_elem.appendChild(CreateSong(&doc, &song, &art, index));
++index;
}
root.appendChild(playlist_elem);
SendDataToClients(doc.toByteArray());
}
void OutgoingXmlCreator::StateChanged(Engine::State state) {
// Send state only if it changed
// When selecting next song, StateChanged is emitted, but we already know
// that we are playing
if (state == last_state_) {
return;
}
last_state_ = state;
QDomDocument doc;
QString action;
switch (state) {
case Engine::Playing: action = RemoteXmlTags::PLAY;
break;
case Engine::Paused: action = RemoteXmlTags::PAUSE;
break;
case Engine::Empty: action = RemoteXmlTags::STOP; // Empty is called when player stopped
break;
default: break;
};
CreateXmlHeader(&doc, action);
SendDataToClients(doc.toByteArray());
}
void OutgoingXmlCreator::CreateXmlHeader(QDomDocument *doc, QString action)
{
// Create the header
QDomProcessingInstruction xml_header = doc->createProcessingInstruction("xml", "version=\"1.0\"");
doc->appendChild(xml_header);
// Create the root
QDomElement root = doc->createElement(RemoteXmlTags::ROOT);
doc->appendChild(root);
// Append the action
QDomElement xml_action = doc->createElement(RemoteXmlTags::ACTION);
QDomText text_action = doc->createTextNode(action);
xml_action.appendChild(text_action);
root.appendChild(xml_action);
}
void OutgoingXmlCreator::SendKeepAlive() {
QDomDocument doc;
CreateXmlHeader(&doc, RemoteXmlTags::KEEP_ALIVE);
SendDataToClients(doc.toByteArray());
}

View File

@ -1,77 +0,0 @@
/* This file is part of Clementine.
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "remotexmltags.h"
// Nodes
const QString RemoteXmlTags::ROOT = "ClementineRemote"; // The root node of every xml
const QString RemoteXmlTags::ACTION = "Action"; // The Node where the action is specified
const QString RemoteXmlTags::SONG = "Song"; // The Node for song metadata
const QString RemoteXmlTags::INFOS = "Infos";
const QString RemoteXmlTags::VERSION = "Version";
// Playlist
const QString RemoteXmlTags::PLAYLIST = "Playlist";
const QString RemoteXmlTags::PLAYLIST_NAME = "name";
const QString RemoteXmlTags::PLAYLIST_ITEMS = "items";
const QString RemoteXmlTags::PLAYLIST_ACTIVE = "active";
// Attributes
const QString RemoteXmlTags::ID = "id";
const QString RemoteXmlTags::INDEX = "index";
const QString RemoteXmlTags::CURRENT_ROW = "currentRow";
// Actions
const QString RemoteXmlTags::CLIENT_CONNTECTED = "Connect"; // A client wants to connect
const QString RemoteXmlTags::CLIENT_DISCONNECT = "Disconnect"; // A Client wants to disconnect
const QString RemoteXmlTags::SEND_INFOS = "Info";
const QString RemoteXmlTags::SEND_METAINFOS = "CurrentMetainfos"; // Sends the metainfos of the current song
const QString RemoteXmlTags::SEND_PLAYLISTS = "Playlists";
const QString RemoteXmlTags::SEND_PLAYLIST_SONGS = "PlaylistSongs";
const QString RemoteXmlTags::REQUEST_PLAYLISTS = "GetPlaylists";
const QString RemoteXmlTags::REQUEST_PLAYLIST_SONGS = "GetPlaylistSongs";
const QString RemoteXmlTags::PLAY = "Play";
const QString RemoteXmlTags::PLAYPAUSE = "PlayPause";
const QString RemoteXmlTags::PAUSE = "Pause";
const QString RemoteXmlTags::STOP = "Stop";
const QString RemoteXmlTags::NEXT = "Next";
const QString RemoteXmlTags::PREV = "Prev";
const QString RemoteXmlTags::CHANGE_SONG = "ChangeSong";
const QString RemoteXmlTags::SET_VOLUME = "SetVolume";
const QString RemoteXmlTags::KEEP_ALIVE = "KeepAlive";
// Tags in Song
const QString RemoteXmlTags::SONG_TITLE = "Title";
const QString RemoteXmlTags::SONG_ARTIST = "Artist";
const QString RemoteXmlTags::SONG_ALBUM = "Album";
const QString RemoteXmlTags::SONG_ALBUMARTIST = "AlbumArtist";
const QString RemoteXmlTags::SONG_LENGTH = "Length";
const QString RemoteXmlTags::SONG_GENRE = "Genre";
const QString RemoteXmlTags::SONG_YEAR = "Year";
const QString RemoteXmlTags::SONG_TRACK = "Track";
const QString RemoteXmlTags::SONG_DISC = "Disc";
const QString RemoteXmlTags::SONG_PLAYCOUNT = "Playcount";
const QString RemoteXmlTags::SONG_ART = "Art";
const QString RemoteXmlTags::VOLUME = "Volume";
RemoteXmlTags::RemoteXmlTags() {
}
RemoteXmlTags::~RemoteXmlTags() {
}

View File

@ -1,65 +0,0 @@
#ifndef REMOTEXMLTAGS_H
#define REMOTEXMLTAGS_H
#include <QString>
class RemoteXmlTags {
public:
RemoteXmlTags();
~RemoteXmlTags();
// Nodes
const static QString ROOT; // The root node of every xml
const static QString ACTION; // The Node where the action is specified
const static QString SONG; // The Node for song metadata
const static QString INFOS; // Info Node
const static QString VERSION; // Holds the Version of Clementine
// Playlist
const static QString PLAYLIST; // Node for Playlist
const static QString PLAYLIST_NAME;
const static QString PLAYLIST_ITEMS; // How may items are in the playlist?
const static QString PLAYLIST_ACTIVE; // Is the playlist active?
// Attributes
const static QString ID;
const static QString INDEX; // Index on the Playlist
const static QString CURRENT_ROW;
// Actions
const static QString CLIENT_CONNTECTED; // A client wants to connect
const static QString CLIENT_DISCONNECT; // A Client wants to disconnect
const static QString SEND_INFOS; // Sending general Infos to client (e.g. Version)
const static QString SEND_METAINFOS; // Sends the metainfos of the current song
const static QString SEND_PLAYLISTS; // Send the List of Playlists to client
const static QString SEND_PLAYLIST_SONGS; // Send the list of Songs to client
const static QString REQUEST_PLAYLISTS; // A client request for playlists
const static QString REQUEST_PLAYLIST_SONGS; // A clients wants to get all the songs in a playlist
const static QString SET_VOLUME;
const static QString PLAY;
const static QString PLAYPAUSE;
const static QString PAUSE;
const static QString STOP;
const static QString NEXT;
const static QString PREV;
const static QString CHANGE_SONG;
const static QString KEEP_ALIVE;
// Tags in Song
const static QString SONG_TITLE;
const static QString SONG_ARTIST;
const static QString SONG_ALBUM;
const static QString SONG_ALBUMARTIST;
const static QString SONG_LENGTH;
const static QString SONG_GENRE;
const static QString SONG_YEAR;
const static QString SONG_TRACK;
const static QString SONG_DISC;
const static QString SONG_PLAYCOUNT;
const static QString SONG_ART;
const static QString VOLUME;
};
#endif // REMOTEXMLTAGS_H