Merge pull request #1 from clementine-player/master

upstream update
This commit is contained in:
Max 2014-09-04 06:27:46 +06:00
commit f4e0e03236
99 changed files with 16677 additions and 13929 deletions

View File

@ -210,6 +210,29 @@ IntReply *AudioProvider::removeFromLibrary(int aid, int oid)
return reply; return reply;
} }
IdListReply *AudioProvider::setBroadcast(int aid, int oid, const IdList &targetIds)
{
Q_D(AudioProvider);
QVariantMap args;
args.insert("audio", QString("%1_%2").arg(oid).arg(aid));
args.insert("target_ids", join(targetIds));
auto reply = d->client->request<IdListReply>("audio.setBroadcast", args, ReplyPrivate::handleIdList);
return reply;
}
IdListReply *AudioProvider::resetBroadcast(const IdList &targetIds)
{
Q_D(AudioProvider);
QVariantMap args;
args.insert("audio","");
args.insert("target_ids", join(targetIds));
auto reply = d->client->request<IdListReply>("audio.setBroadcast", args, ReplyPrivate::handleIdList);
return reply;
}
AudioItemListReply *AudioProvider::getAudiosByIds(const QString &ids) AudioItemListReply *AudioProvider::getAudiosByIds(const QString &ids)
{ {
Q_D(AudioProvider); Q_D(AudioProvider);

View File

@ -26,6 +26,7 @@
#define VK_AUDIO_H #define VK_AUDIO_H
#include <QAbstractListModel> #include <QAbstractListModel>
#include "vk_global.h"
#include "audioitem.h" #include "audioitem.h"
#include "abstractlistmodel.h" #include "abstractlistmodel.h"
#include "reply.h" #include "reply.h"
@ -35,6 +36,7 @@ namespace Vreen {
class Client; class Client;
typedef ReplyBase<AudioItemList> AudioItemListReply; typedef ReplyBase<AudioItemList> AudioItemListReply;
typedef ReplyBase<AudioAlbumItemList> AudioAlbumItemListReply; typedef ReplyBase<AudioAlbumItemList> AudioAlbumItemListReply;
typedef ReplyBase<QList<int>> IdListReply;
class AudioProviderPrivate; class AudioProviderPrivate;
class VK_SHARED_EXPORT AudioProvider : public QObject class VK_SHARED_EXPORT AudioProvider : public QObject
@ -60,6 +62,8 @@ public:
IntReply *getCount(int oid = 0); IntReply *getCount(int oid = 0);
IntReply *addToLibrary(int aid, int oid, int gid = 0); IntReply *addToLibrary(int aid, int oid, int gid = 0);
IntReply *removeFromLibrary(int aid, int oid); IntReply *removeFromLibrary(int aid, int oid);
IdListReply *setBroadcast(int aid, int oid, const IdList& targetIds);
IdListReply *resetBroadcast(const IdList& targetIds);
protected: protected:
QScopedPointer<AudioProviderPrivate> d_ptr; QScopedPointer<AudioProviderPrivate> d_ptr;
}; };

View File

@ -122,6 +122,16 @@ void ReplyPrivate::_q_network_reply_error(QNetworkReply::NetworkError code)
emit q->resultReady(response); emit q->resultReady(response);
} }
QVariant ReplyPrivate::handleIdList(const QVariant &response)
{
IdList ids;
auto list = response.toList();
foreach (auto item, list) {
ids.append(item.toInt());
}
return QVariant::fromValue(ids);
}
QVariant MessageListHandler::operator()(const QVariant &response) QVariant MessageListHandler::operator()(const QVariant &response)
{ {

View File

@ -50,15 +50,16 @@ public:
void _q_reply_finished(); void _q_reply_finished();
void _q_network_reply_error(QNetworkReply::NetworkError); void _q_network_reply_error(QNetworkReply::NetworkError);
static QVariant handleInt(const QVariant &response) { return response.toInt(); } static QVariant handleInt(const QVariant &response) { return response.toInt(); }
static QVariant handleIdList(const QVariant& response);
}; };
struct MessageListHandler { struct MessageListHandler {
MessageListHandler(int clientId) : clientId(clientId) {} MessageListHandler(int clientId) : clientId(clientId) {}
QVariant operator()(const QVariant &response); QVariant operator()(const QVariant &response);
int clientId; int clientId;
}; };
} //namespace Vreen } //namespace Vreen

View File

@ -30,32 +30,31 @@
static const QUrl kVkOAuthEndpoint("https://oauth.vk.com/authorize"); static const QUrl kVkOAuthEndpoint("https://oauth.vk.com/authorize");
static const QUrl kVkOAuthTokenEndpoint("https://oauth.vk.com/access_token"); static const QUrl kVkOAuthTokenEndpoint("https://oauth.vk.com/access_token");
static const QUrl kApiUrl("https://api.vk.com/method/"); static const QUrl kApiUrl("https://api.vk.com/method/");
static const char *kScopeNames[] = { "notify", "friends", "photos", "audio", static const char* kScopeNames[] = {
"video", "docs", "notes", "pages", "status", "offers", "questions", "wall", "notify", "friends", "photos", "audio", "video", "docs",
"groups", "messages", "notifications", "stats", "ads", "offline" }; "notes", "pages", "status", "offers", "questions", "wall",
"groups", "messages", "notifications", "stats", "ads", "offline"};
static const QString kAppID = "3421812"; static const QString kAppID = "3421812";
static const QString kAppSecret = "cY7KMyX46Fq3nscZlbdo"; static const QString kAppSecret = "cY7KMyX46Fq3nscZlbdo";
static const VkConnection::Scopes kScopes = static const VkConnection::Scopes kScopes =
VkConnection::Offline | VkConnection::Offline | VkConnection::Audio | VkConnection::Friends |
VkConnection::Audio | VkConnection::Groups | VkConnection::Status;
VkConnection::Friends |
VkConnection::Groups;
static const char* kSettingsGroup = "Vk.com/oauth"; static const char* kSettingsGroup = "Vk.com/oauth";
VkConnection::VkConnection(QObject* parent) VkConnection::VkConnection(QObject* parent)
: Connection(parent), : Connection(parent),
state_(Vreen::Client::StateOffline), state_(Vreen::Client::StateOffline),
expires_in_(0), expires_in_(0),
uid_(0) { uid_(0) {
loadToken(); loadToken();
} }
VkConnection::~VkConnection() { VkConnection::~VkConnection() {}
}
void VkConnection::connectToHost(const QString& login, const QString& password) { void VkConnection::connectToHost(const QString& login,
const QString& password) {
Q_UNUSED(login) Q_UNUSED(login)
Q_UNUSED(password) Q_UNUSED(password)
if (hasAccount()) { if (hasAccount()) {
@ -84,16 +83,17 @@ void VkConnection::clear() {
} }
bool VkConnection::hasAccount() { bool VkConnection::hasAccount() {
return !access_token_.isNull() return !access_token_.isNull() &&
&& (expires_in_ > static_cast<time_t>(QDateTime::currentDateTime().toTime_t())); (expires_in_ >
static_cast<time_t>(QDateTime::currentDateTime().toTime_t()));
} }
QNetworkRequest VkConnection::makeRequest(const QString& method, const QVariantMap& args) { QNetworkRequest VkConnection::makeRequest(const QString& method,
const QVariantMap& args) {
QUrl url = kApiUrl; QUrl url = kApiUrl;
url.setPath(url.path() % QLatin1Literal("/") % method); url.setPath(url.path() % QLatin1Literal("/") % method);
for (auto it = args.constBegin(); it != args.constEnd(); ++it) { for (auto it = args.constBegin(); it != args.constEnd(); ++it) {
url.addQueryItem(it.key(), url.addQueryItem(it.key(), it.value().toString());
it.value().toString());
} }
url.addEncodedQueryItem("access_token", access_token_); url.addEncodedQueryItem("access_token", access_token_);
return QNetworkRequest(url); return QNetworkRequest(url);
@ -118,9 +118,9 @@ void VkConnection::requestAccessToken() {
qLog(Debug) << "Try to login to Vk.com" << url; qLog(Debug) << "Try to login to Vk.com" << url;
NewClosure(server, SIGNAL(Finished()), NewClosure(server, SIGNAL(Finished()), this,
this, SLOT(codeRecived(LocalRedirectServer*, QUrl)), SLOT(codeRecived(LocalRedirectServer*, QUrl)), server,
server, server->url()); server->url());
QDesktopServices::openUrl(url); QDesktopServices::openUrl(url);
} }

View File

@ -25,44 +25,40 @@
#include "core/taskmanager.h" #include "core/taskmanager.h"
VkMusicCache::VkMusicCache(Application* app, VkService* service) VkMusicCache::VkMusicCache(Application* app, VkService* service)
: QObject(service), : QObject(service),
app_(app), app_(app),
service_(service), service_(service),
current_cashing_index(0), current_song_index(0),
is_downloading(false), is_downloading(false),
is_aborted(false), is_aborted(false),
task_id(0), task_id(0),
file_(NULL), file_(NULL),
network_manager_(new QNetworkAccessManager), network_manager_(new QNetworkAccessManager),
reply_(NULL) { reply_(NULL) {}
}
QUrl VkMusicCache::Get(const QUrl& url) { QUrl VkMusicCache::Get(const QUrl& url) {
QString cached_filename = CachedFilename(url);
QUrl result; QUrl result;
if (InCache(cached_filename)) { if (InCache(url)) {
QString cached_filename = CachedFilename(url);
qLog(Info) << "Use cashed file" << cached_filename; qLog(Info) << "Use cashed file" << cached_filename;
result = QUrl::fromLocalFile(cached_filename); result = QUrl::fromLocalFile(cached_filename);
} else {
result = service_->GetSongPlayUrl(url, false);
if (service_->isCachingEnabled()) {
AddToQueue(cached_filename, result);
current_cashing_index = queue_.size();
}
} }
return result; return result;
} }
void VkMusicCache::ForceCache(const QUrl& url) { void VkMusicCache::AddToCache(const QUrl& url, const QUrl& media_url,
AddToQueue(CachedFilename(url), service_->GetSongPlayUrl(url)); bool force) {
AddToQueue(CachedFilename(url), media_url);
if (!force) {
current_song_index = queue_.size();
}
} }
void VkMusicCache::BreakCurrentCaching() { void VkMusicCache::BreakCurrentCaching() {
if (current_cashing_index > 0) { if (current_song_index > 0) {
// Current song in queue // Current song in queue
queue_.removeAt(current_cashing_index - 1); queue_.removeAt(current_song_index - 1);
} else if (current_cashing_index == 0) { } else if (current_song_index == 0) {
// Current song is downloading // Current song is downloading
if (reply_) { if (reply_) {
reply_->abort(); reply_->abort();
@ -75,7 +71,8 @@ void VkMusicCache::BreakCurrentCaching() {
* Queue operations * Queue operations
*/ */
void VkMusicCache::AddToQueue(const QString& filename, const QUrl& download_url) { void VkMusicCache::AddToQueue(const QString& filename,
const QUrl& download_url) {
DownloadItem item; DownloadItem item;
item.filename = filename; item.filename = filename;
item.url = download_url; item.url = download_url;
@ -93,11 +90,12 @@ void VkMusicCache::DownloadNext() {
} else { } else {
current_download = queue_.first(); current_download = queue_.first();
queue_.pop_front(); queue_.pop_front();
current_cashing_index--; current_song_index--;
// Check file path and file existance first // Check file path and file existance first
if (QFile::exists(current_download.filename)) { if (QFile::exists(current_download.filename)) {
qLog(Warning) << "Tried to overwrite already cached file" << current_download.filename; qLog(Warning) << "Tried to overwrite already cached file"
<< current_download.filename;
return; return;
} }
@ -117,14 +115,15 @@ void VkMusicCache::DownloadNext() {
// Start downloading // Start downloading
is_aborted = false; is_aborted = false;
is_downloading = true; is_downloading = true;
task_id = app_->task_manager()-> task_id = app_->task_manager()->StartTask(
StartTask(tr("Caching %1") tr("Caching %1").arg(QFileInfo(current_download.filename).baseName()));
.arg(QFileInfo(current_download.filename).baseName()));
reply_ = network_manager_->get(QNetworkRequest(current_download.url)); reply_ = network_manager_->get(QNetworkRequest(current_download.url));
connect(reply_, SIGNAL(finished()), SLOT(Downloaded())); connect(reply_, SIGNAL(finished()), SLOT(Downloaded()));
connect(reply_, SIGNAL(readyRead()), SLOT(DownloadReadyToRead())); connect(reply_, SIGNAL(readyRead()), SLOT(DownloadReadyToRead()));
connect(reply_, SIGNAL(downloadProgress(qint64, qint64)), SLOT(DownloadProgress(qint64, qint64))); connect(reply_, SIGNAL(downloadProgress(qint64, qint64)),
qLog(Info)<< "Start cashing" << current_download.filename << "from" << current_download.url; SLOT(DownloadProgress(qint64, qint64)));
qLog(Info) << "Start cashing" << current_download.filename << "from"
<< current_download.url;
} }
} }
@ -154,13 +153,13 @@ void VkMusicCache::Downloaded() {
QString path = service_->cacheDir(); QString path = service_->cacheDir();
if (file_->size() > 0) { if (file_->size() > 0) {
QDir(path).mkpath(QFileInfo(current_download.filename).path()); QDir(path).mkpath(QFileInfo(current_download.filename).path());
if (file_->copy(current_download.filename)) { if (file_->copy(current_download.filename)) {
qLog(Info) << "Cached" << current_download.filename; qLog(Info) << "Cached" << current_download.filename;
} else { } else {
qLog(Error) << "Unable to save" << current_download.filename qLog(Error) << "Unable to save" << current_download.filename << ":"
<< ":" << file_->errorString(); << file_->errorString();
} }
} else { } else {
qLog(Error) << "File" << current_download.filename << "is empty"; qLog(Error) << "File" << current_download.filename << "is empty";
@ -181,12 +180,8 @@ void VkMusicCache::Downloaded() {
* Utils * Utils
*/ */
bool VkMusicCache::InCache(const QString& filename) {
return QFile::exists(filename);
}
bool VkMusicCache::InCache(const QUrl& url) { bool VkMusicCache::InCache(const QUrl& url) {
return InCache(CachedFilename(url)); return QFile::exists(CachedFilename(url));
} }
QString VkMusicCache::CachedFilename(const QUrl& url) { QString VkMusicCache::CachedFilename(const QUrl& url) {
@ -198,7 +193,8 @@ QString VkMusicCache::CachedFilename(const QUrl& url) {
cache_filename.replace("%artist", args[2]); cache_filename.replace("%artist", args[2]);
cache_filename.replace("%title", args[3]); cache_filename.replace("%title", args[3]);
} else { } else {
qLog(Warning) << "Song url with args" << args << "does not contain artist and title" qLog(Warning) << "Song url with args" << args
<< "does not contain artist and title"
<< "use id as file name for cache."; << "use id as file name for cache.";
cache_filename = args[1]; cache_filename = args[1];
} }
@ -209,5 +205,5 @@ QString VkMusicCache::CachedFilename(const QUrl& url) {
return ""; return "";
} }
// TODO(Vk): Maybe use extenstion from link? Seems it's always mp3. // TODO(Vk): Maybe use extenstion from link? Seems it's always mp3.
return cache_dir+QDir::separator()+cache_filename+".mp3"; return cache_dir + QDir::separator() + cache_filename + ".mp3";
} }

View File

@ -30,30 +30,29 @@ class Application;
class VkMusicCache : public QObject { class VkMusicCache : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit VkMusicCache(Application* app, VkService* service); explicit VkMusicCache(Application* app, VkService* service);
~VkMusicCache() {} ~VkMusicCache() {}
// Return file path if file in cache otherwise // Return file path if file in cache otherwise
// return internet url and add song to caching queue // return internet url and add song to caching queue
QUrl Get(const QUrl& url); QUrl Get(const QUrl& url);
void ForceCache(const QUrl& url); void AddToCache(const QUrl& url, const QUrl& media_url, bool force = false);
void BreakCurrentCaching(); void BreakCurrentCaching();
bool InCache(const QUrl& url); bool InCache(const QUrl& url);
private slots: private slots:
bool InCache(const QString& filename);
void AddToQueue(const QString& filename, const QUrl& download_url); void AddToQueue(const QString& filename, const QUrl& download_url);
void DownloadNext(); void DownloadNext();
void DownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void DownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void DownloadReadyToRead(); void DownloadReadyToRead();
void Downloaded(); void Downloaded();
private: private:
struct DownloadItem { struct DownloadItem {
QString filename; QString filename;
QUrl url; QUrl url;
bool operator ==(const DownloadItem& rhv) { bool operator==(const DownloadItem& rhv) {
return filename == rhv.filename; return filename == rhv.filename;
} }
}; };
@ -63,9 +62,10 @@ private:
Application* app_; Application* app_;
VkService* service_; VkService* service_;
QList<DownloadItem> queue_; QList<DownloadItem> queue_;
// Contain index of current song in queue, need for removing if song was skipped. // Contain index of current song in queue, need for removing if song was
// Is zero if song downloading now, and less that zero if current song not caching or cached. // skipped. It's zero if song downloading now, and less that zero
int current_cashing_index; // if current song not caching or cached.
int current_song_index;
DownloadItem current_download; DownloadItem current_download;
bool is_downloading; bool is_downloading;
bool is_aborted; bool is_aborted;

View File

@ -312,7 +312,7 @@ void VkService::EnsureMenuCreated() {
add_song_to_cache_ = context_menu_->addAction(QIcon(":vk/download.png"), add_song_to_cache_ = context_menu_->addAction(QIcon(":vk/download.png"),
tr("Add song to cache"), this, tr("Add song to cache"), this,
SLOT(AddToCache())); SLOT(AddSelectedToCache()));
copy_share_url_ = context_menu_->addAction( copy_share_url_ = context_menu_->addAction(
QIcon(":vk/link.png"), tr("Copy share url to clipboard"), this, QIcon(":vk/link.png"), tr("Copy share url to clipboard"), this,
@ -367,7 +367,7 @@ void VkService::ShowContextMenu(const QPoint& global_pos) {
current.data(InternetModel::Role_SongMetadata).value<Song>(); current.data(InternetModel::Role_SongMetadata).value<Song>();
is_in_mymusic = is_my_music_item || is_in_mymusic = is_my_music_item ||
ExtractIds(selected_song_.url()).owner_id == UserID(); ExtractIds(selected_song_.url()).owner_id == UserID();
is_cached = cache()->InCache(selected_song_.url()); is_cached = cache_->InCache(selected_song_.url());
} }
update_item_->setVisible(is_updatable); update_item_->setVisible(is_updatable);
@ -443,7 +443,7 @@ QList<QAction*> VkService::playlistitem_actions(const Song& song) {
copy_share_url_->setVisible(true); copy_share_url_->setVisible(true);
actions << copy_share_url_; actions << copy_share_url_;
if (!cache()->InCache(selected_song_.url())) { if (!cache_->InCache(selected_song_.url())) {
add_song_to_cache_->setVisible(true); add_song_to_cache_->setVisible(true);
actions << add_song_to_cache_; actions << add_song_to_cache_;
} }
@ -835,7 +835,7 @@ void VkService::AddToMyMusic() {
} }
void VkService::AddToMyMusicCurrent() { void VkService::AddToMyMusicCurrent() {
if (isLoveAddToMyMusic()) { if (isLoveAddToMyMusic() && current_song_.is_valid()) {
selected_song_ = current_song_; selected_song_ = current_song_;
AddToMyMusic(); AddToMyMusic();
} }
@ -852,8 +852,10 @@ void VkService::RemoveFromMyMusic() {
} }
} }
void VkService::AddToCache() { void VkService::AddSelectedToCache() {
url_handler_->ForceAddToCache(selected_song_.url()); QUrl selected_song_media_url =
GetAudioItemFromUrl(selected_song_.url()).url();
cache_->AddToCache(selected_song_.url(), selected_song_media_url, true);
} }
void VkService::CopyShareUrl() { void VkService::CopyShareUrl() {
@ -999,12 +1001,12 @@ SongList VkService::FromAudioList(const Vreen::AudioItemList& list) {
* Url handling * Url handling
*/ */
QUrl VkService::GetSongPlayUrl(const QUrl& url, bool is_playing) { Vreen::AudioItem VkService::GetAudioItemFromUrl(const QUrl& url) {
QStringList tokens = url.path().split('/'); QStringList tokens = url.path().split('/');
if (tokens.count() < 2) { if (tokens.count() < 2) {
qLog(Error) << "Wrong song url" << url; qLog(Error) << "Wrong song url" << url;
return QUrl(); return Vreen::AudioItem();
} }
QString song_id = tokens[1]; QString song_id = tokens[1];
@ -1016,17 +1018,35 @@ QUrl VkService::GetSongPlayUrl(const QUrl& url, bool is_playing) {
bool success = WaitForReply(song_request); bool success = WaitForReply(song_request);
if (success && !song_request->result().isEmpty()) { if (success && !song_request->result().isEmpty()) {
Vreen::AudioItem song = song_request->result()[0]; return song_request->result()[0];
if (is_playing) {
current_song_ = FromAudioItem(song);
current_song_.set_url(url);
}
return song.url();
} }
} }
qLog(Info) << "Unresolved url by id" << song_id; qLog(Info) << "Unresolved url by id" << song_id;
return QUrl(); return Vreen::AudioItem();
}
UrlHandler::LoadResult VkService::GetSongResult(const QUrl& url) {
// Try get from cache
QUrl media_url = cache_->Get(url);
if (media_url.isValid()) {
SongStarting(url);
return UrlHandler::LoadResult(url, UrlHandler::LoadResult::TrackAvailable,
media_url);
}
// Otherwise get fresh link
auto audio_item = GetAudioItemFromUrl(url);
media_url = audio_item.url();
if (media_url.isValid()) {
Song song = FromAudioItem(audio_item);
SongStarting(song);
cache_->AddToCache(url, media_url);
return UrlHandler::LoadResult(url, UrlHandler::LoadResult::TrackAvailable,
media_url, song.length_nanosec());
}
return UrlHandler::LoadResult();
} }
UrlHandler::LoadResult VkService::GetGroupNextSongUrl(const QUrl& url) { UrlHandler::LoadResult VkService::GetGroupNextSongUrl(const QUrl& url) {
@ -1054,7 +1074,7 @@ UrlHandler::LoadResult VkService::GetGroupNextSongUrl(const QUrl& url) {
if (success && !song_request->result().isEmpty()) { if (success && !song_request->result().isEmpty()) {
Vreen::AudioItem song = song_request->result()[0]; Vreen::AudioItem song = song_request->result()[0];
current_group_url_ = url; current_group_url_ = url;
current_song_ = FromAudioItem(song); SongStarting(FromAudioItem(song));
emit StreamMetadataFound(url, current_song_); emit StreamMetadataFound(url, current_song_);
return UrlHandler::LoadResult(url, UrlHandler::LoadResult::TrackAvailable, return UrlHandler::LoadResult(url, UrlHandler::LoadResult::TrackAvailable,
song.url(), current_song_.length_nanosec()); song.url(), current_song_.length_nanosec());
@ -1065,8 +1085,48 @@ UrlHandler::LoadResult VkService::GetGroupNextSongUrl(const QUrl& url) {
return UrlHandler::LoadResult(); return UrlHandler::LoadResult();
} }
void VkService::SetCurrentSongFromUrl(const QUrl& url) { /***
current_song_ = SongFromUrl(url); * Song playing
*/
void VkService::SongStarting(const QUrl& url) {
SongStarting(SongFromUrl(url));
}
void VkService::SongStarting(const Song& song) {
current_song_ = song;
if (isBroadcasting() && HasAccount()) {
auto id = ExtractIds(song.url());
auto reply =
audio_provider_->setBroadcast(id.audio_id, id.owner_id, IdList());
NewClosure(reply, SIGNAL(resultReady(QVariant)), this,
SLOT(BroadcastChangeReceived(Vreen::IntReply*)), reply);
connect(app_->player(), SIGNAL(Stopped()), this, SLOT(SongStopped()),
Qt::UniqueConnection);
qLog(Debug) << "Broadcasting" << song.artist() << "-" << song.title();
}
}
void VkService::SongSkipped() {
current_song_.set_valid(false);
cache_->BreakCurrentCaching();
}
void VkService::SongStopped() {
current_song_.set_valid(false);
if (isBroadcasting() && HasAccount()) {
auto reply = audio_provider_->resetBroadcast(IdList());
NewClosure(reply, SIGNAL(resultReady(QVariant)), this,
SLOT(BroadcastChangeReceived(Vreen::IntReply*)), reply);
disconnect(app_->player(), SIGNAL(Stopped()), this, SLOT(SongStopped()));
qLog(Debug) << "End of broadcasting";
}
}
void VkService::BroadcastChangeReceived(Vreen::IntReply* reply) {
qLog(Debug) << "Broadcast changed for " << reply->result();
} }
/*** /***
@ -1234,6 +1294,12 @@ void VkService::ReloadSettings() {
cacheFilename_ = s.value("cache_filename", kDefCacheFilename).toString(); cacheFilename_ = s.value("cache_filename", kDefCacheFilename).toString();
love_is_add_to_mymusic_ = s.value("love_is_add_to_my_music", false).toBool(); love_is_add_to_mymusic_ = s.value("love_is_add_to_my_music", false).toBool();
groups_in_global_search_ = s.value("groups_in_global_search", false).toBool(); groups_in_global_search_ = s.value("groups_in_global_search", false).toBool();
if (!s.contains("enable_broadcast")) {
// Need to update premissions
Logout();
}
enable_broadcast_ = s.value("enable_broadcast", false).toBool();
} }
void VkService::ClearStandardItem(QStandardItem* item) { void VkService::ClearStandardItem(QStandardItem* item) {

View File

@ -44,11 +44,8 @@ class VkSearchDialog;
* using in bookmarks. * using in bookmarks.
*/ */
class MusicOwner { class MusicOwner {
public: public:
MusicOwner() : MusicOwner() : songs_count_(0), id_(0) {}
songs_count_(0),
id_(0)
{}
explicit MusicOwner(const QUrl& group_url); explicit MusicOwner(const QUrl& group_url);
Song toOwnerRadio() const; Song toOwnerRadio() const;
@ -58,7 +55,7 @@ public:
int song_count() const { return songs_count_; } int song_count() const { return songs_count_; }
static QList<MusicOwner> parseMusicOwnerList(const QVariant& request_result); static QList<MusicOwner> parseMusicOwnerList(const QVariant& request_result);
private: private:
friend QDataStream& operator<<(QDataStream& stream, const MusicOwner& val); friend QDataStream& operator<<(QDataStream& stream, const MusicOwner& val);
friend QDataStream& operator>>(QDataStream& stream, MusicOwner& val); friend QDataStream& operator>>(QDataStream& stream, MusicOwner& val);
friend QDebug operator<<(QDebug d, const MusicOwner& owner); friend QDebug operator<<(QDebug d, const MusicOwner& owner);
@ -66,7 +63,8 @@ private:
int songs_count_; int songs_count_;
int id_; // if id > 0 is user otherwise id group int id_; // if id > 0 is user otherwise id group
QString name_; QString name_;
// name used in url http://vk.com/<screen_name> for example: http://vk.com/shedward // name used in url http://vk.com/<screen_name> for example:
// http://vk.com/shedward
QString screen_name_; QString screen_name_;
QUrl photo_; QUrl photo_;
}; };
@ -84,20 +82,13 @@ QDebug operator<<(QDebug d, const MusicOwner& owner);
* how to react to the received request or quickly skip unwanted. * how to react to the received request or quickly skip unwanted.
*/ */
struct SearchID { struct SearchID {
enum Type { enum Type { GlobalSearch, LocalSearch, MoreLocalSearch, UserOrGroup };
GlobalSearch,
LocalSearch,
MoreLocalSearch,
UserOrGroup
};
explicit SearchID(Type type) explicit SearchID(Type type) : type_(type) { id_ = last_id_++; }
: type_(type) {
id_= last_id_++;
}
int id() const { return id_; } int id() const { return id_; }
Type type() const { return type_; } Type type() const { return type_; }
private:
private:
static uint last_id_; static uint last_id_;
int id_; int id_;
Type type_; Type type_;
@ -109,7 +100,7 @@ private:
class VkService : public InternetService { class VkService : public InternetService {
Q_OBJECT Q_OBJECT
public: public:
explicit VkService(Application* app, InternetModel* parent); explicit VkService(Application* app, InternetModel* parent);
~VkService(); ~VkService();
@ -133,8 +124,12 @@ public:
Type_Search Type_Search
}; };
enum Role { Role_MusicOwnerMetadata = InternetModel::RoleCount, enum Role {
Role_AlbumMetadata }; Role_MusicOwnerMetadata = InternetModel::RoleCount,
Role_AlbumMetadata
};
Application* app() const { return app_; }
/* InternetService interface */ /* InternetService interface */
QStandardItem* CreateRootItem(); QStandardItem* CreateRootItem();
@ -155,13 +150,16 @@ public:
bool WaitForReply(Vreen::Reply* reply); bool WaitForReply(Vreen::Reply* reply);
/* Music */ /* Music */
VkMusicCache* cache() const { return cache_; } void SongStarting(const Song& song);
void SetCurrentSongFromUrl(const QUrl& url); // Used if song taked from cache. void SongStarting(const QUrl& url); // Used if song taked from cache.
QUrl GetSongPlayUrl(const QUrl& url, bool is_playing = true); void SongSkipped();
UrlHandler::LoadResult GetSongResult(const QUrl& url);
Vreen::AudioItem GetAudioItemFromUrl(const QUrl& url);
// Return random song result from group playlist. // Return random song result from group playlist.
UrlHandler::LoadResult GetGroupNextSongUrl(const QUrl& url); UrlHandler::LoadResult GetGroupNextSongUrl(const QUrl& url);
void SongSearch(SearchID id, const QString& query, int count = 50, int offset = 0); void SongSearch(SearchID id, const QString& query, int count = 50,
int offset = 0);
void GroupSearch(SearchID id, const QString& query); void GroupSearch(SearchID id, const QString& query);
/* Settings */ /* Settings */
@ -169,6 +167,7 @@ public:
int maxGlobalSearch() const { return maxGlobalSearch_; } int maxGlobalSearch() const { return maxGlobalSearch_; }
bool isCachingEnabled() const { return cachingEnabled_; } bool isCachingEnabled() const { return cachingEnabled_; }
bool isGroupsInGlobalSearch() const { return groups_in_global_search_; } bool isGroupsInGlobalSearch() const { return groups_in_global_search_; }
bool isBroadcasting() const { return enable_broadcast_; }
QString cacheDir() const { return cacheDir_; } QString cacheDir() const { return cacheDir_; }
QString cacheFilename() const { return cacheFilename_; } QString cacheFilename() const { return cacheFilename_; }
bool isLoveAddToMyMusic() const { return love_is_add_to_mymusic_; } bool isLoveAddToMyMusic() const { return love_is_add_to_mymusic_; }
@ -179,15 +178,16 @@ signals:
void LoginSuccess(bool success); void LoginSuccess(bool success);
void SongSearchResult(const SearchID& id, const SongList& songs); void SongSearchResult(const SearchID& id, const SongList& songs);
void GroupSearchResult(const SearchID& id, const MusicOwnerList& groups); void GroupSearchResult(const SearchID& id, const MusicOwnerList& groups);
void UserOrGroupSearchResult(const SearchID& id, const MusicOwnerList& owners); void UserOrGroupSearchResult(const SearchID& id,
const MusicOwnerList& owners);
void StopWaiting(); void StopWaiting();
public slots: public slots:
void UpdateRoot(); void UpdateRoot();
void ShowConfig(); void ShowConfig();
void FindUserOrGroup(const QString& q); void FindUserOrGroup(const QString& q);
private slots: private slots:
/* Interface */ /* Interface */
void UpdateItem(); void UpdateItem();
@ -197,6 +197,7 @@ private slots:
void Error(Vreen::Client::Error error); void Error(Vreen::Client::Error error);
/* Music */ /* Music */
void SongStopped();
void UpdateMyMusic(); void UpdateMyMusic();
void UpdateBookmarkSongs(QStandardItem* item); void UpdateBookmarkSongs(QStandardItem* item);
void UpdateAlbumSongs(QStandardItem* item); void UpdateAlbumSongs(QStandardItem* item);
@ -208,7 +209,7 @@ private slots:
void AddToMyMusic(); void AddToMyMusic();
void AddToMyMusicCurrent(); void AddToMyMusicCurrent();
void RemoveFromMyMusic(); void RemoveFromMyMusic();
void AddToCache(); void AddSelectedToCache();
void CopyShareUrl(); void CopyShareUrl();
void ShowSearchDialog(); void ShowSearchDialog();
@ -219,14 +220,16 @@ private slots:
void GroupSearchReceived(const SearchID& id, Vreen::Reply* reply); void GroupSearchReceived(const SearchID& id, Vreen::Reply* reply);
void UserOrGroupReceived(const SearchID& id, Vreen::Reply* reply); void UserOrGroupReceived(const SearchID& id, Vreen::Reply* reply);
void AlbumListReceived(Vreen::AudioAlbumItemListReply* reply); void AlbumListReceived(Vreen::AudioAlbumItemListReply* reply);
void BroadcastChangeReceived(Vreen::IntReply* reply);
void AppendLoadedSongs(QStandardItem* item, Vreen::AudioItemListReply* reply); void AppendLoadedSongs(QStandardItem* item, Vreen::AudioItemListReply* reply);
void RecommendationsLoaded(Vreen::AudioItemListReply* reply); void RecommendationsLoaded(Vreen::AudioItemListReply* reply);
void SearchResultLoaded(const SearchID& id, const SongList& songs); void SearchResultLoaded(const SearchID& id, const SongList& songs);
private: private:
/* Interface */ /* Interface */
QStandardItem* CreateAndAppendRow(QStandardItem* parent, VkService::ItemType type); QStandardItem* CreateAndAppendRow(QStandardItem* parent,
VkService::ItemType type);
void ClearStandardItem(QStandardItem* item); void ClearStandardItem(QStandardItem* item);
QStandardItem* GetBookmarkItemById(int id); QStandardItem* GetBookmarkItemById(int id);
void EnsureMenuCreated(); void EnsureMenuCreated();
@ -279,7 +282,7 @@ private:
uint last_search_id_; uint last_search_id_;
QString last_query_; QString last_query_;
Song selected_song_; // Store for context menu actions. Song selected_song_; // Store for context menu actions.
Song current_song_; // Store for actions with now playing song. Song current_song_; // Store for actions with now playing song.
// Store current group url for actions with it. // Store current group url for actions with it.
QUrl current_group_url_; QUrl current_group_url_;
@ -288,6 +291,7 @@ private:
bool cachingEnabled_; bool cachingEnabled_;
bool love_is_add_to_mymusic_; bool love_is_add_to_mymusic_;
bool groups_in_global_search_; bool groups_in_global_search_;
bool enable_broadcast_;
QString cacheDir_; QString cacheDir_;
QString cacheFilename_; QString cacheFilename_;
}; };

View File

@ -25,32 +25,29 @@
#include "core/logging.h" #include "core/logging.h"
#include "internet/vkservice.h" #include "internet/vkservice.h"
VkSettingsPage::VkSettingsPage(SettingsDialog *parent) VkSettingsPage::VkSettingsPage(SettingsDialog* parent)
: SettingsPage(parent), : SettingsPage(parent),
ui_(new Ui::VkSettingsPage), ui_(new Ui::VkSettingsPage),
service_(dialog()->app()->internet_model()->Service<VkService>()) { service_(dialog()->app()->internet_model()->Service<VkService>()) {
ui_->setupUi(this); ui_->setupUi(this);
connect(service_, SIGNAL(LoginSuccess(bool)), connect(service_, SIGNAL(LoginSuccess(bool)), SLOT(LoginSuccess(bool)));
SLOT(LoginSuccess(bool))); connect(ui_->choose_path, SIGNAL(clicked()), SLOT(CacheDirBrowse()));
connect(ui_->choose_path, SIGNAL(clicked()), connect(ui_->reset, SIGNAL(clicked()), SLOT(ResetCasheFilenames()));
SLOT(CacheDirBrowse()));
connect(ui_->reset, SIGNAL(clicked()),
SLOT(ResetCasheFilenames()));
} }
VkSettingsPage::~VkSettingsPage() { VkSettingsPage::~VkSettingsPage() { delete ui_; }
delete ui_;
}
void VkSettingsPage::Load() { void VkSettingsPage::Load() {
service_->ReloadSettings(); service_->ReloadSettings();
ui_->maxGlobalSearch->setValue(service_->maxGlobalSearch()); ui_->max_global_search->setValue(service_->maxGlobalSearch());
ui_->enable_caching->setChecked(service_->isCachingEnabled()); ui_->enable_caching->setChecked(service_->isCachingEnabled());
ui_->cache_dir->setText(service_->cacheDir()); ui_->cache_dir->setText(service_->cacheDir());
ui_->cache_filename->setText(service_->cacheFilename()); ui_->cache_filename->setText(service_->cacheFilename());
ui_->love_button_is_add_to_mymusic->setChecked(service_->isLoveAddToMyMusic()); ui_->love_button_is_add_to_mymusic->setChecked(
service_->isLoveAddToMyMusic());
ui_->groups_in_global_search->setChecked(service_->isGroupsInGlobalSearch()); ui_->groups_in_global_search->setChecked(service_->isGroupsInGlobalSearch());
ui_->enable_broadcast->setChecked(service_->isBroadcasting());
if (service_->HasAccount()) { if (service_->HasAccount()) {
LogoutWidgets(); LogoutWidgets();
@ -63,12 +60,15 @@ void VkSettingsPage::Save() {
QSettings s; QSettings s;
s.beginGroup(VkService::kSettingGroup); s.beginGroup(VkService::kSettingGroup);
s.setValue("max_global_search", ui_->maxGlobalSearch->value()); s.setValue("max_global_search", ui_->max_global_search->value());
s.setValue("cache_enabled", ui_->enable_caching->isChecked()); s.setValue("cache_enabled", ui_->enable_caching->isChecked());
s.setValue("cache_dir", ui_->cache_dir->text()); s.setValue("cache_dir", ui_->cache_dir->text());
s.setValue("cache_filename", ui_->cache_filename->text()); s.setValue("cache_filename", ui_->cache_filename->text());
s.setValue("love_is_add_to_my_music", ui_->love_button_is_add_to_mymusic->isChecked()); s.setValue("love_is_add_to_my_music",
s.setValue("groups_in_global_search", ui_->groups_in_global_search->isChecked()); ui_->love_button_is_add_to_mymusic->isChecked());
s.setValue("groups_in_global_search",
ui_->groups_in_global_search->isChecked());
s.setValue("enable_broadcast", ui_->enable_broadcast->isChecked());
service_->ReloadSettings(); service_->ReloadSettings();
} }
@ -94,7 +94,7 @@ void VkSettingsPage::Logout() {
void VkSettingsPage::CacheDirBrowse() { void VkSettingsPage::CacheDirBrowse() {
QString directory = QFileDialog::getExistingDirectory( QString directory = QFileDialog::getExistingDirectory(
this, tr("Choose Vk.com cache directory"), ui_->cache_dir->text()); this, tr("Choose Vk.com cache directory"), ui_->cache_dir->text());
if (directory.isEmpty()) { if (directory.isEmpty()) {
return; return;
} }
@ -111,10 +111,9 @@ void VkSettingsPage::LoginWidgets() {
ui_->name->setText(""); ui_->name->setText("");
ui_->login_button->setEnabled(true); ui_->login_button->setEnabled(true);
connect(ui_->login_button, SIGNAL(clicked()), connect(ui_->login_button, SIGNAL(clicked()), SLOT(Login()),
SLOT(Login()), Qt::UniqueConnection); Qt::UniqueConnection);
disconnect(ui_->login_button, SIGNAL(clicked()), disconnect(ui_->login_button, SIGNAL(clicked()), this, SLOT(Logout()));
this, SLOT(Logout()));
} }
void VkSettingsPage::LogoutWidgets() { void VkSettingsPage::LogoutWidgets() {
@ -122,12 +121,11 @@ void VkSettingsPage::LogoutWidgets() {
ui_->name->setText(tr("Loading...")); ui_->name->setText(tr("Loading..."));
ui_->login_button->setEnabled(true); ui_->login_button->setEnabled(true);
connect(service_, SIGNAL(NameUpdated(QString)), connect(service_, SIGNAL(NameUpdated(QString)), ui_->name,
ui_->name, SLOT(setText(QString)), Qt::UniqueConnection); SLOT(setText(QString)), Qt::UniqueConnection);
service_->RequestUserProfile(); service_->RequestUserProfile();
connect(ui_->login_button, SIGNAL(clicked()), connect(ui_->login_button, SIGNAL(clicked()), SLOT(Logout()),
SLOT(Logout()), Qt::UniqueConnection); Qt::UniqueConnection);
disconnect(ui_->login_button, SIGNAL(clicked()), disconnect(ui_->login_button, SIGNAL(clicked()), this, SLOT(Login()));
this, SLOT(Login()));
} }

View File

@ -64,12 +64,12 @@
<string>Max global search results</string> <string>Max global search results</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>maxGlobalSearch</cstring> <cstring>max_global_search</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="maxGlobalSearch"> <widget class="QSpinBox" name="max_global_search">
<property name="minimum"> <property name="minimum">
<number>50</number> <number>50</number>
</property> </property>
@ -110,6 +110,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="enable_broadcast">
<property name="text">
<string>Show playing song on your page</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -19,45 +19,39 @@
#include "core/application.h" #include "core/application.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/player.h"
#include "vkservice.h" #include "vkservice.h"
#include "vkmusiccache.h" #include "vkmusiccache.h"
VkUrlHandler::VkUrlHandler(VkService* service, QObject* parent) VkUrlHandler::VkUrlHandler(VkService* service, QObject* parent)
: UrlHandler(parent), : UrlHandler(parent), service_(service) {}
service_(service) {
}
UrlHandler::LoadResult VkUrlHandler::StartLoading(const QUrl& url) { UrlHandler::LoadResult VkUrlHandler::StartLoading(const QUrl& url) {
QStringList args = url.path().split("/"); QStringList args = url.path().split("/");
LoadResult result; LoadResult result;
if (args.size() < 2) { if (args.size() < 2) {
qLog(Error) << "Invalid Vk.com URL: " << url qLog(Error)
<< "Url format should be vk://<source>/<id>." << "Invalid Vk.com URL: " << url
<< "For example vk://song/61145020_166946521/Daughtry/Gone Too Soon"; << "Url format should be vk://<source>/<id>."
<< "For example vk://song/61145020_166946521/Daughtry/Gone Too Soon";
} else { } else {
QString action = url.host(); QString action = url.host();
if (action == "song") { if (action == "song") {
service_->SetCurrentSongFromUrl(url); result = service_->GetSongResult(url);
result = LoadResult(url, LoadResult::TrackAvailable, service_->cache()->Get(url));
} else if (action == "group") { } else if (action == "group") {
result = service_->GetGroupNextSongUrl(url); result = service_->GetGroupNextSongUrl(url);
} else { } else {
qLog(Error) << "Invalid vk.com url action:" << action; qLog(Error) << "Invalid vk.com url action:" << action;
} }
} }
return result; return result;
} }
void VkUrlHandler::TrackSkipped() { void VkUrlHandler::TrackSkipped() { service_->SongSkipped(); }
service_->cache()->BreakCurrentCaching();
}
void VkUrlHandler::ForceAddToCache(const QUrl& url) {
service_->cache()->ForceCache(url);
}
UrlHandler::LoadResult VkUrlHandler::LoadNext(const QUrl& url) { UrlHandler::LoadResult VkUrlHandler::LoadNext(const QUrl& url) {
if (url.host() == "group") { if (url.host() == "group") {

View File

@ -28,16 +28,15 @@ class VkMusicCache;
class VkUrlHandler : public UrlHandler { class VkUrlHandler : public UrlHandler {
Q_OBJECT Q_OBJECT
public: public:
VkUrlHandler(VkService* service, QObject* parent); VkUrlHandler(VkService* service, QObject* parent);
QString scheme() const { return "vk"; } QString scheme() const { return "vk"; }
QIcon icon() const { return QIcon(":providers/vk.png"); } QIcon icon() const { return QIcon(":providers/vk.png"); }
LoadResult StartLoading(const QUrl& url); LoadResult StartLoading(const QUrl& url);
void TrackSkipped(); void TrackSkipped();
void ForceAddToCache(const QUrl& url);
LoadResult LoadNext(const QUrl& url); LoadResult LoadNext(const QUrl& url);
private: private:
VkService* service_; VkService* service_;
}; };

View File

@ -464,7 +464,7 @@ QVariant LibraryModel::AlbumIcon(const QModelIndex& index) {
} }
// Try to load it from the disk cache // Try to load it from the disk cache
std::unique_ptr<QIODevice> cache (icon_cache_->data(QUrl(cache_key))); std::unique_ptr<QIODevice> cache(icon_cache_->data(QUrl(cache_key)));
if (cache) { if (cache) {
QImage cached_pixmap; QImage cached_pixmap;
if (cached_pixmap.load(cache.get(), "XPM")) { if (cached_pixmap.load(cache.get(), "XPM")) {
@ -508,7 +508,7 @@ void LibraryModel::AlbumArtLoaded(quint64 id, const QImage& image) {
} }
// if not already in the disk cache // if not already in the disk cache
std::unique_ptr<QIODevice> cached_img (icon_cache_->data(QUrl(cache_key))); std::unique_ptr<QIODevice> cached_img(icon_cache_->data(QUrl(cache_key)));
if (!cached_img) { if (!cached_img) {
QNetworkCacheMetaData item_metadata; QNetworkCacheMetaData item_metadata;
item_metadata.setSaveToDisk(true); item_metadata.setSaveToDisk(true);
@ -1073,6 +1073,10 @@ QString LibraryModel::SortTextForArtist(QString artist) {
if (artist.startsWith("the ")) { if (artist.startsWith("the ")) {
artist = artist.right(artist.length() - 4) + ", the"; artist = artist.right(artist.length() - 4) + ", the";
} else if (artist.startsWith("a ")) {
artist = artist.right(artist.length() - 2) + ", a";
} else if (artist.startsWith("an ")) {
artist = artist.right(artist.length() - 3) + ", an";
} }
return artist; return artist;

View File

@ -156,6 +156,10 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline* request, const QUrl& url) {
metadata.setUrl(url); metadata.setUrl(url);
QIODevice* cache_file = cache_->prepare(metadata); QIODevice* cache_file = cache_->prepare(metadata);
if (!cache_file) {
qLog(Warning) << "Error writing to moodbar cache";
return;
}
cache_file->write(request->data()); cache_file->write(request->data());
cache_->insert(cache_file); cache_->insert(cache_file);

View File

@ -33,6 +33,7 @@ const char* MusicBrainzClient::kDiscUrl =
"https://musicbrainz.org/ws/2/discid/"; "https://musicbrainz.org/ws/2/discid/";
const char* MusicBrainzClient::kDateRegex = "^[12]\\d{3}"; const char* MusicBrainzClient::kDateRegex = "^[12]\\d{3}";
const int MusicBrainzClient::kDefaultTimeout = 5000; // msec const int MusicBrainzClient::kDefaultTimeout = 5000; // msec
const int MusicBrainzClient::kMaxRequestPerTrack = 3;
MusicBrainzClient::MusicBrainzClient(QObject* parent, MusicBrainzClient::MusicBrainzClient(QObject* parent,
QNetworkAccessManager* network) QNetworkAccessManager* network)
@ -59,6 +60,10 @@ void MusicBrainzClient::Start(int id, const QStringList& mbid_list) {
requests_.insert(id, reply); requests_.insert(id, reply);
timeouts_->AddReply(reply); timeouts_->AddReply(reply);
if (request_number >= kMaxRequestPerTrack) {
break;
}
} }
} }
@ -142,7 +147,7 @@ void MusicBrainzClient::DiscIdRequestFinished(const QString& discid,
} }
} }
emit Finished(artist, album, UniqueResults(ret)); emit Finished(artist, album, UniqueResults(ret, SortResults));
} }
void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id, int request_number) { void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id, int request_number) {
@ -154,7 +159,6 @@ void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id, int reques
"requests removed, while only one was supposed to be removed"; "requests removed, while only one was supposed to be removed";
} }
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() ==
200) { 200) {
QXmlStreamReader reader(reply); QXmlStreamReader reader(reply);
@ -170,8 +174,11 @@ void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id, int reques
} }
} }
} }
qSort(res);
pending_results_[id] << PendingResults(request_number, res); pending_results_[id] << PendingResults(request_number, res);
} else {
qLog(Error) << "Error:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() <<
"http status code received";
qLog(Error) << reply->readAll();
} }
// No more pending requests for this id: emit the results we have. // No more pending requests for this id: emit the results we have.
@ -183,7 +190,7 @@ void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id, int reques
for (const PendingResults& result_list : result_list_list) { for (const PendingResults& result_list : result_list_list) {
ret << result_list.results_; ret << result_list.results_;
} }
emit Finished(id, UniqueResults(ret)); emit Finished(id, UniqueResults(ret, KeepOriginalOrder));
} }
} }
@ -345,8 +352,23 @@ MusicBrainzClient::Release MusicBrainzClient::ParseRelease(
return ret; return ret;
} }
MusicBrainzClient::ResultList& MusicBrainzClient::UniqueResults(ResultList& results) { MusicBrainzClient::ResultList MusicBrainzClient::UniqueResults(
qStableSort(results); const ResultList& results, UniqueResultsSortOption opt) {
results.erase(std::unique(results.begin(), results.end()), results.end());
return results; ResultList ret;
if (opt == SortResults) {
ret = QSet<Result>::fromList(results).toList();
qSort(ret);
} else { // KeepOriginalOrder
// Qt doesn't provide a ordered set (QSet "stores values in an unspecified
// order" according to Qt documentation).
// We might use std::set instead, but it's probably faster to use ResultList
// directly to avoid converting from one structure to another.
for (const Result& res : results) {
if (!ret.contains(res)) {
ret << res;
}
}
}
return ret;
} }

View File

@ -103,6 +103,12 @@ signals:
void DiscIdRequestFinished(const QString& discid, QNetworkReply* reply); void DiscIdRequestFinished(const QString& discid, QNetworkReply* reply);
private: private:
// Used as parameter for UniqueResults
enum UniqueResultsSortOption {
SortResults = 0,
KeepOriginalOrder
};
struct Release { struct Release {
Release() : track_(0), year_(0) {} Release() : track_(0), year_(0) {}
@ -137,14 +143,16 @@ signals:
static ResultList ParseTrack(QXmlStreamReader* reader); static ResultList ParseTrack(QXmlStreamReader* reader);
static void ParseArtist(QXmlStreamReader* reader, QString* artist); static void ParseArtist(QXmlStreamReader* reader, QString* artist);
static Release ParseRelease(QXmlStreamReader* reader); static Release ParseRelease(QXmlStreamReader* reader);
// Remove duplicate from the list. Returns a reference to the input parameter static ResultList UniqueResults(const ResultList& results,
static ResultList& UniqueResults(ResultList& results); UniqueResultsSortOption opt = SortResults);
private: private:
static const char* kTrackUrl; static const char* kTrackUrl;
static const char* kDiscUrl; static const char* kDiscUrl;
static const char* kDateRegex; static const char* kDateRegex;
static const int kDefaultTimeout; static const int kDefaultTimeout;
static const int kMaxRequestPerTrack;
QNetworkAccessManager* network_; QNetworkAccessManager* network_;
NetworkTimeouts* timeouts_; NetworkTimeouts* timeouts_;

View File

@ -89,6 +89,10 @@ const QRgb Playlist::kDynamicHistoryColor = qRgb(0x80, 0x80, 0x80);
const char* Playlist::kSettingsGroup = "Playlist"; const char* Playlist::kSettingsGroup = "Playlist";
const char* Playlist::kPathType = "path_type";
const char* Playlist::kWriteMetadata = "write_metadata";
const char* Playlist::kQuickChangeMenu = "quick_change_menu";
const int Playlist::kUndoStackSize = 20; const int Playlist::kUndoStackSize = 20;
const int Playlist::kUndoItemLimit = 500; const int Playlist::kUndoItemLimit = 500;
@ -1097,9 +1101,8 @@ void Playlist::InsertInternetItems(const InternetModel* model,
switch (item.data(InternetModel::Role_PlayBehaviour).toInt()) { switch (item.data(InternetModel::Role_PlayBehaviour).toInt()) {
case InternetModel::PlayBehaviour_SingleItem: case InternetModel::PlayBehaviour_SingleItem:
playlist_items << shared_ptr<PlaylistItem>(new InternetPlaylistItem( playlist_items << shared_ptr<PlaylistItem>(new InternetPlaylistItem(
model->ServiceForIndex(item), model->ServiceForIndex(item),
item.data(InternetModel::Role_SongMetadata) item.data(InternetModel::Role_SongMetadata).value<Song>()));
.value<Song>()));
break; break;
case InternetModel::PlayBehaviour_UseSongLoader: case InternetModel::PlayBehaviour_UseSongLoader:
@ -1122,7 +1125,7 @@ void Playlist::InsertInternetItems(InternetService* service,
PlaylistItemList playlist_items; PlaylistItemList playlist_items;
for (const Song& song : songs) { for (const Song& song : songs) {
playlist_items << shared_ptr<PlaylistItem>( playlist_items << shared_ptr<PlaylistItem>(
new InternetPlaylistItem(service, song)); new InternetPlaylistItem(service, song));
} }
InsertItems(playlist_items, pos, play_now, enqueue); InsertItems(playlist_items, pos, play_now, enqueue);
@ -1402,10 +1405,10 @@ void Playlist::ReOrderWithoutUndo(const PlaylistItemList& new_items) {
new_rows[new_items[i].get()] = i; new_rows[new_items[i].get()] = i;
} }
for (const QModelIndex& idx: persistentIndexList()) { for (const QModelIndex& idx : persistentIndexList()) {
const PlaylistItem* item = old_items[idx.row()].get(); const PlaylistItem* item = old_items[idx.row()].get();
changePersistentIndex( changePersistentIndex(idx,
idx, index(new_rows[item], idx.column(), idx.parent())); index(new_rows[item], idx.column(), idx.parent()));
} }
layoutChanged(); layoutChanged();
@ -1914,8 +1917,10 @@ void Playlist::ReshuffleIndices() {
std::random_shuffle(shuffled_album_keys.begin(), std::random_shuffle(shuffled_album_keys.begin(),
shuffled_album_keys.end()); shuffled_album_keys.end());
// If the user is currently playing a song, force its album to be first. // If the user is currently playing a song, force its album to be first
if (current_virtual_index_ != -1) { // Or if the song was not playing but it was selected, force its album
// to be first.
if (current_virtual_index_ != -1 || current_row() != -1) {
const QString key = items_[current_row()]->Metadata().AlbumKey(); const QString key = items_[current_row()]->Metadata().AlbumKey();
const int pos = shuffled_album_keys.indexOf(key); const int pos = shuffled_album_keys.indexOf(key);
if (pos >= 1) { if (pos >= 1) {
@ -2104,6 +2109,21 @@ void Playlist::RemoveDuplicateSongs() {
removeRows(rows_to_remove); removeRows(rows_to_remove);
} }
void Playlist::RemoveUnavailableSongs() {
QList<int> rows_to_remove;
for (int row = 0; row < items_.count(); ++row) {
PlaylistItemPtr item = items_[row];
const Song& song = item->Metadata();
// check only local files
if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) {
rows_to_remove.append(row);
}
}
removeRows(rows_to_remove);
}
bool Playlist::ApplyValidityOnCurrentSong(const QUrl& url, bool valid) { bool Playlist::ApplyValidityOnCurrentSong(const QUrl& url, bool valid) {
PlaylistItemPtr current = current_item(); PlaylistItemPtr current = current_item();

View File

@ -134,6 +134,12 @@ class Playlist : public QAbstractListModel {
LastFM_Queued, // Track added to the queue for scrobbling LastFM_Queued, // Track added to the queue for scrobbling
}; };
enum Path {
Path_Automatic = 0, // Automatically select path type
Path_Absolute, // Always use absolute paths
Path_Relative, // Always use relative paths
};
static const char* kCddaMimeType; static const char* kCddaMimeType;
static const char* kRowsMimetype; static const char* kRowsMimetype;
static const char* kPlayNowMimetype; static const char* kPlayNowMimetype;
@ -146,6 +152,10 @@ class Playlist : public QAbstractListModel {
static const char* kSettingsGroup; static const char* kSettingsGroup;
static const char* kPathType;
static const char* kWriteMetadata;
static const char* kQuickChangeMenu;
static const int kUndoStackSize; static const int kUndoStackSize;
static const int kUndoItemLimit; static const int kUndoItemLimit;
@ -304,6 +314,7 @@ class Playlist : public QAbstractListModel {
void Clear(); void Clear();
void RemoveDuplicateSongs(); void RemoveDuplicateSongs();
void RemoveUnavailableSongs();
void Shuffle(); void Shuffle();
void ShuffleModeChanged(PlaylistSequence::ShuffleMode mode); void ShuffleModeChanged(PlaylistSequence::ShuffleMode mode);

View File

@ -51,6 +51,13 @@ PlaylistContainer::PlaylistContainer(QWidget* parent)
filter_timer_(new QTimer(this)) { filter_timer_(new QTimer(this)) {
ui_->setupUi(this); ui_->setupUi(this);
ui_->file_path_box->addItem(tr("Automatic"));
ui_->file_path_box->addItem(tr("Absolute"));
ui_->file_path_box->addItem(tr("Relative"));
connect(ui_->file_path_box, SIGNAL(currentIndexChanged(int)),
SLOT(PathSettingChanged(int)));
no_matches_label_ = new QLabel(ui_->playlist); no_matches_label_ = new QLabel(ui_->playlist);
no_matches_label_->setAlignment(Qt::AlignTop | Qt::AlignHCenter); no_matches_label_->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
no_matches_label_->setAttribute(Qt::WA_TransparentForMouseEvents); no_matches_label_->setAttribute(Qt::WA_TransparentForMouseEvents);
@ -75,6 +82,8 @@ PlaylistContainer::PlaylistContainer(QWidget* parent)
settings_.beginGroup(kSettingsGroup); settings_.beginGroup(kSettingsGroup);
ReloadSettings();
// Tab bar // Tab bar
ui_->tab_bar->setExpanding(false); ui_->tab_bar->setExpanding(false);
ui_->tab_bar->setMovable(true); ui_->tab_bar->setMovable(true);
@ -101,6 +110,28 @@ PlaylistContainer::PlaylistContainer(QWidget* parent)
PlaylistContainer::~PlaylistContainer() { delete ui_; } PlaylistContainer::~PlaylistContainer() { delete ui_; }
void PlaylistContainer::ReloadSettings() {
bool show_menu = settings_.value(Playlist::kQuickChangeMenu, false).toBool();
ui_->line->setVisible(show_menu);
ui_->file_path_label->setVisible(show_menu);
ui_->file_path_box->setVisible(show_menu);
int value =
settings_.value(Playlist::kPathType, Playlist::Path_Automatic).toInt();
Playlist::Path path = static_cast<Playlist::Path>(value);
switch (path) {
case Playlist::Path_Automatic:
ui_->file_path_box->setCurrentIndex(0);
break;
case Playlist::Path_Absolute:
ui_->file_path_box->setCurrentIndex(1);
break;
case Playlist::Path_Relative:
ui_->file_path_box->setCurrentIndex(2);
break;
}
}
PlaylistView* PlaylistContainer::view() const { return ui_->playlist; } PlaylistView* PlaylistContainer::view() const { return ui_->playlist; }
void PlaylistContainer::SetActions(QAction* new_playlist, void PlaylistContainer::SetActions(QAction* new_playlist,
@ -361,14 +392,15 @@ void PlaylistContainer::UpdateNoMatchesLabel() {
QString text; QString text;
if (has_rows && !has_results) { if (has_rows && !has_results) {
if (ui_->filter->text().trimmed().compare("the answer to life the universe " if (ui_->filter->text().trimmed().compare(
"and everything", "the answer to life the universe "
Qt::CaseInsensitive) == 0) { "and everything",
Qt::CaseInsensitive) == 0) {
text = "42"; text = "42";
} else { } else {
text = text = tr(
tr("No matches found. Clear the search box to show the whole playlist " "No matches found. Clear the search box to show the whole playlist "
"again."); "again.");
} }
} }
@ -438,3 +470,7 @@ bool PlaylistContainer::eventFilter(QObject* objectWatched, QEvent* event) {
} }
return QWidget::eventFilter(objectWatched, event); return QWidget::eventFilter(objectWatched, event);
} }
void PlaylistContainer::PathSettingChanged(int index) {
settings_.setValue(Playlist::kPathType, index);
}

View File

@ -21,6 +21,8 @@
#include <QWidget> #include <QWidget>
#include <QSettings> #include <QSettings>
#include "playlist.h"
class Ui_PlaylistContainer; class Ui_PlaylistContainer;
class LineEditInterface; class LineEditInterface;
@ -46,6 +48,8 @@ class PlaylistContainer : public QWidget {
QAction* previous_playlist); QAction* previous_playlist);
void SetManager(PlaylistManager* manager); void SetManager(PlaylistManager* manager);
void ReloadSettings();
PlaylistView* view() const; PlaylistView* view() const;
bool eventFilter(QObject* objectWatched, QEvent* event); bool eventFilter(QObject* objectWatched, QEvent* event);
@ -90,6 +94,8 @@ signals:
void UpdateNoMatchesLabel(); void UpdateNoMatchesLabel();
void PathSettingChanged(int index);
private: private:
void UpdateActiveIcon(const QIcon& icon); void UpdateActiveIcon(const QIcon& icon);
void RepositionNoMatchesLabel(bool force = false); void RepositionNoMatchesLabel(bool force = false);

View File

@ -24,7 +24,16 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
@ -36,7 +45,16 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
@ -104,6 +122,36 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="file_path_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>File Paths:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="file_path_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item> <item>
<widget class="Line" name="line_2"> <widget class="Line" name="line_2">
<property name="orientation"> <property name="orientation">

View File

@ -336,6 +336,10 @@ void PlaylistManager::RemoveDuplicatesCurrent() {
current()->RemoveDuplicateSongs(); current()->RemoveDuplicateSongs();
} }
void PlaylistManager::RemoveUnavailableCurrent() {
current()->RemoveUnavailableSongs();
}
void PlaylistManager::SetActivePlaying() { active()->Playing(); } void PlaylistManager::SetActivePlaying() { active()->Playing(); }
void PlaylistManager::SetActivePaused() { active()->Paused(); } void PlaylistManager::SetActivePaused() { active()->Paused(); }

View File

@ -95,6 +95,7 @@ class PlaylistManagerInterface : public QObject {
virtual void ClearCurrent() = 0; virtual void ClearCurrent() = 0;
virtual void ShuffleCurrent() = 0; virtual void ShuffleCurrent() = 0;
virtual void RemoveDuplicatesCurrent() = 0; virtual void RemoveDuplicatesCurrent() = 0;
virtual void RemoveUnavailableCurrent() = 0;
virtual void SetActivePlaying() = 0; virtual void SetActivePlaying() = 0;
virtual void SetActivePaused() = 0; virtual void SetActivePaused() = 0;
virtual void SetActiveStopped() = 0; virtual void SetActiveStopped() = 0;
@ -204,6 +205,7 @@ class PlaylistManager : public PlaylistManagerInterface {
void ClearCurrent(); void ClearCurrent();
void ShuffleCurrent(); void ShuffleCurrent();
void RemoveDuplicatesCurrent(); void RemoveDuplicatesCurrent();
void RemoveUnavailableCurrent();
void SetActiveStreamMetadata(const QUrl& url, const Song& song); void SetActiveStreamMetadata(const QUrl& url, const Song& song);
// Rate current song using 0.0 - 1.0 scale. // Rate current song using 0.0 - 1.0 scale.
void RateCurrentSong(double rating); void RateCurrentSong(double rating);

View File

@ -19,6 +19,8 @@
#include "core/logging.h" #include "core/logging.h"
#include "core/timeconstants.h" #include "core/timeconstants.h"
#include "playlist/playlist.h"
#include <QBuffer> #include <QBuffer>
#include <QtDebug> #include <QtDebug>
@ -92,7 +94,7 @@ bool M3UParser::ParseMetadata(const QString& line,
metadata->length = length * kNsecPerSec; metadata->length = length * kNsecPerSec;
QString track_info = info.section(',', 1); QString track_info = info.section(',', 1);
QStringList list = track_info.split('-'); QStringList list = track_info.split(" - ");
if (list.size() <= 1) { if (list.size() <= 1) {
metadata->title = track_info; metadata->title = track_info;
return true; return true;
@ -105,15 +107,23 @@ bool M3UParser::ParseMetadata(const QString& line,
void M3UParser::Save(const SongList& songs, QIODevice* device, void M3UParser::Save(const SongList& songs, QIODevice* device,
const QDir& dir) const { const QDir& dir) const {
device->write("#EXTM3U\n"); device->write("#EXTM3U\n");
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
bool writeMetadata = s.value(Playlist::kWriteMetadata, true).toBool();
s.endGroup();
for (const Song& song : songs) { for (const Song& song : songs) {
if (song.url().isEmpty()) { if (song.url().isEmpty()) {
continue; continue;
} }
QString meta = QString("#EXTINF:%1,%2 - %3\n") if (writeMetadata) {
.arg(song.length_nanosec() / kNsecPerSec) QString meta = QString("#EXTINF:%1,%2 - %3\n")
.arg(song.artist()) .arg(song.length_nanosec() / kNsecPerSec)
.arg(song.title()); .arg(song.artist())
device->write(meta.toUtf8()); .arg(song.title());
device->write(meta.toUtf8());
}
device->write(URLOrRelativeFilename(song.url(), dir).toUtf8()); device->write(URLOrRelativeFilename(song.url(), dir).toUtf8());
device->write("\n"); device->write("\n");
} }

View File

@ -20,6 +20,7 @@
#include "library/librarybackend.h" #include "library/librarybackend.h"
#include "library/libraryquery.h" #include "library/libraryquery.h"
#include "library/sqlrow.h" #include "library/sqlrow.h"
#include "playlist/playlist.h"
#include <QUrl> #include <QUrl>
@ -46,8 +47,10 @@ void ParserBase::LoadSong(const QString& filename_or_url, qint64 beginning,
} }
} }
// Convert native separators for Windows paths // Clementine always wants / separators internally. Using
filename = QDir::fromNativeSeparators(filename); // QDir::fromNativeSeparators() only works on the same platform the playlist
// was created on/for, using replace() lets playlists work on any platform.
filename = filename.replace('\\', '/');
// Make the path absolute // Make the path absolute
if (!QDir::isAbsolutePath(filename)) { if (!QDir::isAbsolutePath(filename)) {
@ -87,11 +90,19 @@ QString ParserBase::URLOrRelativeFilename(const QUrl& url,
const QDir& dir) const { const QDir& dir) const {
if (url.scheme() != "file") return url.toString(); if (url.scheme() != "file") return url.toString();
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
int p = s.value(Playlist::kPathType, Playlist::Path_Automatic).toInt();
const Playlist::Path path = static_cast<Playlist::Path>(p);
s.endGroup();
const QString filename = url.toLocalFile(); const QString filename = url.toLocalFile();
if (QDir::isAbsolutePath(filename)) {
if (path != Playlist::Path_Absolute && QDir::isAbsolutePath(filename)) {
const QString relative = dir.relativeFilePath(filename); const QString relative = dir.relativeFilePath(filename);
if (!relative.contains("..")) return relative; if (!relative.startsWith("../") || path == Playlist::Path_Relative)
return relative;
} }
return filename; return filename;
} }

View File

@ -19,6 +19,8 @@
#include "core/timeconstants.h" #include "core/timeconstants.h"
#include "core/utilities.h" #include "core/utilities.h"
#include "playlist/playlist.h"
#include <QDomDocument> #include <QDomDocument>
#include <QFile> #include <QFile>
#include <QIODevice> #include <QIODevice>
@ -111,53 +113,55 @@ void XSPFParser::Save(const SongList& songs, QIODevice* device,
writer.writeAttribute("version", "1"); writer.writeAttribute("version", "1");
writer.writeDefaultNamespace("http://xspf.org/ns/0/"); writer.writeDefaultNamespace("http://xspf.org/ns/0/");
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
bool writeMetadata = s.value(Playlist::kWriteMetadata, true).toBool();
s.endGroup();
StreamElement tracklist("trackList", &writer); StreamElement tracklist("trackList", &writer);
for (const Song& song : songs) { for (const Song& song : songs) {
QString filename_or_url; QString filename_or_url = URLOrRelativeFilename(song.url(), dir).toUtf8();
if (song.url().scheme() == "file") {
// Make the filename relative to the directory we're saving the playlist.
filename_or_url = dir.relativeFilePath(
QFileInfo(song.url().toLocalFile()).absoluteFilePath());
} else {
filename_or_url = song.url().toEncoded();
}
StreamElement track("track", &writer); StreamElement track("track", &writer);
writer.writeTextElement("location", filename_or_url); writer.writeTextElement("location", filename_or_url);
writer.writeTextElement("title", song.title());
if (!song.artist().isEmpty()) {
writer.writeTextElement("creator", song.artist());
}
if (!song.album().isEmpty()) {
writer.writeTextElement("album", song.album());
}
if (song.length_nanosec() != -1) {
writer.writeTextElement(
"duration", QString::number(song.length_nanosec() / kNsecPerMsec));
}
QString art = if (writeMetadata) {
song.art_manual().isEmpty() ? song.art_automatic() : song.art_manual(); writer.writeTextElement("title", song.title());
// Ignore images that are in our resource bundle. if (!song.artist().isEmpty()) {
if (!art.startsWith(":") && !art.isEmpty()) { writer.writeTextElement("creator", song.artist());
QString art_filename; }
if (!art.contains("://")) { if (!song.album().isEmpty()) {
art_filename = art; writer.writeTextElement("album", song.album());
} else if (QUrl(art).scheme() == "file") { }
art_filename = QUrl(art).toLocalFile(); if (song.length_nanosec() != -1) {
writer.writeTextElement(
"duration", QString::number(song.length_nanosec() / kNsecPerMsec));
} }
if (!art_filename.isEmpty()) { QString art = song.art_manual().isEmpty() ? song.art_automatic()
// Make this filename relative to the directory we're saving the : song.art_manual();
// playlist. // Ignore images that are in our resource bundle.
art_filename = dir.relativeFilePath( if (!art.startsWith(":") && !art.isEmpty()) {
QFileInfo(art_filename).absoluteFilePath()); QString art_filename;
} else { if (!art.contains("://")) {
// Just use whatever URL was in the Song. art_filename = art;
art_filename = art; } else if (QUrl(art).scheme() == "file") {
} art_filename = QUrl(art).toLocalFile();
}
writer.writeTextElement("image", art_filename); if (!art_filename.isEmpty() && !(art_filename == "(embedded)")) {
// Make this filename relative to the directory we're saving the
// playlist.
QUrl url = QUrl(art_filename);
url.setScheme("file"); // Need to explicitly set this.
art_filename = URLOrRelativeFilename(url, dir).toUtf8();
} else {
// Just use whatever URL was in the Song.
art_filename = art;
}
writer.writeTextElement("image", art_filename);
}
} }
} }
writer.writeEndDocument(); writer.writeEndDocument();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -133,6 +133,24 @@ void BehaviourSettingsPage::Load() {
s.value("greyoutdeleted", false).toBool()); s.value("greyoutdeleted", false).toBool());
ui_->b_click_edit_inline_->setChecked( ui_->b_click_edit_inline_->setChecked(
s.value("click_edit_inline", true).toBool()); s.value("click_edit_inline", true).toBool());
Playlist::Path path = Playlist::Path(
s.value(Playlist::kPathType, Playlist::Path_Automatic).toInt());
switch (path) {
case Playlist::Path_Automatic:
ui_->b_automatic_path->setChecked(true);
break;
case Playlist::Path_Absolute:
ui_->b_absolute_path->setChecked(true);
break;
case Playlist::Path_Relative:
ui_->b_relative_path->setChecked(true);
break;
}
ui_->b_write_metadata->setChecked(
s.value(Playlist::kWriteMetadata, true).toBool());
ui_->b_quickchange_menu->setChecked(
s.value(Playlist::kQuickChangeMenu, false).toBool());
s.endGroup(); s.endGroup();
s.beginGroup(PlaylistTabBar::kSettingsGroup); s.beginGroup(PlaylistTabBar::kSettingsGroup);
@ -162,6 +180,15 @@ void BehaviourSettingsPage::Save() {
MainWindow::PlayBehaviour menu_playmode = MainWindow::PlayBehaviour( MainWindow::PlayBehaviour menu_playmode = MainWindow::PlayBehaviour(
ui_->menu_playmode->itemData(ui_->menu_playmode->currentIndex()).toInt()); ui_->menu_playmode->itemData(ui_->menu_playmode->currentIndex()).toInt());
Playlist::Path path = Playlist::Path_Automatic;
if (ui_->b_automatic_path->isChecked()) {
path = Playlist::Path_Automatic;
} else if (ui_->b_absolute_path->isChecked()) {
path = Playlist::Path_Absolute;
} else if (ui_->b_relative_path->isChecked()) {
path = Playlist::Path_Relative;
}
s.beginGroup(MainWindow::kSettingsGroup); s.beginGroup(MainWindow::kSettingsGroup);
s.setValue("showtray", ui_->b_show_tray_icon_->isChecked()); s.setValue("showtray", ui_->b_show_tray_icon_->isChecked());
s.setValue("keeprunning", ui_->b_keep_running_->isChecked()); s.setValue("keeprunning", ui_->b_keep_running_->isChecked());
@ -182,6 +209,9 @@ void BehaviourSettingsPage::Save() {
s.beginGroup(Playlist::kSettingsGroup); s.beginGroup(Playlist::kSettingsGroup);
s.setValue("greyoutdeleted", ui_->b_grey_out_deleted_->isChecked()); s.setValue("greyoutdeleted", ui_->b_grey_out_deleted_->isChecked());
s.setValue("click_edit_inline", ui_->b_click_edit_inline_->isChecked()); s.setValue("click_edit_inline", ui_->b_click_edit_inline_->isChecked());
s.setValue(Playlist::kPathType, static_cast<int>(path));
s.setValue(Playlist::kWriteMetadata, ui_->b_write_metadata->isChecked());
s.setValue(Playlist::kQuickChangeMenu, ui_->b_quickchange_menu->isChecked());
s.endGroup(); s.endGroup();
s.beginGroup(PlaylistTabBar::kSettingsGroup); s.beginGroup(PlaylistTabBar::kSettingsGroup);

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>516</width> <width>516</width>
<height>561</height> <height>753</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -235,6 +235,72 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_9">
<property name="title">
<string>When saving a playlist, file paths should be</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="b_automatic_path">
<property name="text">
<string>Automatic</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="b_absolute_path">
<property name="text">
<string>Absolute</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="b_relative_path">
<property name="text">
<string>Relative</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="b_quickchange_menu">
<property name="text">
<string>Show quick change menu</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="b_write_metadata">
<property name="text">
<string>Write metadata</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer_2"> <spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">

View File

@ -161,8 +161,8 @@ const char* MainWindow::kSettingsGroup = "MainWindow";
const char* MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)"); const char* MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)");
namespace { namespace {
const int kTrackSliderUpdateTimeMs = 40; const int kTrackSliderUpdateTimeMs = 40;
const int kTrackPositionUpdateTimeMs = 1000; const int kTrackPositionUpdateTimeMs = 1000;
} }
MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd, MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
@ -368,6 +368,8 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
app_->playlist_manager(), SLOT(ClearCurrent())); app_->playlist_manager(), SLOT(ClearCurrent()));
connect(ui_->action_remove_duplicates, SIGNAL(triggered()), connect(ui_->action_remove_duplicates, SIGNAL(triggered()),
app_->playlist_manager(), SLOT(RemoveDuplicatesCurrent())); app_->playlist_manager(), SLOT(RemoveDuplicatesCurrent()));
connect(ui_->action_remove_unavailable, SIGNAL(triggered()),
app_->playlist_manager(), SLOT(RemoveUnavailableCurrent()));
connect(ui_->action_remove_from_playlist, SIGNAL(triggered()), connect(ui_->action_remove_from_playlist, SIGNAL(triggered()),
SLOT(PlaylistRemoveCurrent())); SLOT(PlaylistRemoveCurrent()));
connect(ui_->action_edit_track, SIGNAL(triggered()), SLOT(EditTracks())); connect(ui_->action_edit_track, SIGNAL(triggered()), SLOT(EditTracks()));
@ -626,6 +628,7 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
playlist_menu_->addAction(ui_->action_clear_playlist); playlist_menu_->addAction(ui_->action_clear_playlist);
playlist_menu_->addAction(ui_->action_shuffle); playlist_menu_->addAction(ui_->action_shuffle);
playlist_menu_->addAction(ui_->action_remove_duplicates); playlist_menu_->addAction(ui_->action_remove_duplicates);
playlist_menu_->addAction(ui_->action_remove_unavailable);
#ifdef Q_OS_DARWIN #ifdef Q_OS_DARWIN
ui_->action_shuffle->setShortcut(QKeySequence()); ui_->action_shuffle->setShortcut(QKeySequence());
@ -976,6 +979,7 @@ void MainWindow::ReloadAllSettings() {
library_view_->ReloadSettings(); library_view_->ReloadSettings();
song_info_view_->ReloadSettings(); song_info_view_->ReloadSettings();
app_->player()->engine()->ReloadSettings(); app_->player()->engine()->ReloadSettings();
ui_->playlist->ReloadSettings();
ui_->playlist->view()->ReloadSettings(); ui_->playlist->view()->ReloadSettings();
app_->internet_model()->ReloadSettings(); app_->internet_model()->ReloadSettings();
#ifdef HAVE_WIIMOTEDEV #ifdef HAVE_WIIMOTEDEV

View File

@ -444,6 +444,7 @@
<addaction name="action_clear_playlist"/> <addaction name="action_clear_playlist"/>
<addaction name="action_shuffle"/> <addaction name="action_shuffle"/>
<addaction name="action_remove_duplicates"/> <addaction name="action_remove_duplicates"/>
<addaction name="action_remove_unavailable"/>
</widget> </widget>
<widget class="QMenu" name="menu_help"> <widget class="QMenu" name="menu_help">
<property name="title"> <property name="title">
@ -849,6 +850,11 @@
<string>Rip audio CD...</string> <string>Rip audio CD...</string>
</property> </property>
</action> </action>
<action name="action_remove_unavailable">
<property name="text">
<string>Remove unavailable tracks from playlist</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>

View File

@ -26,15 +26,14 @@
#include <QMetaType> #include <QMetaType>
#include <QSignalSpy> #include <QSignalSpy>
#include <QString> #include <QString>
#include <QStringList>
#include "mock_networkaccessmanager.h" #include "mock_networkaccessmanager.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "test_utils.h" #include "test_utils.h"
namespace {
typedef QList<MusicBrainzClient::Result> ResultList; typedef QList<MusicBrainzClient::Result> ResultList;
Q_DECLARE_METATYPE(ResultList); Q_DECLARE_METATYPE(ResultList);
};
class MusicBrainzClientTest : public ::testing::Test { class MusicBrainzClientTest : public ::testing::Test {
protected: protected:
@ -193,7 +192,7 @@ TEST_F(MusicBrainzClientTest, ParseTrack) {
// Start the request and get a result. // Start the request and get a result.
// The mbid argument doesn't matter in the test. // The mbid argument doesn't matter in the test.
const int sent_id = 0; const int sent_id = 0;
musicbrainz_client.Start(sent_id, "fooMbid"); musicbrainz_client.Start(sent_id, QStringList() << "fooMbid");
discid_reply->Done(); discid_reply->Done();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
EXPECT_EQ(1, spy.count()); EXPECT_EQ(1, spy.count());
@ -236,7 +235,7 @@ TEST_F(MusicBrainzClientTest, ParseTrackWithMultipleReleases) {
// Start the request and get a result. // Start the request and get a result.
// The mbid argument doesn't matter in the test. // The mbid argument doesn't matter in the test.
const int sent_id = 0; const int sent_id = 0;
musicbrainz_client.Start(sent_id, "fooMbid"); musicbrainz_client.Start(sent_id, QStringList() << "fooMbid");
discid_reply->Done(); discid_reply->Done();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
EXPECT_EQ(1, spy.count()); EXPECT_EQ(1, spy.count());