Network remote can now send songs to clients.
This commit is contained in:
parent
cd85b67ebc
commit
c09d77f413
@ -15,6 +15,7 @@ enum MsgType {
|
||||
OPEN_PLAYLIST = 10;
|
||||
CLOSE_PLAYLIST = 11;
|
||||
GET_LYRICS = 14;
|
||||
DOWNLOAD_SONGS = 15;
|
||||
// Lastfm
|
||||
LOVE = 12;
|
||||
BAN = 13;
|
||||
@ -44,6 +45,7 @@ enum MsgType {
|
||||
ACTIVE_PLAYLIST_CHANGED = 47;
|
||||
FIRST_DATA_SENT_COMPLETE = 48;
|
||||
LYRICS = 49;
|
||||
SONG_FILE_CHUNK = 50;
|
||||
}
|
||||
|
||||
// Valid Engine states
|
||||
@ -70,6 +72,9 @@ message SongMetadata {
|
||||
optional string pretty_length = 12;
|
||||
optional bytes art = 13;
|
||||
optional int32 length = 14;
|
||||
optional bool is_local = 15;
|
||||
optional string filename = 16;
|
||||
optional int32 file_size = 17;
|
||||
}
|
||||
|
||||
// Playlist informations
|
||||
@ -167,6 +172,7 @@ message ResponseUpdateTrackPosition {
|
||||
message RequestConnect {
|
||||
optional int32 auth_code = 1;
|
||||
optional bool send_playlist_songs = 2;
|
||||
optional bool downloader = 3;
|
||||
}
|
||||
|
||||
// Respone, why the connection was closed
|
||||
@ -174,6 +180,7 @@ enum ReasonDisconnect {
|
||||
Server_Shutdown = 1;
|
||||
Wrong_Auth_Code = 2;
|
||||
Not_Authenticated = 3;
|
||||
Download_Forbidden = 4;
|
||||
}
|
||||
message ResponseDisconnect {
|
||||
optional ReasonDisconnect reason_disconnect = 1;
|
||||
@ -224,9 +231,30 @@ message Lyric {
|
||||
optional string content = 3;
|
||||
}
|
||||
|
||||
// Message request for downloading songs
|
||||
enum DownloadItem {
|
||||
CurrentItem = 1;
|
||||
ItemAlbum = 2;
|
||||
APlaylist = 3;
|
||||
}
|
||||
message RequestDownloadSongs {
|
||||
optional DownloadItem download_item = 1;
|
||||
optional int32 playlist_id = 2;
|
||||
}
|
||||
|
||||
message ResponseSongFileChunk {
|
||||
optional int32 chunk_number = 1;
|
||||
optional int32 chunk_count = 2;
|
||||
optional int32 file_number = 3;
|
||||
optional int32 file_count = 4;
|
||||
optional SongMetadata song_metadata = 6; // only sent with first chunk!
|
||||
optional bytes data = 7;
|
||||
optional int32 size = 8;
|
||||
}
|
||||
|
||||
// The message itself
|
||||
message Message {
|
||||
optional int32 version = 1 [default=8];
|
||||
optional int32 version = 1 [default=9];
|
||||
optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
|
||||
|
||||
optional RequestConnect request_connect = 21;
|
||||
@ -239,6 +267,7 @@ message Message {
|
||||
optional RequestRemoveSongs request_remove_songs = 26;
|
||||
optional RequestOpenPlaylist request_open_playlist = 28;
|
||||
optional RequestClosePlaylist request_close_playlist = 29;
|
||||
optional RequestDownloadSongs request_download_songs = 31;
|
||||
|
||||
optional Repeat repeat = 13;
|
||||
optional Shuffle shuffle = 14;
|
||||
@ -252,4 +281,5 @@ message Message {
|
||||
optional ResponseDisconnect response_disconnect = 22;
|
||||
optional ResponseActiveChanged response_active_changed = 24;
|
||||
optional ResponseLyrics response_lyrics = 30;
|
||||
optional ResponseSongFileChunk response_song_file_chunk = 32;
|
||||
}
|
||||
|
@ -93,9 +93,11 @@ bool IncomingDataParser::close_connection() {
|
||||
void IncomingDataParser::Parse(const pb::remote::Message& msg) {
|
||||
close_connection_ = false;
|
||||
|
||||
RemoteClient* client = qobject_cast<RemoteClient*>(sender());
|
||||
|
||||
// Now check what's to do
|
||||
switch (msg.type()) {
|
||||
case pb::remote::CONNECT: ClientConnect(msg);
|
||||
case pb::remote::CONNECT: ClientConnect(client, msg);
|
||||
break;
|
||||
case pb::remote::DISCONNECT: close_connection_ = true;
|
||||
break;
|
||||
@ -144,6 +146,9 @@ void IncomingDataParser::Parse(const pb::remote::Message& msg) {
|
||||
break;
|
||||
case pb::remote::GET_LYRICS: emit GetLyrics();
|
||||
break;
|
||||
case pb::remote::DOWNLOAD_SONGS:
|
||||
emit SendSongs(msg.request_download_songs(), client);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@ -235,7 +240,8 @@ void IncomingDataParser::RemoveSongs(const pb::remote::Message& msg) {
|
||||
emit RemoveSongs(songs);
|
||||
}
|
||||
|
||||
void IncomingDataParser::ClientConnect(const pb::remote::Message& msg) {
|
||||
void IncomingDataParser::ClientConnect(RemoteClient* client, const pb::remote::Message& msg) {
|
||||
|
||||
// Always sned the Clementine infos
|
||||
emit SendClementineInfo();
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "core/player.h"
|
||||
#include "core/application.h"
|
||||
#include "remotecontrolmessages.pb.h"
|
||||
#include "remoteclient.h"
|
||||
|
||||
class IncomingDataParser : public QObject {
|
||||
Q_OBJECT
|
||||
@ -43,6 +44,7 @@ signals:
|
||||
void InsertUrls(const QList<QUrl>& urls, int pos, bool play_now, bool enqueue);
|
||||
void RemoveSongs(const QList<int>& indices);
|
||||
void SeekTo(int seconds);
|
||||
void SendSongs(const pb::remote::RequestDownloadSongs& request, RemoteClient* client);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
@ -54,7 +56,7 @@ private:
|
||||
void SetShuffleMode(const pb::remote::Shuffle& shuffle);
|
||||
void InsertUrls(const pb::remote::Message& msg);
|
||||
void RemoveSongs(const pb::remote::Message& msg);
|
||||
void ClientConnect(const pb::remote::Message& msg);
|
||||
void ClientConnect(RemoteClient* client, const pb::remote::Message& msg);
|
||||
void SendPlaylists(const pb::remote::Message& msg);
|
||||
void OpenPlaylist(const pb::remote::Message& msg);
|
||||
void ClosePlaylist(const pb::remote::Message& msg);
|
||||
|
@ -158,6 +158,11 @@ void NetworkRemote::AcceptConnection() {
|
||||
|
||||
connect(incoming_data_parser_.get(), SIGNAL(GetLyrics()),
|
||||
outgoing_data_creator_.get(), SLOT(GetLyrics()));
|
||||
|
||||
connect(incoming_data_parser_.get(),
|
||||
SIGNAL(SendSongs(pb::remote::RequestDownloadSongs,RemoteClient*)),
|
||||
outgoing_data_creator_.get(),
|
||||
SLOT(SendSongs(pb::remote::RequestDownloadSongs,RemoteClient*)));
|
||||
}
|
||||
|
||||
QTcpServer* server = qobject_cast<QTcpServer*>(sender());
|
||||
|
@ -22,6 +22,9 @@
|
||||
#include "networkremote.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "library/librarybackend.h"
|
||||
|
||||
const quint32 OutgoingDataCreator::kFileChunkSize = 100000; // in Bytes
|
||||
|
||||
OutgoingDataCreator::OutgoingDataCreator(Application* app)
|
||||
: app_(app),
|
||||
@ -137,12 +140,17 @@ void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) {
|
||||
|
||||
RemoteClient* client;
|
||||
foreach(client, *clients_) {
|
||||
// Do not send data to downloaders
|
||||
if (client->isDownloader())
|
||||
continue;
|
||||
|
||||
// Check if the client is still active
|
||||
if (client->State() == QTcpSocket::ConnectedState) {
|
||||
client->SendData(msg);
|
||||
} else {
|
||||
clients_->removeAt(clients_->indexOf(client));
|
||||
delete client;
|
||||
qDebug() << "Client deleted";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -334,6 +342,9 @@ void OutgoingDataCreator::CreateSong(
|
||||
song_metadata->set_track(song.track());
|
||||
song_metadata->set_disc(song.disc());
|
||||
song_metadata->set_playcount(song.playcount());
|
||||
song_metadata->set_is_local(song.url().isLocalFile());
|
||||
song_metadata->set_filename(DataCommaSizeFromQString(song.basefilename()));
|
||||
song_metadata->set_file_size(song.filesize());
|
||||
|
||||
// Append coverart
|
||||
if (!art.isNull()) {
|
||||
@ -529,3 +540,85 @@ void OutgoingDataCreator::SendLyrics(int id, const SongInfoFetcher::Result& resu
|
||||
|
||||
results_.take(id);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendSongs(const pb::remote::RequestDownloadSongs &request,
|
||||
RemoteClient* client) {
|
||||
switch (request.download_item()) {
|
||||
case pb::remote::CurrentItem:
|
||||
SendSingleSong(client, current_song_, 1, 1);
|
||||
break;
|
||||
case pb::remote::ItemAlbum:
|
||||
SendAlbum(client, current_song_);
|
||||
break;
|
||||
case pb::remote::APlaylist:
|
||||
SendPlaylist(client, request.playlist_id());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
client->DisconnectClient(pb::remote::Server_Shutdown);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
|
||||
int song_no, int song_count) {
|
||||
// Only local files!!!
|
||||
if (!song.url().isLocalFile())
|
||||
return;
|
||||
|
||||
// Calculate the number of chunks
|
||||
int chunk_count = qRound((song.filesize() / kFileChunkSize) + 0.5);
|
||||
int chunk_number = 1;
|
||||
|
||||
// Open the file
|
||||
QFile file(song.url().toLocalFile());
|
||||
file.open(QIODevice::ReadOnly);
|
||||
|
||||
while (!file.atEnd()) {
|
||||
QByteArray data = file.read(kFileChunkSize);
|
||||
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::SONG_FILE_CHUNK);
|
||||
|
||||
pb::remote::ResponseSongFileChunk* chunk = msg.mutable_response_song_file_chunk();
|
||||
chunk->set_chunk_count(chunk_count);
|
||||
chunk->set_chunk_number(chunk_number);
|
||||
chunk->set_file_count(song_count);
|
||||
chunk->set_file_number(song_no);
|
||||
chunk->set_size(file.size());
|
||||
chunk->set_data(data.constData(), data.size());
|
||||
if (chunk_number == 1) {
|
||||
int i = app_->playlist_manager()->active()->current_row();
|
||||
CreateSong(
|
||||
song, song.image(), i,
|
||||
msg.mutable_response_song_file_chunk()->mutable_song_metadata());
|
||||
}
|
||||
|
||||
msg.set_version(msg.default_instance().version());
|
||||
client->SendData(&msg);
|
||||
|
||||
chunk_number++;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendAlbum(RemoteClient *client, const Song &song) {
|
||||
SongList album = app_->library_backend()->GetSongsByAlbum(song.album());
|
||||
|
||||
foreach (Song s, album) {
|
||||
SendSingleSong(client, s, album.indexOf(s)+1, album.size());
|
||||
}
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendPlaylist(RemoteClient *client, int playlist_id) {
|
||||
Playlist* playlist = app_->playlist_manager()->playlist(playlist_id);
|
||||
if(!playlist) {
|
||||
qLog(Info) << "Could not find playlist with id = " << playlist_id;
|
||||
return;
|
||||
}
|
||||
SongList song_list = playlist->GetAllSongs();
|
||||
|
||||
foreach (Song s, song_list) {
|
||||
SendSingleSong(client, s, song_list.indexOf(s)+1, song_list.size());
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include "remoteclient.h"
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
|
||||
typedef QList<SongInfoProvider*> ProviderList;
|
||||
|
||||
class OutgoingDataCreator : public QObject {
|
||||
@ -33,6 +32,8 @@ public:
|
||||
OutgoingDataCreator(Application* app);
|
||||
~OutgoingDataCreator();
|
||||
|
||||
static const quint32 kFileChunkSize;
|
||||
|
||||
void SetClients(QList<RemoteClient*>* clients);
|
||||
|
||||
public slots:
|
||||
@ -57,6 +58,7 @@ public slots:
|
||||
void DisconnectAllClients();
|
||||
void GetLyrics();
|
||||
void SendLyrics(int id, const SongInfoFetcher::Result& result);
|
||||
void SendSongs(const pb::remote::RequestDownloadSongs& request, RemoteClient* client);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
@ -83,6 +85,9 @@ private:
|
||||
pb::remote::SongMetadata* song_metadata);
|
||||
void CheckEnabledProviders();
|
||||
SongInfoProvider* ProviderByName(const QString& name) const;
|
||||
void SendSingleSong(RemoteClient* client, const Song& song, int song_no, int song_count);
|
||||
void SendAlbum(RemoteClient* client, const Song& song);
|
||||
void SendPlaylist(RemoteClient* client, int playlist_id);
|
||||
};
|
||||
|
||||
#endif // OUTGOINGDATACREATOR_H
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
: app_(app),
|
||||
downloader_(false),
|
||||
client_(client)
|
||||
{
|
||||
// Open the buffer
|
||||
@ -34,6 +35,8 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
|
||||
// Connect to the slot IncomingData when receiving data
|
||||
connect(client, SIGNAL(readyRead()), this, SLOT(IncomingData()));
|
||||
//connect(client, SIGNAL(disconnected()),
|
||||
// client, SLOT(deleteLater()));
|
||||
|
||||
// Check if we use auth code
|
||||
QSettings s;
|
||||
@ -41,6 +44,7 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
s.beginGroup(NetworkRemote::kSettingsGroup);
|
||||
use_auth_code_ = s.value("use_auth_code", false).toBool();
|
||||
auth_code_ = s.value("auth_code", 0).toInt();
|
||||
allow_downloads_ = s.value("allow_downloads", false).toBool();
|
||||
|
||||
s.endGroup();
|
||||
|
||||
@ -50,6 +54,11 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
|
||||
|
||||
RemoteClient::~RemoteClient() {
|
||||
delete client_;
|
||||
}
|
||||
|
||||
void RemoteClient::setDownloader(bool downloader) {
|
||||
downloader_ = downloader;
|
||||
}
|
||||
|
||||
void RemoteClient::IncomingData() {
|
||||
@ -105,6 +114,24 @@ void RemoteClient::ParseMessage(const QByteArray &data) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if downloads are allowed
|
||||
if (msg.type() == pb::remote::DOWNLOAD_SONGS && !allow_downloads_) {
|
||||
DisconnectClient(pb::remote::Download_Forbidden);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type() == pb::remote::DISCONNECT) {
|
||||
client_->flush();
|
||||
client_->close();
|
||||
qDebug() << "Client disconnected";
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type() == pb::remote::DOWNLOAD_SONGS) {
|
||||
qDebug() << "Downloader";
|
||||
setDownloader(true);
|
||||
}
|
||||
|
||||
// Check if the client has sent the correct auth code
|
||||
if (!authenticated_) {
|
||||
DisconnectClient(pb::remote::Not_Authenticated);
|
||||
@ -132,19 +159,25 @@ void RemoteClient::DisconnectClient(pb::remote::ReasonDisconnect reason) {
|
||||
|
||||
// Sends data to client without check if authenticated
|
||||
void RemoteClient::SendDataToClient(pb::remote::Message *msg) {
|
||||
// Serialize the message
|
||||
std::string data = msg->SerializeAsString();
|
||||
|
||||
// Check if we are still connected
|
||||
if (client_->state() == QTcpSocket::ConnectedState) {
|
||||
// Serialize the message
|
||||
std::string data = msg->SerializeAsString();
|
||||
|
||||
// write the length of the data first
|
||||
QDataStream s(client_);
|
||||
s << qint32(data.length());
|
||||
s.writeRawData(data.data(), data.length());
|
||||
if (downloader_) {
|
||||
// Don't use QDataSteam for large files
|
||||
client_->write(data.data(), data.length());
|
||||
} else {
|
||||
s.writeRawData(data.data(), data.length());
|
||||
}
|
||||
|
||||
// Do NOT flush data here! If the client is already disconnected, it
|
||||
// causes a SIGPIPE termination!!!
|
||||
} else {
|
||||
qDebug() << "Closed";
|
||||
client_->close();
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include <QBuffer>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "incomingdataparser.h"
|
||||
#include "remotecontrolmessages.pb.h"
|
||||
|
||||
class RemoteClient : public QObject {
|
||||
Q_OBJECT
|
||||
@ -17,6 +17,9 @@ public:
|
||||
// This method checks if client is authenticated before sending the data
|
||||
void SendData(pb::remote::Message* msg);
|
||||
QAbstractSocket::SocketState State();
|
||||
void setDownloader(bool downloader);
|
||||
bool isDownloader() { return downloader_; }
|
||||
void DisconnectClient(pb::remote::ReasonDisconnect reason);
|
||||
|
||||
private slots:
|
||||
void IncomingData();
|
||||
@ -26,7 +29,6 @@ signals:
|
||||
|
||||
private:
|
||||
void ParseMessage(const QByteArray& data);
|
||||
void DisconnectClient(pb::remote::ReasonDisconnect reason);
|
||||
|
||||
// Sends data to client without check if authenticated
|
||||
void SendDataToClient(pb::remote::Message* msg);
|
||||
@ -36,6 +38,8 @@ private:
|
||||
bool use_auth_code_;
|
||||
int auth_code_;
|
||||
bool authenticated_;
|
||||
bool allow_downloads_;
|
||||
bool downloader_;
|
||||
|
||||
QTcpSocket* client_;
|
||||
bool reading_protobuf_;
|
||||
|
@ -64,6 +64,8 @@ void NetworkRemoteSettingsPage::Load() {
|
||||
ui_->use_auth_code->setChecked(s.value("use_auth_code", false).toBool());
|
||||
ui_->auth_code->setValue(s.value("auth_code", qrand() % 100000).toInt());
|
||||
|
||||
ui_->allow_downloads->setChecked(s.value("allow_downloads", false).toBool());
|
||||
|
||||
s.endGroup();
|
||||
|
||||
QPixmap android_qr_code(":clementine_remote_qr.png");
|
||||
@ -95,6 +97,7 @@ void NetworkRemoteSettingsPage::Save() {
|
||||
s.setValue("only_non_public_ip", ui_->only_non_public_ip->isChecked());
|
||||
s.setValue("use_auth_code", ui_->use_auth_code->isChecked());
|
||||
s.setValue("auth_code", ui_->auth_code->value());
|
||||
s.setValue("allow_downloads", ui_->allow_downloads->isChecked());
|
||||
|
||||
s.endGroup();
|
||||
|
||||
|
@ -110,6 +110,20 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="allow_downloads">
|
||||
<property name="toolTip">
|
||||
<string>Allow a client to download music from this computer.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Allow downloads</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user