mirror of
https://github.com/clementine-player/Clementine
synced 2024-12-22 15:58:45 +01:00
All changes for ClemRemote v1.0 (in one go)
This commit is contained in:
parent
d7966c8285
commit
9714b0632d
@ -15,7 +15,7 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Note: this file is licensed under the Apache License instead of GPL, so
|
||||
// Note: this file is licensed under the Apache License instead of GPL, so
|
||||
// 3rd party applications or libraries can use another license besides GPL.
|
||||
|
||||
syntax = "proto2";
|
||||
@ -36,6 +36,7 @@ enum MsgType {
|
||||
REMOVE_SONGS = 9;
|
||||
OPEN_PLAYLIST = 10;
|
||||
CLOSE_PLAYLIST = 11;
|
||||
UPDATE_PLAYLIST = 60;
|
||||
GET_LYRICS = 14;
|
||||
DOWNLOAD_SONGS = 15;
|
||||
SONG_OFFER_RESPONSE = 16;
|
||||
@ -46,6 +47,10 @@ enum MsgType {
|
||||
GET_LIBRARY = 18;
|
||||
RATE_SONG = 19;
|
||||
GLOBAL_SEARCH = 100;
|
||||
REQUEST_SAVED_RADIOS = 110;
|
||||
// access Files from remote control
|
||||
REQUEST_FILES = 200;
|
||||
APPEND_FILES = 201;
|
||||
|
||||
// Messages send by both
|
||||
DISCONNECT = 2;
|
||||
@ -79,6 +84,8 @@ enum MsgType {
|
||||
GLOBAL_SEARCH_RESULT = 54;
|
||||
TRANSCODING_FILES = 55;
|
||||
GLOBAL_SEARCH_STATUS = 56;
|
||||
// access Files from remote control
|
||||
LIST_FILES = 202;
|
||||
}
|
||||
|
||||
// Valid Engine states
|
||||
@ -113,8 +120,8 @@ message SongMetadata {
|
||||
STREAM = 99;
|
||||
}
|
||||
|
||||
optional int32 id = 1; // unique id of the song
|
||||
optional int32 index = 2; // Index of the current row of the active playlist
|
||||
optional int32 id = 1; // unique id of the song
|
||||
optional int32 index = 2; // Index of the current row of the active playlist
|
||||
optional string title = 3;
|
||||
optional string album = 4;
|
||||
optional string artist = 5;
|
||||
@ -130,7 +137,7 @@ message SongMetadata {
|
||||
optional bool is_local = 15;
|
||||
optional string filename = 16;
|
||||
optional int32 file_size = 17;
|
||||
optional float rating = 18; // 0 (0 stars) to 1 (5 stars)
|
||||
optional float rating = 18; // 0 (0 stars) to 1 (5 stars)
|
||||
optional string url = 19;
|
||||
optional string art_automatic = 20;
|
||||
optional string art_manual = 21;
|
||||
@ -144,6 +151,7 @@ message Playlist {
|
||||
optional int32 item_count = 3;
|
||||
optional bool active = 4;
|
||||
optional bool closed = 5;
|
||||
optional bool favorite = 6;
|
||||
}
|
||||
|
||||
// Valid Repeatmodes
|
||||
@ -200,6 +208,11 @@ message Shuffle {
|
||||
message ResponseClementineInfo {
|
||||
optional string version = 1;
|
||||
optional EngineState state = 2;
|
||||
|
||||
optional bool allow_downloads = 3;
|
||||
|
||||
// allowed extensions for REQUEST_FILES and LIST_FILES
|
||||
repeated string files_music_extensions = 4;
|
||||
}
|
||||
|
||||
// The current song played
|
||||
@ -210,12 +223,13 @@ message ResponseCurrentMetadata {
|
||||
// The playlists in clementine
|
||||
message ResponsePlaylists {
|
||||
repeated Playlist playlist = 1;
|
||||
optional bool include_closed = 2;
|
||||
}
|
||||
|
||||
// A list of songs in a playlist
|
||||
message ResponsePlaylistSongs {
|
||||
optional Playlist requested_playlist = 1;
|
||||
|
||||
|
||||
// The songs that are in the playlist
|
||||
repeated SongMetadata songs = 2;
|
||||
}
|
||||
@ -226,7 +240,7 @@ message ResponseEngineStateChanged {
|
||||
}
|
||||
|
||||
// Sends the current position of the track
|
||||
message ResponseUpdateTrackPosition {
|
||||
message ResponseUpdateTrackPosition {
|
||||
optional int32 position = 1;
|
||||
}
|
||||
|
||||
@ -262,10 +276,12 @@ message RequestInsertUrls {
|
||||
// In which playlist should the urls be inserted?
|
||||
optional int32 playlist_id = 1;
|
||||
repeated string urls = 2;
|
||||
optional int32 position = 3 [default=-1];
|
||||
optional bool play_now = 4 [default=false];
|
||||
optional bool enqueue = 5 [default=false];
|
||||
optional int32 position = 3 [default = -1];
|
||||
optional bool play_now = 4 [default = false];
|
||||
optional bool enqueue = 5 [default = false];
|
||||
repeated SongMetadata songs = 6;
|
||||
// if we wish to create a new playlist
|
||||
optional string new_playlist_name = 7;
|
||||
}
|
||||
|
||||
// Client want to change track
|
||||
@ -283,6 +299,13 @@ message RequestOpenPlaylist {
|
||||
message RequestClosePlaylist {
|
||||
optional int32 playlist_id = 1;
|
||||
}
|
||||
message RequestUpdatePlaylist {
|
||||
optional int32 playlist_id = 1;
|
||||
optional string new_playlist_name = 2;
|
||||
optional bool favorite = 3;
|
||||
optional bool create_new_playlist = 4;
|
||||
optional bool clear_playlist = 5;
|
||||
}
|
||||
|
||||
// Message containing lyrics
|
||||
message ResponseLyrics {
|
||||
@ -305,6 +328,13 @@ message RequestDownloadSongs {
|
||||
optional DownloadItem download_item = 1;
|
||||
optional int32 playlist_id = 2;
|
||||
repeated string urls = 3;
|
||||
|
||||
// within a Playlist, download only requested songs
|
||||
repeated int32 songs_ids = 4;
|
||||
|
||||
// download from the FileSystem remotely
|
||||
// using the defined root directory and the urls (filenames)
|
||||
optional string relative_path = 5;
|
||||
}
|
||||
|
||||
message ResponseSongFileChunk {
|
||||
@ -312,7 +342,7 @@ message ResponseSongFileChunk {
|
||||
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 SongMetadata song_metadata = 6; // only sent with first chunk!
|
||||
optional bytes data = 7;
|
||||
optional int32 size = 8;
|
||||
optional bytes file_hash = 9;
|
||||
@ -327,11 +357,11 @@ message ResponseLibraryChunk {
|
||||
}
|
||||
|
||||
message ResponseSongOffer {
|
||||
optional bool accepted = 1; // true = client wants to download item
|
||||
optional bool accepted = 1; // true = client wants to download item
|
||||
}
|
||||
|
||||
message RequestRateSong {
|
||||
optional float rating = 1; // 0 to 1
|
||||
optional float rating = 1; // 0 to 1
|
||||
}
|
||||
|
||||
message ResponseDownloadTotalSize {
|
||||
@ -367,10 +397,56 @@ message ResponseGlobalSearchStatus {
|
||||
optional GlobalSearchStatus status = 3;
|
||||
}
|
||||
|
||||
// access the FileSystem remotely from a defined root directory
|
||||
message RequestListFiles {
|
||||
optional string relative_path = 1;
|
||||
}
|
||||
|
||||
message FileMetadata {
|
||||
optional string filename = 1;
|
||||
optional bool is_dir = 2;
|
||||
}
|
||||
|
||||
message ResponseListFiles {
|
||||
enum Error {
|
||||
NONE = 0;
|
||||
ROOT_DIR_NOT_SET = 1;
|
||||
DIR_NOT_ACCESSIBLE = 2;
|
||||
DIR_NOT_EXIST = 3;
|
||||
UNKNOWN = 4;
|
||||
}
|
||||
optional string relative_path = 1;
|
||||
repeated FileMetadata files = 2;
|
||||
optional Error error = 3;
|
||||
}
|
||||
|
||||
message RequestAppendFiles {
|
||||
// where to append the files
|
||||
optional int32 playlist_id = 1;
|
||||
// or we create a new playlist
|
||||
optional string new_playlist_name = 2;
|
||||
|
||||
optional string relative_path = 3;
|
||||
repeated string files = 4;
|
||||
|
||||
optional bool play_now = 5;
|
||||
optional bool clear_first = 6;
|
||||
}
|
||||
|
||||
message Stream {
|
||||
optional string name = 1;
|
||||
optional string url = 2;
|
||||
optional string url_logo = 3;
|
||||
}
|
||||
message ResponseSavedRadios {
|
||||
repeated Stream streams = 1;
|
||||
}
|
||||
|
||||
// The message itself
|
||||
message Message {
|
||||
optional int32 version = 1 [default=21];
|
||||
optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
|
||||
optional int32 version = 1 [default = 21];
|
||||
optional MsgType type = 2
|
||||
[default = UNKNOWN]; // What data is in the message?
|
||||
|
||||
optional RequestConnect request_connect = 21;
|
||||
optional RequestPlaylists request_playlists = 27;
|
||||
@ -382,13 +458,16 @@ message Message {
|
||||
optional RequestRemoveSongs request_remove_songs = 26;
|
||||
optional RequestOpenPlaylist request_open_playlist = 28;
|
||||
optional RequestClosePlaylist request_close_playlist = 29;
|
||||
optional RequestUpdatePlaylist request_update_playlist = 53;
|
||||
optional RequestDownloadSongs request_download_songs = 31;
|
||||
optional RequestRateSong request_rate_song = 35;
|
||||
optional RequestGlobalSearch request_global_search = 37;
|
||||
|
||||
optional RequestListFiles request_list_files = 50;
|
||||
optional RequestAppendFiles request_append_files = 51;
|
||||
|
||||
optional Repeat repeat = 13;
|
||||
optional Shuffle shuffle = 14;
|
||||
|
||||
|
||||
optional ResponseClementineInfo response_clementine_info = 15;
|
||||
optional ResponseCurrentMetadata response_current_metadata = 16;
|
||||
optional ResponsePlaylists response_playlists = 17;
|
||||
@ -405,4 +484,6 @@ message Message {
|
||||
optional ResponseGlobalSearch response_global_search = 38;
|
||||
optional ResponseTranscoderStatus response_transcoder_status = 39;
|
||||
optional ResponseGlobalSearchStatus response_global_search_status = 40;
|
||||
optional ResponseListFiles response_list_files = 52;
|
||||
optional ResponseSavedRadios response_saved_radios = 54;
|
||||
}
|
||||
|
@ -339,6 +339,7 @@ set(SOURCES
|
||||
ui/edittagdialog.cpp
|
||||
ui/lovedialog.cpp
|
||||
ui/equalizer.cpp
|
||||
ui/filechooserwidget.cpp
|
||||
ui/flowlayout.cpp
|
||||
ui/globalshortcutgrabber.cpp
|
||||
ui/globalshortcutssettingspage.cpp
|
||||
@ -625,6 +626,7 @@ set(HEADERS
|
||||
ui/console.h
|
||||
ui/coverfromurldialog.h
|
||||
ui/edittagdialog.h
|
||||
ui/filechooserwidget.h
|
||||
ui/lovedialog.h
|
||||
ui/equalizer.h
|
||||
ui/globalshortcutgrabber.h
|
||||
|
@ -69,6 +69,8 @@ bool Application::kIsPortable = false;
|
||||
const char* Application::kLegacyPortableDataDir = "data";
|
||||
const char* Application::kDefaultPortableDataDir = "clementine-data";
|
||||
const char* Application::kPortableDataDir = nullptr;
|
||||
const QStringList Application::kDefaultMusicExtensionsAllowedRemotely = {
|
||||
"aac", "alac", "flac", "m3u", "m4a", "mp3", "ogg", "wav", "wmv"};
|
||||
|
||||
class ApplicationImpl {
|
||||
public:
|
||||
|
@ -65,6 +65,7 @@ class Application : public QObject {
|
||||
static const char* kPortableDataDir;
|
||||
static const char* kLegacyPortableDataDir;
|
||||
static const char* kDefaultPortableDataDir;
|
||||
static const QStringList kDefaultMusicExtensionsAllowedRemotely;
|
||||
static bool IsPortable() { return kIsPortable; }
|
||||
|
||||
explicit Application(QObject* parent = nullptr);
|
||||
|
@ -36,7 +36,8 @@ class MimeData : public QMimeData {
|
||||
enqueue_next_now_(enqueue_next_now),
|
||||
open_in_new_playlist_(open_in_new_playlist),
|
||||
name_for_new_playlist_(QString()),
|
||||
from_doubleclick_(false) {}
|
||||
from_doubleclick_(false),
|
||||
playlist_id(-1) {}
|
||||
|
||||
// If this is set then MainWindow will not touch any of the other flags.
|
||||
bool override_user_settings_;
|
||||
@ -69,6 +70,10 @@ class MimeData : public QMimeData {
|
||||
// the defaults set by the user.
|
||||
bool from_doubleclick_;
|
||||
|
||||
// The Network Remote can use this MimeData to drop songs on another
|
||||
// playlist than the one currently opened on the server
|
||||
int playlist_id;
|
||||
|
||||
// Returns a pretty name for a playlist containing songs described by this
|
||||
// MimeData
|
||||
// object. By pretty name we mean the value of 'name_for_new_playlist_' or
|
||||
|
@ -79,7 +79,8 @@ void SavedRadio::LoadStreams() {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
s.setArrayIndex(i);
|
||||
streams_ << Stream(QUrl(s.value("url").toString()),
|
||||
s.value("name").toString());
|
||||
s.value("name").toString(),
|
||||
QUrl(s.value("url_logo").toString()));
|
||||
}
|
||||
s.endArray();
|
||||
}
|
||||
@ -94,6 +95,7 @@ void SavedRadio::SaveStreams() {
|
||||
s.setArrayIndex(i);
|
||||
s.setValue("url", streams_[i].url_);
|
||||
s.setValue("name", streams_[i].name_);
|
||||
s.setValue("url_logo", streams_[i].url_logo_);
|
||||
}
|
||||
s.endArray();
|
||||
|
||||
@ -148,15 +150,18 @@ void SavedRadio::Edit() {
|
||||
edit_dialog_->set_save_visible(false);
|
||||
}
|
||||
|
||||
edit_dialog_->set_name(context_item->text());
|
||||
edit_dialog_->set_url(context_item->data(InternetModel::Role_Url).toUrl());
|
||||
if (edit_dialog_->exec() == QDialog::Rejected) return;
|
||||
|
||||
int i = streams_.indexOf(
|
||||
Stream(QUrl(context_item->data(InternetModel::Role_Url).toUrl())));
|
||||
Stream* stream = &streams_[i];
|
||||
|
||||
edit_dialog_->set_name(context_item->text());
|
||||
edit_dialog_->set_url(context_item->data(InternetModel::Role_Url).toUrl());
|
||||
edit_dialog_->set_url_logo(stream->url_logo_);
|
||||
if (edit_dialog_->exec() == QDialog::Rejected) return;
|
||||
|
||||
stream->name_ = edit_dialog_->name();
|
||||
stream->url_ = edit_dialog_->url();
|
||||
stream->url_logo_ = edit_dialog_->url_logo();
|
||||
|
||||
context_item->setText(stream->name_);
|
||||
context_item->setData(stream->url_, InternetModel::Role_Url);
|
||||
@ -173,10 +178,11 @@ void SavedRadio::AddStreamToList(const Stream& stream, QStandardItem* parent) {
|
||||
parent->appendRow(s);
|
||||
}
|
||||
|
||||
void SavedRadio::Add(const QUrl& url, const QString& name) {
|
||||
void SavedRadio::Add(const QUrl& url, const QString& name,
|
||||
const QUrl& url_logo) {
|
||||
if (streams_.contains(Stream(url))) return;
|
||||
|
||||
Stream stream(url, name);
|
||||
Stream stream(url, name, url_logo);
|
||||
streams_ << stream;
|
||||
|
||||
if (!root_->data(InternetModel::Role_CanLazyLoad).toBool()) {
|
||||
|
@ -41,14 +41,16 @@ class SavedRadio : public InternetService {
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
explicit Stream(const QUrl& url, const QString& name = QString())
|
||||
: url_(url), name_(name) {}
|
||||
explicit Stream(const QUrl& url, const QString& name = QString(),
|
||||
const QUrl& url_logo = QUrl())
|
||||
: url_(url), name_(name), url_logo_(url_logo) {}
|
||||
|
||||
// For QList::contains
|
||||
bool operator==(const Stream& other) const { return url_ == other.url_; }
|
||||
|
||||
QUrl url_;
|
||||
QString name_;
|
||||
QUrl url_logo_;
|
||||
};
|
||||
typedef QList<Stream> StreamList;
|
||||
|
||||
@ -60,9 +62,10 @@ class SavedRadio : public InternetService {
|
||||
|
||||
void ShowContextMenu(const QPoint& global_pos);
|
||||
|
||||
void Add(const QUrl& url, const QString& name = QString());
|
||||
void Add(const QUrl& url, const QString& name = QString(),
|
||||
const QUrl& url_logo = QUrl());
|
||||
|
||||
StreamList Streams() const { return streams_; }
|
||||
const StreamList& Streams() const { return streams_; }
|
||||
|
||||
signals:
|
||||
void ShowAddStreamDialog();
|
||||
|
@ -17,9 +17,11 @@
|
||||
|
||||
#include "incomingdataparser.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "engines/enginebase.h"
|
||||
#include "internet/core/internetmodel.h"
|
||||
@ -36,47 +38,55 @@ IncomingDataParser::IncomingDataParser(Application* app) : app_(app) {
|
||||
ReloadSettings();
|
||||
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
|
||||
|
||||
Player* player = app_->player();
|
||||
PlaylistManager* playlist_manager = app_->playlist_manager();
|
||||
|
||||
// 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(StopAfterCurrent()), app_->player(),
|
||||
SLOT(StopAfterCurrent()));
|
||||
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(SeekTo(int)), app_->player(), SLOT(SeekTo(int)));
|
||||
connect(this, SIGNAL(Enque(int, int)), app_->playlist_manager(),
|
||||
connect(this, SIGNAL(Play()), player, SLOT(Play()));
|
||||
connect(this, SIGNAL(PlayPause()), player, SLOT(PlayPause()));
|
||||
connect(this, SIGNAL(Pause()), player, SLOT(Pause()));
|
||||
connect(this, SIGNAL(Stop()), player, SLOT(Stop()));
|
||||
connect(this, SIGNAL(StopAfterCurrent()), player, SLOT(StopAfterCurrent()));
|
||||
connect(this, SIGNAL(Next()), player, SLOT(Next()));
|
||||
connect(this, SIGNAL(Previous()), player, SLOT(Previous()));
|
||||
connect(this, SIGNAL(SetVolume(int)), player, SLOT(SetVolume(int)));
|
||||
connect(this, SIGNAL(PlayAt(int, Engine::TrackChangeFlags, bool)), player,
|
||||
SLOT(PlayAt(int, Engine::TrackChangeFlags, bool)));
|
||||
connect(this, SIGNAL(SeekTo(int)), player, SLOT(SeekTo(int)));
|
||||
connect(this, SIGNAL(Enque(int, int)), playlist_manager,
|
||||
SLOT(Enque(int, int)));
|
||||
|
||||
connect(this, SIGNAL(SetActivePlaylist(int)), app_->playlist_manager(),
|
||||
connect(this, SIGNAL(SetActivePlaylist(int)), playlist_manager,
|
||||
SLOT(SetActivePlaylist(int)));
|
||||
connect(this, SIGNAL(ShuffleCurrent()), app_->playlist_manager(),
|
||||
connect(this, SIGNAL(ShuffleCurrent()), playlist_manager,
|
||||
SLOT(ShuffleCurrent()));
|
||||
connect(this, SIGNAL(SetRepeatMode(PlaylistSequence::RepeatMode)),
|
||||
app_->playlist_manager()->sequence(),
|
||||
playlist_manager->sequence(),
|
||||
SLOT(SetRepeatMode(PlaylistSequence::RepeatMode)));
|
||||
connect(this, SIGNAL(SetShuffleMode(PlaylistSequence::ShuffleMode)),
|
||||
app_->playlist_manager()->sequence(),
|
||||
playlist_manager->sequence(),
|
||||
SLOT(SetShuffleMode(PlaylistSequence::ShuffleMode)));
|
||||
connect(this, SIGNAL(InsertUrls(int, const QList<QUrl>&, int, bool, bool)),
|
||||
app_->playlist_manager(),
|
||||
playlist_manager,
|
||||
SLOT(InsertUrls(int, const QList<QUrl>&, int, bool, bool)));
|
||||
connect(this, SIGNAL(InsertSongs(int, const SongList&, int, bool, bool)),
|
||||
app_->playlist_manager(),
|
||||
playlist_manager,
|
||||
SLOT(InsertSongs(int, const SongList&, int, bool, bool)));
|
||||
connect(this, SIGNAL(RemoveSongs(int, const QList<int>&)),
|
||||
app_->playlist_manager(),
|
||||
connect(this, SIGNAL(RemoveSongs(int, const QList<int>&)), playlist_manager,
|
||||
SLOT(RemoveItemsWithoutUndo(int, const QList<int>&)));
|
||||
connect(this, SIGNAL(Open(int)), app_->playlist_manager(), SLOT(Open(int)));
|
||||
connect(this, SIGNAL(Close(int)), app_->playlist_manager(), SLOT(Close(int)));
|
||||
connect(this, SIGNAL(New(const QString&)), playlist_manager,
|
||||
SLOT(New(const QString&)));
|
||||
connect(this, SIGNAL(Open(int)), playlist_manager, SLOT(Open(int)));
|
||||
connect(this, SIGNAL(Close(int)), playlist_manager, SLOT(Close(int)));
|
||||
connect(this, SIGNAL(Clear(int)), playlist_manager, SLOT(Clear(int)));
|
||||
connect(this, SIGNAL(Rename(int, const QString&)), playlist_manager,
|
||||
SLOT(Rename(int, const QString&)));
|
||||
connect(this, SIGNAL(Favorite(int, bool)), playlist_manager,
|
||||
SLOT(Favorite(int, bool)));
|
||||
|
||||
connect(this, SIGNAL(RateCurrentSong(double)), app_->playlist_manager(),
|
||||
connect(this, SIGNAL(RateCurrentSong(double)), playlist_manager,
|
||||
SLOT(RateCurrentSong(double)));
|
||||
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
@ -99,7 +109,6 @@ bool IncomingDataParser::close_connection() { return 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
|
||||
@ -167,6 +176,9 @@ void IncomingDataParser::Parse(const pb::remote::Message& msg) {
|
||||
case pb::remote::CLOSE_PLAYLIST:
|
||||
ClosePlaylist(msg);
|
||||
break;
|
||||
case pb::remote::UPDATE_PLAYLIST:
|
||||
UpdatePlaylist(msg);
|
||||
break;
|
||||
case pb::remote::LOVE:
|
||||
emit Love();
|
||||
break;
|
||||
@ -192,6 +204,18 @@ void IncomingDataParser::Parse(const pb::remote::Message& msg) {
|
||||
case pb::remote::GLOBAL_SEARCH:
|
||||
GlobalSearch(client, msg);
|
||||
break;
|
||||
case pb::remote::REQUEST_FILES:
|
||||
emit SendListFiles(
|
||||
QString::fromStdString(msg.request_list_files().relative_path()),
|
||||
client);
|
||||
break;
|
||||
case pb::remote::APPEND_FILES:
|
||||
AppendFilesToPlaylist(msg);
|
||||
break;
|
||||
case pb::remote::REQUEST_SAVED_RADIOS:
|
||||
emit SendSavedRadios(client);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -267,6 +291,7 @@ void IncomingDataParser::SetShuffleMode(const pb::remote::Shuffle& shuffle) {
|
||||
|
||||
void IncomingDataParser::InsertUrls(const pb::remote::Message& msg) {
|
||||
const pb::remote::RequestInsertUrls& request = msg.request_insert_urls();
|
||||
int playlist_id = request.playlist_id();
|
||||
|
||||
// Insert plain urls without metadata
|
||||
if (!request.urls().empty()) {
|
||||
@ -276,9 +301,13 @@ void IncomingDataParser::InsertUrls(const pb::remote::Message& msg) {
|
||||
urls << QUrl(QStringFromStdString(s));
|
||||
}
|
||||
|
||||
if (request.has_new_playlist_name())
|
||||
playlist_id =
|
||||
app_->playlist_manager()->New(request.new_playlist_name().c_str());
|
||||
|
||||
// Insert the urls
|
||||
emit InsertUrls(request.playlist_id(), urls, request.position(),
|
||||
request.play_now(), request.enqueue());
|
||||
emit InsertUrls(playlist_id, urls, request.position(), request.play_now(),
|
||||
request.enqueue());
|
||||
}
|
||||
|
||||
// Add songs with metadata if present
|
||||
@ -287,6 +316,13 @@ void IncomingDataParser::InsertUrls(const pb::remote::Message& msg) {
|
||||
for (int i = 0; i < request.songs().size(); i++) {
|
||||
songs << CreateSongFromProtobuf(request.songs(i));
|
||||
}
|
||||
|
||||
// create a new playlist if required and not already done above by
|
||||
// InsertUrls
|
||||
if (request.has_new_playlist_name() && playlist_id == request.playlist_id())
|
||||
playlist_id =
|
||||
app_->playlist_manager()->New(request.new_playlist_name().c_str());
|
||||
|
||||
emit InsertSongs(request.playlist_id(), songs, request.position(),
|
||||
request.play_now(), request.enqueue());
|
||||
}
|
||||
@ -338,6 +374,28 @@ void IncomingDataParser::ClosePlaylist(const pb::remote::Message& msg) {
|
||||
emit Close(msg.request_close_playlist().playlist_id());
|
||||
}
|
||||
|
||||
void IncomingDataParser::UpdatePlaylist(const pb::remote::Message& msg) {
|
||||
const pb::remote::RequestUpdatePlaylist& req_update =
|
||||
msg.request_update_playlist();
|
||||
if (req_update.has_create_new_playlist() &&
|
||||
req_update.create_new_playlist()) {
|
||||
emit New(req_update.has_new_playlist_name()
|
||||
? req_update.new_playlist_name().c_str()
|
||||
: "New Playlist");
|
||||
return;
|
||||
}
|
||||
if (req_update.has_clear_playlist() && req_update.clear_playlist()) {
|
||||
emit Clear(req_update.playlist_id());
|
||||
return;
|
||||
}
|
||||
if (req_update.has_new_playlist_name() &&
|
||||
req_update.new_playlist_name().size())
|
||||
emit Rename(req_update.playlist_id(),
|
||||
req_update.new_playlist_name().c_str());
|
||||
if (req_update.has_favorite())
|
||||
emit Favorite(req_update.playlist_id(), req_update.favorite());
|
||||
}
|
||||
|
||||
void IncomingDataParser::RateSong(const pb::remote::Message& msg) {
|
||||
double rating = (double)msg.request_rate_song().rating();
|
||||
emit RateCurrentSong(rating);
|
||||
@ -370,3 +428,63 @@ Song IncomingDataParser::CreateSongFromProtobuf(
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
void IncomingDataParser::AppendFilesToPlaylist(const pb::remote::Message& msg) {
|
||||
if (files_root_folder_.isEmpty()) { // should never happen...
|
||||
qLog(Warning) << "Remote root dir is not set although receiving "
|
||||
"APPEND_FILES request...";
|
||||
return;
|
||||
}
|
||||
QDir root_dir(files_root_folder_);
|
||||
if (!root_dir.exists()) {
|
||||
qLog(Warning) << "Remote root dir doesn't exist...";
|
||||
return;
|
||||
}
|
||||
|
||||
const pb::remote::RequestAppendFiles& req_append = msg.request_append_files();
|
||||
QString relative_path = QString::fromStdString(req_append.relative_path());
|
||||
if (relative_path.startsWith("/")) relative_path.remove(0, 1);
|
||||
|
||||
QFileInfo fi_folder(root_dir, relative_path);
|
||||
if (!fi_folder.exists())
|
||||
qLog(Warning) << "Remote relative path " << relative_path
|
||||
<< " doesn't exist...";
|
||||
else if (!fi_folder.isDir())
|
||||
qLog(Warning) << "Remote relative path " << relative_path
|
||||
<< " is not a directory...";
|
||||
else if (root_dir.relativeFilePath(fi_folder.absoluteFilePath())
|
||||
.startsWith("../"))
|
||||
qLog(Warning) << "Remote relative path " << relative_path
|
||||
<< " should not be accessed...";
|
||||
else {
|
||||
QList<QUrl> urls;
|
||||
QDir dir(fi_folder.absoluteFilePath());
|
||||
for (const auto& file : req_append.files()) {
|
||||
QFileInfo fi(dir, file.c_str());
|
||||
if (fi.exists()) urls << QUrl::fromLocalFile(fi.canonicalFilePath());
|
||||
}
|
||||
if (urls.size()) {
|
||||
MimeData* data = new MimeData;
|
||||
data->setUrls(urls);
|
||||
if (req_append.has_play_now()) data->play_now_ = req_append.play_now();
|
||||
if (req_append.has_clear_first())
|
||||
data->clear_first_ = req_append.clear_first();
|
||||
if (req_append.has_new_playlist_name()) {
|
||||
QString playlist_name =
|
||||
QString::fromStdString(req_append.new_playlist_name());
|
||||
if (!playlist_name.isEmpty()) {
|
||||
data->open_in_new_playlist_ = true;
|
||||
data->name_for_new_playlist_ = playlist_name;
|
||||
}
|
||||
} else if (req_append.has_playlist_id()) {
|
||||
// if playing we will drop the files in another playlist
|
||||
if (app_->player()->GetState() == Engine::Playing)
|
||||
data->playlist_id = req_append.playlist_id();
|
||||
else
|
||||
// as me may play the song, we change the current playlist
|
||||
emit SetCurrentPlaylist(req_append.playlist_id());
|
||||
}
|
||||
emit AddToPlaylistSignal(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,10 @@ class IncomingDataParser : public QObject {
|
||||
|
||||
bool close_connection();
|
||||
|
||||
void SetRemoteRootFiles(const QString& files_root_folder) {
|
||||
files_root_folder_ = files_root_folder;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void Parse(const pb::remote::Message& msg);
|
||||
void ReloadSettings();
|
||||
@ -25,8 +29,12 @@ class IncomingDataParser : public QObject {
|
||||
void SendAllPlaylists();
|
||||
void SendAllActivePlaylists();
|
||||
void SendPlaylistSongs(int id);
|
||||
void New(const QString& new_playlist_name);
|
||||
void Open(int id);
|
||||
void Clear(int id);
|
||||
void Close(int id);
|
||||
void Rename(int id, const QString& new_playlist_name);
|
||||
void Favorite(int id, bool favorite);
|
||||
void GetLyrics();
|
||||
void Love();
|
||||
void Ban();
|
||||
@ -56,10 +64,16 @@ class IncomingDataParser : public QObject {
|
||||
|
||||
void DoGlobalSearch(QString, RemoteClient*);
|
||||
|
||||
void SendSavedRadios(RemoteClient* client);
|
||||
void SendListFiles(QString, RemoteClient*);
|
||||
void AddToPlaylistSignal(QMimeData* data);
|
||||
void SetCurrentPlaylist(int id);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
bool close_connection_;
|
||||
MainWindow::PlaylistAddBehaviour doubleclick_playlist_addmode_;
|
||||
QString files_root_folder_;
|
||||
|
||||
void GetPlaylistSongs(const pb::remote::Message& msg);
|
||||
void ChangeSong(const pb::remote::Message& msg);
|
||||
@ -71,8 +85,10 @@ class IncomingDataParser : public QObject {
|
||||
void SendPlaylists(const pb::remote::Message& msg);
|
||||
void OpenPlaylist(const pb::remote::Message& msg);
|
||||
void ClosePlaylist(const pb::remote::Message& msg);
|
||||
void UpdatePlaylist(const pb::remote::Message& msg);
|
||||
void RateSong(const pb::remote::Message& msg);
|
||||
void GlobalSearch(RemoteClient* client, const pb::remote::Message& msg);
|
||||
void AppendFilesToPlaylist(const pb::remote::Message& msg);
|
||||
|
||||
Song CreateSongFromProtobuf(const pb::remote::SongMetadata& pb);
|
||||
};
|
||||
|
@ -72,6 +72,11 @@ void NetworkRemote::SetupServer() {
|
||||
SLOT(AcceptConnection()));
|
||||
connect(server_ipv6_.get(), SIGNAL(newConnection()), this,
|
||||
SLOT(AcceptConnection()));
|
||||
|
||||
connect(incoming_data_parser_.get(), SIGNAL(AddToPlaylistSignal(QMimeData*)),
|
||||
SIGNAL(AddToPlaylistSignal(QMimeData*)));
|
||||
connect(incoming_data_parser_.get(), SIGNAL(SetCurrentPlaylist(int)),
|
||||
SIGNAL(SetCurrentPlaylist(int)));
|
||||
}
|
||||
|
||||
void NetworkRemote::StartServer() {
|
||||
@ -171,6 +176,13 @@ void NetworkRemote::AcceptConnection() {
|
||||
SIGNAL(DoGlobalSearch(QString, RemoteClient*)),
|
||||
outgoing_data_creator_.get(),
|
||||
SLOT(DoGlobalSearch(QString, RemoteClient*)));
|
||||
|
||||
connect(incoming_data_parser_.get(),
|
||||
SIGNAL(SendListFiles(QString, RemoteClient*)),
|
||||
outgoing_data_creator_.get(),
|
||||
SLOT(SendListFiles(QString, RemoteClient*)));
|
||||
connect(incoming_data_parser_.get(), SIGNAL(SendSavedRadios(RemoteClient*)),
|
||||
outgoing_data_creator_.get(), SLOT(SendSavedRadios(RemoteClient*)));
|
||||
}
|
||||
|
||||
QTcpServer* server = qobject_cast<QTcpServer*>(sender());
|
||||
@ -213,6 +225,14 @@ void NetworkRemote::CreateRemoteClient(QTcpSocket* client_socket) {
|
||||
RemoteClient* client = new RemoteClient(app_, client_socket);
|
||||
clients_.push_back(client);
|
||||
|
||||
// Update the Remote Root Files for the latest Client
|
||||
outgoing_data_creator_->SetMusicExtensions(
|
||||
client->files_music_extensions());
|
||||
outgoing_data_creator_->SetRemoteRootFiles(client->files_root_folder());
|
||||
incoming_data_parser_->SetRemoteRootFiles(client->files_root_folder());
|
||||
// update OutgoingDataCreator with latest allow_downloads setting
|
||||
outgoing_data_creator_->SetAllowDownloads(client->allow_downloads());
|
||||
|
||||
// Connect the signal to parse data
|
||||
connect(client, SIGNAL(Parse(pb::remote::Message)),
|
||||
incoming_data_parser_.get(), SLOT(Parse(pb::remote::Message)));
|
||||
|
@ -13,6 +13,7 @@ class QImage;
|
||||
class QTcpServer;
|
||||
class QTcpSocket;
|
||||
class RemoteClient;
|
||||
class QMimeData;
|
||||
|
||||
class NetworkRemote : public QObject {
|
||||
Q_OBJECT
|
||||
@ -24,6 +25,10 @@ class NetworkRemote : public QObject {
|
||||
explicit NetworkRemote(Application* app, QObject* parent = nullptr);
|
||||
~NetworkRemote();
|
||||
|
||||
signals:
|
||||
void AddToPlaylistSignal(QMimeData* data);
|
||||
void SetCurrentPlaylist(int id);
|
||||
|
||||
public slots:
|
||||
void SetupServer();
|
||||
void StartServer();
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "outgoingdatacreator.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
#include <cmath>
|
||||
@ -26,6 +27,8 @@
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "globalsearch/librarysearchprovider.h"
|
||||
#include "internet/core/internetmodel.h"
|
||||
#include "internet/internetradio/savedradio.h"
|
||||
#include "library/librarybackend.h"
|
||||
#include "networkremote.h"
|
||||
#include "ui/iconloader.h"
|
||||
@ -180,10 +183,15 @@ void OutgoingDataCreator::SendClementineInfo() {
|
||||
msg.mutable_response_clementine_info();
|
||||
SetEngineState(info);
|
||||
|
||||
// allowed extensions for REQUEST_FILES and LIST_FILES
|
||||
for (const QString& ext : files_music_extensions_)
|
||||
*info->add_files_music_extensions() = ext.toStdString();
|
||||
|
||||
QString version =
|
||||
QString("%1 %2").arg(QCoreApplication::applicationName(),
|
||||
QCoreApplication::applicationVersion());
|
||||
info->set_version(version.toLatin1());
|
||||
info->set_allow_downloads(allow_downloads_);
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
@ -208,20 +216,22 @@ void OutgoingDataCreator::SetEngineState(
|
||||
|
||||
void OutgoingDataCreator::SendAllPlaylists() {
|
||||
// Get all Playlists
|
||||
QList<Playlist*> app_playlists = app_->playlist_manager()->GetAllPlaylists();
|
||||
int active_playlist = app_->playlist_manager()->active_id();
|
||||
PlaylistManager* playlist_manager = app_->playlist_manager();
|
||||
int active_playlist = playlist_manager->active_id();
|
||||
|
||||
// Create message
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::PLAYLISTS);
|
||||
|
||||
pb::remote::ResponsePlaylists* playlists = msg.mutable_response_playlists();
|
||||
playlists->set_include_closed(true);
|
||||
|
||||
// Get all playlists, even ones that are hidden in the UI.
|
||||
for (const PlaylistBackend::Playlist& p :
|
||||
app_->playlist_backend()->GetAllPlaylists()) {
|
||||
bool playlist_open = app_->playlist_manager()->IsPlaylistOpen(p.id);
|
||||
int item_count = playlist_open ? app_playlists.at(p.id)->rowCount() : 0;
|
||||
bool playlist_open = playlist_manager->IsPlaylistOpen(p.id);
|
||||
int item_count =
|
||||
playlist_open ? playlist_manager->playlist(p.id)->rowCount() : 0;
|
||||
|
||||
// Create a new playlist
|
||||
pb::remote::Playlist* playlist = playlists->add_playlist();
|
||||
@ -230,6 +240,7 @@ void OutgoingDataCreator::SendAllPlaylists() {
|
||||
playlist->set_active((p.id == active_playlist));
|
||||
playlist->set_item_count(item_count);
|
||||
playlist->set_closed(!playlist_open);
|
||||
playlist->set_favorite(p.favorite);
|
||||
}
|
||||
|
||||
SendDataToClients(&msg);
|
||||
@ -259,6 +270,7 @@ void OutgoingDataCreator::SendAllActivePlaylists() {
|
||||
playlist->set_active((p->id() == active_playlist));
|
||||
playlist->set_item_count(p->rowCount());
|
||||
playlist->set_closed(false);
|
||||
playlist->set_favorite(p->is_favorite());
|
||||
}
|
||||
|
||||
SendDataToClients(&msg);
|
||||
@ -289,8 +301,9 @@ void OutgoingDataCreator::PlaylistRenamed(int id, const QString& new_name) {
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendFirstData(bool send_playlist_songs) {
|
||||
Player* player = app_->player();
|
||||
// First Send the current song
|
||||
PlaylistItemPtr item = app_->player()->GetCurrentItem();
|
||||
PlaylistItemPtr item = player->GetCurrentItem();
|
||||
if (!item) {
|
||||
qLog(Info) << "No current item found!";
|
||||
}
|
||||
@ -298,11 +311,11 @@ void OutgoingDataCreator::SendFirstData(bool send_playlist_songs) {
|
||||
CurrentSongChanged(current_song_, current_uri_, current_image_);
|
||||
|
||||
// then the current volume
|
||||
VolumeChanged(app_->player()->GetVolume());
|
||||
VolumeChanged(player->GetVolume());
|
||||
|
||||
// Check if we need to start the track position timer
|
||||
if (!track_position_timer_->isActive() &&
|
||||
app_->player()->engine()->state() == Engine::Playing) {
|
||||
player->engine()->state() == Engine::Playing) {
|
||||
track_position_timer_->start(1000);
|
||||
}
|
||||
|
||||
@ -546,11 +559,11 @@ void OutgoingDataCreator::UpdateTrackPosition() {
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::UPDATE_TRACK_POSITION);
|
||||
|
||||
int position = std::floor(
|
||||
float(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5);
|
||||
qint64 position_nanosec = app_->player()->engine()->position_nanosec();
|
||||
int position = static_cast<int>(
|
||||
std::floor(static_cast<double>(position_nanosec) / kNsecPerSec + 0.5));
|
||||
|
||||
if (app_->player()->engine()->position_nanosec() >
|
||||
current_song_.length_nanosec())
|
||||
if (position_nanosec > current_song_.length_nanosec())
|
||||
position = last_track_position_;
|
||||
|
||||
msg.mutable_response_update_track_position()->set_position(position);
|
||||
@ -744,3 +757,71 @@ void OutgoingDataCreator::SearchFinished(int id) {
|
||||
|
||||
qLog(Debug) << "SearchFinished" << req.id_ << req.query_;
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendListFiles(QString relative_path,
|
||||
RemoteClient* client) {
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::LIST_FILES);
|
||||
pb::remote::ResponseListFiles* files = msg.mutable_response_list_files();
|
||||
// Security checks
|
||||
if (files_root_folder_.isEmpty()) {
|
||||
files->set_error(pb::remote::ResponseListFiles::ROOT_DIR_NOT_SET);
|
||||
SendDataToClients(&msg);
|
||||
return;
|
||||
}
|
||||
|
||||
QDir root_dir(files_root_folder_);
|
||||
if (!root_dir.exists())
|
||||
files->set_error(pb::remote::ResponseListFiles::ROOT_DIR_NOT_SET);
|
||||
else if (relative_path.startsWith("..") || relative_path.startsWith("./.."))
|
||||
files->set_error(pb::remote::ResponseListFiles::DIR_NOT_ACCESSIBLE);
|
||||
else {
|
||||
if (relative_path.startsWith("/")) relative_path.remove(0, 1);
|
||||
|
||||
QFileInfo fi_folder(root_dir, relative_path);
|
||||
if (!fi_folder.exists())
|
||||
files->set_error(pb::remote::ResponseListFiles::DIR_NOT_EXIST);
|
||||
else if (!fi_folder.isDir())
|
||||
files->set_error(pb::remote::ResponseListFiles::DIR_NOT_EXIST);
|
||||
else if (root_dir.relativeFilePath(fi_folder.absoluteFilePath())
|
||||
.startsWith("../"))
|
||||
files->set_error(pb::remote::ResponseListFiles::DIR_NOT_ACCESSIBLE);
|
||||
else {
|
||||
files->set_relative_path(
|
||||
root_dir.relativeFilePath(fi_folder.absoluteFilePath())
|
||||
.toStdString());
|
||||
QDir dir(fi_folder.absoluteFilePath());
|
||||
dir.setFilter(QDir::NoDotAndDotDot | QDir::AllEntries);
|
||||
dir.setSorting(QDir::Name | QDir::DirsFirst);
|
||||
|
||||
for (const QFileInfo& fi : dir.entryInfoList()) {
|
||||
if (fi.isDir() || files_music_extensions_.contains(fi.suffix())) {
|
||||
pb::remote::FileMetadata* pb_file = files->add_files();
|
||||
pb_file->set_is_dir(fi.isDir());
|
||||
pb_file->set_filename(fi.fileName().toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
client->SendData(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendSavedRadios(RemoteClient* client) {
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::REQUEST_SAVED_RADIOS);
|
||||
|
||||
SavedRadio* radio_service = static_cast<SavedRadio*>(
|
||||
InternetModel::ServiceByName(SavedRadio::kServiceName));
|
||||
if (radio_service) {
|
||||
pb::remote::ResponseSavedRadios* radios =
|
||||
msg.mutable_response_saved_radios();
|
||||
for (const auto& stream : radio_service->Streams()) {
|
||||
pb::remote::Stream* pb_stream = radios->add_streams();
|
||||
pb_stream->set_name(stream.name_.toStdString());
|
||||
pb_stream->set_url(stream.url_.toString().toStdString());
|
||||
if (!stream.url_logo_.isEmpty())
|
||||
pb_stream->set_url_logo(stream.url_logo_.toString().toStdString());
|
||||
}
|
||||
}
|
||||
client->SendData(&msg);
|
||||
}
|
||||
|
@ -47,7 +47,15 @@ class OutgoingDataCreator : public QObject {
|
||||
static const quint32 kFileChunkSize;
|
||||
|
||||
void SetClients(QList<RemoteClient*>* clients);
|
||||
|
||||
void SetRemoteRootFiles(const QString& files_root_folder) {
|
||||
files_root_folder_ = files_root_folder;
|
||||
}
|
||||
void SetMusicExtensions(const QStringList& files_music_extensions) {
|
||||
files_music_extensions_ = files_music_extensions;
|
||||
}
|
||||
void SetAllowDownloads(bool allow_downloads) {
|
||||
allow_downloads_ = allow_downloads;
|
||||
}
|
||||
static void CreateSong(const Song& song, const QImage& art, const int index,
|
||||
pb::remote::SongMetadata* song_metadata);
|
||||
|
||||
@ -83,6 +91,9 @@ class OutgoingDataCreator : public QObject {
|
||||
void ResultsAvailable(int id, const SearchProvider::ResultList& results);
|
||||
void SearchFinished(int id);
|
||||
|
||||
void SendListFiles(QString relative_path, RemoteClient* client);
|
||||
void SendSavedRadios(RemoteClient* client);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
QList<RemoteClient*>* clients_;
|
||||
@ -95,6 +106,9 @@ class OutgoingDataCreator : public QObject {
|
||||
int keep_alive_timeout_;
|
||||
int last_track_position_;
|
||||
bool aww_;
|
||||
QString files_root_folder_;
|
||||
QStringList files_music_extensions_;
|
||||
bool allow_downloads_;
|
||||
|
||||
std::unique_ptr<UltimateLyricsReader> ultimate_reader_;
|
||||
QMap<int, SongInfoFetcher::Result> results_;
|
||||
|
@ -28,9 +28,6 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
downloader_(false),
|
||||
client_(client),
|
||||
song_sender_(new SongSender(app, this)) {
|
||||
// Open the buffer
|
||||
buffer_.setData(QByteArray());
|
||||
buffer_.open(QIODevice::ReadWrite);
|
||||
reading_protobuf_ = false;
|
||||
|
||||
// Connect to the slot IncomingData when receiving data
|
||||
@ -43,7 +40,11 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
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();
|
||||
|
||||
files_root_folder_ = s.value("files_root_folder", "").toString();
|
||||
files_music_extensions_ =
|
||||
s.value("files_music_extensions",
|
||||
Application::kDefaultMusicExtensionsAllowedRemotely)
|
||||
.toStringList();
|
||||
s.endGroup();
|
||||
|
||||
// If we don't use an auth code, we don't need to authenticate the client.
|
||||
@ -86,17 +87,16 @@ void RemoteClient::IncomingData() {
|
||||
}
|
||||
|
||||
// Read some of the message
|
||||
buffer_.write(client_->read(expected_length_ - buffer_.size()));
|
||||
buffer_.append(
|
||||
client_->read(static_cast<qint32>(expected_length_) - buffer_.size()));
|
||||
|
||||
// Did we get everything?
|
||||
if (buffer_.size() == expected_length_) {
|
||||
if (buffer_.size() == static_cast<qint32>(expected_length_)) {
|
||||
// Parse the message
|
||||
ParseMessage(buffer_.data());
|
||||
ParseMessage(buffer_);
|
||||
|
||||
// Clear the buffer
|
||||
buffer_.close();
|
||||
buffer_.setData(QByteArray());
|
||||
buffer_.open(QIODevice::ReadWrite);
|
||||
buffer_.clear();
|
||||
reading_protobuf_ = false;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
#ifndef REMOTECLIENT_H
|
||||
#define REMOTECLIENT_H
|
||||
|
||||
#include <QAbstractSocket>
|
||||
#include <QBuffer>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "core/application.h"
|
||||
@ -23,6 +21,11 @@ class RemoteClient : public QObject {
|
||||
void DisconnectClient(pb::remote::ReasonDisconnect reason);
|
||||
|
||||
SongSender* song_sender() { return song_sender_; }
|
||||
const QString& files_root_folder() const { return files_root_folder_; }
|
||||
const QStringList& files_music_extensions() const {
|
||||
return files_music_extensions_;
|
||||
}
|
||||
bool allow_downloads() const { return allow_downloads_; }
|
||||
|
||||
private slots:
|
||||
void IncomingData();
|
||||
@ -47,8 +50,11 @@ class RemoteClient : public QObject {
|
||||
QTcpSocket* client_;
|
||||
bool reading_protobuf_;
|
||||
quint32 expected_length_;
|
||||
QBuffer buffer_;
|
||||
QByteArray buffer_;
|
||||
SongSender* song_sender_;
|
||||
|
||||
QString files_root_folder_;
|
||||
QStringList files_music_extensions_;
|
||||
};
|
||||
|
||||
#endif // REMOTECLIENT_H
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "songsender.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "core/application.h"
|
||||
@ -87,7 +88,7 @@ void SongSender::SendSongs(const pb::remote::RequestDownloadSongs& request) {
|
||||
}
|
||||
break;
|
||||
case pb::remote::APlaylist:
|
||||
SendPlaylist(request.playlist_id());
|
||||
SendPlaylist(request);
|
||||
break;
|
||||
case pb::remote::Urls:
|
||||
SendUrls(request);
|
||||
@ -321,7 +322,8 @@ void SongSender::SendAlbum(const Song& song) {
|
||||
}
|
||||
}
|
||||
|
||||
void SongSender::SendPlaylist(int playlist_id) {
|
||||
void SongSender::SendPlaylist(const pb::remote::RequestDownloadSongs& request) {
|
||||
int playlist_id = request.playlist_id();
|
||||
Playlist* playlist = app_->playlist_manager()->playlist(playlist_id);
|
||||
if (!playlist) {
|
||||
qLog(Info) << "Could not find playlist with id = " << playlist_id;
|
||||
@ -329,17 +331,22 @@ void SongSender::SendPlaylist(int playlist_id) {
|
||||
}
|
||||
SongList song_list = playlist->GetAllSongs();
|
||||
|
||||
QList<int> requested_ids;
|
||||
for (auto song_id : request.songs_ids()) requested_ids << song_id;
|
||||
|
||||
// Count the local songs
|
||||
int count = 0;
|
||||
for (Song s : song_list) {
|
||||
if (s.url().scheme() == "file") {
|
||||
if (s.url().scheme() == "file" &&
|
||||
(requested_ids.isEmpty() || requested_ids.contains(s.id()))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
for (Song s : song_list) {
|
||||
// Only local files!
|
||||
if (s.url().scheme() == "file") {
|
||||
if (s.url().scheme() == "file" &&
|
||||
(requested_ids.isEmpty() || requested_ids.contains(s.id()))) {
|
||||
DownloadItem item(s, song_list.indexOf(s) + 1, count);
|
||||
download_queue_.append(item);
|
||||
}
|
||||
@ -350,14 +357,45 @@ void SongSender::SendUrls(const pb::remote::RequestDownloadSongs& request) {
|
||||
SongList song_list;
|
||||
|
||||
// First gather all valid songs
|
||||
for (auto it = request.urls().begin(); it != request.urls().end(); ++it) {
|
||||
std::string s = *it;
|
||||
QUrl url = QUrl(QStringFromStdString(s));
|
||||
if (request.has_relative_path()) {
|
||||
// Security checks, cf OutgoingDataCreator::SendListFiles
|
||||
const QString& files_root_folder = client_->files_root_folder();
|
||||
if (files_root_folder.isEmpty()) return;
|
||||
QDir root_dir(files_root_folder);
|
||||
QString relative_path(request.relative_path().c_str());
|
||||
if (!root_dir.exists() || relative_path.startsWith("..") ||
|
||||
relative_path.startsWith("./.."))
|
||||
return;
|
||||
if (relative_path.startsWith("/")) relative_path.remove(0, 1);
|
||||
|
||||
Song song = app_->library_backend()->GetSongByUrl(url);
|
||||
QFileInfo fi_folder(root_dir, relative_path);
|
||||
if (!fi_folder.exists() || !fi_folder.isDir() ||
|
||||
root_dir.relativeFilePath(fi_folder.absoluteFilePath())
|
||||
.startsWith("../"))
|
||||
return;
|
||||
|
||||
if (song.is_valid() && song.url().scheme() == "file") {
|
||||
song_list.append(song);
|
||||
QDir dir(fi_folder.absoluteFilePath());
|
||||
const QStringList& files_music_extensions =
|
||||
client_->files_music_extensions();
|
||||
for (const std::string& s : request.urls()) {
|
||||
QFileInfo fi(dir, s.c_str());
|
||||
if (fi.exists() && fi.isFile() &&
|
||||
files_music_extensions.contains(fi.suffix())) {
|
||||
Song song;
|
||||
song.set_basefilename(fi.fileName());
|
||||
song.set_filesize(fi.size());
|
||||
song.set_url(QUrl::fromLocalFile(fi.absoluteFilePath()));
|
||||
song.set_valid(true);
|
||||
song_list.append(song);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const std::string& s : request.urls()) {
|
||||
QUrl url = QUrl(QStringFromStdString(s));
|
||||
Song song = app_->library_backend()->GetSongByUrl(url);
|
||||
if (song.is_valid() && song.url().scheme() == "file") {
|
||||
song_list.append(song);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ class SongSender : public QObject {
|
||||
|
||||
void SendSingleSong(DownloadItem download_item);
|
||||
void SendAlbum(const Song& song);
|
||||
void SendPlaylist(int playlist_id);
|
||||
void SendPlaylist(const pb::remote::RequestDownloadSongs& request);
|
||||
void SendUrls(const pb::remote::RequestDownloadSongs& request);
|
||||
void OfferNextSong();
|
||||
void SendTotalFileSize();
|
||||
|
@ -140,9 +140,9 @@ Playlist* PlaylistManager::AddPlaylist(int id, const QString& name,
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PlaylistManager::New(const QString& name, const SongList& songs,
|
||||
const QString& special_type) {
|
||||
if (name.isNull()) return;
|
||||
int PlaylistManager::New(const QString& name, const SongList& songs,
|
||||
const QString& special_type) {
|
||||
if (name.isNull()) return -1;
|
||||
|
||||
int id = playlist_backend_->CreatePlaylist(name, special_type);
|
||||
|
||||
@ -157,6 +157,8 @@ void PlaylistManager::New(const QString& name, const SongList& songs,
|
||||
if (name == tr("Playlist")) {
|
||||
Rename(id, QString("%1 %2").arg(name).arg(id));
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void PlaylistManager::Load(const QString& filename) {
|
||||
@ -295,6 +297,11 @@ void PlaylistManager::Favorite(int id, bool favorite) {
|
||||
emit PlaylistFavorited(id, favorite);
|
||||
}
|
||||
|
||||
void PlaylistManager::Clear(int id) {
|
||||
if (playlists_.count() <= 1 || !playlists_.contains(id)) return;
|
||||
playlists_[id].p->Clear();
|
||||
}
|
||||
|
||||
bool PlaylistManager::Close(int id) {
|
||||
// Won't allow removing the last playlist
|
||||
if (playlists_.count() <= 1 || !playlists_.contains(id)) return false;
|
||||
@ -490,6 +497,7 @@ void PlaylistManager::InsertUrls(int id, const QList<QUrl>& urls, int pos,
|
||||
bool play_now, bool enqueue) {
|
||||
Q_ASSERT(playlists_.contains(id));
|
||||
|
||||
if (play_now && active_ != id) SetActivePlaylist(id);
|
||||
playlists_[id].p->InsertUrls(urls, pos, play_now, enqueue);
|
||||
}
|
||||
|
||||
@ -497,6 +505,7 @@ void PlaylistManager::InsertSongs(int id, const SongList& songs, int pos,
|
||||
bool play_now, bool enqueue) {
|
||||
Q_ASSERT(playlists_.contains(id));
|
||||
|
||||
if (play_now && active_ != id) SetActivePlaylist(id);
|
||||
playlists_[id].p->InsertSongs(songs, pos, play_now, enqueue);
|
||||
}
|
||||
|
||||
|
@ -73,8 +73,8 @@ class PlaylistManagerInterface : public QObject {
|
||||
virtual PlaylistContainer* playlist_container() const = 0;
|
||||
|
||||
public slots:
|
||||
virtual void New(const QString& name, const SongList& songs = SongList(),
|
||||
const QString& special_type = QString()) = 0;
|
||||
virtual int New(const QString& name, const SongList& songs = SongList(),
|
||||
const QString& special_type = QString()) = 0;
|
||||
virtual void Load(const QString& filename) = 0;
|
||||
virtual void Save(int id, const QString& filename,
|
||||
Playlist::Path path_type) = 0;
|
||||
@ -181,8 +181,8 @@ class PlaylistManager : public PlaylistManagerInterface {
|
||||
PlaylistContainer* playlist_container() const { return playlist_container_; }
|
||||
|
||||
public slots:
|
||||
void New(const QString& name, const SongList& songs = SongList(),
|
||||
const QString& special_type = QString());
|
||||
int New(const QString& name, const SongList& songs = SongList(),
|
||||
const QString& special_type = QString());
|
||||
void Load(const QString& filename);
|
||||
void Save(int id, const QString& filename, Playlist::Path path_type);
|
||||
// Display a file dialog to let user choose a file before saving the file
|
||||
@ -190,6 +190,7 @@ class PlaylistManager : public PlaylistManagerInterface {
|
||||
void Rename(int id, const QString& new_name);
|
||||
void Favorite(int id, bool favorite);
|
||||
void Delete(int id);
|
||||
void Clear(int id);
|
||||
bool Close(int id);
|
||||
void Open(int id);
|
||||
void ChangePlaylistOrder(const QList<int>& ids);
|
||||
|
@ -41,30 +41,33 @@ AddStreamDialog::AddStreamDialog(QWidget* parent)
|
||||
ui_->save->setChecked(s.value("save", true).toBool());
|
||||
ui_->url->setText(s.value("url").toString());
|
||||
ui_->name->setText(s.value("name").toString());
|
||||
ui_->url_logo->setText(s.value("url_logo").toString());
|
||||
}
|
||||
|
||||
AddStreamDialog::~AddStreamDialog() { delete ui_; }
|
||||
|
||||
QUrl AddStreamDialog::url() const { return QUrl(ui_->url->text()); }
|
||||
|
||||
QString AddStreamDialog::name() const { return ui_->name->text(); }
|
||||
QUrl AddStreamDialog::url_logo() const { return QUrl(ui_->url_logo->text()); }
|
||||
|
||||
void AddStreamDialog::set_name(const QString& name) {
|
||||
ui_->name->setText(name);
|
||||
}
|
||||
|
||||
void AddStreamDialog::set_url(const QUrl& url) {
|
||||
ui_->url->setText(url.toString());
|
||||
}
|
||||
void AddStreamDialog::set_url_logo(const QUrl& url) {
|
||||
ui_->url_logo->setText(url.toString());
|
||||
}
|
||||
|
||||
void AddStreamDialog::set_save_visible(bool visible) {
|
||||
ui_->save->setVisible(visible);
|
||||
if (!visible) ui_->name_container->setEnabled(true);
|
||||
if (!visible) ui_->details_container->setEnabled(true);
|
||||
}
|
||||
|
||||
void AddStreamDialog::accept() {
|
||||
if (ui_->save->isChecked() && saved_radio_) {
|
||||
saved_radio_->Add(url(), name());
|
||||
saved_radio_->Add(url(), name(), url_logo());
|
||||
}
|
||||
|
||||
// Save settings
|
||||
|
@ -33,6 +33,7 @@ class AddStreamDialog : public QDialog {
|
||||
|
||||
void set_url(const QUrl& url);
|
||||
void set_name(const QString& name);
|
||||
void set_url_logo(const QUrl& url);
|
||||
void set_save_visible(bool visible);
|
||||
void set_add_on_accept(SavedRadio* saved_radio) {
|
||||
saved_radio_ = saved_radio;
|
||||
@ -40,6 +41,7 @@ class AddStreamDialog : public QDialog {
|
||||
|
||||
QUrl url() const;
|
||||
QString name() const;
|
||||
QUrl url_logo() const;
|
||||
|
||||
void accept();
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>168</height>
|
||||
<height>220</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -39,21 +39,31 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="name_container" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<widget class="QWidget" name="details_container" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Give it a name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>URL of its Logo:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="url_logo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -127,7 +137,7 @@
|
||||
<connection>
|
||||
<sender>save</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>name_container</receiver>
|
||||
<receiver>details_container</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
|
@ -796,6 +796,12 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
|
||||
connect(InternetModel::Service<SavedRadio>(), SIGNAL(ShowAddStreamDialog()),
|
||||
SLOT(AddStream()));
|
||||
|
||||
// Network Remote
|
||||
connect(app->network_remote(), SIGNAL(AddToPlaylistSignal(QMimeData*)),
|
||||
SLOT(AddToPlaylist(QMimeData*)));
|
||||
connect(app->network_remote(), SIGNAL(SetCurrentPlaylist(int)),
|
||||
app_->playlist_manager(), SLOT(SetCurrentPlaylist(int)));
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
mac::SetApplicationHandler(this);
|
||||
#endif
|
||||
@ -1669,6 +1675,7 @@ void MainWindow::ApplyPlayBehaviour(MainWindow::PlayBehaviour b,
|
||||
void MainWindow::AddToPlaylist(QMimeData* data) {
|
||||
if (!data) return;
|
||||
|
||||
Playlist* playlist = nullptr;
|
||||
if (MimeData* mime_data = qobject_cast<MimeData*>(data)) {
|
||||
// Should we replace the flags with the user's preference?
|
||||
if (mime_data->override_user_settings_) {
|
||||
@ -1684,10 +1691,14 @@ void MainWindow::AddToPlaylist(QMimeData* data) {
|
||||
if (mime_data->open_in_new_playlist_) {
|
||||
app_->playlist_manager()->New(mime_data->get_name_for_new_playlist());
|
||||
}
|
||||
// or shall we drop the songs in another playlist?
|
||||
else if (mime_data->playlist_id != -1)
|
||||
playlist = app_->playlist_manager()->playlist(mime_data->playlist_id);
|
||||
}
|
||||
|
||||
app_->playlist_manager()->current()->dropMimeData(data, Qt::CopyAction, -1, 0,
|
||||
QModelIndex());
|
||||
if (!playlist) playlist = app_->playlist_manager()->current();
|
||||
|
||||
playlist->dropMimeData(data, Qt::CopyAction, -1, 0, QModelIndex());
|
||||
delete data;
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,13 @@ void NetworkRemoteSettingsPage::Load() {
|
||||
}
|
||||
}
|
||||
|
||||
ui_->files_root_folder->SetPath(s.value("files_root_folder", "").toString());
|
||||
ui_->files_music_extensions->setText(
|
||||
s.value("files_music_extensions",
|
||||
Application::kDefaultMusicExtensionsAllowedRemotely)
|
||||
.toStringList()
|
||||
.join(","));
|
||||
|
||||
s.endGroup();
|
||||
|
||||
// Get local ip addresses
|
||||
@ -147,6 +154,17 @@ void NetworkRemoteSettingsPage::Save() {
|
||||
.value<TranscoderPreset>();
|
||||
s.setValue("last_output_format", preset.codec_mimetype_);
|
||||
|
||||
s.setValue("files_root_folder", ui_->files_root_folder->Path());
|
||||
|
||||
QStringList files_music_extensions;
|
||||
for (const QString& extension :
|
||||
ui_->files_music_extensions->text().split(",")) {
|
||||
QString ext = extension.trimmed();
|
||||
if (ext.size() > 0 && ext.size() < 8) // no empty string, less than 8 char
|
||||
files_music_extensions << ext;
|
||||
}
|
||||
s.setValue("files_music_extensions", files_music_extensions);
|
||||
|
||||
s.endGroup();
|
||||
|
||||
if (NetworkRemoteHelper::Instance()) {
|
||||
|
@ -101,7 +101,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="toolTip">
|
||||
<string>Enter this IP in the App to connect to Clementine.</string>
|
||||
@ -111,7 +111,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="9" column="1">
|
||||
<widget class="QLabel" name="ip_address">
|
||||
<property name="text">
|
||||
<string notr="true">127.0.0.1</string>
|
||||
@ -128,7 +128,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="download_settings_container">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
@ -172,6 +172,39 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="toolTip">
|
||||
<string>Root folder that will be browsable from the network remote</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Files root folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="FileChooserWidget" name="files_root_folder" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="files_music_extensions"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="toolTip">
|
||||
<string>coma separated list of the allowed extensions that will be visible from the network remote (ex: m3u,mp3,flac,ogg,wav)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Music extensions remotely visible</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -259,6 +292,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>FileChooserWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">ui/filechooserwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user