1
0
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:
Matthieu Bruel 2020-11-27 13:05:53 +01:00 committed by John Maguire
parent d7966c8285
commit 9714b0632d
25 changed files with 606 additions and 113 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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

View File

@ -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()) {

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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);
};

View File

@ -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)));

View File

@ -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();

View File

@ -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);
}

View File

@ -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_;

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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();

View File

@ -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">

View File

@ -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;
}

View File

@ -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()) {

View File

@ -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>