Improve album cover searching and cover manager, use HttpStatusCodeAttribute and QSslError for services
- Improve album cover manager - Change art_automatic and art_manual to QUrl - Refresh collection album covers when new album covers are fetched - Fix automatic album cover searching for local files outside of the collection - Make all Json services check HttpStatusCodeAttribute - Show detailed SSL errors for Subsonic, Tidal and Qobuz
This commit is contained in:
parent
c92a7967ea
commit
65780e1672
|
@ -196,7 +196,7 @@ set(SOURCES
|
|||
covermanager/coversearchstatistics.cpp
|
||||
covermanager/coversearchstatisticsdialog.cpp
|
||||
covermanager/coverexportrunnable.cpp
|
||||
covermanager/currentartloader.cpp
|
||||
covermanager/currentalbumcoverloader.cpp
|
||||
covermanager/coverfromurldialog.cpp
|
||||
covermanager/lastfmcoverprovider.cpp
|
||||
covermanager/musicbrainzcoverprovider.cpp
|
||||
|
@ -378,7 +378,7 @@ set(HEADERS
|
|||
covermanager/coverproviders.h
|
||||
covermanager/coversearchstatisticsdialog.h
|
||||
covermanager/coverexportrunnable.h
|
||||
covermanager/currentartloader.h
|
||||
covermanager/currentalbumcoverloader.h
|
||||
covermanager/coverfromurldialog.h
|
||||
covermanager/lastfmcoverprovider.h
|
||||
covermanager/musicbrainzcoverprovider.h
|
||||
|
|
|
@ -976,8 +976,8 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
|||
info.artist = compilation ? QString() : query.Value(1).toString();
|
||||
info.album_artist = compilation ? QString() : query.Value(2).toString();
|
||||
info.album_name = query.Value(0).toString();
|
||||
info.art_automatic = query.Value(5).toString();
|
||||
info.art_manual = query.Value(6).toString();
|
||||
info.art_automatic = query.Value(5).toUrl();
|
||||
info.art_manual = query.Value(6).toUrl();
|
||||
info.first_url = QUrl::fromEncoded(query.Value(7).toByteArray());
|
||||
|
||||
if ((info.artist == last_artist || info.album_artist == last_album_artist) && info.album_name == last_album)
|
||||
|
@ -1015,8 +1015,8 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &artist, c
|
|||
if (!ExecQuery(&query)) return ret;
|
||||
|
||||
if (query.Next()) {
|
||||
ret.art_automatic = query.Value(0).toString();
|
||||
ret.art_manual = query.Value(1).toString();
|
||||
ret.art_automatic = query.Value(0).toUrl();
|
||||
ret.art_manual = query.Value(1).toUrl();
|
||||
ret.first_url = QUrl::fromEncoded(query.Value(2).toByteArray());
|
||||
}
|
||||
|
||||
|
@ -1024,13 +1024,13 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &artist, c
|
|||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QString &art) {
|
||||
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) {
|
||||
|
||||
metaObject()->invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, artist), Q_ARG(QString, albumartist), Q_ARG(QString, album), Q_ARG(QString, art));
|
||||
metaObject()->invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, artist), Q_ARG(QString, albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url));
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QString &art) {
|
||||
void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
@ -1040,10 +1040,10 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
|
|||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("album", album);
|
||||
|
||||
if (!albumartist.isNull() && !albumartist.isEmpty()) {
|
||||
if (!albumartist.isEmpty()) {
|
||||
query.AddWhere("albumartist", albumartist);
|
||||
}
|
||||
else if (!artist.isNull()) {
|
||||
else if (!artist.isEmpty()) {
|
||||
query.AddWhere("artist", artist);
|
||||
}
|
||||
|
||||
|
@ -1057,7 +1057,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
|
|||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql(QString("UPDATE %1 SET art_manual = :art WHERE album = :album AND unavailable = 0").arg(songs_table_));
|
||||
QString sql(QString("UPDATE %1 SET art_manual = :cover WHERE album = :album AND unavailable = 0").arg(songs_table_));
|
||||
|
||||
if (!albumartist.isNull() && !albumartist.isEmpty()) {
|
||||
sql += " AND albumartist = :albumartist";
|
||||
|
@ -1068,12 +1068,12 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
|
|||
|
||||
QSqlQuery q(db);
|
||||
q.prepare(sql);
|
||||
q.bindValue(":art", art);
|
||||
q.bindValue(":cover", cover_url);
|
||||
q.bindValue(":album", album);
|
||||
if (!albumartist.isNull() && !albumartist.isEmpty()) {
|
||||
if (!albumartist.isEmpty()) {
|
||||
q.bindValue(":albumartist", albumartist);
|
||||
}
|
||||
else if (!artist.isNull()) {
|
||||
else if (!artist.isEmpty()) {
|
||||
q.bindValue(":artist", artist);
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class CollectionBackendInterface : public QObject {
|
|||
|
||||
struct Album {
|
||||
Album() {}
|
||||
Album(const QString &_artist, const QString &_album_artist, const QString &_album_name, const QString &_art_automatic, const QString &_art_manual, const QUrl &_first_url) :
|
||||
Album(const QString &_artist, const QString &_album_artist, const QString &_album_name, const QUrl &_art_automatic, const QUrl &_art_manual, const QUrl &_first_url) :
|
||||
artist(_artist),
|
||||
album_artist(_album_artist),
|
||||
album_name(_album_name),
|
||||
|
@ -68,8 +68,8 @@ class CollectionBackendInterface : public QObject {
|
|||
QString album_artist;
|
||||
QString album_name;
|
||||
|
||||
QString art_automatic;
|
||||
QString art_manual;
|
||||
QUrl art_automatic;
|
||||
QUrl art_manual;
|
||||
QUrl first_url;
|
||||
};
|
||||
typedef QList<Album> AlbumList;
|
||||
|
@ -99,7 +99,7 @@ class CollectionBackendInterface : public QObject {
|
|||
virtual AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
|
||||
virtual void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QString &art) = 0;
|
||||
virtual void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) = 0;
|
||||
virtual Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) = 0;
|
||||
|
||||
virtual Song GetSongById(int id) = 0;
|
||||
|
@ -155,7 +155,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions());
|
||||
AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions());
|
||||
|
||||
void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QString &art);
|
||||
void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url);
|
||||
Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album);
|
||||
|
||||
Song GetSongById(int id);
|
||||
|
@ -193,7 +193,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
void MarkSongsUnavailable(const SongList &songs, bool unavailable = true);
|
||||
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
||||
void UpdateCompilations();
|
||||
void UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QString &art);
|
||||
void UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url);
|
||||
void ForceCompilation(const QString &album, const QList<QString> &artists, bool on);
|
||||
void IncrementPlayCount(int id);
|
||||
void IncrementSkipCount(int id, float progress);
|
||||
|
|
|
@ -107,10 +107,7 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
|
|||
cover_loader_options_.scale_output_image_ = true;
|
||||
|
||||
if (app_)
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage)));
|
||||
|
||||
//icon_cache_->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_CacheRoot) + "/pixmapcache");
|
||||
//icon_cache_->setMaximumCacheSize(CollectionModel::kIconCacheSize);
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage)));
|
||||
|
||||
QIcon nocover = IconLoader::Load("cdcase");
|
||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
@ -422,7 +419,11 @@ void CollectionModel::SongsDeleted(const SongList &songs) {
|
|||
|
||||
if (node->parent != root_) parents << node->parent;
|
||||
|
||||
beginRemoveRows(ItemToIndex(node->parent), node->row, node->row);
|
||||
QModelIndex idx = ItemToIndex(node->parent);
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(idx);
|
||||
QPixmapCache::remove(cache_key);
|
||||
|
||||
beginRemoveRows(idx, node->row, node->row);
|
||||
node->parent->Delete(node->row);
|
||||
song_nodes_.remove(song.id());
|
||||
endRemoveRows();
|
||||
|
@ -491,51 +492,47 @@ void CollectionModel::SongsDeleted(const SongList &songs) {
|
|||
|
||||
}
|
||||
|
||||
QString CollectionModel::AlbumIconPixmapCacheKey(const QModelIndex &index) const {
|
||||
QString CollectionModel::AlbumIconPixmapCacheKey(const QModelIndex &idx) const {
|
||||
|
||||
QStringList path;
|
||||
QModelIndex index_copy(index);
|
||||
while (index_copy.isValid()) {
|
||||
path.prepend(index_copy.data().toString());
|
||||
index_copy = index_copy.parent();
|
||||
QModelIndex idx_copy(idx);
|
||||
while (idx_copy.isValid()) {
|
||||
//const CollectionItem *item = IndexToItem(idx_copy);
|
||||
//if (item && group_by_[item->container_level] == GroupBy_Album) {
|
||||
// QString album = idx_copy.data().toString();
|
||||
// album.remove(Song::kAlbumRemoveDisc);
|
||||
// path.prepend(album);
|
||||
//}
|
||||
//else {
|
||||
path.prepend(idx_copy.data().toString());
|
||||
//}
|
||||
idx_copy = idx_copy.parent();
|
||||
}
|
||||
|
||||
return "collectionart:" + path.join("/");
|
||||
|
||||
}
|
||||
|
||||
QVariant CollectionModel::AlbumIcon(const QModelIndex &index) {
|
||||
QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
||||
|
||||
CollectionItem *item = IndexToItem(index);
|
||||
CollectionItem *item = IndexToItem(idx);
|
||||
if (!item) return no_cover_icon_;
|
||||
|
||||
// Check the cache for a pixmap we already loaded.
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(index);
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(idx);
|
||||
|
||||
QPixmap cached_pixmap;
|
||||
if (QPixmapCache::find(cache_key, &cached_pixmap)) {
|
||||
return cached_pixmap;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Try to load it from the disk cache
|
||||
std::unique_ptr<QIODevice> cache(icon_cache_->data(QUrl(cache_key)));
|
||||
if (cache) {
|
||||
QImage cached_image;
|
||||
if (cached_image.load(cache.get(), "XPM")) {
|
||||
QPixmapCache::insert(cache_key, QPixmap::fromImage(cached_image));
|
||||
return QPixmap::fromImage(cached_image);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Maybe we're loading a pixmap already?
|
||||
if (pending_cache_keys_.contains(cache_key)) {
|
||||
return no_cover_icon_;
|
||||
}
|
||||
|
||||
// No art is cached and we're not loading it already. Load art for the first song in the album.
|
||||
SongList songs = GetChildSongs(index);
|
||||
SongList songs = GetChildSongs(idx);
|
||||
if (!songs.isEmpty()) {
|
||||
const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, songs.first());
|
||||
pending_art_[id] = ItemAndCacheKey(item, cache_key);
|
||||
|
@ -546,14 +543,16 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &index) {
|
|||
|
||||
}
|
||||
|
||||
void CollectionModel::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||
void CollectionModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (!pending_art_.contains(id)) return;
|
||||
|
||||
ItemAndCacheKey item_and_cache_key = pending_art_.take(id);
|
||||
CollectionItem *item = item_and_cache_key.first;
|
||||
const QString &cache_key = item_and_cache_key.second;
|
||||
|
||||
if (!item) return;
|
||||
|
||||
const QString &cache_key = item_and_cache_key.second;
|
||||
|
||||
pending_cache_keys_.remove(cache_key);
|
||||
|
||||
// Insert this image in the cache.
|
||||
|
@ -562,35 +561,19 @@ void CollectionModel::AlbumArtLoaded(quint64 id, const QImage &image) {
|
|||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||
}
|
||||
else {
|
||||
//qLog(Debug) << cache_key;
|
||||
QPixmap image_pixmap;
|
||||
image_pixmap = QPixmap::fromImage(image);
|
||||
QPixmapCache::insert(cache_key, image_pixmap);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// if not already in the disk cache
|
||||
std::unique_ptr<QIODevice> cached_img(icon_cache_->data(QUrl(cache_key)));
|
||||
if (!cached_img && !image.isNull()) {
|
||||
QNetworkCacheMetaData item_metadata;
|
||||
item_metadata.setSaveToDisk(true);
|
||||
item_metadata.setUrl(QUrl(cache_key));
|
||||
QIODevice *cache = icon_cache_->prepare(item_metadata);
|
||||
if (cache) {
|
||||
image.save(cache, "XPM");
|
||||
icon_cache_->insert(cache);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const QModelIndex index = ItemToIndex(item);
|
||||
emit dataChanged(index, index);
|
||||
const QModelIndex idx = ItemToIndex(item);
|
||||
emit dataChanged(idx, idx);
|
||||
|
||||
}
|
||||
|
||||
QVariant CollectionModel::data(const QModelIndex &index, int role) const {
|
||||
QVariant CollectionModel::data(const QModelIndex &idx, int role) const {
|
||||
|
||||
const CollectionItem *item = IndexToItem(index);
|
||||
const CollectionItem *item = IndexToItem(idx);
|
||||
|
||||
// Handle a special case for returning album artwork instead of a generic CD icon.
|
||||
// this is here instead of in the other data() function to let us use the
|
||||
|
@ -604,7 +587,7 @@ QVariant CollectionModel::data(const QModelIndex &index, int role) const {
|
|||
}
|
||||
if (is_album_node) {
|
||||
// It has const behaviour some of the time - that's ok right?
|
||||
return const_cast<CollectionModel*>(this)->AlbumIcon(index);
|
||||
return const_cast<CollectionModel*>(this)->AlbumIcon(idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1326,9 +1309,9 @@ QString CollectionModel::SortTextForSong(const Song &song) {
|
|||
|
||||
}
|
||||
|
||||
Qt::ItemFlags CollectionModel::flags(const QModelIndex &index) const {
|
||||
Qt::ItemFlags CollectionModel::flags(const QModelIndex &idx) const {
|
||||
|
||||
switch (IndexToItem(index)->type) {
|
||||
switch (IndexToItem(idx)->type) {
|
||||
case CollectionItem::Type_Song:
|
||||
case CollectionItem::Type_Container:
|
||||
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
|
||||
|
@ -1355,8 +1338,8 @@ QMimeData *CollectionModel::mimeData(const QModelIndexList &indexes) const {
|
|||
|
||||
data->backend = backend_;
|
||||
|
||||
for (const QModelIndex &index : indexes) {
|
||||
GetChildSongs(IndexToItem(index), &urls, &data->songs, &song_ids);
|
||||
for (const QModelIndex &idx : indexes) {
|
||||
GetChildSongs(IndexToItem(idx), &urls, &data->songs, &song_ids);
|
||||
}
|
||||
|
||||
data->setUrls(urls);
|
||||
|
@ -1410,15 +1393,15 @@ SongList CollectionModel::GetChildSongs(const QModelIndexList &indexes) const {
|
|||
SongList ret;
|
||||
QSet<int> song_ids;
|
||||
|
||||
for (const QModelIndex &index : indexes) {
|
||||
GetChildSongs(IndexToItem(index), &dontcare, &ret, &song_ids);
|
||||
for (const QModelIndex &idx : indexes) {
|
||||
GetChildSongs(IndexToItem(idx), &dontcare, &ret, &song_ids);
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
SongList CollectionModel::GetChildSongs(const QModelIndex &index) const {
|
||||
return GetChildSongs(QModelIndexList() << index);
|
||||
SongList CollectionModel::GetChildSongs(const QModelIndex &idx) const {
|
||||
return GetChildSongs(QModelIndexList() << idx);
|
||||
}
|
||||
|
||||
void CollectionModel::SetFilterAge(int age) {
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
#include <QImage>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/simpletreemodel.h"
|
||||
|
@ -135,7 +134,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||
|
||||
// Get information about the collection
|
||||
void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
|
||||
SongList GetChildSongs(const QModelIndex &index) const;
|
||||
SongList GetChildSongs(const QModelIndex &idx) const;
|
||||
SongList GetChildSongs(const QModelIndexList &indexes) const;
|
||||
|
||||
// Might be accurate
|
||||
|
@ -144,8 +143,8 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||
int total_album_count() const { return total_album_count_; }
|
||||
|
||||
// QAbstractItemModel
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &idx) const;
|
||||
QStringList mimeTypes() const;
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||
bool canFetchMore(const QModelIndex &parent) const;
|
||||
|
@ -203,7 +202,7 @@ signals:
|
|||
// Called after ResetAsync
|
||||
void ResetAsyncQueryFinished(QFuture<CollectionModel::QueryResult> future);
|
||||
|
||||
void AlbumArtLoaded(quint64 id, const QImage &image);
|
||||
void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
private:
|
||||
// Provides some optimisations for loading the list of items in the root.
|
||||
|
@ -236,8 +235,8 @@ signals:
|
|||
QString DividerDisplayText(GroupBy type, const QString &key) const;
|
||||
|
||||
// Helpers
|
||||
QString AlbumIconPixmapCacheKey(const QModelIndex &index) const;
|
||||
QVariant AlbumIcon(const QModelIndex &index);
|
||||
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
||||
QVariant AlbumIcon(const QModelIndex &idx);
|
||||
QVariant data(const CollectionItem *item, int role) const;
|
||||
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||
|
||||
|
@ -270,8 +269,6 @@ signals:
|
|||
QIcon playlists_dir_icon_;
|
||||
QIcon playlist_icon_;
|
||||
|
||||
QNetworkDiskCache *icon_cache_;
|
||||
|
||||
int init_task_id_;
|
||||
|
||||
bool use_pretty_covers_;
|
||||
|
|
|
@ -382,8 +382,8 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||
bool changed = (matching_song.mtime() != qMax(file_info.lastModified().toTime_t(), song_cue_mtime)) || cue_deleted || cue_added;
|
||||
|
||||
// Also want to look to see whether the album art has changed
|
||||
QString image = ImageForSong(file, album_art);
|
||||
if ((matching_song.art_automatic().isEmpty() && !image.isEmpty()) || (!matching_song.art_automatic().isEmpty() && !matching_song.has_embedded_cover() && !QFile::exists(matching_song.art_automatic()))) {
|
||||
QUrl image = ImageForSong(file, album_art);
|
||||
if ((matching_song.art_automatic().isEmpty() && !image.isEmpty()) || (!matching_song.art_automatic().isEmpty() && !matching_song.has_embedded_cover() && !QFile::exists(matching_song.art_automatic().toLocalFile()))) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
@ -415,7 +415,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||
|
||||
qLog(Debug) << file << "created";
|
||||
// choose an image for the song(s)
|
||||
QString image = ImageForSong(file, album_art);
|
||||
QUrl image = ImageForSong(file, album_art);
|
||||
|
||||
for (Song song : song_list) {
|
||||
song.set_directory_id(t->dir());
|
||||
|
@ -457,7 +457,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||
|
||||
}
|
||||
|
||||
void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &matching_cue, const QString &image, ScanTransaction *t) {
|
||||
void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &matching_cue, const QUrl &image, ScanTransaction *t) {
|
||||
|
||||
QFile cue(matching_cue);
|
||||
cue.open(QIODevice::ReadOnly);
|
||||
|
@ -497,7 +497,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QStr
|
|||
|
||||
}
|
||||
|
||||
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, const Song &matching_song, const QString &image, bool cue_deleted, ScanTransaction *t) {
|
||||
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, const Song &matching_song, const QUrl &image, bool cue_deleted, ScanTransaction *t) {
|
||||
|
||||
// If a cue got deleted, we turn it's first section into the new 'raw' (cueless) song and we just remove the rest of the sections from the collection
|
||||
if (cue_deleted) {
|
||||
|
@ -562,7 +562,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
|||
|
||||
}
|
||||
|
||||
void CollectionWatcher::PreserveUserSetData(const QString &file, const QString &image, const Song &matching_song, Song *out, ScanTransaction *t) {
|
||||
void CollectionWatcher::PreserveUserSetData(const QString &file, const QUrl &image, const Song &matching_song, Song *out, ScanTransaction *t) {
|
||||
|
||||
out->set_id(matching_song.id());
|
||||
|
||||
|
@ -731,20 +731,27 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
|||
|
||||
}
|
||||
|
||||
QString CollectionWatcher::ImageForSong(const QString &path, QMap<QString, QStringList> &album_art) {
|
||||
QUrl CollectionWatcher::ImageForSong(const QString &path, QMap<QString, QStringList> &album_art) {
|
||||
|
||||
QString dir(DirectoryPart(path));
|
||||
|
||||
if (album_art.contains(dir)) {
|
||||
if (album_art[dir].count() == 1)
|
||||
return album_art[dir][0];
|
||||
if (album_art[dir].count() == 1) {
|
||||
QUrl url;
|
||||
url.setScheme("file");
|
||||
url.setPath(album_art[dir][0]);
|
||||
return url;
|
||||
}
|
||||
else {
|
||||
QString best_image = PickBestImage(album_art[dir]);
|
||||
album_art[dir] = QStringList() << best_image;
|
||||
return best_image;
|
||||
QUrl url;
|
||||
url.setScheme("file");
|
||||
url.setPath(best_image);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
return QUrl();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -155,17 +155,17 @@ signals:
|
|||
inline static QString ExtensionPart(const QString &fileName);
|
||||
inline static QString DirectoryPart(const QString &fileName);
|
||||
QString PickBestImage(const QStringList &images);
|
||||
QString ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
||||
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
||||
void AddWatch(const Directory &dir, const QString &path);
|
||||
uint GetMtimeForCue(const QString &cue_path);
|
||||
void PerformScan(bool incremental, bool ignore_mtimes);
|
||||
|
||||
// Updates the sections of a cue associated and altered (according to mtime) media file during a scan.
|
||||
void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &matching_cue, const QString &image, ScanTransaction *t);
|
||||
void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &matching_cue, const QUrl &image, ScanTransaction *t);
|
||||
// Updates a single non-cue associated and altered (according to mtime) song during a scan.
|
||||
void UpdateNonCueAssociatedSong(const QString &file, const Song &matching_song, const QString &image, bool cue_deleted, ScanTransaction *t);
|
||||
void UpdateNonCueAssociatedSong(const QString &file, const Song &matching_song, const QUrl &image, bool cue_deleted, ScanTransaction *t);
|
||||
// Updates a new song with some metadata taken from it's equivalent old song (for example rating and score).
|
||||
void PreserveUserSetData(const QString &file, const QString &image, const Song &matching_song, Song *out, ScanTransaction *t);
|
||||
void PreserveUserSetData(const QString &file, const QUrl &image, const Song &matching_song, Song *out, ScanTransaction *t);
|
||||
// Scans a single media file that's present on the disk but not yet in the collection.
|
||||
// It may result in a multiple files added to the collection when the media file has many sections (like a CUE related media file).
|
||||
SongList ScanNewFile(const QString &file, const QString &path, const QString &matching_cue, QSet<QString> *cues_processed);
|
||||
|
|
|
@ -81,7 +81,7 @@ ContextAlbumsModel::ContextAlbumsModel(CollectionBackend *backend, Application *
|
|||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage)));
|
||||
|
||||
QIcon nocover = IconLoader::Load("cdcase");
|
||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
@ -165,14 +165,16 @@ QVariant ContextAlbumsModel::AlbumIcon(const QModelIndex &index) {
|
|||
|
||||
}
|
||||
|
||||
void ContextAlbumsModel::AlbumArtLoaded(quint64 id, const QImage &image) {
|
||||
void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (!pending_art_.contains(id)) return;
|
||||
|
||||
ItemAndCacheKey item_and_cache_key = pending_art_.take(id);
|
||||
CollectionItem *item = item_and_cache_key.first;
|
||||
const QString &cache_key = item_and_cache_key.second;
|
||||
|
||||
CollectionItem *item = item_and_cache_key.first;
|
||||
if (!item) return;
|
||||
|
||||
const QString &cache_key = item_and_cache_key.second;
|
||||
pending_cache_keys_.remove(cache_key);
|
||||
|
||||
// Insert this image in the cache.
|
||||
|
@ -181,7 +183,6 @@ void ContextAlbumsModel::AlbumArtLoaded(quint64 id, const QImage &image) {
|
|||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||
}
|
||||
else {
|
||||
//qLog(Debug) << cache_key;
|
||||
QPixmap image_pixmap;
|
||||
image_pixmap = QPixmap::fromImage(image);
|
||||
QPixmapCache::insert(cache_key, image_pixmap);
|
||||
|
|
|
@ -109,7 +109,7 @@ class ContextAlbumsModel : public SimpleTreeModel<CollectionItem> {
|
|||
void LazyPopulate(CollectionItem *item, bool signal);
|
||||
|
||||
private slots:
|
||||
void AlbumArtLoaded(quint64 id, const QImage &image);
|
||||
void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
private:
|
||||
QueryResult RunQuery(CollectionItem *parent);
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
#include "collection/collectionview.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#include "lyrics/lyricsfetcher.h"
|
||||
|
||||
#include "contextview.h"
|
||||
|
@ -117,9 +117,7 @@ void ContextView::Init(Application *app, CollectionView *collectionview, AlbumCo
|
|||
connect(collectionview_, SIGNAL(TotalArtistCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||
connect(collectionview_, SIGNAL(TotalAlbumCountUpdated_()), this, SLOT(UpdateNoSong()));
|
||||
connect(lyrics_fetcher_, SIGNAL(LyricsFetched(const quint64, const QString&, const QString&)), this, SLOT(UpdateLyrics(const quint64, const QString&, const QString&)));
|
||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
|
||||
connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()), this, SLOT(AutomaticCoverSearchDone()));
|
||||
connect(album_cover_choice_controller_->search_cover_auto_action(), SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
|
||||
|
||||
AddActions();
|
||||
|
||||
|
@ -595,7 +593,7 @@ void ContextView::ScaleCover() {
|
|||
|
||||
}
|
||||
|
||||
void ContextView::AlbumArtLoaded(const Song &song, const QString&, const QImage &image) {
|
||||
void ContextView::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (song.id() != song_playing_.id() || song.url() != song_playing_.url()) return;
|
||||
if (song.effective_albumartist() != song_playing_.effective_albumartist() || song.effective_album() != song_playing_.effective_album() || song.title() != song_playing_.title()) return;
|
||||
|
@ -605,7 +603,6 @@ void ContextView::AlbumArtLoaded(const Song &song, const QString&, const QImage
|
|||
downloading_covers_ = false;
|
||||
song_ = song;
|
||||
SetImage(image);
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
||||
|
||||
|
@ -634,28 +631,15 @@ void ContextView::SetImage(const QImage &image) {
|
|||
|
||||
}
|
||||
|
||||
void ContextView::GetCoverAutomatically() {
|
||||
void ContextView::SearchCoverInProgress() {
|
||||
|
||||
// Search for cover automatically?
|
||||
bool search =
|
||||
album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
|
||||
!song_.has_manually_unset_cover() &&
|
||||
song_.art_automatic().isEmpty() &&
|
||||
song_.art_manual().isEmpty() &&
|
||||
!song_.effective_albumartist().isEmpty() &&
|
||||
!song_.effective_album().isEmpty();
|
||||
downloading_covers_ = true;
|
||||
|
||||
if (search) {
|
||||
downloading_covers_ = true;
|
||||
// This is done in mainwindow instead to avoid searching multiple times (ContextView & PlayingWidget)
|
||||
//album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||
|
||||
// Show a spinner animation
|
||||
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
||||
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
||||
spinner_animation_->start();
|
||||
update();
|
||||
}
|
||||
// Show a spinner animation
|
||||
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
||||
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
||||
spinner_animation_->start();
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
|
@ -703,7 +687,3 @@ void ContextView::ActionShowLyrics() {
|
|||
lyrics_id_ = lyrics_fetcher_->Search(song_.artist(), song_.album(), song_.title());
|
||||
}
|
||||
}
|
||||
|
||||
void ContextView::SearchCoverAutomatically() {
|
||||
GetCoverAutomatically();
|
||||
}
|
||||
|
|
|
@ -131,9 +131,9 @@ class ContextView : public QWidget {
|
|||
void ActionShowAlbums();
|
||||
void ActionShowLyrics();
|
||||
void UpdateLyrics(const quint64 id, const QString &provider, const QString &lyrics);
|
||||
void SearchCoverAutomatically();
|
||||
void SearchCoverInProgress();
|
||||
void AutomaticCoverSearchDone();
|
||||
void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
|
||||
void FadePreviousTrack(qreal value);
|
||||
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
#include "playlist/playlistmanager.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/coverproviders.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#include "covermanager/lastfmcoverprovider.h"
|
||||
#include "covermanager/discogscoverprovider.h"
|
||||
#include "covermanager/musicbrainzcoverprovider.h"
|
||||
|
@ -132,7 +132,7 @@ class ApplicationImpl {
|
|||
app->MoveToNewThread(loader);
|
||||
return loader;
|
||||
}),
|
||||
current_art_loader_([=]() { return new CurrentArtLoader(app, app); }),
|
||||
current_albumcover_loader_([=]() { return new CurrentAlbumCoverLoader(app, app); }),
|
||||
lyrics_providers_([=]() {
|
||||
LyricsProviders *lyrics_providers = new LyricsProviders(app);
|
||||
lyrics_providers->AddProvider(new AuddLyricsProvider(app));
|
||||
|
@ -182,7 +182,7 @@ class ApplicationImpl {
|
|||
Lazy<PlaylistManager> playlist_manager_;
|
||||
Lazy<CoverProviders> cover_providers_;
|
||||
Lazy<AlbumCoverLoader> album_cover_loader_;
|
||||
Lazy<CurrentArtLoader> current_art_loader_;
|
||||
Lazy<CurrentAlbumCoverLoader> current_albumcover_loader_;
|
||||
Lazy<LyricsProviders> lyrics_providers_;
|
||||
Lazy<InternetServices> internet_services_;
|
||||
#ifdef HAVE_TIDAL
|
||||
|
@ -259,7 +259,7 @@ CollectionBackend *Application::collection_backend() const { return collection()
|
|||
CollectionModel *Application::collection_model() const { return collection()->model(); }
|
||||
AlbumCoverLoader *Application::album_cover_loader() const { return p_->album_cover_loader_.get(); }
|
||||
CoverProviders *Application::cover_providers() const { return p_->cover_providers_.get(); }
|
||||
CurrentArtLoader *Application::current_art_loader() const { return p_->current_art_loader_.get(); }
|
||||
CurrentAlbumCoverLoader *Application::current_albumcover_loader() const { return p_->current_albumcover_loader_.get(); }
|
||||
LyricsProviders *Application::lyrics_providers() const { return p_->lyrics_providers_.get(); }
|
||||
PlaylistBackend *Application::playlist_backend() const { return p_->playlist_backend_.get(); }
|
||||
PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); }
|
||||
|
|
|
@ -54,7 +54,7 @@ class DeviceManager;
|
|||
#endif
|
||||
class CoverProviders;
|
||||
class AlbumCoverLoader;
|
||||
class CurrentArtLoader;
|
||||
class CurrentAlbumCoverLoader;
|
||||
class LyricsProviders;
|
||||
class AudioScrobbler;
|
||||
class InternetServices;
|
||||
|
@ -92,7 +92,7 @@ class Application : public QObject {
|
|||
|
||||
CoverProviders *cover_providers() const;
|
||||
AlbumCoverLoader *album_cover_loader() const;
|
||||
CurrentArtLoader *current_art_loader() const;
|
||||
CurrentAlbumCoverLoader *current_albumcover_loader() const;
|
||||
|
||||
LyricsProviders *lyrics_providers() const;
|
||||
|
||||
|
|
|
@ -26,8 +26,9 @@
|
|||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QAction>
|
||||
#include <QUrl>
|
||||
#include <QPixmap>
|
||||
#include <QAction>
|
||||
|
||||
#include "systemtrayicon.h"
|
||||
|
||||
|
@ -42,7 +43,7 @@ class MacSystemTrayIcon : public SystemTrayIcon {
|
|||
|
||||
void SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *love, QAction *quit);
|
||||
|
||||
void SetNowPlaying(const Song& song, const QString& image_path);
|
||||
void SetNowPlaying(const Song& song, const QUrl &cover_url);
|
||||
void ClearNowPlaying();
|
||||
|
||||
private:
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <QApplication>
|
||||
#include <QAction>
|
||||
#include <QIcon>
|
||||
#include <QUrl>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <AppKit/NSMenu.h>
|
||||
|
@ -205,6 +206,6 @@ void MacSystemTrayIcon::ClearNowPlaying() {
|
|||
p_->ClearNowPlaying();
|
||||
}
|
||||
|
||||
void MacSystemTrayIcon::SetNowPlaying(const Song& song, const QString& image_path) {
|
||||
void MacSystemTrayIcon::SetNowPlaying(const Song& song, const QUrl& cover_url) {
|
||||
p_->ShowNowPlaying(song.artist(), song.PrettyTitle());
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@
|
|||
#include "covermanager/albumcovermanager.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#ifndef Q_OS_WIN
|
||||
# include "device/devicemanager.h"
|
||||
# include "device/devicestatefiltermodel.h"
|
||||
|
@ -251,8 +251,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
ui_->menu_help->menuAction()->setVisible(false);
|
||||
#endif
|
||||
|
||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
|
||||
album_cover_choice_controller_->SetApplication(app);
|
||||
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
|
||||
album_cover_choice_controller_->Init(app);
|
||||
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
|
||||
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
|
||||
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
|
||||
|
@ -263,7 +263,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
|
||||
ui_->multi_loading_indicator->SetTaskManager(app_->task_manager());
|
||||
context_view_->Init(app_, collection_view_->view(), album_cover_choice_controller_);
|
||||
ui_->widget_playing->SetApplication(app_, album_cover_choice_controller_);
|
||||
ui_->widget_playing->Init(app_, album_cover_choice_controller_);
|
||||
|
||||
// Initialise the search widget
|
||||
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
|
||||
|
@ -521,10 +521,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
connect(ui_->track_slider, SIGNAL(Previous()), app_->player(), SLOT(Previous()));
|
||||
connect(ui_->track_slider, SIGNAL(Next()), app_->player(), SLOT(Next()));
|
||||
|
||||
// Context connections
|
||||
|
||||
connect(context_view_->albums(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
|
||||
// Collection connections
|
||||
connect(collection_view_->view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
connect(collection_view_->view(), SIGNAL(ShowConfigDialog()), SLOT(ShowCollectionConfig()));
|
||||
|
@ -576,8 +572,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
connect(tidal_view_->albums_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
connect(tidal_view_->songs_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
connect(tidal_view_->search_view(), SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
TidalService *tidalservice = qobject_cast<TidalService*> (app_->internet_services()->ServiceBySource(Song::Source_Tidal));
|
||||
if (tidalservice)
|
||||
if (TidalService *tidalservice = qobject_cast<TidalService*> (app_->internet_services()->ServiceBySource(Song::Source_Tidal)))
|
||||
connect(this, SIGNAL(AuthorisationUrlReceived(const QUrl&)), tidalservice, SLOT(AuthorisationUrlReceived(const QUrl&)));
|
||||
#endif
|
||||
|
||||
|
@ -710,6 +705,9 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
connect(app_->player(), SIGNAL(Playing()), context_view_, SLOT(Playing()));
|
||||
connect(app_->player(), SIGNAL(Stopped()), context_view_, SLOT(Stopped()));
|
||||
connect(app_->player(), SIGNAL(Error()), context_view_, SLOT(Error()));
|
||||
connect(this, SIGNAL(AlbumCoverReady(Song, QUrl, QImage)), context_view_, SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
|
||||
connect(this, SIGNAL(SearchCoverInProgress()), context_view_, SLOT(SearchCoverInProgress()));
|
||||
connect(context_view_->albums(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
|
||||
// Analyzer
|
||||
connect(ui_->analyzer, SIGNAL(WheelEvent(int)), SLOT(VolumeWheelEvent(int)));
|
||||
|
@ -735,6 +733,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||
connect(app_->player(), SIGNAL(Stopped()), ui_->widget_playing, SLOT(Stopped()));
|
||||
connect(app_->player(), SIGNAL(Error()), ui_->widget_playing, SLOT(Error()));
|
||||
connect(ui_->widget_playing, SIGNAL(ShowAboveStatusBarChanged(bool)), SLOT(PlayingWidgetPositionChanged(bool)));
|
||||
connect(this, SIGNAL(AlbumCoverReady(Song, QUrl, QImage)), ui_->widget_playing, SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
|
||||
connect(this, SIGNAL(SearchCoverInProgress()), ui_->widget_playing, SLOT(SearchCoverInProgress()));
|
||||
|
||||
connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole()));
|
||||
PlayingWidgetPositionChanged(ui_->widget_playing->show_above_status_bar());
|
||||
|
@ -942,6 +942,7 @@ void MainWindow::ReloadAllSettings() {
|
|||
osd_->ReloadSettings();
|
||||
collection_view_->ReloadSettings();
|
||||
ui_->playlist->view()->ReloadSettings();
|
||||
app_->album_cover_loader()->ReloadSettings();
|
||||
album_cover_choice_controller_->ReloadSettings();
|
||||
if (cover_manager_.get()) cover_manager_->ReloadSettings();
|
||||
#ifdef HAVE_TIDAL
|
||||
|
@ -962,6 +963,41 @@ void MainWindow::RefreshStyleSheet() {
|
|||
setStyleSheet(contents);
|
||||
}
|
||||
|
||||
void MainWindow::SaveSettings() {
|
||||
|
||||
SaveGeometry();
|
||||
SavePlaybackStatus();
|
||||
ui_->tabs->SaveSettings(kSettingsGroup);
|
||||
ui_->playlist->view()->SaveGeometry();
|
||||
ui_->playlist->view()->SaveSettings();
|
||||
app_->scrobbler()->WriteCache();
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::Exit() {
|
||||
|
||||
SaveSettings();
|
||||
|
||||
if (app_->player()->engine()->is_fadeout_enabled()) {
|
||||
// To shut down the application when fadeout will be finished
|
||||
connect(app_->player()->engine(), SIGNAL(FadeoutFinishedSignal()), qApp, SLOT(quit()));
|
||||
if (app_->player()->GetState() == Engine::Playing) {
|
||||
app_->player()->Stop();
|
||||
hide();
|
||||
if (tray_icon_) tray_icon_->SetVisible(false);
|
||||
return; // Don't quit the application now: wait for the fadeout finished signal
|
||||
}
|
||||
}
|
||||
|
||||
qApp->quit();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::EngineChanged(Engine::EngineType enginetype) {
|
||||
|
||||
ui_->action_equalizer->setEnabled(enginetype == Engine::EngineType::GStreamer || enginetype == Engine::EngineType::Xine);
|
||||
|
@ -2331,30 +2367,6 @@ bool MainWindow::winEvent(MSG *msg, long*) {
|
|||
}
|
||||
#endif // Q_OS_WIN32
|
||||
|
||||
void MainWindow::Exit() {
|
||||
|
||||
SaveGeometry();
|
||||
SavePlaybackStatus();
|
||||
ui_->tabs->SaveSettings(kSettingsGroup);
|
||||
ui_->playlist->view()->SaveGeometry();
|
||||
ui_->playlist->view()->SaveSettings();
|
||||
app_->scrobbler()->WriteCache();
|
||||
|
||||
if (app_->player()->engine()->is_fadeout_enabled()) {
|
||||
// To shut down the application when fadeout will be finished
|
||||
connect(app_->player()->engine(), SIGNAL(FadeoutFinishedSignal()), qApp, SLOT(quit()));
|
||||
if (app_->player()->GetState() == Engine::Playing) {
|
||||
app_->player()->Stop();
|
||||
hide();
|
||||
if (tray_icon_) tray_icon_->SetVisible(false);
|
||||
return; // Don't quit the application now: wait for the fadeout finished signal
|
||||
}
|
||||
}
|
||||
|
||||
qApp->quit();
|
||||
|
||||
}
|
||||
|
||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||
void MainWindow::AutoCompleteTags() {
|
||||
|
||||
|
@ -2475,20 +2487,19 @@ void MainWindow::ShowCover() {
|
|||
|
||||
void MainWindow::SearchCoverAutomatically() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||
s.endGroup();
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::AlbumArtLoaded(const Song &song, const QString&, const QImage &image) {
|
||||
void MainWindow::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (song.effective_albumartist() != song_playing_.effective_albumartist() || song.effective_album() != song_playing_.effective_album() || song.title() != song_playing_.title()) return;
|
||||
|
||||
song_ = song;
|
||||
image_original_ = image;
|
||||
|
||||
emit AlbumCoverReady(song, cover_url, image);
|
||||
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
||||
|
@ -2497,14 +2508,18 @@ void MainWindow::GetCoverAutomatically() {
|
|||
|
||||
// Search for cover automatically?
|
||||
bool search =
|
||||
(song_.source() == Song::Source_LocalFile || song_.source() == Song::Source_Collection || song_.source() == Song::Source_CDDA) &&
|
||||
album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
|
||||
!song_.has_manually_unset_cover() &&
|
||||
song_.art_automatic().isEmpty() &&
|
||||
song_.art_manual().isEmpty() &&
|
||||
!song_.art_automatic_is_valid() &&
|
||||
!song_.art_manual_is_valid() &&
|
||||
!song_.effective_albumartist().isEmpty() &&
|
||||
!song_.effective_album().isEmpty();
|
||||
|
||||
if (search) album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||
if (search) {
|
||||
album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||
emit SearchCoverInProgress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -127,6 +127,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||
bool LoadUrl(const QString& url);
|
||||
|
||||
signals:
|
||||
void AlbumCoverReady(const Song &song, const QUrl &cover_url, const QImage &image);
|
||||
void SearchCoverInProgress();
|
||||
// Signals that stop playing after track was toggled.
|
||||
void StopAfterToggled(bool stop);
|
||||
|
||||
|
@ -253,7 +255,7 @@ signals:
|
|||
void UnsetCover();
|
||||
void ShowCover();
|
||||
void SearchCoverAutomatically();
|
||||
void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
void ScrobblingEnabledChanged(bool value);
|
||||
void ScrobbleButtonVisibilityChanged(bool value);
|
||||
|
@ -262,6 +264,8 @@ signals:
|
|||
|
||||
private:
|
||||
|
||||
void SaveSettings();
|
||||
|
||||
void ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour b, MimeData *data) const;
|
||||
void ApplyPlayBehaviour(BehaviourSettingsPage::PlayBehaviour b, MimeData *data) const;
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
#include "playlist/playlistitem.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "playlist/playlistsequence.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
|
||||
#include <core/mpris2_player.h>
|
||||
#include <core/mpris2_playlists.h>
|
||||
|
@ -120,7 +120,7 @@ Mpris2::Mpris2(Application *app, QObject *parent)
|
|||
return;
|
||||
}
|
||||
|
||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song,QString,QImage)), SLOT(ArtLoaded(Song,QString)));
|
||||
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
|
||||
|
||||
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
|
||||
connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged()));
|
||||
|
@ -376,7 +376,7 @@ QString Mpris2::current_track_id() const {
|
|||
// We send Metadata change notification as soon as the process of changing song starts...
|
||||
void Mpris2::CurrentSongChanged(const Song &song) {
|
||||
|
||||
ArtLoaded(song, "");
|
||||
AlbumCoverLoaded(song, QUrl(), QImage());
|
||||
EmitNotification("CanPlay");
|
||||
EmitNotification("CanPause");
|
||||
EmitNotification("CanGoNext", CanGoNext());
|
||||
|
@ -386,7 +386,7 @@ void Mpris2::CurrentSongChanged(const Song &song) {
|
|||
}
|
||||
|
||||
// ... and we add the cover information later, when it's available.
|
||||
void Mpris2::ArtLoaded(const Song &song, const QString &art_uri) {
|
||||
void Mpris2::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
last_metadata_ = QVariantMap();
|
||||
song.ToXesam(&last_metadata_);
|
||||
|
@ -394,8 +394,8 @@ void Mpris2::ArtLoaded(const Song &song, const QString &art_uri) {
|
|||
using mpris::AddMetadata;
|
||||
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
|
||||
|
||||
if (!art_uri.isEmpty()) {
|
||||
AddMetadata("mpris:artUrl", art_uri, &last_metadata_);
|
||||
if (!cover_url.isValid()) {
|
||||
AddMetadata("mpris:artUrl", cover_url.toLocalFile(), &last_metadata_);
|
||||
}
|
||||
|
||||
AddMetadata("year", song.year(), &last_metadata_);
|
||||
|
|
|
@ -207,7 +207,7 @@ signals:
|
|||
void PlaylistChanged(const MprisPlaylist &playlist);
|
||||
|
||||
private slots:
|
||||
void ArtLoaded(const Song &song, const QString &art_uri);
|
||||
void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
|
||||
void EngineStateChanged(Engine::State newState);
|
||||
void VolumeChanged();
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <QMenu>
|
||||
#include <QIcon>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QtEvents>
|
||||
#include <QSettings>
|
||||
|
||||
|
@ -235,14 +236,14 @@ void QtSystemTrayIcon::SetVisible(bool visible) {
|
|||
tray_->setVisible(visible);
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QString &image_path) {
|
||||
void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QUrl &cover_url) {
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Windows doesn't support HTML in tooltips, so just show something basic
|
||||
tray_->setToolTip(song.PrettyTitleWithArtist());
|
||||
#else
|
||||
|
||||
int columns = image_path == nullptr ? 1 : 2;
|
||||
int columns = cover_url.isEmpty() ? 1 : 2;
|
||||
|
||||
QString tooltip(pattern_);
|
||||
|
||||
|
@ -260,7 +261,7 @@ void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QString &image_path
|
|||
tooltip.replace("%lengthValue", song.PrettyLength().toHtmlEscaped());
|
||||
|
||||
if (columns == 2) {
|
||||
QString final_path = image_path.startsWith("file://") ? image_path.mid(7) : image_path;
|
||||
QString final_path = cover_url.isLocalFile() ? cover_url.path() : cover_url.toString();
|
||||
if (de_ == "kde") {
|
||||
tooltip.replace("%image", "<img src=\"" % final_path % "\" />");
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <QObject>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QPixmap>
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
|
@ -52,7 +53,7 @@ class QtSystemTrayIcon : public SystemTrayIcon {
|
|||
|
||||
void ShowPopup(const QString &summary, const QString &message, int timeout);
|
||||
|
||||
void SetNowPlaying(const Song &song, const QString &image_path);
|
||||
void SetNowPlaying(const Song &song, const QUrl &cover_url);
|
||||
void ClearNowPlaying();
|
||||
|
||||
bool MuteEnabled() { return action_mute_->isVisible(); }
|
||||
|
|
|
@ -210,8 +210,8 @@ struct Song::Private : public QSharedData {
|
|||
bool compilation_off_; // Set by the user
|
||||
|
||||
// Filenames to album art for this song.
|
||||
QString art_automatic_; // Guessed by CollectionWatcher
|
||||
QString art_manual_; // Set by the user - should take priority
|
||||
QUrl art_automatic_; // Guessed by CollectionWatcher
|
||||
QUrl art_manual_; // Set by the user - should take priority
|
||||
|
||||
QString cue_path_; // If the song has a CUE, this contains it's path.
|
||||
|
||||
|
@ -325,12 +325,12 @@ int Song::playcount() const { return d->playcount_; }
|
|||
int Song::skipcount() const { return d->skipcount_; }
|
||||
int Song::lastplayed() const { return d->lastplayed_; }
|
||||
|
||||
const QString &Song::art_automatic() const { return d->art_automatic_; }
|
||||
const QString &Song::art_manual() const { return d->art_manual_; }
|
||||
bool Song::has_manually_unset_cover() const { return d->art_manual_ == kManuallyUnsetCover; }
|
||||
void Song::manually_unset_cover() { d->art_manual_ = kManuallyUnsetCover; }
|
||||
bool Song::has_embedded_cover() const { return d->art_automatic_ == kEmbeddedCover; }
|
||||
void Song::set_embedded_cover() { d->art_automatic_ = kEmbeddedCover; }
|
||||
const QUrl &Song::art_automatic() const { return d->art_automatic_; }
|
||||
const QUrl &Song::art_manual() const { return d->art_manual_; }
|
||||
bool Song::has_manually_unset_cover() const { return d->art_manual_.path() == kManuallyUnsetCover; }
|
||||
void Song::manually_unset_cover() { d->art_manual_.clear(); d->art_manual_.setPath(kManuallyUnsetCover); }
|
||||
bool Song::has_embedded_cover() const { return d->art_automatic_.path() == kEmbeddedCover; }
|
||||
void Song::set_embedded_cover() { d->art_automatic_.clear(); d->art_automatic_.setPath(kEmbeddedCover); }
|
||||
const QImage &Song::image() const { return d->image_; }
|
||||
|
||||
const QString &Song::cue_path() const { return d->cue_path_; }
|
||||
|
@ -341,6 +341,26 @@ bool Song::is_metadata_good() const { return !d->title_.isEmpty() && !d->album_.
|
|||
bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal || d->source_ == Source_Subsonic || d->source_ == Source_Qobuz; }
|
||||
bool Song::is_cdda() const { return d->source_ == Source_CDDA; }
|
||||
|
||||
bool Song::art_automatic_is_valid() const {
|
||||
return (
|
||||
(d->art_automatic_.path() == kManuallyUnsetCover) ||
|
||||
(d->art_automatic_.path() == kEmbeddedCover) ||
|
||||
(d->art_automatic_.isValid() && !d->art_automatic_.isLocalFile()) ||
|
||||
(d->art_automatic_.isLocalFile() && QFile::exists(d->art_automatic_.toLocalFile())) ||
|
||||
(d->art_automatic_.scheme().isEmpty() && !d->art_automatic_.path().isEmpty() && QFile::exists(d->art_automatic_.path()))
|
||||
);
|
||||
}
|
||||
|
||||
bool Song::art_manual_is_valid() const {
|
||||
return (
|
||||
(d->art_manual_.path() == kManuallyUnsetCover) ||
|
||||
(d->art_manual_.path() == kEmbeddedCover) ||
|
||||
(d->art_manual_.isValid() && !d->art_manual_.isLocalFile()) ||
|
||||
(d->art_manual_.isLocalFile() && QFile::exists(d->art_manual_.toLocalFile())) ||
|
||||
(d->art_manual_.scheme().isEmpty() && !d->art_manual_.path().isEmpty() && QFile::exists(d->art_manual_.path()))
|
||||
);
|
||||
}
|
||||
|
||||
const QString &Song::error() const { return d->error_; }
|
||||
|
||||
void Song::set_id(int id) { d->id_ = id; }
|
||||
|
@ -415,8 +435,8 @@ void Song::set_compilation_detected(bool v) { d->compilation_detected_ = v; }
|
|||
void Song::set_compilation_on(bool v) { d->compilation_on_ = v; }
|
||||
void Song::set_compilation_off(bool v) { d->compilation_off_ = v; }
|
||||
|
||||
void Song::set_art_automatic(const QString &v) { d->art_automatic_ = v; }
|
||||
void Song::set_art_manual(const QString &v) { d->art_manual_ = v; }
|
||||
void Song::set_art_automatic(const QUrl &v) { d->art_automatic_ = v; }
|
||||
void Song::set_art_manual(const QUrl &v) { d->art_manual_ = v; }
|
||||
void Song::set_cue_path(const QString &v) { d->cue_path_ = v; }
|
||||
|
||||
void Song::set_image(const QImage &i) { d->image_ = i; }
|
||||
|
@ -677,7 +697,7 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata &pb) {
|
|||
}
|
||||
|
||||
if (pb.has_art_automatic()) {
|
||||
d->art_automatic_ = QStringFromStdString(pb.art_automatic());
|
||||
set_art_automatic(QUrl::fromEncoded(QByteArray(pb.art_automatic().data(), pb.art_automatic().size())));
|
||||
}
|
||||
|
||||
InitArtManual();
|
||||
|
@ -687,6 +707,7 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata &pb) {
|
|||
void Song::ToProtobuf(pb::tagreader::SongMetadata *pb) const {
|
||||
|
||||
const QByteArray url(d->url_.toEncoded());
|
||||
const QByteArray art_automatic(d->art_automatic_.toEncoded());
|
||||
|
||||
pb->set_valid(d->valid_);
|
||||
pb->set_title(DataCommaSizeFromQString(d->title_));
|
||||
|
@ -717,7 +738,7 @@ void Song::ToProtobuf(pb::tagreader::SongMetadata *pb) const {
|
|||
pb->set_ctime(d->ctime_);
|
||||
pb->set_filesize(d->filesize_);
|
||||
pb->set_suspicious_tags(d->suspicious_tags_);
|
||||
pb->set_art_automatic(DataCommaSizeFromQString(d->art_automatic_));
|
||||
pb->set_art_automatic(art_automatic.constData(), art_automatic.size());
|
||||
pb->set_filetype(static_cast<pb::tagreader::SongMetadata_FileType>(d->filetype_));
|
||||
|
||||
}
|
||||
|
@ -866,10 +887,10 @@ void Song::InitFromQuery(const SqlRow &q, bool reliable_metadata, int col) {
|
|||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "art_automatic") {
|
||||
d->art_automatic_ = q.value(x).toString();
|
||||
set_art_automatic(QUrl::fromEncoded(tostr(x).toUtf8()));
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "art_manual") {
|
||||
d->art_manual_ = q.value(x).toString();
|
||||
set_art_manual(QUrl::fromEncoded(tostr(x).toUtf8()));
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "effective_albumartist") {
|
||||
|
@ -927,9 +948,10 @@ void Song::InitArtManual() {
|
|||
// If we don't have an art, check if we have one in the cache
|
||||
if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty()) {
|
||||
QString filename(Utilities::Sha1CoverHash(d->artist_, album2).toHex() + ".jpg");
|
||||
QString path(AlbumCoverLoader::ImageCacheDir() + "/" + filename);
|
||||
QString path(AlbumCoverLoader::ImageCacheDir(d->source_) + "/" + filename);
|
||||
if (QFile::exists(path)) {
|
||||
d->art_manual_ = path;
|
||||
d->art_manual_.setScheme("file");
|
||||
d->art_manual_.setPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1117,7 +1139,7 @@ void Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) {
|
|||
if (!bundle.genre.isEmpty()) d->genre_ = bundle.genre;
|
||||
if (bundle.length > 0) set_length_nanosec(bundle.length);
|
||||
if (bundle.year > 0) d->year_ = bundle.year;
|
||||
if (bundle.tracknr > 0) d->track_ = bundle.tracknr;
|
||||
if (bundle.track > 0) d->track_ = bundle.track;
|
||||
if (bundle.filetype != FileType_Unknown) d->filetype_ = bundle.filetype;
|
||||
if (bundle.samplerate > 0) d->samplerate_ = bundle.samplerate;
|
||||
if (bundle.bitdepth > 0) d->samplerate_ = bundle.bitdepth;
|
||||
|
|
|
@ -228,8 +228,8 @@ class Song {
|
|||
int skipcount() const;
|
||||
int lastplayed() const;
|
||||
|
||||
const QString &art_automatic() const;
|
||||
const QString &art_manual() const;
|
||||
const QUrl &art_automatic() const;
|
||||
const QUrl &art_manual() const;
|
||||
|
||||
const QString &cue_path() const;
|
||||
bool has_cue() const;
|
||||
|
@ -242,6 +242,8 @@ class Song {
|
|||
bool is_stream() const;
|
||||
bool is_cdda() const;
|
||||
bool is_metadata_good() const;
|
||||
bool art_automatic_is_valid() const;
|
||||
bool art_manual_is_valid() const;
|
||||
|
||||
// Playlist views are special because you don't want to fill in album artists automatically for compilations, but you do for normal albums:
|
||||
const QString &playlist_albumartist() const;
|
||||
|
@ -324,8 +326,8 @@ class Song {
|
|||
void set_compilation_on(bool v);
|
||||
void set_compilation_off(bool v);
|
||||
|
||||
void set_art_automatic(const QString &v);
|
||||
void set_art_manual(const QString &v);
|
||||
void set_art_automatic(const QUrl &v);
|
||||
void set_art_manual(const QUrl &v);
|
||||
|
||||
void set_cue_path(const QString &v);
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, Q
|
|||
|
||||
cover_options_.desired_height_ = 16;
|
||||
|
||||
connect(cover_loader_, SIGNAL(ImageLoaded(quint64, QImage)), SLOT(ImageLoaded(quint64, QImage)));
|
||||
connect(cover_loader_, SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(ImageLoaded(quint64, QUrl, QImage)));
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::SetModel(QAbstractItemModel *model) {
|
||||
|
@ -56,7 +56,7 @@ void StandardItemIconLoader::SetModel(QAbstractItemModel *model) {
|
|||
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::LoadIcon(const QString &art_automatic, const QString &art_manual, QStandardItem *for_item) {
|
||||
void StandardItemIconLoader::LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item) {
|
||||
|
||||
const quint64 id = cover_loader_->LoadImageAsync(cover_options_, art_automatic, art_manual);
|
||||
pending_covers_[id] = for_item;
|
||||
|
@ -92,7 +92,7 @@ void StandardItemIconLoader::ModelReset() {
|
|||
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::ImageLoaded(quint64 id, const QImage &image) {
|
||||
void StandardItemIconLoader::ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
QStandardItem *item = pending_covers_.take(id);
|
||||
if (!item) return;
|
||||
|
|
|
@ -49,11 +49,11 @@ class StandardItemIconLoader : public QObject {
|
|||
|
||||
void SetModel(QAbstractItemModel *model);
|
||||
|
||||
void LoadIcon(const QString &art_automatic, const QString &art_manual, QStandardItem *for_item);
|
||||
void LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item);
|
||||
void LoadIcon(const Song &song, QStandardItem *for_item);
|
||||
|
||||
private slots:
|
||||
void ImageLoaded(quint64 id, const QImage &image);
|
||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
void RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end);
|
||||
void ModelReset();
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class SystemTrayIcon : public QObject {
|
|||
// Called by the OSD
|
||||
virtual void ShowPopup(const QString &summary, const QString &message, int timeout) {}
|
||||
// If this get's invoked with image_path equal to nullptr, the tooltip should still be shown - just without the cover art.
|
||||
virtual void SetNowPlaying(const Song &song, const QString &image_path) {}
|
||||
virtual void SetNowPlaying(const Song &song, const QUrl &cover_url) {}
|
||||
virtual void ClearNowPlaying() {}
|
||||
|
||||
virtual bool MuteEnabled() { return false; }
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -53,12 +54,14 @@
|
|||
#include "collection/collectionbackend.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "organise/organiseformat.h"
|
||||
#include "internet/internetservices.h"
|
||||
#include "internet/internetservice.h"
|
||||
#include "albumcoverchoicecontroller.h"
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoversearcher.h"
|
||||
#include "coverfromurldialog.h"
|
||||
#include "currentartloader.h"
|
||||
#include "currentalbumcoverloader.h"
|
||||
|
||||
const char *AlbumCoverChoiceController::kLoadImageFileFilter = QT_TR_NOOP("Images (*.png *.jpg *.jpeg *.bmp *.gif *.xpm *.pbm *.pgm *.ppm *.xbm)");
|
||||
const char *AlbumCoverChoiceController::kSaveImageFileFilter = QT_TR_NOOP("Images (*.png *.jpg *.jpeg *.bmp *.xpm *.pbm *.ppm *.xbm)");
|
||||
|
@ -100,6 +103,18 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
|
|||
|
||||
AlbumCoverChoiceController::~AlbumCoverChoiceController() {}
|
||||
|
||||
void AlbumCoverChoiceController::Init(Application *app) {
|
||||
|
||||
app_ = app;
|
||||
|
||||
cover_fetcher_ = new AlbumCoverFetcher(app_->cover_providers(), this);
|
||||
cover_searcher_ = new AlbumCoverSearcher(QIcon(":/pictures/cdcase.png"), app, this);
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(const quint64, const QUrl&, const QImage&, CoverSearchStatistics)), this, SLOT(AlbumCoverFetched(const quint64, const QUrl&, const QImage&, CoverSearchStatistics)));
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
|
@ -114,37 +129,28 @@ void AlbumCoverChoiceController::ReloadSettings() {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SetApplication(Application *app) {
|
||||
|
||||
app_ = app;
|
||||
|
||||
cover_fetcher_ = new AlbumCoverFetcher(app_->cover_providers(), this);
|
||||
cover_searcher_ = new AlbumCoverSearcher(QIcon(":/pictures/cdcase.png"), app, this);
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)), this, SLOT(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)));
|
||||
|
||||
}
|
||||
|
||||
QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
|
||||
return QList<QAction*>() << cover_from_file_ << cover_to_file_ << separator_ << cover_from_url_ << search_for_cover_ << unset_cover_ << show_cover_;
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
|
||||
QUrl AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
|
||||
|
||||
QString cover = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||
|
||||
if (cover.isNull()) return QString();
|
||||
if (cover_file.isNull()) return QUrl();
|
||||
|
||||
// Can we load the image?
|
||||
QImage image(cover);
|
||||
QImage image(cover_file);
|
||||
|
||||
if (!image.isNull()) {
|
||||
SaveCover(song, cover);
|
||||
return cover;
|
||||
if (image.isNull()) {
|
||||
return QUrl();
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
QUrl cover_url;
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(cover_file);
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -178,38 +184,43 @@ QString AlbumCoverChoiceController::GetInitialPathForFileDialog(const Song &song
|
|||
|
||||
// Art automatic is first to show user which cover the album may be using now;
|
||||
// The song is using it if there's no manual path but we cannot use manual path here because it can contain cached paths
|
||||
if (!song.art_automatic().isEmpty() && !song.has_embedded_cover()) {
|
||||
return song.art_automatic();
|
||||
|
||||
if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() && !song.has_embedded_cover()) {
|
||||
if (song.art_automatic().scheme().isEmpty() && QFile::exists(QFileInfo(song.art_automatic().path()).path())) {
|
||||
return song.art_automatic().path();
|
||||
}
|
||||
else if (song.art_automatic().scheme() == "file" && QFile::exists(QFileInfo(song.art_automatic().toLocalFile()).path())) {
|
||||
return song.art_automatic().toLocalFile();
|
||||
}
|
||||
// If no automatic art, start in the song's folder
|
||||
}
|
||||
else if (!song.url().isEmpty() && song.url().toLocalFile().contains('/')) {
|
||||
return song.url().toLocalFile().section('/', 0, -2) + filename;
|
||||
// Fallback - start in home
|
||||
}
|
||||
else {
|
||||
return QDir::home().absolutePath() + filename;
|
||||
}
|
||||
|
||||
return QDir::home().absolutePath() + filename;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
|
||||
QUrl AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
|
||||
|
||||
if (!cover_from_url_dialog_) { cover_from_url_dialog_ = new CoverFromURLDialog(this); }
|
||||
|
||||
QImage image = cover_from_url_dialog_->Exec();
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverToFileAutomatic(song, image);
|
||||
if (cover.isEmpty()) return QString();
|
||||
SaveCover(song, cover);
|
||||
return cover;
|
||||
if (image.isNull()) {
|
||||
return QUrl();
|
||||
}
|
||||
else {
|
||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
||||
if (cover_url.isEmpty()) return QUrl();
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
}
|
||||
else { return QString(); }
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SearchForCover(Song *song) {
|
||||
QUrl AlbumCoverChoiceController::SearchForCover(Song *song) {
|
||||
|
||||
QString album = song->effective_album();
|
||||
album.remove(Song::kAlbumRemoveDisc);
|
||||
|
@ -218,23 +229,26 @@ QString AlbumCoverChoiceController::SearchForCover(Song *song) {
|
|||
// Get something sensible to stick in the search box
|
||||
QImage image = cover_searcher_->Exec(song->effective_albumartist(), album);
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverToFileAutomatic(song, image);
|
||||
if (cover.isEmpty()) return QString();
|
||||
SaveCover(song, cover);
|
||||
|
||||
return cover;
|
||||
if (image.isNull()) {
|
||||
return QUrl();
|
||||
}
|
||||
else {
|
||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
||||
if (cover_url.isEmpty()) return QUrl();
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
}
|
||||
else { return QString(); }
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::UnsetCover(Song *song) {
|
||||
QUrl AlbumCoverChoiceController::UnsetCover(Song *song) {
|
||||
|
||||
QString cover = Song::kManuallyUnsetCover;
|
||||
SaveCover(song, cover);
|
||||
QUrl cover_url;
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(Song::kManuallyUnsetCover);
|
||||
SaveCoverToSong(song, cover_url);
|
||||
|
||||
return cover;
|
||||
return cover_url;
|
||||
|
||||
}
|
||||
|
||||
|
@ -307,7 +321,7 @@ void AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
|
||||
Song song;
|
||||
if (cover_fetching_tasks_.contains(id)) {
|
||||
|
@ -315,93 +329,76 @@ void AlbumCoverChoiceController::AlbumCoverFetched(quint64 id, const QImage &ima
|
|||
}
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverToFileAutomatic(&song, image);
|
||||
if (cover.isEmpty()) return;
|
||||
SaveCover(&song, cover);
|
||||
QUrl new_cover_url = SaveCoverToFileAutomatic(&song, cover_url, image, false);
|
||||
if (!new_cover_url.isEmpty()) SaveCoverToSong(&song, new_cover_url);
|
||||
}
|
||||
|
||||
emit AutomaticCoverSearchDone();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SaveCover(Song *song, const QString &cover) {
|
||||
void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_url) {
|
||||
|
||||
if (song->is_valid() && song->id() != -1) {
|
||||
song->set_art_manual(cover);
|
||||
app_->collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover);
|
||||
if (!song->is_valid()) return;
|
||||
|
||||
if (song->url() == app_->current_art_loader()->last_song().url()) {
|
||||
app_->current_art_loader()->LoadArt(*song);
|
||||
song->set_art_manual(cover_url);
|
||||
|
||||
if (song->id() != -1) { // Update the backends.
|
||||
switch (song->source()) {
|
||||
case Song::Source_Collection:
|
||||
app_->collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
|
||||
break;
|
||||
case Song::Source_LocalFile:
|
||||
case Song::Source_CDDA:
|
||||
case Song::Source_Device:
|
||||
case Song::Source_Stream:
|
||||
case Song::Source_Unknown:
|
||||
break;
|
||||
case Song::Source_Tidal:
|
||||
case Song::Source_Qobuz:
|
||||
case Song::Source_Subsonic:
|
||||
InternetService *service = app_->internet_services()->ServiceBySource(song->source());
|
||||
if (!service) break;
|
||||
if (service->artists_collection_backend())
|
||||
service->artists_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
|
||||
if (service->albums_collection_backend())
|
||||
service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
|
||||
if (service->songs_collection_backend())
|
||||
service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (song->url() == app_->current_albumcover_loader()->last_song().url()) {
|
||||
app_->current_albumcover_loader()->LoadAlbumCover(*song);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const QImage &image) {
|
||||
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const QUrl &cover_url, const QImage &image, const bool overwrite) {
|
||||
|
||||
QString albumartist(song->effective_albumartist());
|
||||
QString artist(song->artist());
|
||||
QString album(song->effective_album());
|
||||
album.remove(Song::kAlbumRemoveDisc);
|
||||
|
||||
return SaveCoverToFileAutomatic(albumartist, artist, album, song->url().adjusted(QUrl::RemoveFilename).path(), image);
|
||||
return SaveCoverToFileAutomatic(song->source(), song->effective_albumartist(), song->effective_album(), song->album_id(), song->url().adjusted(QUrl::RemoveFilename).path(), cover_url, image, overwrite);
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SaveCoverToFileAutomatic(const QString &albumartist, const QString &artist, const QString &album, const QString &album_dir, const QImage &image) {
|
||||
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QImage &image, const bool overwrite) {
|
||||
|
||||
QString album_new(album);
|
||||
album_new.remove(Song::kAlbumRemoveDisc);
|
||||
QString filepath = app_->album_cover_loader()->CoverFilePath(source, artist, album, album_id, album_dir, cover_url);
|
||||
if (filepath.isEmpty()) return QUrl();
|
||||
|
||||
QString path;
|
||||
QString filename;
|
||||
if (cover_album_dir_) {
|
||||
path = album_dir;
|
||||
}
|
||||
else {
|
||||
path = AlbumCoverLoader::ImageCacheDir();
|
||||
QUrl new_cover_url;
|
||||
new_cover_url.setScheme("file");
|
||||
new_cover_url.setPath(filepath);
|
||||
|
||||
// Don't overwrite when saving in album dir if the filename is set to pattern unless the "overwrite" is set.
|
||||
if (source == Song::Source_Collection && QFile::exists(filepath) && !cover_overwrite_ && !overwrite && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern) {
|
||||
return new_cover_url;
|
||||
}
|
||||
|
||||
if (path.right(1) == QDir::separator()) {
|
||||
path.chop(1);
|
||||
}
|
||||
if (!image.save(filepath, "JPG") && !QFile::exists(filepath)) return QUrl();
|
||||
|
||||
QDir dir;
|
||||
if (!dir.mkpath(path)) {
|
||||
qLog(Error) << "Unable to create directory" << path;
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
|
||||
filename = CreateCoverFilename(albumartist, artist, album_new) + ".jpg";
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
if (cover_lowercase_) filename = filename.toLower();
|
||||
if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-");
|
||||
}
|
||||
else {
|
||||
filename = Utilities::Sha1CoverHash(albumartist, album_new).toHex() + ".jpg";
|
||||
}
|
||||
|
||||
QString filepath(path + "/" + filename);
|
||||
|
||||
// Don't overwrite when saving in album dir if the filename is set to pattern unless the "cover_overwrite" is set.
|
||||
if (QFile::exists(filepath) && !cover_overwrite_ && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern) {
|
||||
return filepath;
|
||||
}
|
||||
|
||||
image.save(filepath, "JPG");
|
||||
|
||||
return filepath;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::CreateCoverFilename(const QString &albumartist, const QString &artist, const QString &album) {
|
||||
|
||||
QString filename(cover_pattern_);
|
||||
filename.replace("%albumartist", albumartist);
|
||||
filename.replace("%artist", artist);
|
||||
filename.replace("%album", album);
|
||||
return filename;
|
||||
return new_cover_url;
|
||||
|
||||
}
|
||||
|
||||
|
@ -426,7 +423,7 @@ bool AlbumCoverChoiceController::CanAcceptDrag(const QDragEnterEvent *e) {
|
|||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
||||
QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
||||
|
||||
for (const QUrl &url : e->mimeData()->urls()) {
|
||||
|
||||
|
@ -434,21 +431,21 @@ QString AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
|||
const QString suffix = QFileInfo(filename).suffix().toLower();
|
||||
|
||||
if (IsKnownImageExtension(suffix)) {
|
||||
SaveCover(song, filename);
|
||||
return filename;
|
||||
SaveCoverToSong(song, url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
if (e->mimeData()->hasImage()) {
|
||||
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
|
||||
if (!image.isNull()) {
|
||||
QString cover_path = SaveCoverToFileAutomatic(song, image);
|
||||
if (cover_path.isEmpty()) return QString();
|
||||
SaveCover(song, cover_path);
|
||||
return cover_path;
|
||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
||||
if (cover_url.isEmpty()) return QUrl();
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
return QUrl();
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -58,7 +59,7 @@ class AlbumCoverChoiceController : public QWidget {
|
|||
AlbumCoverChoiceController(QWidget *parent = nullptr);
|
||||
~AlbumCoverChoiceController();
|
||||
|
||||
void SetApplication(Application *app);
|
||||
void Init(Application *app);
|
||||
void ReloadSettings();
|
||||
|
||||
// Getters for all QActions implemented by this controller.
|
||||
|
@ -85,7 +86,7 @@ class AlbumCoverChoiceController : public QWidget {
|
|||
|
||||
// Lets the user choose a cover from disk. If no cover will be chosen or the chosen cover will not be a proper image, this returns an empty string.
|
||||
// Otherwise, the path to the chosen cover will be returned.
|
||||
QString LoadCoverFromFile(Song *song);
|
||||
QUrl LoadCoverFromFile(Song *song);
|
||||
|
||||
// Shows a dialog that allows user to save the given image on disk.
|
||||
// The image is supposed to be the cover of the given song's album.
|
||||
|
@ -93,14 +94,14 @@ class AlbumCoverChoiceController : public QWidget {
|
|||
|
||||
// Downloads the cover from an URL given by user.
|
||||
// This returns the downloaded image or null image if something went wrong for example when user cancelled the dialog.
|
||||
QString LoadCoverFromURL(Song *song);
|
||||
QUrl LoadCoverFromURL(Song *song);
|
||||
|
||||
// Lets the user choose a cover among all that have been found on last.fm.
|
||||
// Returns the chosen cover or null cover if user didn't choose anything.
|
||||
QString SearchForCover(Song *song);
|
||||
QUrl SearchForCover(Song *song);
|
||||
|
||||
// Returns a path which indicates that the cover has been unset manually.
|
||||
QString UnsetCover(Song *song);
|
||||
QUrl UnsetCover(Song *song);
|
||||
|
||||
// Shows the cover of given song in it's original size.
|
||||
void ShowCover(const Song &song);
|
||||
|
@ -111,15 +112,14 @@ class AlbumCoverChoiceController : public QWidget {
|
|||
void SearchCoverAutomatically(const Song &song);
|
||||
|
||||
// Saves the chosen cover as manual cover path of this song in collection.
|
||||
void SaveCover(Song *song, const QString &cover);
|
||||
void SaveCoverToSong(Song *song, const QUrl &cover_url);
|
||||
|
||||
// Saves the cover that the user picked through a drag and drop operation.
|
||||
QString SaveCover(Song *song, const QDropEvent *e);
|
||||
QUrl SaveCover(Song *song, const QDropEvent *e);
|
||||
|
||||
// Saves the given image in album directory or cache as a cover for 'album artist' - 'album'. The method returns path of the image.
|
||||
QString SaveCoverToFileAutomatic(const QString &albumartist, const QString &artist, const QString &album, const QString &album_dir, const QImage &image);
|
||||
QString SaveCoverToFileAutomatic(const Song *song, const QImage &image);
|
||||
QString CreateCoverFilename(const QString &albumartist, const QString &artist, const QString &album);
|
||||
QUrl SaveCoverToFileAutomatic(const Song *song, const QUrl &cover_url, const QImage &image, const bool overwrite = false);
|
||||
QUrl SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QImage &image, const bool overwrite = false);
|
||||
|
||||
static bool CanAcceptDrag(const QDragEnterEvent *e);
|
||||
|
||||
|
@ -127,7 +127,7 @@ signals:
|
|||
void AutomaticCoverSearchDone();
|
||||
|
||||
private slots:
|
||||
void AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
void AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
|
||||
private:
|
||||
QString GetInitialPathForFileDialog(const Song &song, const QString &filename);
|
||||
|
|
|
@ -114,15 +114,15 @@ void AlbumCoverFetcher::StartRequests() {
|
|||
AlbumCoverFetcherSearch *search = new AlbumCoverFetcherSearch(request, network_, this);
|
||||
active_requests_.insert(request.id, search);
|
||||
|
||||
connect(search, SIGNAL(SearchFinished(quint64, CoverSearchResults)), SLOT(SingleSearchFinished(quint64, CoverSearchResults)));
|
||||
connect(search, SIGNAL(AlbumCoverFetched(quint64, const QImage&)), SLOT(SingleCoverFetched(quint64, const QImage&)));
|
||||
connect(search, SIGNAL(SearchFinished(const quint64, const CoverSearchResults)), SLOT(SingleSearchFinished(const quint64, const CoverSearchResults)));
|
||||
connect(search, SIGNAL(AlbumCoverFetched(const quint64, const QUrl&, const QImage&)), SLOT(SingleCoverFetched(const quint64, const QUrl&, const QImage&)));
|
||||
|
||||
search->Start(cover_providers_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResults results) {
|
||||
void AlbumCoverFetcher::SingleSearchFinished(const quint64 request_id, const CoverSearchResults results) {
|
||||
|
||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||
if (!search) return;
|
||||
|
@ -132,13 +132,13 @@ void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResu
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::SingleCoverFetched(quint64 request_id, const QImage &image) {
|
||||
void AlbumCoverFetcher::SingleCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||
if (!search) return;
|
||||
|
||||
search->deleteLater();
|
||||
emit AlbumCoverFetched(request_id, image, search->statistics());
|
||||
emit AlbumCoverFetched(request_id, cover_url, image, search->statistics());
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QVector>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
class CoverProviders;
|
||||
|
@ -45,6 +44,8 @@ struct CoverSearchStatistics;
|
|||
|
||||
// This class represents a single search-for-cover request. It identifies and describes the request.
|
||||
struct CoverSearchRequest {
|
||||
CoverSearchRequest() : id(-1), search(false), fetchall(false) {}
|
||||
|
||||
// An unique (for one AlbumCoverFetcher) request identifier
|
||||
quint64 id;
|
||||
|
||||
|
@ -61,6 +62,8 @@ struct CoverSearchRequest {
|
|||
|
||||
// This structure represents a single result of some album's cover search request.
|
||||
struct CoverSearchResult {
|
||||
CoverSearchResult() : score(0.0) {}
|
||||
|
||||
// Used for grouping in the user interface.
|
||||
QString provider;
|
||||
|
||||
|
@ -92,17 +95,17 @@ class AlbumCoverFetcher : public QObject {
|
|||
static const int kMaxConcurrentRequests;
|
||||
|
||||
quint64 SearchForCovers(const QString &artist, const QString &album);
|
||||
quint64 FetchAlbumCover(const QString &artist, const QString &album, bool fetchall);
|
||||
quint64 FetchAlbumCover(const QString &artist, const QString &album, const bool fetchall);
|
||||
|
||||
void Clear();
|
||||
|
||||
signals:
|
||||
void AlbumCoverFetched(quint64, const QImage &cover, const CoverSearchStatistics &statistics);
|
||||
void SearchFinished(quint64, const CoverSearchResults &results, const CoverSearchStatistics &statistics);
|
||||
void AlbumCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &cover, const CoverSearchStatistics &statistics);
|
||||
void SearchFinished(const quint64 request_id, const CoverSearchResults &results, const CoverSearchStatistics &statistics);
|
||||
|
||||
private slots:
|
||||
void SingleSearchFinished(quint64, CoverSearchResults results);
|
||||
void SingleCoverFetched(quint64, const QImage &cover);
|
||||
void SingleSearchFinished(const quint64, const CoverSearchResults results);
|
||||
void SingleCoverFetched(const quint64, const QUrl &cover_url, const QImage &cover);
|
||||
void StartRequests();
|
||||
|
||||
private:
|
||||
|
|
|
@ -68,7 +68,7 @@ AlbumCoverFetcherSearch::AlbumCoverFetcherSearch(
|
|||
|
||||
void AlbumCoverFetcherSearch::TerminateSearch() {
|
||||
|
||||
for (int id : pending_requests_.keys()) {
|
||||
for (quint64 id : pending_requests_.keys()) {
|
||||
pending_requests_.take(id)->CancelSearch(id);
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
|
|||
continue;
|
||||
}
|
||||
|
||||
connect(provider, SIGNAL(SearchFinished(int, QList<CoverSearchResult>)), SLOT(ProviderSearchFinished(int, QList<CoverSearchResult>)));
|
||||
connect(provider, SIGNAL(SearchFinished(int, CoverSearchResults)), SLOT(ProviderSearchFinished(int, CoverSearchResults)));
|
||||
const int id = cover_providers->NextId();
|
||||
const bool success = provider->StartSearch(request_.artist, request_.album, id);
|
||||
|
||||
|
@ -107,7 +107,7 @@ static bool CompareProviders(const CoverSearchResult &a, const CoverSearchResult
|
|||
return a.provider < b.provider;
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::ProviderSearchFinished(int id, const QList<CoverSearchResult> &results) {
|
||||
void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverSearchResults &results) {
|
||||
|
||||
if (!pending_requests_.contains(id)) return;
|
||||
CoverProvider *provider = pending_requests_.take(id);
|
||||
|
@ -155,7 +155,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
|
|||
// No results?
|
||||
if (results_.isEmpty()) {
|
||||
statistics_.missing_images_++;
|
||||
emit AlbumCoverFetched(request_.id, QImage());
|
||||
emit AlbumCoverFetched(request_.id, QUrl(), QImage());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -265,10 +265,12 @@ float AlbumCoverFetcherSearch::ScoreImage(const QImage &image) const {
|
|||
|
||||
void AlbumCoverFetcherSearch::SendBestImage() {
|
||||
|
||||
QUrl cover_url;
|
||||
QImage image;
|
||||
|
||||
if (!candidate_images_.isEmpty()) {
|
||||
const CandidateImage best_image = candidate_images_.values().back();
|
||||
cover_url = best_image.first.image_url;
|
||||
image = best_image.second;
|
||||
|
||||
qLog(Info) << "Using " << best_image.first.image_url << "from" << best_image.first.provider << "with score" << best_image.first.score;
|
||||
|
@ -282,7 +284,7 @@ void AlbumCoverFetcherSearch::SendBestImage() {
|
|||
statistics_.missing_images_++;
|
||||
}
|
||||
|
||||
emit AlbumCoverFetched(request_.id, image);
|
||||
emit AlbumCoverFetched(request_.id, cover_url, image);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -60,13 +60,13 @@ class AlbumCoverFetcherSearch : public QObject {
|
|||
|
||||
signals:
|
||||
// It's the end of search (when there was no fetch-me-a-cover request).
|
||||
void SearchFinished(quint64, const CoverSearchResults &results);
|
||||
void SearchFinished(const quint64, const CoverSearchResults &results);
|
||||
|
||||
// It's the end of search and we've fetched a cover.
|
||||
void AlbumCoverFetched(quint64, const QImage &cover);
|
||||
void AlbumCoverFetched(const quint64, const QUrl &cover_url, const QImage &cover);
|
||||
|
||||
private slots:
|
||||
void ProviderSearchFinished(int id, const QList<CoverSearchResult> &results);
|
||||
void ProviderSearchFinished(const int id, const CoverSearchResults &results);
|
||||
void ProviderCoverFetchFinished(RedirectFollower *reply);
|
||||
void TerminateSearch();
|
||||
|
||||
|
@ -106,4 +106,3 @@ class AlbumCoverFetcherSearch : public QObject {
|
|||
};
|
||||
|
||||
#endif // ALBUMCOVERFETCHERSEARCH_H
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,6 +23,7 @@
|
|||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QDir>
|
||||
#include <QQueue>
|
||||
#include <QMutex>
|
||||
#include <QStandardPaths>
|
||||
|
@ -37,11 +39,15 @@
|
|||
#include <QPainter>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/utilities.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "organise/organiseformat.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
|
||||
|
@ -49,13 +55,147 @@ AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
|||
: QObject(parent),
|
||||
stop_requested_(false),
|
||||
next_id_(1),
|
||||
network_(new NetworkAccessManager(this)){}
|
||||
network_(new NetworkAccessManager(this)),
|
||||
cover_album_dir_(false),
|
||||
cover_filename_(CollectionSettingsPage::SaveCover_Hash),
|
||||
cover_overwrite_(false),
|
||||
cover_lowercase_(true),
|
||||
cover_replace_spaces_(true)
|
||||
{
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
QString AlbumCoverLoader::ImageCacheDir() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers";
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::CancelTask(quint64 id) {
|
||||
void AlbumCoverLoader::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||
cover_album_dir_ = s.value("cover_album_dir", false).toBool();
|
||||
cover_filename_ = CollectionSettingsPage::SaveCover(s.value("cover_filename", CollectionSettingsPage::SaveCover_Hash).toInt());
|
||||
cover_pattern_ = s.value("cover_pattern", "%albumartist-%album").toString();
|
||||
cover_overwrite_ = s.value("cover_overwrite", false).toBool();
|
||||
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
|
||||
cover_replace_spaces_ = s.value("cover_replace_spaces", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverLoader::ImageCacheDir(const Song::Source source) {
|
||||
|
||||
switch (source) {
|
||||
case Song::Source_LocalFile:
|
||||
case Song::Source_Collection:
|
||||
case Song::Source_CDDA:
|
||||
case Song::Source_Device:
|
||||
case Song::Source_Stream:
|
||||
case Song::Source_Unknown:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers";
|
||||
case Song::Source_Tidal:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers";
|
||||
case Song::Source_Qobuz:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers";
|
||||
case Song::Source_Subsonic:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers";
|
||||
}
|
||||
|
||||
return QString();
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url) {
|
||||
|
||||
album.remove(Song::kAlbumRemoveDisc);
|
||||
|
||||
QString path;
|
||||
if (source == Song::Source_Collection && cover_album_dir_ && !album_dir.isEmpty()) {
|
||||
path = album_dir;
|
||||
}
|
||||
else {
|
||||
path = AlbumCoverLoader::ImageCacheDir(source);
|
||||
}
|
||||
|
||||
if (path.right(1) == QDir::separator()) {
|
||||
path.chop(1);
|
||||
}
|
||||
|
||||
QDir dir;
|
||||
if (!dir.mkpath(path)) {
|
||||
qLog(Error) << "Unable to create directory" << path;
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString filename;
|
||||
if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
|
||||
filename = CreateCoverFilename(artist, album) + ".jpg";
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
if (cover_lowercase_) filename = filename.toLower();
|
||||
if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-");
|
||||
}
|
||||
else {
|
||||
switch (source) {
|
||||
case Song::Source_Collection:
|
||||
case Song::Source_LocalFile:
|
||||
case Song::Source_CDDA:
|
||||
case Song::Source_Device:
|
||||
case Song::Source_Stream:
|
||||
case Song::Source_Unknown:
|
||||
filename = Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg";
|
||||
break;
|
||||
case Song::Source_Tidal:
|
||||
filename = album_id + "-" + cover_url.fileName();
|
||||
break;
|
||||
case Song::Source_Qobuz:
|
||||
case Song::Source_Subsonic:
|
||||
filename = AlbumCoverFileName(artist, album);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (filename.isEmpty()) return QString();
|
||||
|
||||
QString filepath(path + "/" + filename);
|
||||
|
||||
return filepath;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverLoader::AlbumCoverFileName(QString artist, QString album) {
|
||||
|
||||
artist.remove('/');
|
||||
album.remove('/');
|
||||
|
||||
QString filename = artist + "-" + album + ".jpg";
|
||||
filename = filename.toLower();
|
||||
filename.replace(' ', '-');
|
||||
filename.replace("--", "-");
|
||||
filename.replace(230, "ae");
|
||||
filename.replace(198, "AE");
|
||||
filename.replace(246, 'o');
|
||||
filename.replace(248, 'o');
|
||||
filename.replace(214, 'O');
|
||||
filename.replace(216, 'O');
|
||||
filename.replace(228, 'a');
|
||||
filename.replace(229, 'a');
|
||||
filename.replace(196, 'A');
|
||||
filename.replace(197, 'A');
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
|
||||
return filename;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverLoader::CreateCoverFilename(const QString &artist, const QString &album) {
|
||||
|
||||
QString filename(cover_pattern_);
|
||||
filename.replace("%albumartist", artist);
|
||||
filename.replace("%artist", artist);
|
||||
filename.replace("%album", album);
|
||||
return filename;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::CancelTask(const quint64 id) {
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
|
||||
|
@ -83,7 +223,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options,
|
|||
return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url().toLocalFile(), song.image());
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QString &art_automatic, const QString &art_manual, const QString &song_filename, const QImage &embedded_image) {
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QString &song_filename, const QImage &embedded_image) {
|
||||
|
||||
Task task;
|
||||
task.options = options;
|
||||
|
@ -102,6 +242,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
|
|||
metaObject()->invokeMethod(this, "ProcessTasks", Qt::QueuedConnection);
|
||||
|
||||
return task.id;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::ProcessTasks() {
|
||||
|
@ -129,12 +270,13 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
|
|||
|
||||
if (result.loaded_success) {
|
||||
QImage scaled = ScaleAndPad(task->options, result.image);
|
||||
emit ImageLoaded(task->id, scaled);
|
||||
emit ImageLoaded(task->id, scaled, result.image);
|
||||
emit ImageLoaded(task->id, result.cover_url, scaled);
|
||||
emit ImageLoaded(task->id, result.cover_url, scaled, result.image);
|
||||
return;
|
||||
}
|
||||
|
||||
NextState(task);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::NextState(Task *task) {
|
||||
|
@ -146,8 +288,8 @@ void AlbumCoverLoader::NextState(Task *task) {
|
|||
}
|
||||
else {
|
||||
// Give up
|
||||
emit ImageLoaded(task->id, task->options.default_output_image_);
|
||||
emit ImageLoaded(task->id, task->options.default_output_image_, task->options.default_output_image_);
|
||||
emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_);
|
||||
emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_, task->options.default_output_image_);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -156,47 +298,56 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(const Task &task)
|
|||
|
||||
// An image embedded in the song itself takes priority
|
||||
if (!task.embedded_image.isNull())
|
||||
return TryLoadResult(false, true, ScaleAndPad(task.options, task.embedded_image));
|
||||
return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, task.embedded_image));
|
||||
|
||||
QUrl cover_url;
|
||||
|
||||
QString filename;
|
||||
switch (task.state) {
|
||||
case State_TryingAuto: filename = task.art_automatic; break;
|
||||
case State_TryingManual: filename = task.art_manual; break;
|
||||
case State_TryingAuto: cover_url = task.art_automatic; break;
|
||||
case State_TryingManual: cover_url = task.art_manual; break;
|
||||
}
|
||||
|
||||
if (filename == Song::kManuallyUnsetCover)
|
||||
return TryLoadResult(false, true, task.options.default_output_image_);
|
||||
if (cover_url.path() == Song::kManuallyUnsetCover)
|
||||
return TryLoadResult(false, true, QUrl(), task.options.default_output_image_);
|
||||
|
||||
if (filename == Song::kEmbeddedCover && !task.song_filename.isEmpty()) {
|
||||
else if (cover_url.path() == Song::kEmbeddedCover && !task.song_filename.isEmpty()) {
|
||||
const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_filename);
|
||||
|
||||
if (!taglib_image.isNull())
|
||||
return TryLoadResult(false, true, ScaleAndPad(task.options, taglib_image));
|
||||
return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, taglib_image));
|
||||
}
|
||||
|
||||
if (filename.toLower().startsWith("http://") || filename.toLower().startsWith("https://")) {
|
||||
|
||||
QUrl url(filename);
|
||||
QNetworkReply *reply = network_->get(QNetworkRequest(url));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*)), reply);
|
||||
|
||||
remote_tasks_.insert(reply, task);
|
||||
return TryLoadResult(true, false, QImage());
|
||||
if (cover_url.path().isEmpty()) {
|
||||
return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
|
||||
}
|
||||
else if (filename.isEmpty()) {
|
||||
// Avoid "QFSFileEngine::open: No file name specified" messages if we know that the filename is empty
|
||||
return TryLoadResult(false, false, task.options.default_output_image_);
|
||||
else {
|
||||
if (cover_url.scheme() == "file") {
|
||||
QImage image(cover_url.toLocalFile());
|
||||
return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image);
|
||||
}
|
||||
else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
|
||||
QImage image(cover_url.path());
|
||||
return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image);
|
||||
}
|
||||
else if (!cover_url.scheme().isEmpty()) { // Assume remote URL
|
||||
|
||||
QNetworkReply *reply = network_->get(QNetworkRequest(cover_url));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, const QUrl&)), reply, cover_url);
|
||||
|
||||
remote_tasks_.insert(reply, task);
|
||||
return TryLoadResult(true, false, cover_url, QImage());
|
||||
}
|
||||
}
|
||||
|
||||
QImage image(filename);
|
||||
return TryLoadResult(false, !image.isNull(), image.isNull() ? task.options.default_output_image_ : image);
|
||||
return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
||||
void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (!remote_tasks_.contains(reply)) return;
|
||||
Task task = remote_tasks_.take(reply);
|
||||
|
||||
// Handle redirects.
|
||||
|
@ -208,7 +359,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
|||
QNetworkRequest request = reply->request();
|
||||
request.setUrl(redirect.toUrl());
|
||||
QNetworkReply *redirected_reply = network_->get(request);
|
||||
NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*)), redirected_reply);
|
||||
NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, const QUrl&)), redirected_reply, redirect.toUrl());
|
||||
|
||||
remote_tasks_.insert(redirected_reply, task);
|
||||
return;
|
||||
|
@ -219,8 +370,8 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
|||
QImage image;
|
||||
if (image.load(reply, 0)) {
|
||||
QImage scaled = ScaleAndPad(task.options, image);
|
||||
emit ImageLoaded(task.id, scaled);
|
||||
emit ImageLoaded(task.id, scaled, image);
|
||||
emit ImageLoaded(task.id, cover_url, scaled);
|
||||
emit ImageLoaded(task.id, cover_url, scaled, image);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -256,16 +407,30 @@ QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, con
|
|||
|
||||
}
|
||||
|
||||
QPixmap AlbumCoverLoader::TryLoadPixmap(const QString &automatic, const QString &manual, const QString &filename) {
|
||||
QPixmap AlbumCoverLoader::TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QString &filename) {
|
||||
|
||||
QPixmap ret;
|
||||
if (manual == Song::kManuallyUnsetCover) return ret;
|
||||
if (!manual.isEmpty()) ret.load(manual);
|
||||
if (manual.path() == Song::kManuallyUnsetCover) return ret;
|
||||
if (!manual.path().isEmpty()) {
|
||||
if (manual.scheme().isEmpty()) {
|
||||
ret.load(manual.path());
|
||||
}
|
||||
else if (manual.scheme() == "file") {
|
||||
ret.load(manual.toLocalFile());
|
||||
}
|
||||
}
|
||||
if (ret.isNull()) {
|
||||
if (automatic == Song::kEmbeddedCover && !filename.isNull())
|
||||
if (automatic.path() == Song::kEmbeddedCover && !filename.isEmpty()) {
|
||||
ret = QPixmap::fromImage(TagReaderClient::Instance()->LoadEmbeddedArtBlocking(filename));
|
||||
else if (!automatic.isEmpty())
|
||||
ret.load(automatic);
|
||||
}
|
||||
else if (!automatic.path().isEmpty()) {
|
||||
if (automatic.scheme().isEmpty()) {
|
||||
ret.load(automatic.path());
|
||||
}
|
||||
else if (manual.scheme() == "file") {
|
||||
ret.load(automatic.toLocalFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -37,6 +38,7 @@
|
|||
#include <QNetworkReply>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
|
||||
class Song;
|
||||
|
@ -48,26 +50,31 @@ class AlbumCoverLoader : public QObject {
|
|||
public:
|
||||
explicit AlbumCoverLoader(QObject *parent = nullptr);
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
void Stop() { stop_requested_ = true; }
|
||||
|
||||
static QString ImageCacheDir();
|
||||
static QString ImageCacheDir(const Song::Source source);
|
||||
QString CreateCoverFilename(const QString &artist, const QString &album);
|
||||
QString CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url);
|
||||
QString AlbumCoverFileName(QString artist, QString album);
|
||||
|
||||
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
|
||||
virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QString &art_automatic, const QString &art_manual, const QString &song_filename = QString(), const QImage &embedded_image = QImage());
|
||||
virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QString &song_filename = QString(), const QImage &embedded_image = QImage());
|
||||
|
||||
void CancelTask(quint64 id);
|
||||
void CancelTask(const quint64 id);
|
||||
void CancelTasks(const QSet<quint64> &ids);
|
||||
|
||||
static QPixmap TryLoadPixmap(const QString &automatic, const QString &manual, const QString &filename = QString());
|
||||
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QString &filename = QString());
|
||||
static QImage ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
|
||||
|
||||
signals:
|
||||
void ImageLoaded(quint64 id, const QImage &image);
|
||||
void ImageLoaded(quint64 id, const QImage &scaled, const QImage &original);
|
||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original);
|
||||
|
||||
protected slots:
|
||||
void ProcessTasks();
|
||||
void RemoteFetchFinished(QNetworkReply *reply);
|
||||
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
|
||||
|
||||
protected:
|
||||
enum State {
|
||||
|
@ -81,8 +88,8 @@ signals:
|
|||
AlbumCoverLoaderOptions options;
|
||||
|
||||
quint64 id;
|
||||
QString art_automatic;
|
||||
QString art_manual;
|
||||
QUrl art_automatic;
|
||||
QUrl art_manual;
|
||||
QString song_filename;
|
||||
QImage embedded_image;
|
||||
State state;
|
||||
|
@ -90,10 +97,12 @@ signals:
|
|||
};
|
||||
|
||||
struct TryLoadResult {
|
||||
TryLoadResult(bool async, bool success, const QImage &i) : started_async(async), loaded_success(success), image(i) {}
|
||||
TryLoadResult(bool async, bool success, const QUrl &_cover_url, const QImage &_image) : started_async(async), loaded_success(success), cover_url(_cover_url), image(_image) {}
|
||||
|
||||
bool started_async;
|
||||
bool loaded_success;
|
||||
|
||||
QUrl cover_url;
|
||||
QImage image;
|
||||
};
|
||||
|
||||
|
@ -111,6 +120,14 @@ signals:
|
|||
NetworkAccessManager *network_;
|
||||
|
||||
static const int kMaxRedirects = 3;
|
||||
|
||||
bool cover_album_dir_;
|
||||
CollectionSettingsPage::SaveCover cover_filename_;
|
||||
QString cover_pattern_;
|
||||
bool cover_overwrite_;
|
||||
bool cover_lowercase_;
|
||||
bool cover_replace_spaces_;
|
||||
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERLOADER_H
|
||||
|
|
|
@ -39,4 +39,3 @@ struct AlbumCoverLoaderOptions {
|
|||
};
|
||||
|
||||
#endif // ALBUMCOVERLOADEROPTIONS_H
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
|||
ui_->action_add_to_playlist->setIcon(IconLoader::Load("media-play" ));
|
||||
ui_->action_load->setIcon(IconLoader::Load("media-play" ));
|
||||
|
||||
album_cover_choice_controller_->SetApplication(app_);
|
||||
album_cover_choice_controller_->Init(app_);
|
||||
|
||||
cover_searcher_ = new AlbumCoverSearcher(no_cover_item_icon_, app_, this);
|
||||
cover_export_ = new AlbumCoverExport(this);
|
||||
|
@ -146,7 +146,7 @@ AlbumCoverManager::~AlbumCoverManager() {
|
|||
}
|
||||
|
||||
void AlbumCoverManager::ReloadSettings() {
|
||||
album_cover_choice_controller_->ReloadSettings();
|
||||
app_->album_cover_loader()->ReloadSettings();
|
||||
}
|
||||
|
||||
CollectionBackend *AlbumCoverManager::backend() const {
|
||||
|
@ -198,7 +198,7 @@ void AlbumCoverManager::Init() {
|
|||
connect(ui_->view, SIGNAL(clicked()), ui_->view, SLOT(showMenu()));
|
||||
connect(ui_->button_fetch, SIGNAL(clicked()), SLOT(FetchAlbumCovers()));
|
||||
connect(ui_->export_covers, SIGNAL(clicked()), SLOT(ExportCovers()));
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)), SLOT(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)));
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(const quint64, const QUrl&, const QImage&, const CoverSearchStatistics&)), SLOT(AlbumCoverFetched(const quint64, const QUrl&, const QImage&, const CoverSearchStatistics&)));
|
||||
connect(ui_->action_fetch, SIGNAL(triggered()), SLOT(FetchSingleCover()));
|
||||
connect(ui_->albums, SIGNAL(doubleClicked(QModelIndex)), SLOT(AlbumDoubleClicked(QModelIndex)));
|
||||
connect(ui_->action_add_to_playlist, SIGNAL(triggered()), SLOT(AddSelectedToPlaylist()));
|
||||
|
@ -214,7 +214,7 @@ void AlbumCoverManager::Init() {
|
|||
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
|
||||
}
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(CoverImageLoaded(quint64, QImage)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(const quint64, const QUrl&, const QImage&)), SLOT(CoverImageLoaded(const quint64, const QUrl&, const QImage&)));
|
||||
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
|
@ -357,7 +357,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::CoverImageLoaded(quint64 id, const QImage &image) {
|
||||
void AlbumCoverManager::CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (!cover_loading_tasks_.contains(id)) return;
|
||||
|
||||
|
@ -455,14 +455,14 @@ void AlbumCoverManager::FetchAlbumCovers() {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
|
||||
if (!cover_fetching_tasks_.contains(id))
|
||||
return;
|
||||
|
||||
QListWidgetItem *item = cover_fetching_tasks_.take(id);
|
||||
if (!image.isNull()) {
|
||||
SaveAndSetCover(item, image);
|
||||
SaveAndSetCover(item, cover_url, image);
|
||||
}
|
||||
|
||||
if (cover_fetching_tasks_.isEmpty()) {
|
||||
|
@ -537,7 +537,7 @@ Song AlbumCoverManager::GetFirstSelectedAsSong() {
|
|||
|
||||
Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
|
||||
|
||||
Song result;
|
||||
Song result(Song::Source_Collection);
|
||||
|
||||
QString title = item->data(Role_AlbumName).toString();
|
||||
QString artist_name = EffectiveAlbumArtistName(*item);
|
||||
|
@ -554,8 +554,8 @@ Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
|
|||
|
||||
result.set_url(item->data(Role_FirstUrl).toUrl());
|
||||
|
||||
result.set_art_automatic(item->data(Role_PathAutomatic).toString());
|
||||
result.set_art_manual(item->data(Role_PathManual).toString());
|
||||
result.set_art_automatic(item->data(Role_PathAutomatic).toUrl());
|
||||
result.set_art_manual(item->data(Role_PathManual).toUrl());
|
||||
|
||||
// force validity
|
||||
result.set_valid(true);
|
||||
|
@ -587,10 +587,10 @@ void AlbumCoverManager::FetchSingleCover() {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UpdateCoverInList(QListWidgetItem *item, const QString &cover) {
|
||||
void AlbumCoverManager::UpdateCoverInList(QListWidgetItem *item, const QUrl &cover_url) {
|
||||
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QString(), cover);
|
||||
item->setData(Role_PathManual, cover);
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), cover_url);
|
||||
item->setData(Role_PathManual, cover_url);
|
||||
cover_loading_tasks_[id] = item;
|
||||
|
||||
}
|
||||
|
@ -602,10 +602,10 @@ void AlbumCoverManager::LoadCoverFromFile() {
|
|||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->LoadCoverFromFile(&song);
|
||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromFile(&song);
|
||||
|
||||
if (!cover.isEmpty()) {
|
||||
UpdateCoverInList(item, cover);
|
||||
if (!cover_url.isEmpty()) {
|
||||
UpdateCoverInList(item, cover_url);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -622,11 +622,23 @@ void AlbumCoverManager::SaveCoverToFile() {
|
|||
image = no_cover_image_;
|
||||
}
|
||||
else {
|
||||
if (!song.art_manual().isEmpty() && QFile::exists(song.art_manual())) {
|
||||
image = QImage(song.art_manual());
|
||||
if (!song.art_manual().isEmpty() && !song.art_manual().path().isEmpty() &&
|
||||
(
|
||||
(song.art_manual().scheme().isEmpty() && QFile::exists(song.art_manual().path()))
|
||||
||
|
||||
(song.art_manual().scheme() == "file" && QFile::exists(song.art_manual().toLocalFile()))
|
||||
)
|
||||
) {
|
||||
image = QImage(song.art_manual().toLocalFile());
|
||||
}
|
||||
else if(!song.art_automatic().isEmpty() && QFile::exists(song.art_automatic())) {
|
||||
image = QImage(song.art_automatic());
|
||||
if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() &&
|
||||
(
|
||||
(song.art_automatic().scheme().isEmpty() && QFile::exists(song.art_automatic().path()))
|
||||
||
|
||||
(song.art_automatic().scheme() == "file" && QFile::exists(song.art_automatic().toLocalFile()))
|
||||
)
|
||||
) {
|
||||
image = QImage(song.art_automatic().toLocalFile());
|
||||
}
|
||||
else {
|
||||
image = no_cover_image_;
|
||||
|
@ -644,10 +656,10 @@ void AlbumCoverManager::LoadCoverFromURL() {
|
|||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->LoadCoverFromURL(&song);
|
||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromURL(&song);
|
||||
|
||||
if (!cover.isEmpty()) {
|
||||
UpdateCoverInList(item, cover);
|
||||
if (!cover_url.isEmpty()) {
|
||||
UpdateCoverInList(item, cover_url);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -659,18 +671,18 @@ void AlbumCoverManager::SearchForCover() {
|
|||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->SearchForCover(&song);
|
||||
if (cover.isEmpty()) return;
|
||||
QUrl cover_url = album_cover_choice_controller_->SearchForCover(&song);
|
||||
if (cover_url.isEmpty()) return;
|
||||
|
||||
// Force the found cover on all of the selected items
|
||||
for (QListWidgetItem *current : context_menu_items_) {
|
||||
// Don't save the first one twice
|
||||
if (current != item) {
|
||||
Song current_song = ItemAsSong(current);
|
||||
album_cover_choice_controller_->SaveCover(¤t_song, cover);
|
||||
album_cover_choice_controller_->SaveCoverToSong(¤t_song, cover_url);
|
||||
}
|
||||
|
||||
UpdateCoverInList(current, cover);
|
||||
UpdateCoverInList(current, cover_url);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -682,17 +694,17 @@ void AlbumCoverManager::UnsetCover() {
|
|||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->UnsetCover(&song);
|
||||
QUrl cover_url = album_cover_choice_controller_->UnsetCover(&song);
|
||||
|
||||
// Force the 'none' cover on all of the selected items
|
||||
for (QListWidgetItem *current : context_menu_items_) {
|
||||
current->setIcon(no_cover_item_icon_);
|
||||
current->setData(Role_PathManual, cover);
|
||||
current->setData(Role_PathManual, cover_url);
|
||||
|
||||
// Don't save the first one twice
|
||||
if (current != item) {
|
||||
Song current_song = ItemAsSong(current);
|
||||
album_cover_choice_controller_->SaveCover(¤t_song, cover);
|
||||
album_cover_choice_controller_->SaveCoverToSong(¤t_song, cover_url);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -776,22 +788,22 @@ void AlbumCoverManager::LoadSelectedToPlaylist() {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QImage &image) {
|
||||
void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
const QString artist = item->data(Role_ArtistName).toString();
|
||||
const QString albumartist = item->data(Role_AlbumArtistName).toString();
|
||||
const QString album = item->data(Role_AlbumName).toString();
|
||||
const QUrl url = item->data(Role_FirstUrl).toUrl();
|
||||
|
||||
QString path = album_cover_choice_controller_->SaveCoverToFileAutomatic((!albumartist.isEmpty() ? albumartist : artist), artist, album, url.adjusted(QUrl::RemoveFilename).path(), image);
|
||||
if (path.isEmpty()) return;
|
||||
QUrl new_cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(Song::Source_Collection, (!albumartist.isEmpty() ? albumartist : artist), album, QString(), url.adjusted(QUrl::RemoveFilename).path(), cover_url, image, false);
|
||||
if (new_cover_url.isEmpty()) return;
|
||||
|
||||
// Save the image in the database
|
||||
collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, path);
|
||||
collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, new_cover_url);
|
||||
|
||||
// Update the icon in our list
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QString(), path);
|
||||
item->setData(Role_PathManual, path);
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), new_cover_url);
|
||||
item->setData(Role_PathManual, new_cover_url);
|
||||
cover_loading_tasks_[id] = item;
|
||||
|
||||
}
|
||||
|
|
|
@ -93,11 +93,11 @@ class AlbumCoverManager : public QMainWindow {
|
|||
|
||||
private slots:
|
||||
void ArtistChanged(QListWidgetItem *current);
|
||||
void CoverImageLoaded(quint64 id, const QImage &image);
|
||||
void CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
void UpdateFilter();
|
||||
void FetchAlbumCovers();
|
||||
void ExportCovers();
|
||||
void AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
void AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
void CancelRequests();
|
||||
|
||||
// On the context menu
|
||||
|
@ -115,7 +115,7 @@ class AlbumCoverManager : public QMainWindow {
|
|||
void AddSelectedToPlaylist();
|
||||
void LoadSelectedToPlaylist();
|
||||
|
||||
void UpdateCoverInList(QListWidgetItem *item, const QString &cover);
|
||||
void UpdateCoverInList(QListWidgetItem *item, const QUrl &cover);
|
||||
void UpdateExportStatus(int exported, int bad, int count);
|
||||
|
||||
private:
|
||||
|
@ -152,7 +152,7 @@ class AlbumCoverManager : public QMainWindow {
|
|||
|
||||
void UpdateStatusText();
|
||||
bool ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const;
|
||||
void SaveAndSetCover(QListWidgetItem *item, const QImage &image);
|
||||
void SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
private:
|
||||
Ui_CoverManager *ui_;
|
||||
|
|
|
@ -127,7 +127,7 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
|
|||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(ImageLoaded(quint64, QImage)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(ImageLoaded(quint64, QUrl, QImage)));
|
||||
|
||||
connect(ui_->search, SIGNAL(clicked()), SLOT(Search()));
|
||||
connect(ui_->covers, SIGNAL(doubleClicked(QModelIndex)), SLOT(CoverDoubleClicked(QModelIndex)));
|
||||
|
@ -145,7 +145,7 @@ AlbumCoverSearcher::~AlbumCoverSearcher() {
|
|||
void AlbumCoverSearcher::Init(AlbumCoverFetcher *fetcher) {
|
||||
|
||||
fetcher_ = fetcher;
|
||||
connect(fetcher_, SIGNAL(SearchFinished(quint64,CoverSearchResults,CoverSearchStatistics)), SLOT(SearchFinished(quint64, CoverSearchResults)));
|
||||
connect(fetcher_, SIGNAL(SearchFinished(quint64, CoverSearchResults, CoverSearchStatistics)), SLOT(SearchFinished(quint64, CoverSearchResults)));
|
||||
|
||||
}
|
||||
|
||||
|
@ -197,7 +197,7 @@ void AlbumCoverSearcher::Search() {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &results) {
|
||||
void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResults &results) {
|
||||
|
||||
if (id != id_)
|
||||
return;
|
||||
|
@ -212,7 +212,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &re
|
|||
for (const CoverSearchResult &result : results) {
|
||||
if (result.image_url.isEmpty()) continue;
|
||||
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url.toString(), QString());
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url, QUrl());
|
||||
|
||||
QStandardItem *item = new QStandardItem;
|
||||
item->setIcon(no_cover_icon_);
|
||||
|
@ -232,7 +232,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &re
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::ImageLoaded(quint64 id, const QImage &image) {
|
||||
void AlbumCoverSearcher::ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (!cover_loading_tasks_.contains(id)) return;
|
||||
QStandardItem *item = cover_loading_tasks_.take(id);
|
||||
|
|
|
@ -87,8 +87,8 @@ protected:
|
|||
|
||||
private slots:
|
||||
void Search();
|
||||
void SearchFinished(quint64 id, const CoverSearchResults &results);
|
||||
void ImageLoaded(quint64 id, const QImage &image);
|
||||
void SearchFinished(const quint64 id, const CoverSearchResults &results);
|
||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
void CoverDoubleClicked(const QModelIndex &index);
|
||||
|
||||
|
|
|
@ -59,11 +59,11 @@ QString CoverExportRunnable::GetCoverPath() {
|
|||
// Export downloaded covers?
|
||||
}
|
||||
else if (!song_.art_manual().isEmpty() && dialog_result_.export_downloaded_) {
|
||||
return song_.art_manual();
|
||||
return song_.art_manual().toLocalFile();
|
||||
// Export embedded covers?
|
||||
}
|
||||
else if (!song_.art_automatic().isEmpty() && song_.art_automatic() == Song::kEmbeddedCover && dialog_result_.export_embedded_) {
|
||||
return song_.art_automatic();
|
||||
else if (!song_.art_automatic().isEmpty() && song_.art_automatic().path() == Song::kEmbeddedCover && dialog_result_.export_embedded_) {
|
||||
return song_.art_automatic().toLocalFile();
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
|
@ -168,8 +168,7 @@ void CoverExportRunnable::ExportCover() {
|
|||
return;
|
||||
}
|
||||
|
||||
// we're handling overwrite as remove + copy so we need to delete the old file
|
||||
// first
|
||||
// We're handling overwrite as remove + copy so we need to delete the old file first
|
||||
if (dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) {
|
||||
if (!QFile::remove(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
|
@ -186,7 +185,7 @@ void CoverExportRunnable::ExportCover() {
|
|||
}
|
||||
}
|
||||
else {
|
||||
// automatic or manual cover, available in an image file
|
||||
// Automatic or manual cover, available in an image file
|
||||
if (!QFile::copy(cover_path, new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
|
|
|
@ -29,8 +29,9 @@
|
|||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "albumcoverfetcher.h"
|
||||
|
||||
class Application;
|
||||
struct CoverSearchResult;
|
||||
|
||||
// Each implementation of this interface downloads covers from one online service.
|
||||
// There are no limitations on what this service might be - last.fm, Amazon, Google Images - you name it.
|
||||
|
@ -53,7 +54,7 @@ class CoverProvider : public QObject {
|
|||
virtual void CancelSearch(int id) {}
|
||||
|
||||
signals:
|
||||
void SearchFinished(int id, const QList<CoverSearchResult>& results);
|
||||
void SearchFinished(int id, const CoverSearchResults& results);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
|
|
@ -28,63 +28,71 @@
|
|||
#include <QString>
|
||||
#include <QStringBuilder>
|
||||
#include <QImage>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "currentartloader.h"
|
||||
#include "currentalbumcoverloader.h"
|
||||
|
||||
CurrentArtLoader::CurrentArtLoader(Application *app, QObject *parent)
|
||||
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
temp_file_pattern_(QDir::tempPath() + "/strawberry-art-XXXXXX.jpg"),
|
||||
id_(0)
|
||||
temp_file_pattern_(QDir::tempPath() + "/strawberry-cover-XXXXXX.jpg"),
|
||||
id_(0)
|
||||
{
|
||||
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
options_.default_output_image_ = QImage(":/pictures/cdcase.png");
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(TempArtLoaded(quint64, QImage)));
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadArt(Song)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(TempAlbumCoverLoaded(quint64, QUrl, QImage)));
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadAlbumCover(Song)));
|
||||
|
||||
}
|
||||
|
||||
CurrentArtLoader::~CurrentArtLoader() {}
|
||||
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {}
|
||||
|
||||
void CurrentArtLoader::LoadArt(const Song &song) {
|
||||
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
|
||||
last_song_ = song;
|
||||
id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_);
|
||||
}
|
||||
|
||||
void CurrentArtLoader::TempArtLoaded(quint64 id, const QImage &image) {
|
||||
void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image) {
|
||||
|
||||
if (id != id_) return;
|
||||
id_ = 0;
|
||||
|
||||
QString uri;
|
||||
QString thumbnail_uri;
|
||||
QUrl cover_url;
|
||||
QUrl thumbnail_url;
|
||||
QImage thumbnail;
|
||||
|
||||
if (!image.isNull()) {
|
||||
temp_art_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_art_->setAutoRemove(true);
|
||||
temp_art_->open();
|
||||
image.save(temp_art_->fileName(), "JPEG");
|
||||
|
||||
QString filename;
|
||||
|
||||
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_cover_->setAutoRemove(true);
|
||||
temp_cover_->open();
|
||||
|
||||
image.save(temp_cover_->fileName(), "JPEG");
|
||||
|
||||
// Scale the image down to make a thumbnail. It's a bit crap doing it here since it's the GUI thread, but the alternative is hard.
|
||||
temp_art_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_art_thumbnail_->open();
|
||||
temp_art_thumbnail_->setAutoRemove(true);
|
||||
temp_cover_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_cover_thumbnail_->open();
|
||||
temp_cover_thumbnail_->setAutoRemove(true);
|
||||
thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation);
|
||||
thumbnail.save(temp_art_thumbnail_->fileName(), "JPEG");
|
||||
thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG");
|
||||
|
||||
uri = "file://" + temp_art_->fileName();
|
||||
thumbnail_uri = "file://" + temp_art_thumbnail_->fileName();
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(temp_cover_->fileName());
|
||||
|
||||
thumbnail_url.setScheme("file");
|
||||
thumbnail_url.setPath(temp_cover_thumbnail_->fileName());
|
||||
}
|
||||
|
||||
emit ArtLoaded(last_song_, uri, image);
|
||||
emit ThumbnailLoaded(last_song_, thumbnail_uri, thumbnail);
|
||||
emit AlbumCoverLoaded(last_song_, cover_url, image);
|
||||
emit ThumbnailLoaded(last_song_, thumbnail_url, thumbnail);
|
||||
|
||||
}
|
|
@ -18,8 +18,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#ifndef CURRENTARTLOADER_H
|
||||
#define CURRENTARTLOADER_H
|
||||
#ifndef CURRENTALBUMCOVERLOADER_H
|
||||
#define CURRENTALBUMCOVERLOADER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
@ -36,25 +36,25 @@
|
|||
|
||||
class Application;
|
||||
|
||||
class CurrentArtLoader : public QObject {
|
||||
class CurrentAlbumCoverLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CurrentArtLoader(Application *app, QObject *parent = nullptr);
|
||||
~CurrentArtLoader();
|
||||
explicit CurrentAlbumCoverLoader(Application *app, QObject *parent = nullptr);
|
||||
~CurrentAlbumCoverLoader();
|
||||
|
||||
const AlbumCoverLoaderOptions &options() const { return options_; }
|
||||
const Song &last_song() const { return last_song_; }
|
||||
|
||||
public slots:
|
||||
void LoadArt(const Song &song);
|
||||
void LoadAlbumCover(const Song &song);
|
||||
|
||||
signals:
|
||||
void ArtLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
void ThumbnailLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
signals:
|
||||
void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
|
||||
void ThumbnailLoaded(const Song &song, const QUrl &thumbnail_uri, const QImage &image);
|
||||
|
||||
private slots:
|
||||
void TempArtLoaded(quint64 id, const QImage &image);
|
||||
void TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
@ -62,11 +62,12 @@ signals:
|
|||
|
||||
QString temp_file_pattern_;
|
||||
|
||||
std::unique_ptr<QTemporaryFile> temp_art_;
|
||||
std::unique_ptr<QTemporaryFile> temp_art_thumbnail_;
|
||||
std::unique_ptr<QTemporaryFile> temp_cover_;
|
||||
std::unique_ptr<QTemporaryFile> temp_cover_thumbnail_;
|
||||
quint64 id_;
|
||||
|
||||
Song last_song_;
|
||||
|
||||
};
|
||||
|
||||
#endif // CURRENTARTLOADER_H
|
||||
#endif // CURRENTALBUMCOVERLOADER_H
|
|
@ -53,13 +53,13 @@ DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): Cov
|
|||
bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> Parameters;
|
||||
typedef QList<Param> Params;
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
Parameters params = Parameters() << Param("output", "json")
|
||||
<< Param("q", QString(artist + " " + album))
|
||||
<< Param("limit", QString::number(kLimit));
|
||||
const Params params = Params() << Param("output", "json")
|
||||
<< Param("q", QString(artist + " " + album))
|
||||
<< Param("limit", QString::number(kLimit));
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Param ¶m : params) {
|
||||
|
@ -88,18 +88,18 @@ QByteArray DeezerCoverProvider::GetReplyData(QNetworkReply *reply) {
|
|||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
QString error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(error);
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" - then use that instead.
|
||||
// See if there is Json data containing "error" object - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
QString error;
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error")) {
|
||||
QJsonValue json_value_error = json_obj["error"];
|
||||
|
@ -108,12 +108,19 @@ QByteArray DeezerCoverProvider::GetReplyData(QNetworkReply *reply) {
|
|||
int code = json_error["code"].toInt();
|
||||
QString message = json_error["message"].toString();
|
||||
QString type = json_error["type"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
error = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
|
|
@ -184,30 +184,32 @@ QByteArray DiscogsCoverProvider::GetReplyData(QNetworkReply *reply) {
|
|||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
QString error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(error);
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "message" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("message")) {
|
||||
failure_reason = json_obj["message"].toString();
|
||||
error = json_obj["message"].toString();
|
||||
}
|
||||
}
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ class Application;
|
|||
|
||||
// This struct represents a single search-for-cover request. It identifies and describes the request.
|
||||
struct DiscogsCoverSearchContext {
|
||||
DiscogsCoverSearchContext() : id(-1), r_count(0) {}
|
||||
|
||||
// The unique request identifier
|
||||
int id;
|
||||
|
@ -55,6 +56,7 @@ Q_DECLARE_METATYPE(DiscogsCoverSearchContext)
|
|||
|
||||
// This struct represents a single release request. It identifies and describes the request.
|
||||
struct DiscogsCoverReleaseContext {
|
||||
DiscogsCoverReleaseContext() : id(-1) {}
|
||||
|
||||
int id; // The unique request identifier
|
||||
int s_id; // The search request identifier
|
||||
|
@ -102,4 +104,3 @@ class DiscogsCoverProvider : public CoverProvider {
|
|||
};
|
||||
|
||||
#endif // DISCOGSCOVERPROVIDER_H
|
||||
|
||||
|
|
|
@ -204,32 +204,33 @@ QByteArray LastFmCoverProvider::GetReplyData(QNetworkReply *reply) {
|
|||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" and "message" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error") && json_obj.contains("message")) {
|
||||
int error = json_obj["error"].toInt();
|
||||
int code = json_obj["error"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = "Error: " + QString::number(error) + ": " + message;
|
||||
error = "Error: " + QString::number(code) + ": " + message;
|
||||
}
|
||||
}
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ QByteArray MusicbrainzCoverProvider::GetReplyData(QNetworkReply *reply) {
|
|||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
|
@ -192,22 +192,24 @@ QByteArray MusicbrainzCoverProvider::GetReplyData(QNetworkReply *reply) {
|
|||
else {
|
||||
// See if there is Json data containing "error" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error")) {
|
||||
failure_reason = json_obj["error"].toString();
|
||||
error = json_obj["error"].toString();
|
||||
}
|
||||
}
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
|
|
@ -57,14 +57,14 @@ TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) :
|
|||
|
||||
}
|
||||
|
||||
bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
|
||||
|
||||
if (!service_ || !service_->authenticated()) return false;
|
||||
|
||||
QList<Param> parameters;
|
||||
parameters << Param("query", QString(artist + " " + album));
|
||||
parameters << Param("limit", QString::number(kLimit));
|
||||
QNetworkReply *reply = CreateRequest("search/albums", parameters);
|
||||
ParamList params = ParamList() << Param("query", QString(artist + " " + album))
|
||||
<< Param("limit", QString::number(kLimit));
|
||||
|
||||
QNetworkReply *reply = CreateRequest("search/albums", params);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, int)), reply, id);
|
||||
|
||||
return true;
|
||||
|
@ -73,20 +73,13 @@ bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album
|
|||
|
||||
void TidalCoverProvider::CancelSearch(int id) {}
|
||||
|
||||
QNetworkReply *TidalCoverProvider::CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_supplied) {
|
||||
QNetworkReply *TidalCoverProvider::CreateRequest(const QString &ressource_name, const ParamList ¶ms_supplied) {
|
||||
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> ParamList;
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
ParamList parameters = ParamList()
|
||||
<< params_supplied
|
||||
<< Param("sessionId", service_->session_id())
|
||||
<< Param("countryCode", service_->country_code());
|
||||
const ParamList params = ParamList() << params_supplied
|
||||
<< Param("countryCode", service_->country_code());
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Param& param : parameters) {
|
||||
for (const Param ¶m : params) {
|
||||
EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
|
||||
url_query.addQueryItem(encoded_param.first, encoded_param.second);
|
||||
}
|
||||
|
@ -95,7 +88,8 @@ QNetworkReply *TidalCoverProvider::CreateRequest(const QString &ressource_name,
|
|||
url.setQuery(url_query);
|
||||
QNetworkRequest req(url);
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
req.setRawHeader("X-Tidal-SessionId", service_->session_id().toUtf8());
|
||||
if (!service_->access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + service_->access_token().toUtf8());
|
||||
if (!service_->session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", service_->session_id().toUtf8());
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
|
||||
return reply;
|
||||
|
@ -106,38 +100,42 @@ QByteArray TidalCoverProvider::GetReplyData(QNetworkReply *reply, QString &error
|
|||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "userMessage" - then use that instead.
|
||||
// See if there is Json data containing "status" and "userMessage" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
int status = 0;
|
||||
int sub_status = 0;
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("userMessage")) {
|
||||
status = json_obj["status"].toInt();
|
||||
sub_status = json_obj["subStatus"].toInt();
|
||||
QString user_message = json_obj["userMessage"].toString();
|
||||
failure_reason = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
error = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
if (status == 401 && sub_status == 6001) { // User does not have a valid session
|
||||
service_->Logout();
|
||||
}
|
||||
error = Error(failure_reason);
|
||||
error = Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
@ -195,7 +193,7 @@ QJsonValue TidalCoverProvider::ExtractItems(QJsonObject &json_obj, QString &erro
|
|||
|
||||
}
|
||||
|
||||
void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, int id) {
|
||||
void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
|
|
|
@ -43,20 +43,21 @@ class TidalCoverProvider : public CoverProvider {
|
|||
|
||||
public:
|
||||
explicit TidalCoverProvider(Application *app, QObject *parent = nullptr);
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
bool StartSearch(const QString &artist, const QString &album, const int id);
|
||||
void CancelSearch(int id);
|
||||
|
||||
private slots:
|
||||
void HandleSearchReply(QNetworkReply *reply, int id);
|
||||
void HandleSearchReply(QNetworkReply *reply, const int id);
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> ParamList;
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
static const char *kApiUrl;
|
||||
static const char *kResourcesUrl;
|
||||
static const char *kApiTokenB64;
|
||||
static const int kLimit;
|
||||
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_supplied);
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const ParamList ¶ms_supplied);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QByteArray &data, QString &error);
|
||||
|
|
|
@ -107,7 +107,7 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
|
|||
|
||||
cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, QImage(":/pictures/cdcase.png"));
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64,QImage,QImage)), SLOT(ArtLoaded(quint64,QImage,QImage)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage, QImage)));
|
||||
|
||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||
connect(tag_fetcher_, SIGNAL(ResultAvailable(Song, SongList)), results_dialog_, SLOT(FetchTagFinished(Song, SongList)), Qt::QueuedConnection);
|
||||
|
@ -116,7 +116,7 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
|
|||
connect(results_dialog_, SIGNAL(finished(int)), tag_fetcher_, SLOT(Cancel()));
|
||||
#endif
|
||||
|
||||
album_cover_choice_controller_->SetApplication(app_);
|
||||
album_cover_choice_controller_->Init(app_);
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
|
||||
|
@ -505,13 +505,13 @@ void EditTagDialog::UpdateSummaryTab(const Song &song) {
|
|||
art_is_set = false;
|
||||
}
|
||||
else if (!song.art_manual().isEmpty()) {
|
||||
summary += tr("Cover art set from %1").arg(song.art_manual()).toHtmlEscaped();
|
||||
summary += tr("Cover art set from %1").arg(song.art_manual().toString()).toHtmlEscaped();
|
||||
}
|
||||
else if (song.has_embedded_cover()) {
|
||||
summary += tr("Cover art from embedded image");
|
||||
}
|
||||
else if (!song.art_automatic().isEmpty()) {
|
||||
summary += tr("Cover art loaded automatically from %1").arg(song.art_automatic()).toHtmlEscaped();
|
||||
summary += tr("Cover art loaded automatically from %1").arg(song.art_automatic().toString()).toHtmlEscaped();
|
||||
}
|
||||
else {
|
||||
summary += tr("Cover art not set").toHtmlEscaped();
|
||||
|
@ -559,7 +559,7 @@ void EditTagDialog::UpdateStatisticsTab(const Song &song) {
|
|||
|
||||
}
|
||||
|
||||
void EditTagDialog::ArtLoaded(quint64 id, const QImage &scaled, const QImage &original) {
|
||||
void EditTagDialog::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original) {
|
||||
|
||||
if (id == cover_art_id_) {
|
||||
ui_->art->setPixmap(QPixmap::fromImage(scaled));
|
||||
|
@ -623,9 +623,9 @@ void EditTagDialog::LoadCoverFromFile() {
|
|||
|
||||
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
|
||||
|
||||
QString cover = album_cover_choice_controller_->LoadCoverFromFile(song);
|
||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromFile(song);
|
||||
|
||||
if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover);
|
||||
if (!cover_url.isEmpty()) UpdateCoverOf(*song, sel, cover_url);
|
||||
|
||||
}
|
||||
|
||||
|
@ -645,9 +645,9 @@ void EditTagDialog::LoadCoverFromURL() {
|
|||
|
||||
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
|
||||
|
||||
QString cover = album_cover_choice_controller_->LoadCoverFromURL(song);
|
||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromURL(song);
|
||||
|
||||
if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover);
|
||||
if (!cover_url.isEmpty()) UpdateCoverOf(*song, sel, cover_url);
|
||||
}
|
||||
|
||||
void EditTagDialog::SearchForCover() {
|
||||
|
@ -657,9 +657,9 @@ void EditTagDialog::SearchForCover() {
|
|||
|
||||
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
|
||||
|
||||
QString cover = album_cover_choice_controller_->SearchForCover(song);
|
||||
QUrl cover_url = album_cover_choice_controller_->SearchForCover(song);
|
||||
|
||||
if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover);
|
||||
if (!cover_url.isEmpty()) UpdateCoverOf(*song, sel, cover_url);
|
||||
}
|
||||
|
||||
void EditTagDialog::UnsetCover() {
|
||||
|
@ -669,8 +669,8 @@ void EditTagDialog::UnsetCover() {
|
|||
|
||||
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
|
||||
|
||||
QString cover = album_cover_choice_controller_->UnsetCover(song);
|
||||
UpdateCoverOf(*song, sel, cover);
|
||||
QUrl cover_url = album_cover_choice_controller_->UnsetCover(song);
|
||||
UpdateCoverOf(*song, sel, cover_url);
|
||||
}
|
||||
|
||||
void EditTagDialog::ShowCover() {
|
||||
|
@ -683,7 +683,7 @@ void EditTagDialog::ShowCover() {
|
|||
album_cover_choice_controller_->ShowCover(*song);
|
||||
}
|
||||
|
||||
void EditTagDialog::UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QString &cover) {
|
||||
void EditTagDialog::UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QUrl &cover_url) {
|
||||
|
||||
if (!selected.is_valid() || selected.id() == -1) return;
|
||||
|
||||
|
@ -696,7 +696,7 @@ void EditTagDialog::UpdateCoverOf(const Song &selected, const QModelIndexList &s
|
|||
|
||||
Song *other_song = &data_[i].original_;
|
||||
if (selected.artist() == other_song->artist() && selected.album() == other_song->album()) {
|
||||
other_song->set_art_manual(cover);
|
||||
other_song->set_art_manual(cover_url);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -779,9 +779,9 @@ bool EditTagDialog::eventFilter(QObject *o, QEvent *e) {
|
|||
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
|
||||
Song *song = GetFirstSelected();
|
||||
|
||||
const QString cover = album_cover_choice_controller_->SaveCover(song, event);
|
||||
if (!cover.isEmpty()) {
|
||||
UpdateCoverOf(*song, sel, cover);
|
||||
const QUrl cover_url = album_cover_choice_controller_->SaveCover(song, event);
|
||||
if (!cover_url.isEmpty()) {
|
||||
UpdateCoverOf(*song, sel, cover_url);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
@ -114,7 +114,7 @@ class EditTagDialog : public QDialog {
|
|||
void FetchTagSongChosen(const Song &original_song, const Song &new_metadata);
|
||||
#endif
|
||||
|
||||
void ArtLoaded(quint64 id, const QImage &scaled, const QImage &original);
|
||||
void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original);
|
||||
|
||||
void LoadCoverFromFile();
|
||||
void SaveCoverToFile();
|
||||
|
@ -139,7 +139,7 @@ class EditTagDialog : public QDialog {
|
|||
};
|
||||
|
||||
Song *GetFirstSelected();
|
||||
void UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QString &cover);
|
||||
void UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QUrl &cover_url);
|
||||
|
||||
bool DoesValueVary(const QModelIndexList &sel, const QString &id) const;
|
||||
bool IsValueModified(const QModelIndexList &sel, const QString &id) const;
|
||||
|
|
|
@ -206,6 +206,7 @@ private:
|
|||
};
|
||||
|
||||
struct SimpleMetaBundle {
|
||||
SimpleMetaBundle() : length(-1), year(-1), track(-1), samplerate(-1), bitdepth(-1) {}
|
||||
QUrl url;
|
||||
QString title;
|
||||
QString artist;
|
||||
|
@ -214,7 +215,7 @@ struct SimpleMetaBundle {
|
|||
QString genre;
|
||||
qlonglong length;
|
||||
int year;
|
||||
int tracknr;
|
||||
int track;
|
||||
Song::FileType filetype;
|
||||
int samplerate;
|
||||
int bitdepth;
|
||||
|
|
|
@ -624,7 +624,7 @@ void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
|
|||
bundle.album = ParseStrTag(taglist, GST_TAG_ALBUM);
|
||||
bundle.length = 0;
|
||||
bundle.year = 0;
|
||||
bundle.tracknr = 0;
|
||||
bundle.track = 0;
|
||||
bundle.filetype = Song::FileType_Unknown;
|
||||
bundle.samplerate = 0;
|
||||
bundle.bitdepth = 0;
|
||||
|
|
|
@ -821,7 +821,7 @@ Engine::SimpleMetaBundle XineEngine::FetchMetaData() const {
|
|||
bundle.length = 0;
|
||||
|
||||
bundle.year = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_YEAR)).toInt();
|
||||
bundle.tracknr = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_TRACK_NUMBER)).toInt();
|
||||
bundle.track = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_TRACK_NUMBER)).toInt();
|
||||
|
||||
bundle.samplerate = xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_SAMPLERATE);
|
||||
bundle.bitdepth = xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_BITS);
|
||||
|
@ -834,7 +834,7 @@ Engine::SimpleMetaBundle XineEngine::FetchMetaData() const {
|
|||
<< bundle.genre
|
||||
<< bundle.length
|
||||
<< bundle.year
|
||||
<< bundle.tracknr
|
||||
<< bundle.track
|
||||
<< bundle.samplerate
|
||||
<< bundle.bitdepth
|
||||
<< bundle.bitrate;
|
||||
|
|
|
@ -61,7 +61,7 @@ InternetSearch::InternetSearch(Application *app, Song::Source source, QObject *p
|
|||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(const quint64, const QImage&)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage)));
|
||||
connect(this, SIGNAL(SearchAsyncSig(const int, const QString&, const SearchType)), this, SLOT(DoSearchAsync(const int, const QString&, const SearchType)));
|
||||
|
||||
connect(service_, SIGNAL(SearchUpdateStatus(const int, const QString&)), SLOT(UpdateStatusSlot(const int, const QString&)));
|
||||
|
@ -206,7 +206,7 @@ bool InternetSearch::FindCachedPixmap(const InternetSearch::Result &result, QPix
|
|||
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
|
||||
}
|
||||
|
||||
int InternetSearch::LoadArtAsync(const InternetSearch::Result &result) {
|
||||
int InternetSearch::LoadAlbumCoverAsync(const InternetSearch::Result &result) {
|
||||
|
||||
const int id = art_searches_next_id_++;
|
||||
|
||||
|
@ -219,7 +219,7 @@ int InternetSearch::LoadArtAsync(const InternetSearch::Result &result) {
|
|||
|
||||
}
|
||||
|
||||
void InternetSearch::AlbumArtLoaded(const quint64 id, const QImage &image) {
|
||||
void InternetSearch::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (!cover_loader_tasks_.contains(id)) return;
|
||||
int orig_id = cover_loader_tasks_.take(id);
|
||||
|
@ -229,7 +229,7 @@ void InternetSearch::AlbumArtLoaded(const quint64 id, const QImage &image) {
|
|||
QPixmap pixmap = QPixmap::fromImage(image);
|
||||
pixmap_cache_.insert(key, pixmap);
|
||||
|
||||
emit ArtLoaded(orig_id, pixmap);
|
||||
emit AlbumCoverLoaded(orig_id, pixmap);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ class InternetSearch : public QObject {
|
|||
InternetService *service() const { return service_; }
|
||||
|
||||
int SearchAsync(const QString &query, SearchType type);
|
||||
int LoadArtAsync(const InternetSearch::Result &result);
|
||||
int LoadAlbumCoverAsync(const InternetSearch::Result &result);
|
||||
|
||||
void CancelSearch(const int id);
|
||||
void CancelArt(const int id);
|
||||
|
@ -86,7 +86,7 @@ class InternetSearch : public QObject {
|
|||
void ProgressSetMaximum(const int id, const int progress);
|
||||
void UpdateProgress(const int id, const int max);
|
||||
|
||||
void ArtLoaded(const int id, const QPixmap &pixmap);
|
||||
void AlbumCoverLoaded(const int id, const QPixmap &pixmap);
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -117,7 +117,7 @@ class InternetSearch : public QObject {
|
|||
void DoSearchAsync(const int id, const QString &query, const SearchType type);
|
||||
void SearchDone(const int service_id, const SongList &songs, const QString &error);
|
||||
|
||||
void AlbumArtLoaded(const quint64 id, const QImage &image);
|
||||
void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
void UpdateStatusSlot(const int id, const QString &text);
|
||||
void ProgressSetMaximumSlot(const int id, const int progress);
|
||||
|
|
|
@ -30,7 +30,7 @@ InternetSearchItemDelegate::InternetSearchItemDelegate(InternetSearchView *view)
|
|||
void InternetSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
|
||||
// Tell the view we painted this item so it can lazy load some art.
|
||||
const_cast<InternetSearchView*>(view_)->LazyLoadArt(index);
|
||||
const_cast<InternetSearchView*>(view_)->LazyLoadAlbumCover(index);
|
||||
|
||||
CollectionItemDelegate::paint(painter, option, index);
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ void InternetSearchView::Init(Application *app, InternetSearch *engine, const QS
|
|||
|
||||
connect(engine_, SIGNAL(AddResults(const int, InternetSearch::ResultList)), SLOT(AddResults(const int, const InternetSearch::ResultList)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(SearchError(const int, const QString&)), SLOT(SearchError(const int, const QString&)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(ArtLoaded(const int, const QPixmap&)), SLOT(ArtLoaded(const int, const QPixmap&)), Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(AlbumCoverLoaded(const int, const QPixmap&)), SLOT(AlbumCoverLoaded(const int, const QPixmap&)), Qt::QueuedConnection);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
|
@ -294,7 +294,7 @@ void InternetSearchView::SwapModels() {
|
|||
|
||||
}
|
||||
|
||||
void InternetSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
||||
void InternetSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) {
|
||||
|
||||
if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) {
|
||||
return;
|
||||
|
@ -332,12 +332,12 @@ void InternetSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
|||
const InternetSearch::Result result = item->data(InternetSearchModel::Role_Result).value<InternetSearch::Result>();
|
||||
|
||||
// Load the art.
|
||||
int id = engine_->LoadArtAsync(result);
|
||||
int id = engine_->LoadAlbumCoverAsync(result);
|
||||
art_requests_[id] = source_index;
|
||||
|
||||
}
|
||||
|
||||
void InternetSearchView::ArtLoaded(const int id, const QPixmap &pixmap) {
|
||||
void InternetSearchView::AlbumCoverLoaded(const int id, const QPixmap &pixmap) {
|
||||
|
||||
if (!art_requests_.contains(id)) return;
|
||||
QModelIndex index = art_requests_.take(id);
|
||||
|
@ -545,7 +545,7 @@ void InternetSearchView::SetGroupBy(const CollectionModel::Grouping &g) {
|
|||
|
||||
// Clear requests: changing "group by" on the models will cause all the items to be removed/added again,
|
||||
// so all the QModelIndex here will become invalid. New requests will be created for those
|
||||
// songs when they will be displayed again anyway (when InternetSearchItemDelegate::paint will call LazyLoadArt)
|
||||
// songs when they will be displayed again anyway (when InternetSearchItemDelegate::paint will call LazyLoadAlbumCover)
|
||||
art_requests_.clear();
|
||||
// Update the models
|
||||
front_model_->SetGroupBy(g, true);
|
||||
|
|
|
@ -63,7 +63,7 @@ class InternetSearchView : public QWidget {
|
|||
|
||||
static const int kSwapModelsTimeoutMsec;
|
||||
|
||||
void LazyLoadArt(const QModelIndex &index);
|
||||
void LazyLoadAlbumCover(const QModelIndex &index);
|
||||
|
||||
void showEvent(QShowEvent *e);
|
||||
void hideEvent(QHideEvent *e);
|
||||
|
@ -89,7 +89,7 @@ class InternetSearchView : public QWidget {
|
|||
void UpdateProgress(const int id, const int max);
|
||||
void AddResults(const int id, const InternetSearch::ResultList &results);
|
||||
void SearchError(const int id, const QString &error);
|
||||
void ArtLoaded(const int id, const QPixmap &pixmap);
|
||||
void AlbumCoverLoaded(const int id, const QPixmap &pixmap);
|
||||
|
||||
void FocusOnFilter(QKeyEvent *event);
|
||||
|
||||
|
|
|
@ -37,19 +37,21 @@ class LyricsProviders;
|
|||
class LyricsFetcherSearch;
|
||||
|
||||
struct LyricsSearchRequest {
|
||||
quint64 id = -1;
|
||||
LyricsSearchRequest() : id(-1) {}
|
||||
quint64 id;
|
||||
QString artist;
|
||||
QString album;
|
||||
QString title;
|
||||
};
|
||||
|
||||
struct LyricsSearchResult {
|
||||
LyricsSearchResult() : score(0.0) {}
|
||||
QString provider;
|
||||
QString artist;
|
||||
QString album;
|
||||
QString title;
|
||||
QString lyrics;
|
||||
float score = 0.0;
|
||||
float score;
|
||||
};
|
||||
Q_DECLARE_METATYPE(LyricsSearchResult);
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
using std::stable_sort;
|
||||
|
||||
|
@ -117,6 +118,12 @@ void AcoustidClient::RequestFinished(QNetworkReply *reply, const int request_id)
|
|||
requests_.remove(request_id);
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError || reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qLog(Error) << QString("Acoustid: %1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
qLog(Error) << QString("Acoustid: Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
emit Finished(request_id, QStringList());
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ QByteArray MusicBrainzClient::GetReplyData(QNetworkReply *reply, QString &error)
|
|||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "core/taskmanager.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/song.h"
|
||||
#include "organise.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
# include "transcoder/transcoder.h"
|
||||
|
@ -200,11 +201,21 @@ void Organise::ProcessSomeFiles() {
|
|||
job.albumcover_ = albumcover_;
|
||||
job.remove_original_ = !copy_;
|
||||
|
||||
if (!task.song_info_.song_.art_manual().isEmpty()) {
|
||||
job.cover_source_ = task.song_info_.song_.art_manual();
|
||||
if (task.song_info_.song_.art_manual_is_valid() && task.song_info_.song_.art_manual().path() != Song::kManuallyUnsetCover) {
|
||||
if (task.song_info_.song_.art_manual().scheme() == "file" && QFile::exists(task.song_info_.song_.art_manual().toLocalFile())) {
|
||||
job.cover_source_ = task.song_info_.song_.art_manual().toLocalFile();
|
||||
}
|
||||
else if (task.song_info_.song_.art_manual().scheme().isEmpty() && QFile::exists(task.song_info_.song_.art_manual().path())) {
|
||||
job.cover_source_ = task.song_info_.song_.art_manual().path();
|
||||
}
|
||||
}
|
||||
else if (!task.song_info_.song_.art_automatic().isEmpty()) {
|
||||
job.cover_source_ = task.song_info_.song_.art_automatic();
|
||||
else if (task.song_info_.song_.art_automatic_is_valid() && task.song_info_.song_.art_automatic().path() != Song::kEmbeddedCover) {
|
||||
if (task.song_info_.song_.art_automatic().scheme() == "file" && QFile::exists(task.song_info_.song_.art_automatic().toLocalFile())) {
|
||||
job.cover_source_ = task.song_info_.song_.art_automatic().toLocalFile();
|
||||
}
|
||||
else if (task.song_info_.song_.art_automatic().scheme().isEmpty() && QFile::exists(task.song_info_.song_.art_automatic().path())) {
|
||||
job.cover_source_ = task.song_info_.song_.art_automatic().path();
|
||||
}
|
||||
}
|
||||
if (!job.cover_source_.isEmpty()) {
|
||||
job.cover_dest_ = QFileInfo(job.destination_).path() + "/" + QFileInfo(job.cover_source_).fileName();
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
#include "playlistdelegates.h"
|
||||
#include "playlistheader.h"
|
||||
#include "playlistview.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
#include "settings/playlistsettingspage.h"
|
||||
|
||||
|
@ -212,7 +212,7 @@ void PlaylistView::SetApplication(Application *app) {
|
|||
|
||||
Q_ASSERT(app);
|
||||
app_ = app;
|
||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)), SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&)));
|
||||
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(const Song&, const QUrl&, const QImage&)), SLOT(CurrentSongChanged(const Song&, const QUrl&, const QImage&)));
|
||||
connect(app_->player(), SIGNAL(Paused()), SLOT(StopGlowing()));
|
||||
connect(app_->player(), SIGNAL(Playing()), SLOT(StartGlowing()));
|
||||
connect(app_->player(), SIGNAL(Stopped()), SLOT(StopGlowing()));
|
||||
|
@ -1210,7 +1210,7 @@ void PlaylistView::CopyCurrentSongToClipboard() const {
|
|||
|
||||
}
|
||||
|
||||
void PlaylistView::CurrentSongChanged(const Song &song, const QString &uri, const QImage &song_art) {
|
||||
void PlaylistView::CurrentSongChanged(const Song &song, const QUrl &cover_url, const QImage &song_art) {
|
||||
|
||||
if (current_song_cover_art_ == song_art) return;
|
||||
|
||||
|
@ -1262,6 +1262,7 @@ void PlaylistView::set_background_image(const QImage &image) {
|
|||
|
||||
if (isVisible()) {
|
||||
previous_background_image_opacity_ = 1.0;
|
||||
if (fade_animation_->state() == QTimeLine::Running) fade_animation_->stop();
|
||||
fade_animation_->start();
|
||||
}
|
||||
|
||||
|
@ -1280,7 +1281,7 @@ void PlaylistView::FadePreviousBackgroundImage(qreal value) {
|
|||
}
|
||||
|
||||
void PlaylistView::PlayerStopped() {
|
||||
CurrentSongChanged(Song(), QString(), QImage());
|
||||
CurrentSongChanged(Song(), QUrl(), QImage());
|
||||
}
|
||||
|
||||
void PlaylistView::focusInEvent(QFocusEvent *event) {
|
||||
|
|
|
@ -126,7 +126,7 @@ class PlaylistView : public QTreeView {
|
|||
void SetColumnAlignment(int section, Qt::Alignment alignment);
|
||||
|
||||
void CopyCurrentSongToClipboard() const;
|
||||
void CurrentSongChanged(const Song &new_song, const QString &uri, const QImage &cover_art);
|
||||
void CurrentSongChanged(const Song &new_song, const QUrl &cover_url, const QImage &song_art);
|
||||
void PlayerStopped();
|
||||
|
||||
signals:
|
||||
|
|
|
@ -168,32 +168,18 @@ void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir,
|
|||
writer.writeTextElement("trackNum", QString::number(song.track()));
|
||||
}
|
||||
|
||||
QString art = song.art_manual().isEmpty() ? song.art_automatic() : song.art_manual();
|
||||
QUrl cover_url = song.art_manual().isEmpty() || song.art_manual().path().isEmpty() ? song.art_automatic() : song.art_manual();
|
||||
// Ignore images that are in our resource bundle.
|
||||
if (!art.startsWith(":") && !art.isEmpty()) {
|
||||
QString art_filename;
|
||||
if (!art.contains("://")) {
|
||||
art_filename = art;
|
||||
if (!cover_url.isEmpty() && !cover_url.path().isEmpty() && cover_url.path() != Song::kManuallyUnsetCover && cover_url.path() != Song::kEmbeddedCover) {
|
||||
if (cover_url.scheme().isEmpty()) {
|
||||
cover_url.setScheme("file");
|
||||
}
|
||||
else if (QUrl(art).scheme() == "file") {
|
||||
art_filename = QUrl(art).toLocalFile();
|
||||
}
|
||||
|
||||
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 = URLOrFilename(url, dir, path_type).toUtf8();
|
||||
}
|
||||
else {
|
||||
// Just use whatever URL was in the Song.
|
||||
art_filename = art;
|
||||
}
|
||||
|
||||
writer.writeTextElement("image", art_filename);
|
||||
QString cover_filename = URLOrFilename(cover_url, dir, path_type).toUtf8();
|
||||
writer.writeTextElement("image", cover_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.writeEndDocument();
|
||||
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ QNetworkReply *QobuzBaseRequest::CreateRequest(const QString &ressource_name, co
|
|||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandleSSLErrors(QList<QSslError>)));
|
||||
replies_ << reply;
|
||||
|
||||
//qLog(Debug) << "Qobuz: Sending request" << url;
|
||||
|
@ -85,7 +86,15 @@ QNetworkReply *QobuzBaseRequest::CreateRequest(const QString &ressource_name, co
|
|||
|
||||
}
|
||||
|
||||
QByteArray QobuzBaseRequest::GetReplyData(QNetworkReply *reply, QString &error) {
|
||||
void QobuzBaseRequest::HandleSSLErrors(QList<QSslError> ssl_errors) {
|
||||
|
||||
for (QSslError &ssl_error : ssl_errors) {
|
||||
Error(ssl_error.errorString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QByteArray QobuzBaseRequest::GetReplyData(QNetworkReply *reply) {
|
||||
|
||||
if (replies_.contains(reply)) {
|
||||
replies_.removeAll(reply);
|
||||
|
@ -94,39 +103,38 @@ QByteArray QobuzBaseRequest::GetReplyData(QNetworkReply *reply, QString &error)
|
|||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
error = Error(QString("Received HTTP code %1").arg(http_code));
|
||||
}
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "status", "code" and "message" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QString error;
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("code") && json_obj.contains("message")) {
|
||||
QString status = json_obj["status"].toString();
|
||||
int code = json_obj["code"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
error = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
error = Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
@ -135,29 +143,29 @@ QByteArray QobuzBaseRequest::GetReplyData(QNetworkReply *reply, QString &error)
|
|||
|
||||
}
|
||||
|
||||
QJsonObject QobuzBaseRequest::ExtractJsonObj(QByteArray &data, QString &error) {
|
||||
QJsonObject QobuzBaseRequest::ExtractJsonObj(QByteArray &data) {
|
||||
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
|
||||
if (json_error.error != QJsonParseError::NoError) {
|
||||
error = Error("Reply from server missing Json data.", data);
|
||||
Error("Reply from server missing Json data.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
error = Error("Received empty Json document.", data);
|
||||
Error("Received empty Json document.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
error = Error("Json document is not an object.", json_doc);
|
||||
Error("Json document is not an object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
error = Error("Received empty Json object.", json_doc);
|
||||
Error("Received empty Json object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
|
@ -165,18 +173,18 @@ QJsonObject QobuzBaseRequest::ExtractJsonObj(QByteArray &data, QString &error) {
|
|||
|
||||
}
|
||||
|
||||
QJsonValue QobuzBaseRequest::ExtractItems(QByteArray &data, QString &error) {
|
||||
QJsonValue QobuzBaseRequest::ExtractItems(QByteArray &data) {
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) return QJsonValue();
|
||||
return ExtractItems(json_obj, error);
|
||||
return ExtractItems(json_obj);
|
||||
|
||||
}
|
||||
|
||||
QJsonValue QobuzBaseRequest::ExtractItems(QJsonObject &json_obj, QString &error) {
|
||||
QJsonValue QobuzBaseRequest::ExtractItems(QJsonObject &json_obj) {
|
||||
|
||||
if (!json_obj.contains("items")) {
|
||||
error = Error("Json reply is missing items.", json_obj);
|
||||
Error("Json reply is missing items.", json_obj);
|
||||
return QJsonArray();
|
||||
}
|
||||
QJsonValue json_items = json_obj["items"];
|
||||
|
@ -184,11 +192,12 @@ QJsonValue QobuzBaseRequest::ExtractItems(QJsonObject &json_obj, QString &error)
|
|||
|
||||
}
|
||||
|
||||
QString QobuzBaseRequest::Error(QString error, QVariant debug) {
|
||||
QString QobuzBaseRequest::ErrorsToHTML(const QStringList &errors) {
|
||||
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
return error;
|
||||
QString error_html;
|
||||
for (const QString &error : errors) {
|
||||
error_html += error + "<br />";
|
||||
}
|
||||
return error_html;
|
||||
|
||||
}
|
||||
|
|
|
@ -71,12 +71,13 @@ class QobuzBaseRequest : public QObject {
|
|||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QJsonObject &json_obj, QString &error);
|
||||
QByteArray GetReplyData(QNetworkReply *reply);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data);
|
||||
QJsonValue ExtractItems(QByteArray &data);
|
||||
QJsonValue ExtractItems(QJsonObject &json_obj);
|
||||
|
||||
virtual QString Error(QString error, QVariant debug = QVariant());
|
||||
virtual void Error(const QString &error, const QVariant &debug = QVariant()) = 0;
|
||||
QString ErrorsToHTML(const QStringList &errors);
|
||||
|
||||
QString api_url() { return QString(kApiUrl); }
|
||||
QString app_id() { return service_->app_id(); }
|
||||
|
@ -95,6 +96,9 @@ class QobuzBaseRequest : public QObject {
|
|||
int max_login_attempts() { return service_->max_login_attempts(); }
|
||||
int login_attempts() { return service_->login_attempts(); }
|
||||
|
||||
private slots:
|
||||
void HandleSSLErrors(QList<QSslError> ssl_errors);
|
||||
|
||||
private:
|
||||
|
||||
static const char *kApiUrl;
|
||||
|
|
|
@ -154,8 +154,7 @@ void QobuzFavoriteRequest::AddFavoritesReply(QNetworkReply *reply, const Favorit
|
|||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
return;
|
||||
|
@ -258,8 +257,7 @@ void QobuzFavoriteRequest::RemoveFavoritesReply(QNetworkReply *reply, const Favo
|
|||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
return;
|
||||
}
|
||||
|
@ -279,3 +277,10 @@ void QobuzFavoriteRequest::RemoveFavoritesReply(QNetworkReply *reply, const Favo
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
void QobuzFavoriteRequest::Error(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ class QobuzFavoriteRequest : public QobuzBaseRequest {
|
|||
void RemoveFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs);
|
||||
|
||||
private:
|
||||
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||
QString FavoriteText(const FavoriteType type);
|
||||
void AddFavorites(const FavoriteType type, const SongList &songs);
|
||||
void RemoveFavorites(const FavoriteType type, const SongList &songs);
|
||||
|
|
|
@ -35,7 +35,8 @@
|
|||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "organise/organiseformat.h"
|
||||
#include "core/application.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "qobuzservice.h"
|
||||
#include "qobuzurlhandler.h"
|
||||
#include "qobuzrequest.h"
|
||||
|
@ -47,10 +48,11 @@ const int QobuzRequest::kMaxConcurrentArtistAlbumsRequests = 3;
|
|||
const int QobuzRequest::kMaxConcurrentAlbumSongsRequests = 3;
|
||||
const int QobuzRequest::kMaxConcurrentAlbumCoverRequests = 1;
|
||||
|
||||
QobuzRequest::QobuzRequest(QobuzService *service, QobuzUrlHandler *url_handler, NetworkAccessManager *network, QueryType type, QObject *parent)
|
||||
QobuzRequest::QobuzRequest(QobuzService *service, QobuzUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QueryType type, QObject *parent)
|
||||
: QobuzBaseRequest(service, network, parent),
|
||||
service_(service),
|
||||
url_handler_(url_handler),
|
||||
app_(app),
|
||||
network_(network),
|
||||
type_(type),
|
||||
query_id_(-1),
|
||||
|
@ -300,8 +302,7 @@ void QobuzRequest::AddSongsSearchRequest(const int offset) {
|
|||
|
||||
void QobuzRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
|
||||
--artists_requests_active_;
|
||||
|
||||
|
@ -312,7 +313,7 @@ void QobuzRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
ArtistsFinishCheck();
|
||||
return;
|
||||
|
@ -363,7 +364,7 @@ void QobuzRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
|
|||
emit UpdateProgress(query_id_, artists_received_);
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(json_obj_artists, error);
|
||||
QJsonValue json_value = ExtractItems(json_obj_artists);
|
||||
if (!json_value.isArray()) {
|
||||
ArtistsFinishCheck();
|
||||
return;
|
||||
|
@ -496,8 +497,7 @@ void QobuzRequest::ArtistAlbumsReplyReceived(QNetworkReply *reply, const qint64
|
|||
|
||||
void QobuzRequest::AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_requested, const int limit_requested, const int offset_requested) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
|
||||
if (finished_) return;
|
||||
|
||||
|
@ -506,7 +506,7 @@ void QobuzRequest::AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_r
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
AlbumsFinishCheck(artist_id_requested);
|
||||
return;
|
||||
|
@ -559,7 +559,7 @@ void QobuzRequest::AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_r
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(json_obj_albums, error);
|
||||
QJsonValue json_value = ExtractItems(json_obj_albums);
|
||||
if (!json_value.isArray()) {
|
||||
AlbumsFinishCheck(artist_id_requested);
|
||||
return;
|
||||
|
@ -726,8 +726,7 @@ void QobuzRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64 ar
|
|||
|
||||
void QobuzRequest::SongsReceived(QNetworkReply *reply, const qint64 artist_id_requested, const QString &album_id_requested, const int limit_requested, const int offset_requested, const QString &album_artist_requested, const QString &album_requested) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
|
||||
if (finished_) return;
|
||||
|
||||
|
@ -736,7 +735,7 @@ void QobuzRequest::SongsReceived(QNetworkReply *reply, const qint64 artist_id_re
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist_requested, album_requested);
|
||||
return;
|
||||
|
@ -825,7 +824,7 @@ void QobuzRequest::SongsReceived(QNetworkReply *reply, const qint64 artist_id_re
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(json_obj_tracks, error);
|
||||
QJsonValue json_value = ExtractItems(json_obj_tracks);
|
||||
if (!json_value.isArray()) {
|
||||
SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist, album);
|
||||
return;
|
||||
|
@ -1045,7 +1044,7 @@ int QobuzRequest::ParseSong(Song &song, const QJsonObject &json_obj, qint64 arti
|
|||
song.set_track(track);
|
||||
song.set_url(url);
|
||||
song.set_length_nanosec(duration);
|
||||
song.set_art_automatic(cover_url.toEncoded());
|
||||
song.set_art_automatic(cover_url);
|
||||
song.set_comment(copyright);
|
||||
song.set_directory_id(0);
|
||||
song.set_filetype(Song::FileType_Stream);
|
||||
|
@ -1082,12 +1081,13 @@ void QobuzRequest::AddAlbumCoverRequest(Song &song) {
|
|||
return;
|
||||
}
|
||||
|
||||
album_covers_requests_sent_.insertMulti(cover_url, &song);
|
||||
++album_covers_requested_;
|
||||
|
||||
AlbumCoverRequest request;
|
||||
request.url = cover_url;
|
||||
request.filename = AlbumCoverFileName(song);
|
||||
request.filename = app_->album_cover_loader()->CoverFilePath(song.source(), song.effective_albumartist(), song.effective_album(), song.album_id(), QString(), cover_url);
|
||||
if (request.filename.isEmpty()) return;
|
||||
|
||||
album_covers_requests_sent_.insertMulti(cover_url, &song);
|
||||
++album_covers_requested_;
|
||||
|
||||
album_cover_requests_queue_.enqueue(request);
|
||||
|
||||
|
@ -1132,9 +1132,8 @@ void QobuzRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_ur
|
|||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
album_covers_requests_sent_.remove(cover_url);
|
||||
AlbumCoverFinishCheck();
|
||||
return;
|
||||
|
@ -1142,7 +1141,7 @@ void QobuzRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_ur
|
|||
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
error = Error(QString("Received empty image data for %1").arg(cover_url.toString()));
|
||||
Error(QString("Received empty image data for %1").arg(cover_url.toString()));
|
||||
album_covers_requests_sent_.remove(cover_url);
|
||||
AlbumCoverFinishCheck();
|
||||
return;
|
||||
|
@ -1151,57 +1150,26 @@ void QobuzRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_ur
|
|||
QImage image;
|
||||
if (image.loadFromData(data)) {
|
||||
|
||||
QDir dir;
|
||||
if (dir.mkpath(service_->CoverCacheDir())) {
|
||||
QString filepath(service_->CoverCacheDir() + "/" + filename);
|
||||
if (image.save(filepath, "JPG")) {
|
||||
while (album_covers_requests_sent_.contains(cover_url)) {
|
||||
Song *song = album_covers_requests_sent_.take(cover_url);
|
||||
song->set_art_automatic(filepath);
|
||||
}
|
||||
if (image.save(filename, "JPG")) {
|
||||
while (album_covers_requests_sent_.contains(cover_url)) {
|
||||
Song *song = album_covers_requests_sent_.take(cover_url);
|
||||
QUrl cover_url;
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(filename);
|
||||
song->set_art_automatic(cover_url);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
album_covers_requests_sent_.remove(cover_url);
|
||||
error = Error(QString("Error decoding image data from %1").arg(cover_url.toString()));
|
||||
Error(QString("Error decoding image data from %1").arg(cover_url.toString()));
|
||||
}
|
||||
|
||||
AlbumCoverFinishCheck();
|
||||
|
||||
}
|
||||
|
||||
QString QobuzRequest::AlbumCoverFileName(const Song &song) {
|
||||
|
||||
QString artist = song.effective_albumartist();
|
||||
QString album = song.effective_album();
|
||||
QString title = song.title();
|
||||
|
||||
artist.remove('/');
|
||||
album.remove('/');
|
||||
title.remove('/');
|
||||
|
||||
QString filename = artist + "-" + album + ".jpg";
|
||||
filename = filename.toLower();
|
||||
filename.replace(' ', '-');
|
||||
filename.replace("--", "-");
|
||||
filename.replace(230, "ae");
|
||||
filename.replace(198, "AE");
|
||||
filename.replace(246, 'o');
|
||||
filename.replace(248, 'o');
|
||||
filename.replace(214, 'O');
|
||||
filename.replace(216, 'O');
|
||||
filename.replace(228, 'a');
|
||||
filename.replace(229, 'a');
|
||||
filename.replace(196, 'A');
|
||||
filename.replace(197, 'A');
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
|
||||
return filename;
|
||||
|
||||
}
|
||||
|
||||
void QobuzRequest::AlbumCoverFinishCheck() {
|
||||
|
||||
if (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests)
|
||||
|
@ -1245,28 +1213,24 @@ void QobuzRequest::FinishCheck() {
|
|||
if (songs_.isEmpty() && errors_.isEmpty())
|
||||
emit Results(query_id_, songs_, tr("Unknown error"));
|
||||
else
|
||||
emit Results(query_id_, songs_, errors_);
|
||||
emit Results(query_id_, songs_, ErrorsToHTML(errors_));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString QobuzRequest::Error(QString error, QVariant debug) {
|
||||
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
void QobuzRequest::Error(const QString &error, const QVariant &debug) {
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
errors_ += error;
|
||||
errors_ += "<br />";
|
||||
errors_ << error;
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
}
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
FinishCheck();
|
||||
|
||||
return error;
|
||||
|
||||
}
|
||||
|
||||
void QobuzRequest::Warn(QString error, QVariant debug) {
|
||||
void QobuzRequest::Warn(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <QMultiMap>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
|
@ -40,6 +41,7 @@
|
|||
#include "core/song.h"
|
||||
#include "qobuzbaserequest.h"
|
||||
|
||||
class Application;
|
||||
class NetworkAccessManager;
|
||||
class QobuzService;
|
||||
class QobuzUrlHandler;
|
||||
|
@ -49,7 +51,7 @@ class QobuzRequest : public QobuzBaseRequest {
|
|||
|
||||
public:
|
||||
|
||||
QobuzRequest(QobuzService *service, QobuzUrlHandler *url_handler, NetworkAccessManager *network, QueryType type, QObject *parent);
|
||||
QobuzRequest(QobuzService *service, QobuzUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QueryType type, QObject *parent);
|
||||
~QobuzRequest();
|
||||
|
||||
void ReloadSettings();
|
||||
|
@ -141,8 +143,8 @@ class QobuzRequest : public QobuzBaseRequest {
|
|||
void AlbumCoverFinishCheck();
|
||||
|
||||
void FinishCheck();
|
||||
void Warn(QString error, QVariant debug = QVariant());
|
||||
QString Error(QString error, QVariant debug = QVariant());
|
||||
void Warn(const QString &error, const QVariant &debug = QVariant());
|
||||
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||
|
||||
static const int kMaxConcurrentArtistsRequests;
|
||||
static const int kMaxConcurrentAlbumsRequests;
|
||||
|
@ -153,6 +155,7 @@ class QobuzRequest : public QobuzBaseRequest {
|
|||
|
||||
QobuzService *service_;
|
||||
QobuzUrlHandler *url_handler_;
|
||||
Application *app_;
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
QueryType type_;
|
||||
|
@ -193,7 +196,7 @@ class QobuzRequest : public QobuzBaseRequest {
|
|||
int album_covers_received_;
|
||||
|
||||
SongList songs_;
|
||||
QString errors_;
|
||||
QStringList errors_;
|
||||
bool no_results_;
|
||||
QList<QNetworkReply*> album_cover_replies_;
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QDesktopServices>
|
||||
#include <QCryptographicHash>
|
||||
#include <QByteArray>
|
||||
|
@ -213,10 +212,6 @@ void QobuzService::ReloadSettings() {
|
|||
|
||||
}
|
||||
|
||||
QString QobuzService::CoverCacheDir() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers";
|
||||
}
|
||||
|
||||
void QobuzService::SendLogin() {
|
||||
SendLogin(app_id_, username_, password_);
|
||||
}
|
||||
|
@ -224,6 +219,7 @@ void QobuzService::SendLogin() {
|
|||
void QobuzService::SendLogin(const QString &app_id, const QString &username, const QString &password) {
|
||||
|
||||
emit UpdateStatus(tr("Authenticating..."));
|
||||
login_errors_.clear();
|
||||
|
||||
login_sent_ = true;
|
||||
++login_attempts_;
|
||||
|
@ -248,20 +244,30 @@ void QobuzService::SendLogin(const QString &app_id, const QString &username, con
|
|||
|
||||
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
|
||||
QNetworkReply *reply = network_->post(req, query);
|
||||
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandleLoginSSLErrors(QList<QSslError>)));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply);
|
||||
|
||||
qLog(Debug) << "Qobuz: Sending request" << url << query;
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::HandleLoginSSLErrors(QList<QSslError> ssl_errors) {
|
||||
|
||||
for (QSslError &ssl_error : ssl_errors) {
|
||||
login_errors_ += ssl_error.errorString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::HandleAuthReply(QNetworkReply *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
login_sent_ = false;
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError || reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
LoginError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
return;
|
||||
|
@ -271,29 +277,29 @@ void QobuzService::HandleAuthReply(QNetworkReply *reply) {
|
|||
QByteArray data(reply->readAll());
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
QString failure_reason;
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("code") && json_obj.contains("message")) {
|
||||
QString status = json_obj["status"].toString();
|
||||
int code = json_obj["code"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
login_errors_ << QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (login_errors_.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
login_errors_ << QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
login_errors_ << QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
LoginError(failure_reason);
|
||||
LoginError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code != 200) {
|
||||
LoginError(QString("Received HTTP code %1").arg(http_code));
|
||||
return;
|
||||
}
|
||||
login_errors_.clear();
|
||||
|
||||
QByteArray data(reply->readAll());
|
||||
QJsonParseError json_error;
|
||||
|
@ -406,7 +412,7 @@ void QobuzService::GetArtists() {
|
|||
|
||||
ResetArtistsRequest();
|
||||
|
||||
artists_request_.reset(new QobuzRequest(this, url_handler_, network_, QobuzBaseRequest::QueryType_Artists, this));
|
||||
artists_request_.reset(new QobuzRequest(this, url_handler_, app_, network_, QobuzBaseRequest::QueryType_Artists, this));
|
||||
|
||||
connect(artists_request_.get(), SIGNAL(Results(const int, const SongList&, const QString&)), SLOT(ArtistsResultsReceived(const int, const SongList&, const QString&)));
|
||||
connect(artists_request_.get(), SIGNAL(UpdateStatus(const int, const QString&)), SLOT(ArtistsUpdateStatusReceived(const int, const QString&)));
|
||||
|
@ -456,7 +462,7 @@ void QobuzService::GetAlbums() {
|
|||
}
|
||||
|
||||
ResetAlbumsRequest();
|
||||
albums_request_.reset(new QobuzRequest(this, url_handler_, network_, QobuzBaseRequest::QueryType_Albums, this));
|
||||
albums_request_.reset(new QobuzRequest(this, url_handler_, app_, network_, QobuzBaseRequest::QueryType_Albums, this));
|
||||
connect(albums_request_.get(), SIGNAL(Results(const int, const SongList&, const QString&)), SLOT(AlbumsResultsReceived(const int, const SongList&, const QString&)));
|
||||
connect(albums_request_.get(), SIGNAL(UpdateStatus(const int, const QString&)), SLOT(AlbumsUpdateStatusReceived(const int, const QString&)));
|
||||
connect(albums_request_.get(), SIGNAL(ProgressSetMaximum(const int, const int)), SLOT(AlbumsProgressSetMaximumReceived(const int, const int)));
|
||||
|
@ -505,7 +511,7 @@ void QobuzService::GetSongs() {
|
|||
}
|
||||
|
||||
ResetSongsRequest();
|
||||
songs_request_.reset(new QobuzRequest(this, url_handler_, network_, QobuzBaseRequest::QueryType_Songs, this));
|
||||
songs_request_.reset(new QobuzRequest(this, url_handler_, app_, network_, QobuzBaseRequest::QueryType_Songs, this));
|
||||
connect(songs_request_.get(), SIGNAL(Results(const int, const SongList&, const QString&)), SLOT(SongsResultsReceived(const int, const SongList&, const QString&)));
|
||||
connect(songs_request_.get(), SIGNAL(UpdateStatus(const int, const QString&)), SLOT(SongsUpdateStatusReceived(const int, const QString&)));
|
||||
connect(songs_request_.get(), SIGNAL(ProgressSetMaximum(const int, const int)), SLOT(SongsProgressSetMaximumReceived(const int, const int)));
|
||||
|
@ -586,7 +592,7 @@ void QobuzService::SendSearch() {
|
|||
return;
|
||||
}
|
||||
|
||||
search_request_.reset(new QobuzRequest(this, url_handler_, network_, type, this));
|
||||
search_request_.reset(new QobuzRequest(this, url_handler_, app_, network_, type, this));
|
||||
|
||||
connect(search_request_.get(), SIGNAL(Results(const int, const SongList&, const QString&)), SLOT(SearchResultsReceived(const int, const SongList&, const QString&)));
|
||||
connect(search_request_.get(), SIGNAL(UpdateStatus(const int, const QString&)), SIGNAL(SearchUpdateStatus(const int, const QString&)));
|
||||
|
@ -631,14 +637,20 @@ void QobuzService::HandleStreamURLFinished(const QUrl &original_url, const QUrl
|
|||
|
||||
}
|
||||
|
||||
QString QobuzService::LoginError(QString error, QVariant debug) {
|
||||
void QobuzService::LoginError(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
if (!error.isEmpty()) login_errors_ << error;
|
||||
|
||||
QString error_html;
|
||||
for (const QString &error : login_errors_) {
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
error_html += error + "<br />";
|
||||
}
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
emit LoginFailure(error);
|
||||
emit LoginComplete(false, error);
|
||||
emit LoginFailure(error_html);
|
||||
emit LoginComplete(false, error_html);
|
||||
|
||||
return error;
|
||||
login_errors_.clear();
|
||||
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ class QobuzService : public InternetService {
|
|||
static const Song::Source kSource;
|
||||
|
||||
void ReloadSettings();
|
||||
QString CoverCacheDir();
|
||||
|
||||
void Logout();
|
||||
int Search(const QString &query, InternetSearch::SearchType type);
|
||||
|
@ -69,6 +68,7 @@ class QobuzService : public InternetService {
|
|||
|
||||
const int max_login_attempts() { return kLoginAttempts; }
|
||||
|
||||
Application *app() { return app_; }
|
||||
QString app_id() { return app_id_; }
|
||||
QString app_secret() { return app_secret_; }
|
||||
QString username() { return username_; }
|
||||
|
@ -124,6 +124,7 @@ class QobuzService : public InternetService {
|
|||
|
||||
private slots:
|
||||
void SendLogin();
|
||||
void HandleLoginSSLErrors(QList<QSslError> ssl_errors);
|
||||
void HandleAuthReply(QNetworkReply *reply);
|
||||
void ResetLoginAttempts();
|
||||
void StartSearch();
|
||||
|
@ -150,7 +151,7 @@ class QobuzService : public InternetService {
|
|||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
void SendSearch();
|
||||
QString LoginError(QString error, QVariant debug = QVariant());
|
||||
void LoginError(const QString &error = QString(), const QVariant &debug = QVariant());
|
||||
|
||||
static const char *kAuthUrl;
|
||||
static const int kLoginAttempts;
|
||||
|
@ -214,6 +215,8 @@ class QobuzService : public InternetService {
|
|||
|
||||
QList<QobuzStreamURLRequest*> stream_url_requests_;
|
||||
|
||||
QStringList login_errors_;
|
||||
|
||||
};
|
||||
|
||||
#endif // QOBUZSERVICE_H
|
||||
|
|
|
@ -67,7 +67,7 @@ void QobuzStreamURLRequest::LoginComplete(bool success, QString error) {
|
|||
need_login_ = false;
|
||||
|
||||
if (!success) {
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -150,42 +150,40 @@ void QobuzStreamURLRequest::StreamURLReceived() {
|
|||
disconnect(reply_, 0, nullptr, 0);
|
||||
reply_->deleteLater();
|
||||
|
||||
QString error;
|
||||
|
||||
QByteArray data = GetReplyData(reply_, error);
|
||||
QByteArray data = GetReplyData(reply_);
|
||||
if (data.isEmpty()) {
|
||||
reply_ = nullptr;
|
||||
if (!authenticated() && login_sent() && tries_ <= 1) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
reply_ = nullptr;
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("track_id")) {
|
||||
error = Error("Invalid Json reply, stream url is missing track_id.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error("Invalid Json reply, stream url is missing track_id.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
int track_id = json_obj["track_id"].toInt();
|
||||
if (track_id != song_id_) {
|
||||
error = Error("Incorrect track ID returned.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error("Incorrect track ID returned.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("mime_type") || !json_obj.contains("url")) {
|
||||
error = Error("Invalid Json reply, stream url is missing url or mime_type.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error("Invalid Json reply, stream url is missing url or mime_type.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -204,8 +202,8 @@ void QobuzStreamURLRequest::StreamURLReceived() {
|
|||
}
|
||||
|
||||
if (!url.isValid()) {
|
||||
error = Error("Returned stream url is invalid.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, filetype, -1, -1, -1, error);
|
||||
Error("Returned stream url is invalid.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, filetype, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -225,3 +223,14 @@ void QobuzStreamURLRequest::StreamURLReceived() {
|
|||
emit StreamURLFinished(original_url_, url, filetype, samplerate, bit_depth, duration);
|
||||
|
||||
}
|
||||
|
||||
void QobuzStreamURLRequest::Error(const QString &error, const QVariant &debug) {
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
errors_ << error;
|
||||
}
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/song.h"
|
||||
|
@ -57,12 +59,15 @@ class QobuzStreamURLRequest : public QobuzBaseRequest {
|
|||
void StreamURLReceived();
|
||||
|
||||
private:
|
||||
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||
|
||||
QobuzService *service_;
|
||||
QNetworkReply *reply_;
|
||||
QUrl original_url_;
|
||||
int song_id_;
|
||||
int tries_;
|
||||
bool need_login_;
|
||||
QStringList errors_;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -197,31 +197,32 @@ void ListenBrainzScrobbler::AuthenticateReplyFinished(QNetworkReply *reply) {
|
|||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
AuthError(failure_reason);
|
||||
AuthError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" and "error_description" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error") && json_obj.contains("error_description")) {
|
||||
QString error = json_obj["error"].toString();
|
||||
failure_reason = json_obj["error_description"].toString();
|
||||
QString error_code = json_obj["error"].toString();
|
||||
error = json_obj["error_description"].toString();
|
||||
}
|
||||
}
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
AuthError(failure_reason);
|
||||
AuthError(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -286,40 +287,38 @@ QByteArray ListenBrainzScrobbler::GetReplyData(QNetworkReply *reply) {
|
|||
else {
|
||||
if (reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString error_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(error_reason);
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "code" and "error" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString error_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("code") && json_obj.contains("error")) {
|
||||
int error_code = json_obj["code"].toInt();
|
||||
QString error_message = json_obj["error"].toString();
|
||||
error_reason = QString("%1 (%2)").arg(error_message).arg(error_code);
|
||||
error = QString("%1 (%2)").arg(error_message).arg(error_code);
|
||||
}
|
||||
else {
|
||||
error_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
}
|
||||
else {
|
||||
error_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
if (reply->error() == QNetworkReply::ContentAccessDenied || reply->error() == QNetworkReply::ContentOperationNotPermittedError || reply->error() == QNetworkReply::AuthenticationRequiredError) {
|
||||
// Session is probably expired
|
||||
Logout();
|
||||
Error(error_reason);
|
||||
}
|
||||
else if (reply->error() == QNetworkReply::ContentNotFoundError) { // Ignore this error
|
||||
Error(error_reason);
|
||||
}
|
||||
else { // Fail
|
||||
Error(error_reason);
|
||||
}
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
|
|
@ -224,30 +224,34 @@ void ScrobblingAPI20::AuthenticateReplyFinished(QNetworkReply *reply) {
|
|||
else {
|
||||
if (reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
AuthError(failure_reason);
|
||||
AuthError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" and "message" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error") && json_obj.contains("message")) {
|
||||
int error = json_obj["error"].toInt();
|
||||
int code = json_obj["error"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = "Error: " + QString::number(error) + ": " + message;
|
||||
error = "Error: " + QString::number(code) + ": " + message;
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
AuthError(failure_reason);
|
||||
AuthError(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -348,31 +352,32 @@ QByteArray ScrobblingAPI20::GetReplyData(QNetworkReply *reply) {
|
|||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString error_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(error_reason);
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
QString error;
|
||||
// See if there is Json data containing "error" and "message" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
int error_code = -1;
|
||||
QString error_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error") && json_obj.contains("message")) {
|
||||
error_code = json_obj["error"].toInt();
|
||||
QString error_message = json_obj["message"].toString();
|
||||
error_reason = QString("%1 (%2)").arg(error_message).arg(error_code);
|
||||
}
|
||||
else {
|
||||
error_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("%1 (%2)").arg(error_message).arg(error_code);
|
||||
}
|
||||
}
|
||||
else {
|
||||
error_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
if (reply->error() == QNetworkReply::ContentAccessDenied ||
|
||||
reply->error() == QNetworkReply::ContentOperationNotPermittedError ||
|
||||
|
@ -382,14 +387,8 @@ QByteArray ScrobblingAPI20::GetReplyData(QNetworkReply *reply) {
|
|||
){
|
||||
// Session is probably expired
|
||||
Logout();
|
||||
Error(error_reason);
|
||||
}
|
||||
else if (reply->error() == QNetworkReply::ContentNotFoundError) { // Ignore this error
|
||||
Error(error_reason);
|
||||
}
|
||||
else { // Fail
|
||||
Error(error_reason);
|
||||
}
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ QNetworkReply *SubsonicBaseRequest::CreateGetRequest(const QString &ressource_na
|
|||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandleSSLErrors(QList<QSslError>)));
|
||||
replies_ << reply;
|
||||
|
||||
//qLog(Debug) << "Subsonic: Sending request" << url;
|
||||
|
@ -108,7 +109,15 @@ QNetworkReply *SubsonicBaseRequest::CreateGetRequest(const QString &ressource_na
|
|||
|
||||
}
|
||||
|
||||
QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply, QString &error) {
|
||||
void SubsonicBaseRequest::HandleSSLErrors(QList<QSslError> ssl_errors) {
|
||||
|
||||
for (QSslError &ssl_error : ssl_errors) {
|
||||
Error(ssl_error.errorString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply) {
|
||||
|
||||
if (replies_.contains(reply)) {
|
||||
replies_.removeAll(reply);
|
||||
|
@ -117,26 +126,20 @@ QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply, QString &erro
|
|||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
error = Error(QString("Received HTTP code %1").arg(http_code));
|
||||
}
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QString error;
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("error")) {
|
||||
|
@ -146,15 +149,20 @@ QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply, QString &erro
|
|||
if (!json_obj.isEmpty() && json_obj.contains("code") && json_obj.contains("message")) {
|
||||
int code = json_obj["code"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
error = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
error = Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,40 +170,40 @@ QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply, QString &erro
|
|||
|
||||
}
|
||||
|
||||
QJsonObject SubsonicBaseRequest::ExtractJsonObj(QByteArray &data, QString &error) {
|
||||
QJsonObject SubsonicBaseRequest::ExtractJsonObj(QByteArray &data) {
|
||||
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
|
||||
if (json_error.error != QJsonParseError::NoError) {
|
||||
error = Error("Reply from server missing Json data.", data);
|
||||
Error("Reply from server missing Json data.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
error = Error("Received empty Json document.", data);
|
||||
Error("Received empty Json document.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
error = Error("Json document is not an object.", json_doc);
|
||||
Error("Json document is not an object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
error = Error("Received empty Json object.", json_doc);
|
||||
Error("Received empty Json object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_obj.contains("subsonic-response")) {
|
||||
error = Error("Json reply is missing subsonic-response.", json_obj);
|
||||
Error("Json reply is missing subsonic-response.", json_obj);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonValue json_response = json_obj["subsonic-response"];
|
||||
if (!json_response.isObject()) {
|
||||
error = Error("Json response is not an object.", json_response);
|
||||
Error("Json response is not an object.", json_response);
|
||||
return QJsonObject();
|
||||
}
|
||||
json_obj = json_response.toObject();
|
||||
|
@ -204,11 +212,12 @@ QJsonObject SubsonicBaseRequest::ExtractJsonObj(QByteArray &data, QString &error
|
|||
|
||||
}
|
||||
|
||||
QString SubsonicBaseRequest::Error(QString error, QVariant debug) {
|
||||
QString SubsonicBaseRequest::ErrorsToHTML(const QStringList &errors) {
|
||||
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
return error;
|
||||
QString error_html;
|
||||
for (const QString &error : errors) {
|
||||
error_html += error + "<br />";
|
||||
}
|
||||
return error_html;
|
||||
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
#include "internet/internetsearch.h"
|
||||
#include "subsonicservice.h"
|
||||
|
||||
class Application;
|
||||
class NetworkAccessManager;
|
||||
class SubsonicUrlHandler;
|
||||
class CollectionBackend;
|
||||
|
@ -61,10 +60,11 @@ class SubsonicBaseRequest : public QObject {
|
|||
|
||||
QUrl CreateUrl(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
||||
QNetworkReply *CreateGetRequest(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||
QByteArray GetReplyData(QNetworkReply *reply);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data);
|
||||
|
||||
virtual QString Error(QString error, QVariant debug = QVariant());
|
||||
virtual void Error(const QString &error, const QVariant &debug = QVariant()) = 0;
|
||||
QString ErrorsToHTML(const QStringList &errors);
|
||||
|
||||
QString client_name() { return service_->client_name(); }
|
||||
QString api_version() { return service_->api_version(); }
|
||||
|
@ -74,6 +74,9 @@ class SubsonicBaseRequest : public QObject {
|
|||
bool verify_certificate() { return service_->verify_certificate(); }
|
||||
bool download_album_covers() { return service_->download_album_covers(); }
|
||||
|
||||
private slots:
|
||||
void HandleSSLErrors(QList<QSslError> ssl_errors);
|
||||
|
||||
private:
|
||||
|
||||
SubsonicService *service_;
|
||||
|
|
|
@ -28,18 +28,20 @@
|
|||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslError>
|
||||
#include <QSslConfiguration>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "organise/organiseformat.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "subsonicservice.h"
|
||||
#include "subsonicurlhandler.h"
|
||||
#include "subsonicrequest.h"
|
||||
|
@ -48,10 +50,11 @@ const int SubsonicRequest::kMaxConcurrentAlbumsRequests = 3;
|
|||
const int SubsonicRequest::kMaxConcurrentAlbumSongsRequests = 3;
|
||||
const int SubsonicRequest::kMaxConcurrentAlbumCoverRequests = 1;
|
||||
|
||||
SubsonicRequest::SubsonicRequest(SubsonicService *service, SubsonicUrlHandler *url_handler, NetworkAccessManager *network, QObject *parent)
|
||||
SubsonicRequest::SubsonicRequest(SubsonicService *service, SubsonicUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QObject *parent)
|
||||
: SubsonicBaseRequest(service, network, parent),
|
||||
service_(service),
|
||||
url_handler_(url_handler),
|
||||
app_(app),
|
||||
network_(network),
|
||||
finished_(false),
|
||||
albums_requests_active_(0),
|
||||
|
@ -141,8 +144,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
|||
|
||||
--albums_requests_active_;
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
|
||||
if (finished_) return;
|
||||
|
||||
|
@ -151,7 +153,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
return;
|
||||
|
@ -180,7 +182,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
|||
}
|
||||
|
||||
if (!json_obj.contains("albumList") && !json_obj.contains("albumList2")) {
|
||||
error = Error("Json reply is missing albumList.", json_obj);
|
||||
Error("Json reply is missing albumList.", json_obj);
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
return;
|
||||
}
|
||||
|
@ -189,7 +191,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
|||
else if (json_obj.contains("albumList2")) json_albumlist = json_obj["albumList2"];
|
||||
|
||||
if (!json_albumlist.isObject()) {
|
||||
error = Error("Json album list is not an object.", json_albumlist);
|
||||
Error("Json album list is not an object.", json_albumlist);
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
}
|
||||
json_obj = json_albumlist.toObject();
|
||||
|
@ -200,7 +202,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
|||
}
|
||||
|
||||
if (!json_obj.contains("album")) {
|
||||
error = Error("Json album list does not contain album array.", json_obj);
|
||||
Error("Json album list does not contain album array.", json_obj);
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
}
|
||||
QJsonValue json_album = json_obj["album"];
|
||||
|
@ -210,7 +212,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
|||
return;
|
||||
}
|
||||
if (!json_album.isArray()) {
|
||||
error = Error("Json album is not an array.", json_album);
|
||||
Error("Json album is not an array.", json_album);
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
}
|
||||
QJsonArray json_albums = json_album.toArray();
|
||||
|
@ -329,8 +331,7 @@ void SubsonicRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64
|
|||
|
||||
emit UpdateProgress(album_songs_received_);
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
|
||||
if (finished_) return;
|
||||
|
||||
|
@ -339,7 +340,7 @@ void SubsonicRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
|
@ -367,27 +368,27 @@ void SubsonicRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64
|
|||
}
|
||||
|
||||
if (!json_obj.contains("album")) {
|
||||
error = Error("Json reply is missing albumList.", json_obj);
|
||||
Error("Json reply is missing albumList.", json_obj);
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
}
|
||||
QJsonValue json_album = json_obj["album"];
|
||||
|
||||
if (!json_album.isObject()) {
|
||||
error = Error("Json album is not an object.", json_album);
|
||||
Error("Json album is not an object.", json_album);
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
}
|
||||
QJsonObject json_album_obj = json_album.toObject();
|
||||
|
||||
if (!json_album_obj.contains("song")) {
|
||||
error = Error("Json album object does not contain song array.", json_obj);
|
||||
Error("Json album object does not contain song array.", json_obj);
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
}
|
||||
QJsonValue json_song = json_album_obj["song"];
|
||||
if (!json_song.isArray()) {
|
||||
error = Error("Json song is not an array.", json_album_obj);
|
||||
Error("Json song is not an array.", json_album_obj);
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
}
|
||||
|
@ -531,7 +532,7 @@ int SubsonicRequest::ParseSong(Song &song, const QJsonObject &json_obj, const qi
|
|||
if (year > 0) song.set_year(year);
|
||||
song.set_url(url);
|
||||
song.set_length_nanosec(duration);
|
||||
if (cover_url.isValid()) song.set_art_automatic(cover_url.toEncoded());
|
||||
if (cover_url.isValid()) song.set_art_automatic(cover_url);
|
||||
song.set_genre(genre);
|
||||
song.set_directory_id(0);
|
||||
song.set_filetype(filetype);
|
||||
|
@ -561,56 +562,27 @@ void SubsonicRequest::GetAlbumCovers() {
|
|||
|
||||
void SubsonicRequest::AddAlbumCoverRequest(Song &song) {
|
||||
|
||||
QUrl url(song.art_automatic());
|
||||
if (!url.isValid()) return;
|
||||
QUrl cover_url(song.art_automatic());
|
||||
if (!cover_url.isValid()) return;
|
||||
|
||||
if (album_covers_requests_sent_.contains(song.album_id())) {
|
||||
album_covers_requests_sent_.insertMulti(song.album_id(), &song);
|
||||
return;
|
||||
}
|
||||
|
||||
AlbumCoverRequest request;
|
||||
request.album_id = song.album_id();
|
||||
request.url = cover_url;
|
||||
request.filename = app_->album_cover_loader()->CoverFilePath(song.source(), song.effective_albumartist(), song.effective_album(), song.album_id(), QString(), cover_url);
|
||||
if (request.filename.isEmpty()) return;
|
||||
|
||||
album_covers_requests_sent_.insertMulti(song.album_id(), &song);
|
||||
++album_covers_requested_;
|
||||
|
||||
AlbumCoverRequest request;
|
||||
request.album_id = song.album_id();
|
||||
request.url = url;
|
||||
request.filename = AlbumCoverFileName(song);
|
||||
|
||||
album_cover_requests_queue_.enqueue(request);
|
||||
|
||||
}
|
||||
|
||||
QString SubsonicRequest::AlbumCoverFileName(const Song &song) {
|
||||
|
||||
QString artist = song.effective_albumartist();
|
||||
QString album = song.effective_album();
|
||||
QString title = song.title();
|
||||
|
||||
artist.remove('/');
|
||||
album.remove('/');
|
||||
title.remove('/');
|
||||
|
||||
QString filename = artist + "-" + album + ".jpg";
|
||||
filename = filename.toLower();
|
||||
filename.replace(' ', '-');
|
||||
filename.replace("--", "-");
|
||||
filename.replace(230, "ae");
|
||||
filename.replace(198, "AE");
|
||||
filename.replace(246, 'o');
|
||||
filename.replace(248, 'o');
|
||||
filename.replace(214, 'O');
|
||||
filename.replace(216, 'O');
|
||||
filename.replace(228, 'a');
|
||||
filename.replace(229, 'a');
|
||||
filename.replace(196, 'A');
|
||||
filename.replace(197, 'A');
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
|
||||
return filename;
|
||||
|
||||
}
|
||||
|
||||
void SubsonicRequest::FlushAlbumCoverRequests() {
|
||||
|
||||
while (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests) {
|
||||
|
@ -657,9 +629,8 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &al
|
|||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
AlbumCoverFinishCheck();
|
||||
return;
|
||||
|
@ -667,7 +638,7 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &al
|
|||
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
error = Error(QString("Received empty image data for %1").arg(url.toString()));
|
||||
Error(QString("Received empty image data for %1").arg(url.toString()));
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
AlbumCoverFinishCheck();
|
||||
return;
|
||||
|
@ -675,22 +646,19 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &al
|
|||
|
||||
QImage image;
|
||||
if (image.loadFromData(data)) {
|
||||
|
||||
QDir dir;
|
||||
if (dir.mkpath(service_->CoverCacheDir())) {
|
||||
QString filepath(service_->CoverCacheDir() + "/" + filename);
|
||||
if (image.save(filepath, "JPG")) {
|
||||
while (album_covers_requests_sent_.contains(album_id)) {
|
||||
Song *song = album_covers_requests_sent_.take(album_id);
|
||||
song->set_art_automatic(filepath);
|
||||
}
|
||||
if (image.save(filename, "JPG")) {
|
||||
while (album_covers_requests_sent_.contains(album_id)) {
|
||||
Song *song = album_covers_requests_sent_.take(album_id);
|
||||
QUrl cover_url;
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(filename);
|
||||
song->set_art_automatic(cover_url);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
error = Error(QString("Error decoding image data from %1").arg(url.toString()));
|
||||
Error(QString("Error decoding image data from %1").arg(url.toString()));
|
||||
}
|
||||
|
||||
AlbumCoverFinishCheck();
|
||||
|
@ -729,29 +697,26 @@ void SubsonicRequest::FinishCheck() {
|
|||
if (songs_.isEmpty() && errors_.isEmpty())
|
||||
emit Results(songs_, tr("Unknown error"));
|
||||
else
|
||||
emit Results(songs_, errors_);
|
||||
emit Results(songs_, ErrorsToHTML(errors_));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString SubsonicRequest::Error(QString error, QVariant debug) {
|
||||
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
void SubsonicRequest::Error(const QString &error, const QVariant &debug) {
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
errors_ += error;
|
||||
errors_ += "<br />";
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
errors_ << error;
|
||||
}
|
||||
FinishCheck();
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
return error;
|
||||
FinishCheck();
|
||||
|
||||
}
|
||||
|
||||
void SubsonicRequest::Warn(QString error, QVariant debug) {
|
||||
void SubsonicRequest::Warn(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
|
|
@ -27,19 +27,18 @@
|
|||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "subsonicbaserequest.h"
|
||||
|
||||
class Application;
|
||||
class NetworkAccessManager;
|
||||
class SubsonicService;
|
||||
class SubsonicUrlHandler;
|
||||
|
@ -49,7 +48,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
|||
|
||||
public:
|
||||
|
||||
SubsonicRequest(SubsonicService *service, SubsonicUrlHandler *url_handler, NetworkAccessManager *network, QObject *parent);
|
||||
SubsonicRequest(SubsonicService *service, SubsonicUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QObject *parent);
|
||||
~SubsonicRequest();
|
||||
|
||||
void ReloadSettings();
|
||||
|
@ -64,7 +63,6 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
|||
void UpdateProgress(const int max);
|
||||
|
||||
private slots:
|
||||
|
||||
void AlbumsReplyReceived(QNetworkReply *reply, const int offset_requested);
|
||||
void AlbumSongsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const qint64 album_id, const QString &album_artist);
|
||||
void AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url, const QString &filename);
|
||||
|
@ -95,7 +93,6 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
|||
void SongsFinishCheck();
|
||||
|
||||
void AddAlbumSongsRequest(const qint64 artist_id, const qint64 album_id, const QString &album_artist, const int offset = 0);
|
||||
QString AlbumCoverFileName(const Song &song);
|
||||
void FlushAlbumSongsRequests();
|
||||
|
||||
int ParseSong(Song &song, const QJsonObject &json_obj, const qint64 artist_id_requested = 0, const qint64 album_id_requested = 0, const QString &album_artist = QString());
|
||||
|
@ -106,8 +103,8 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
|||
void AlbumCoverFinishCheck();
|
||||
|
||||
void FinishCheck();
|
||||
void Warn(QString error, QVariant debug = QVariant());
|
||||
QString Error(QString error, QVariant debug = QVariant());
|
||||
void Warn(const QString &error, const QVariant &debug = QVariant());
|
||||
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||
|
||||
static const int kMaxConcurrentAlbumsRequests;
|
||||
static const int kMaxConcurrentArtistAlbumsRequests;
|
||||
|
@ -116,6 +113,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
|||
|
||||
SubsonicService *service_;
|
||||
SubsonicUrlHandler *url_handler_;
|
||||
Application *app_;
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
bool finished_;
|
||||
|
@ -138,7 +136,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
|||
int album_covers_received_;
|
||||
|
||||
SongList songs_;
|
||||
QString errors_;
|
||||
QStringList errors_;
|
||||
bool no_results_;
|
||||
QList<QNetworkReply*> album_cover_replies_;
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QByteArray>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
|
@ -32,6 +31,7 @@
|
|||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslError>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
@ -118,10 +118,6 @@ void SubsonicService::ReloadSettings() {
|
|||
|
||||
}
|
||||
|
||||
QString SubsonicService::CoverCacheDir() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers";
|
||||
}
|
||||
|
||||
void SubsonicService::SendPing() {
|
||||
SendPing(server_url_, username_, password_);
|
||||
}
|
||||
|
@ -158,19 +154,29 @@ void SubsonicService::SendPing(QUrl url, const QString &username, const QString
|
|||
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
errors_.clear();
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandlePingSSLErrors(QList<QSslError>)));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandlePingReply(QNetworkReply*)), reply);
|
||||
|
||||
//qLog(Debug) << "Subsonic: Sending request" << url << query;
|
||||
|
||||
}
|
||||
|
||||
void SubsonicService::HandlePingSSLErrors(QList<QSslError> ssl_errors) {
|
||||
|
||||
for (QSslError &ssl_error : ssl_errors) {
|
||||
errors_ += ssl_error.errorString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SubsonicService::HandlePingReply(QNetworkReply *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError || reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
PingError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
return;
|
||||
|
@ -180,7 +186,6 @@ void SubsonicService::HandlePingReply(QNetworkReply *reply) {
|
|||
QByteArray data = reply->readAll();
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("error")) {
|
||||
|
@ -190,24 +195,25 @@ void SubsonicService::HandlePingReply(QNetworkReply *reply) {
|
|||
if (!json_obj.isEmpty() && json_obj.contains("code") && json_obj.contains("message")) {
|
||||
int code = json_obj["code"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
errors_ << QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (errors_.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
errors_ << QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
errors_ << QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
PingError(failure_reason);
|
||||
PingError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code != 200) {
|
||||
PingError(QString("Received HTTP code %1").arg(http_code));
|
||||
return;
|
||||
}
|
||||
errors_.clear();
|
||||
|
||||
QByteArray data(reply->readAll());
|
||||
|
||||
|
@ -330,7 +336,7 @@ void SubsonicService::GetSongs() {
|
|||
}
|
||||
|
||||
ResetSongsRequest();
|
||||
songs_request_.reset(new SubsonicRequest(this, url_handler_, network_, this));
|
||||
songs_request_.reset(new SubsonicRequest(this, url_handler_, app_, network_, this));
|
||||
connect(songs_request_.get(), SIGNAL(Results(const SongList&, const QString&)), SLOT(SongsResultsReceived(const SongList&, const QString&)));
|
||||
connect(songs_request_.get(), SIGNAL(UpdateStatus(const QString&)), SIGNAL(SongsUpdateStatus(const QString&)));
|
||||
connect(songs_request_.get(), SIGNAL(ProgressSetMaximum(const int)), SIGNAL(SongsProgressSetMaximum(const int)));
|
||||
|
@ -346,14 +352,20 @@ void SubsonicService::SongsResultsReceived(const SongList &songs, const QString
|
|||
|
||||
}
|
||||
|
||||
QString SubsonicService::PingError(QString error, QVariant debug) {
|
||||
void SubsonicService::PingError(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
if (!error.isEmpty()) errors_ << error;
|
||||
|
||||
QString error_html;
|
||||
for (const QString &error : errors_) {
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
error_html += error + "<br />";
|
||||
}
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
emit TestFailure(error);
|
||||
emit TestComplete(false, error);
|
||||
emit TestFailure(error_html);
|
||||
emit TestComplete(false, error_html);
|
||||
|
||||
return error;
|
||||
errors_.clear();
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
|
@ -59,7 +60,8 @@ class SubsonicService : public InternetService {
|
|||
static const Song::Source kSource;
|
||||
|
||||
void ReloadSettings();
|
||||
QString CoverCacheDir();
|
||||
|
||||
Application *app() { return app_; }
|
||||
|
||||
QString client_name() { return kClientName; }
|
||||
QString api_version() { return kApiVersion; }
|
||||
|
@ -89,6 +91,8 @@ class SubsonicService : public InternetService {
|
|||
void ResetSongsRequest();
|
||||
|
||||
private slots:
|
||||
//void HandlePingSSLErrors(QNetworkReply *reply, QList<QSslError> ssl_errors);
|
||||
void HandlePingSSLErrors(QList<QSslError> ssl_errors);
|
||||
void HandlePingReply(QNetworkReply *reply);
|
||||
void SongsResultsReceived(const SongList &songs, const QString &error);
|
||||
|
||||
|
@ -99,7 +103,7 @@ class SubsonicService : public InternetService {
|
|||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
QString PingError(QString error, QVariant debug = QVariant());
|
||||
void PingError(const QString &error = QString(), const QVariant &debug = QVariant());
|
||||
|
||||
static const char *kClientName;
|
||||
static const char *kApiVersion;
|
||||
|
@ -122,6 +126,8 @@ class SubsonicService : public InternetService {
|
|||
bool verify_certificate_;
|
||||
bool download_album_covers_;
|
||||
|
||||
QStringList errors_;
|
||||
|
||||
};
|
||||
|
||||
#endif // SUBSONICSERVICE_H
|
||||
|
|
|
@ -65,3 +65,4 @@ UrlHandler::LoadResult SubsonicUrlHandler::StartLoading(const QUrl &url) {
|
|||
return LoadResult(url, LoadResult::TrackAvailable, media_url);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ class SubsonicUrlHandler : public UrlHandler {
|
|||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
void Error(const QString &error, const QVariant &debug) {}
|
||||
|
||||
SubsonicService *service_;
|
||||
|
||||
};
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslError>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
@ -39,7 +40,8 @@
|
|||
#include "tidalservice.h"
|
||||
#include "tidalbaserequest.h"
|
||||
|
||||
const char *TidalBaseRequest::kApiUrl = "https://api.tidalhifi.com/v1";
|
||||
//const char *TidalBaseRequest::kApiUrl = "https://api.tidalhifi.com/v1";
|
||||
const char *TidalBaseRequest::kApiUrl = "https://192.168.1.112";
|
||||
|
||||
TidalBaseRequest::TidalBaseRequest(TidalService *service, NetworkAccessManager *network, QObject *parent) :
|
||||
QObject(parent),
|
||||
|
@ -77,6 +79,7 @@ QNetworkReply *TidalBaseRequest::CreateRequest(const QString &ressource_name, co
|
|||
if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
|
||||
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandleSSLErrors(QList<QSslError>)));
|
||||
replies_ << reply;
|
||||
|
||||
//qLog(Debug) << "Tidal: Sending request" << url;
|
||||
|
@ -85,7 +88,15 @@ QNetworkReply *TidalBaseRequest::CreateRequest(const QString &ressource_name, co
|
|||
|
||||
}
|
||||
|
||||
QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, QString &error, const bool send_login) {
|
||||
void TidalBaseRequest::HandleSSLErrors(QList<QSslError> ssl_errors) {
|
||||
|
||||
for (QSslError &ssl_error : ssl_errors) {
|
||||
Error(ssl_error.errorString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, const bool send_login) {
|
||||
|
||||
if (replies_.contains(reply)) {
|
||||
replies_.removeAll(reply);
|
||||
|
@ -94,54 +105,51 @@ QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, QString &error,
|
|||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
error = Error(QString("Received HTTP code %1").arg(http_code));
|
||||
}
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "userMessage" - then use that instead.
|
||||
// See if there is Json data containing "status" and "userMessage" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
int status = 0;
|
||||
int sub_status = 0;
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("userMessage")) {
|
||||
status = json_obj["status"].toInt();
|
||||
sub_status = json_obj["subStatus"].toInt();
|
||||
QString user_message = json_obj["userMessage"].toString();
|
||||
failure_reason = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
error = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
if (status == 401 && sub_status == 6001) { // User does not have a valid session
|
||||
emit service_->Logout();
|
||||
if (!oauth() && send_login && login_attempts() < max_login_attempts() && !api_token().isEmpty() && !username().isEmpty() && !password().isEmpty()) {
|
||||
qLog(Error) << "Tidal:" << failure_reason;
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
qLog(Info) << "Tidal:" << "Attempting to login.";
|
||||
NeedLogin();
|
||||
emit service_->Login();
|
||||
}
|
||||
else {
|
||||
error = Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
}
|
||||
else { // Fail
|
||||
error = Error(failure_reason);
|
||||
else {
|
||||
Error(error);
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
|
@ -151,29 +159,29 @@ QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, QString &error,
|
|||
|
||||
}
|
||||
|
||||
QJsonObject TidalBaseRequest::ExtractJsonObj(QByteArray &data, QString &error) {
|
||||
QJsonObject TidalBaseRequest::ExtractJsonObj(QByteArray &data) {
|
||||
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
|
||||
if (json_error.error != QJsonParseError::NoError) {
|
||||
error = Error("Reply from server missing Json data.", data);
|
||||
Error("Reply from server missing Json data.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
error = Error("Received empty Json document.", data);
|
||||
Error("Received empty Json document.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
error = Error("Json document is not an object.", json_doc);
|
||||
Error("Json document is not an object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
error = Error("Received empty Json object.", json_doc);
|
||||
Error("Received empty Json object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
|
@ -181,18 +189,18 @@ QJsonObject TidalBaseRequest::ExtractJsonObj(QByteArray &data, QString &error) {
|
|||
|
||||
}
|
||||
|
||||
QJsonValue TidalBaseRequest::ExtractItems(QByteArray &data, QString &error) {
|
||||
QJsonValue TidalBaseRequest::ExtractItems(QByteArray &data) {
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) return QJsonValue();
|
||||
return ExtractItems(json_obj, error);
|
||||
return ExtractItems(json_obj);
|
||||
|
||||
}
|
||||
|
||||
QJsonValue TidalBaseRequest::ExtractItems(QJsonObject &json_obj, QString &error) {
|
||||
QJsonValue TidalBaseRequest::ExtractItems(QJsonObject &json_obj) {
|
||||
|
||||
if (!json_obj.contains("items")) {
|
||||
error = Error("Json reply is missing items.", json_obj);
|
||||
if (!json_obj.contains("items_")) {
|
||||
Error("Json reply is missing items.", json_obj);
|
||||
return QJsonArray();
|
||||
}
|
||||
QJsonValue json_items = json_obj["items"];
|
||||
|
@ -200,11 +208,12 @@ QJsonValue TidalBaseRequest::ExtractItems(QJsonObject &json_obj, QString &error)
|
|||
|
||||
}
|
||||
|
||||
QString TidalBaseRequest::Error(QString error, QVariant debug) {
|
||||
QString TidalBaseRequest::ErrorsToHTML(const QStringList &errors) {
|
||||
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
return error;
|
||||
QString error_html;
|
||||
for (const QString &error : errors) {
|
||||
error_html += error + "<br />";
|
||||
}
|
||||
return error_html;
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslError>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
@ -71,12 +72,13 @@ class TidalBaseRequest : public QObject {
|
|||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error, const bool send_login);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QJsonObject &json_obj, QString &error);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, const bool send_login);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data);
|
||||
QJsonValue ExtractItems(QByteArray &data);
|
||||
QJsonValue ExtractItems(QJsonObject &json_obj);
|
||||
|
||||
virtual QString Error(QString error, QVariant debug = QVariant());
|
||||
virtual void Error(const QString &error, const QVariant &debug = QVariant()) = 0;
|
||||
QString ErrorsToHTML(const QStringList &errors);
|
||||
|
||||
QString api_url() { return QString(kApiUrl); }
|
||||
const bool oauth() { return service_->oauth(); }
|
||||
|
@ -100,6 +102,9 @@ class TidalBaseRequest : public QObject {
|
|||
int login_attempts() { return service_->login_attempts(); }
|
||||
|
||||
virtual void NeedLogin() = 0;
|
||||
|
||||
private slots:
|
||||
void HandleSSLErrors(QList<QSslError> ssl_errors);
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ void TidalFavoriteRequest::AddFavoritesReply(QNetworkReply *reply, const Favorit
|
|||
}
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error, false);
|
||||
QByteArray data = GetReplyData(reply, false);
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
return;
|
||||
|
@ -265,7 +265,7 @@ void TidalFavoriteRequest::RemoveFavoritesReply(QNetworkReply *reply, const Favo
|
|||
}
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error, false);
|
||||
QByteArray data = GetReplyData(reply, false);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
return;
|
||||
}
|
||||
|
@ -285,3 +285,10 @@ void TidalFavoriteRequest::RemoveFavoritesReply(QNetworkReply *reply, const Favo
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
void TidalFavoriteRequest::Error(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,10 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "tidalbaserequest.h"
|
||||
#include "core/song.h"
|
||||
|
@ -69,6 +72,7 @@ class TidalFavoriteRequest : public TidalBaseRequest {
|
|||
void RemoveFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs);
|
||||
|
||||
private:
|
||||
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||
QString FavoriteText(const FavoriteType type);
|
||||
void AddFavorites(const FavoriteType type, const SongList &songs);
|
||||
void RemoveFavorites(const FavoriteType type, const SongList songs);
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
|
@ -35,6 +34,8 @@
|
|||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/application.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "tidalservice.h"
|
||||
#include "tidalurlhandler.h"
|
||||
#include "tidalrequest.h"
|
||||
|
@ -47,10 +48,11 @@ const int TidalRequest::kMaxConcurrentArtistAlbumsRequests = 3;
|
|||
const int TidalRequest::kMaxConcurrentAlbumSongsRequests = 3;
|
||||
const int TidalRequest::kMaxConcurrentAlbumCoverRequests = 1;
|
||||
|
||||
TidalRequest::TidalRequest(TidalService *service, TidalUrlHandler *url_handler, NetworkAccessManager *network, QueryType type, QObject *parent)
|
||||
TidalRequest::TidalRequest(TidalService *service, TidalUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QueryType type, QObject *parent)
|
||||
: TidalBaseRequest(service, network, parent),
|
||||
service_(service),
|
||||
url_handler_(url_handler),
|
||||
app_(app),
|
||||
network_(network),
|
||||
type_(type),
|
||||
fetchalbums_(service->fetchalbums()),
|
||||
|
@ -309,8 +311,7 @@ void TidalRequest::AddSongsSearchRequest(const int offset) {
|
|||
|
||||
void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error, (offset_requested == 0));
|
||||
QByteArray data = GetReplyData(reply, (offset_requested == 0));
|
||||
|
||||
--artists_requests_active_;
|
||||
|
||||
|
@ -321,7 +322,7 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
ArtistsFinishCheck();
|
||||
return;
|
||||
|
@ -359,7 +360,7 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
|
|||
emit UpdateProgress(query_id_, artists_received_);
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(json_obj, error);
|
||||
QJsonValue json_value = ExtractItems(json_obj);
|
||||
if (!json_value.isArray()) {
|
||||
ArtistsFinishCheck();
|
||||
return;
|
||||
|
@ -490,8 +491,7 @@ void TidalRequest::ArtistAlbumsReplyReceived(QNetworkReply *reply, const qint64
|
|||
|
||||
void TidalRequest::AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_requested, const int limit_requested, const int offset_requested, const bool auto_login) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error, auto_login);
|
||||
QByteArray data = GetReplyData(reply, auto_login);
|
||||
|
||||
if (finished_) return;
|
||||
|
||||
|
@ -500,7 +500,7 @@ void TidalRequest::AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_r
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
AlbumsFinishCheck(artist_id_requested);
|
||||
return;
|
||||
|
@ -525,7 +525,7 @@ void TidalRequest::AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_r
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(json_obj, error);
|
||||
QJsonValue json_value = ExtractItems(json_obj);
|
||||
if (!json_value.isArray()) {
|
||||
AlbumsFinishCheck(artist_id_requested);
|
||||
return;
|
||||
|
@ -736,8 +736,7 @@ void TidalRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64 ar
|
|||
|
||||
void TidalRequest::SongsReceived(QNetworkReply *reply, const qint64 artist_id, const qint64 album_id, const int limit_requested, const int offset_requested, const bool auto_login, const QString &album_artist) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error, auto_login);
|
||||
QByteArray data = GetReplyData(reply, auto_login);
|
||||
|
||||
if (finished_) return;
|
||||
|
||||
|
@ -746,7 +745,7 @@ void TidalRequest::SongsReceived(QNetworkReply *reply, const qint64 artist_id, c
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist);
|
||||
return;
|
||||
|
@ -771,7 +770,7 @@ void TidalRequest::SongsReceived(QNetworkReply *reply, const qint64 artist_id, c
|
|||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(json_obj, error);
|
||||
QJsonValue json_value = ExtractItems(json_obj);
|
||||
if (!json_value.isArray()) {
|
||||
SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist);
|
||||
return;
|
||||
|
@ -986,7 +985,7 @@ int TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const qint6
|
|||
song.set_disc(disc);
|
||||
song.set_url(url);
|
||||
song.set_length_nanosec(duration);
|
||||
song.set_art_automatic(cover_url.toEncoded());
|
||||
song.set_art_automatic(cover_url);
|
||||
song.set_comment(copyright);
|
||||
song.set_directory_id(0);
|
||||
song.set_filetype(Song::FileType_Stream);
|
||||
|
@ -1020,12 +1019,14 @@ void TidalRequest::AddAlbumCoverRequest(Song &song) {
|
|||
return;
|
||||
}
|
||||
|
||||
album_covers_requests_sent_.insertMulti(song.album_id(), &song);
|
||||
++album_covers_requested_;
|
||||
|
||||
AlbumCoverRequest request;
|
||||
request.album_id = song.album_id();
|
||||
request.url = QUrl(song.art_automatic());
|
||||
request.filename = app_->album_cover_loader()->CoverFilePath(song.source(), song.effective_albumartist(), song.effective_album(), song.album_id(), QString(), request.url);
|
||||
if (request.filename.isEmpty()) return;
|
||||
|
||||
album_covers_requests_sent_.insertMulti(song.album_id(), &song);
|
||||
++album_covers_requested_;
|
||||
|
||||
album_cover_requests_queue_.enqueue(request);
|
||||
|
||||
|
@ -1041,13 +1042,13 @@ void TidalRequest::FlushAlbumCoverRequests() {
|
|||
QNetworkRequest req(request.url);
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
album_cover_replies_ << reply;
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumCoverReceived(QNetworkReply*, const QString&, const QUrl&)), reply, request.album_id, request.url);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumCoverReceived(QNetworkReply*, const QString&, const QUrl&, const QString&)), reply, request.album_id, request.url, request.filename);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url) {
|
||||
void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url, const QString &filename) {
|
||||
|
||||
if (album_cover_replies_.contains(reply)) {
|
||||
album_cover_replies_.removeAll(reply);
|
||||
|
@ -1070,9 +1071,8 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
|
|||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
AlbumCoverFinishCheck();
|
||||
return;
|
||||
|
@ -1080,7 +1080,7 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
|
|||
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
error = Error(QString("Received empty image data for %1").arg(url.toString()));
|
||||
Error(QString("Received empty image data for %1").arg(url.toString()));
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
AlbumCoverFinishCheck();
|
||||
return;
|
||||
|
@ -1089,21 +1089,20 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
|
|||
QImage image;
|
||||
if (image.loadFromData(data)) {
|
||||
|
||||
QDir dir;
|
||||
if (dir.mkpath(service_->CoverCacheDir())) {
|
||||
QString filename(service_->CoverCacheDir() + "/" + album_id + "-" + url.fileName());
|
||||
if (image.save(filename, "JPG")) {
|
||||
while (album_covers_requests_sent_.contains(album_id)) {
|
||||
Song *song = album_covers_requests_sent_.take(album_id);
|
||||
song->set_art_automatic(filename);
|
||||
}
|
||||
if (image.save(filename, "JPG")) {
|
||||
while (album_covers_requests_sent_.contains(album_id)) {
|
||||
Song *song = album_covers_requests_sent_.take(album_id);
|
||||
QUrl cover_url;
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(filename);
|
||||
song->set_art_automatic(cover_url);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
error = Error(QString("Error decoding image data from %1").arg(url.toString()));
|
||||
Error(QString("Error decoding image data from %1").arg(url.toString()));
|
||||
}
|
||||
|
||||
AlbumCoverFinishCheck();
|
||||
|
@ -1155,24 +1154,22 @@ void TidalRequest::FinishCheck() {
|
|||
if (songs_.isEmpty() && errors_.isEmpty())
|
||||
emit Results(query_id_, songs_, tr("Unknown error"));
|
||||
else
|
||||
emit Results(query_id_, songs_, errors_);
|
||||
emit Results(query_id_, songs_, ErrorsToHTML(errors_));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString TidalRequest::Error(QString error, QVariant debug) {
|
||||
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
void TidalRequest::Error(const QString &error, const QVariant &debug) {
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
errors_ += error;
|
||||
errors_ += "<br />";
|
||||
errors_ << error;
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
}
|
||||
FinishCheck();
|
||||
|
||||
return error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
FinishCheck();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <QMultiMap>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
|
@ -49,7 +50,7 @@ class TidalRequest : public TidalBaseRequest {
|
|||
|
||||
public:
|
||||
|
||||
TidalRequest(TidalService *service, TidalUrlHandler *url_handler, NetworkAccessManager *network, QueryType type, QObject *parent);
|
||||
TidalRequest(TidalService *service, TidalUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QueryType type, QObject *parent);
|
||||
~TidalRequest();
|
||||
|
||||
void ReloadSettings();
|
||||
|
@ -82,7 +83,7 @@ class TidalRequest : public TidalBaseRequest {
|
|||
|
||||
void ArtistAlbumsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const int offset_requested);
|
||||
void AlbumSongsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const qint64 album_id, const int offset_requested, const QString &album_artist);
|
||||
void AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url);
|
||||
void AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url, const QString &filename);
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
|
@ -100,6 +101,7 @@ class TidalRequest : public TidalBaseRequest {
|
|||
qint64 artist_id = 0;
|
||||
QString album_id = 0;
|
||||
QUrl url;
|
||||
QString filename;
|
||||
};
|
||||
|
||||
const bool IsQuery() { return (type_ == QueryType_Artists || type_ == QueryType_Albums || type_ == QueryType_Songs); }
|
||||
|
@ -142,7 +144,7 @@ class TidalRequest : public TidalBaseRequest {
|
|||
|
||||
void FinishCheck();
|
||||
void Warn(QString error, QVariant debug = QVariant());
|
||||
QString Error(QString error, QVariant debug = QVariant());
|
||||
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||
|
||||
static const char *kResourcesUrl;
|
||||
static const int kMaxConcurrentArtistsRequests;
|
||||
|
@ -154,6 +156,7 @@ class TidalRequest : public TidalBaseRequest {
|
|||
|
||||
TidalService *service_;
|
||||
TidalUrlHandler *url_handler_;
|
||||
Application *app_;
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
QueryType type_;
|
||||
|
@ -197,7 +200,7 @@ class TidalRequest : public TidalBaseRequest {
|
|||
int album_covers_received_;
|
||||
|
||||
SongList songs_;
|
||||
QString errors_;
|
||||
QStringList errors_;
|
||||
bool need_login_;
|
||||
bool no_results_;
|
||||
QList<QNetworkReply*> album_cover_replies_;
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QDesktopServices>
|
||||
#include <QCryptographicHash>
|
||||
#include <QByteArray>
|
||||
|
@ -34,6 +33,7 @@
|
|||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslError>
|
||||
#include <QTimer>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
|
@ -228,10 +228,6 @@ void TidalService::ReloadSettings() {
|
|||
|
||||
}
|
||||
|
||||
QString TidalService::CoverCacheDir() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers";
|
||||
}
|
||||
|
||||
void TidalService::StartAuthorisation() {
|
||||
|
||||
login_sent_ = true;
|
||||
|
@ -315,7 +311,10 @@ void TidalService::AuthorisationUrlReceived(const QUrl &url) {
|
|||
QUrl url(kOAuthAccessTokenUrl);
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
|
||||
|
||||
login_errors_.clear();
|
||||
QNetworkReply *reply = network_->post(request, query);
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandleLoginSSLErrors(QList<QSslError>)));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AccessTokenRequestFinished(QNetworkReply*)), reply);
|
||||
|
||||
}
|
||||
|
@ -328,47 +327,53 @@ void TidalService::AuthorisationUrlReceived(const QUrl &url) {
|
|||
|
||||
}
|
||||
|
||||
void TidalService::HandleLoginSSLErrors(QList<QSslError> ssl_errors) {
|
||||
|
||||
for (QSslError &ssl_error : ssl_errors) {
|
||||
login_errors_ += ssl_error.errorString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TidalService::AccessTokenRequestFinished(QNetworkReply *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
login_sent_ = false;
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError || reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
LoginError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "redirectUri" then use that instead.
|
||||
// See if there is Json data containing "status" and "userMessage" then use that instead.
|
||||
QByteArray data(reply->readAll());
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
QString failure_reason;
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("userMessage")) {
|
||||
int status = json_obj["status"].toInt();
|
||||
int sub_status = json_obj["subStatus"].toInt();
|
||||
QString user_message = json_obj["userMessage"].toString();
|
||||
failure_reason = QString("Authentication failure: %1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
login_errors_ << QString("Authentication failure: %1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (login_errors_.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
login_errors_ << QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
login_errors_ << QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
LoginError(failure_reason);
|
||||
LoginError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code != 200) {
|
||||
LoginError(QString("Received HTTP code %1").arg(http_code));
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data(reply->readAll());
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
|
@ -474,6 +479,7 @@ void TidalService::SendLogin(const QString &api_token, const QString &username,
|
|||
|
||||
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
|
||||
QNetworkReply *reply = network_->post(req, query);
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandleLoginSSLErrors(QList<QSslError>)));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply);
|
||||
|
||||
//qLog(Debug) << "Tidal: Sending request" << url << query;
|
||||
|
@ -486,10 +492,11 @@ void TidalService::HandleAuthReply(QNetworkReply *reply) {
|
|||
|
||||
login_sent_ = false;
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError || reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
LoginError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
login_errors_.clear();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
|
@ -497,24 +504,31 @@ void TidalService::HandleAuthReply(QNetworkReply *reply) {
|
|||
QByteArray data(reply->readAll());
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
QString failure_reason;
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("userMessage")) {
|
||||
int status = json_obj["status"].toInt();
|
||||
int sub_status = json_obj["subStatus"].toInt();
|
||||
QString user_message = json_obj["userMessage"].toString();
|
||||
failure_reason = QString("Authentication failure: %1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
login_errors_ << QString("Authentication failure: %1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (login_errors_.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
login_errors_ << QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
login_errors_ << QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
LoginError(failure_reason);
|
||||
LoginError();
|
||||
login_errors_.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
login_errors_.clear();
|
||||
|
||||
QByteArray data(reply->readAll());
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
|
@ -645,7 +659,7 @@ void TidalService::GetArtists() {
|
|||
|
||||
ResetArtistsRequest();
|
||||
|
||||
artists_request_.reset(new TidalRequest(this, url_handler_, network_, TidalBaseRequest::QueryType_Artists, this));
|
||||
artists_request_.reset(new TidalRequest(this, url_handler_, app_, network_, TidalBaseRequest::QueryType_Artists, this));
|
||||
|
||||
connect(artists_request_.get(), SIGNAL(Results(const int, const SongList&, const QString&)), SLOT(ArtistsResultsReceived(const int, const SongList&, const QString&)));
|
||||
connect(artists_request_.get(), SIGNAL(UpdateStatus(const int, const QString&)), SLOT(ArtistsUpdateStatusReceived(const int, const QString&)));
|
||||
|
@ -699,7 +713,7 @@ void TidalService::GetAlbums() {
|
|||
}
|
||||
|
||||
ResetAlbumsRequest();
|
||||
albums_request_.reset(new TidalRequest(this, url_handler_, network_, TidalBaseRequest::QueryType_Albums, this));
|
||||
albums_request_.reset(new TidalRequest(this, url_handler_, app_, network_, TidalBaseRequest::QueryType_Albums, this));
|
||||
connect(albums_request_.get(), SIGNAL(Results(const int, const SongList&, const QString&)), SLOT(AlbumsResultsReceived(const int, const SongList&, const QString&)));
|
||||
connect(albums_request_.get(), SIGNAL(UpdateStatus(const int, const QString&)), SLOT(AlbumsUpdateStatusReceived(const int, const QString&)));
|
||||
connect(albums_request_.get(), SIGNAL(ProgressSetMaximum(const int, const int)), SLOT(AlbumsProgressSetMaximumReceived(const int, const int)));
|
||||
|
@ -752,7 +766,7 @@ void TidalService::GetSongs() {
|
|||
}
|
||||
|
||||
ResetSongsRequest();
|
||||
songs_request_.reset(new TidalRequest(this, url_handler_, network_, TidalBaseRequest::QueryType_Songs, this));
|
||||
songs_request_.reset(new TidalRequest(this, url_handler_, app_, network_, TidalBaseRequest::QueryType_Songs, this));
|
||||
connect(songs_request_.get(), SIGNAL(Results(const int, const SongList&, const QString&)), SLOT(SongsResultsReceived(const int, const SongList&, const QString&)));
|
||||
connect(songs_request_.get(), SIGNAL(UpdateStatus(const int, const QString&)), SLOT(SongsUpdateStatusReceived(const int, const QString&)));
|
||||
connect(songs_request_.get(), SIGNAL(ProgressSetMaximum(const int, const int)), SLOT(SongsProgressSetMaximumReceived(const int, const int)));
|
||||
|
@ -842,7 +856,7 @@ void TidalService::SendSearch() {
|
|||
return;
|
||||
}
|
||||
|
||||
search_request_.reset(new TidalRequest(this, url_handler_, network_, type, this));
|
||||
search_request_.reset(new TidalRequest(this, url_handler_, app_, network_, type, this));
|
||||
|
||||
connect(search_request_.get(), SIGNAL(Results(const int, const SongList&, const QString&)), SLOT(SearchResultsReceived(const int, const SongList&, const QString&)));
|
||||
connect(search_request_.get(), SIGNAL(UpdateStatus(const int, const QString&)), SIGNAL(SearchUpdateStatus(const int, const QString&)));
|
||||
|
@ -894,14 +908,20 @@ void TidalService::HandleStreamURLFinished(const QUrl &original_url, const QUrl
|
|||
|
||||
}
|
||||
|
||||
QString TidalService::LoginError(QString error, QVariant debug) {
|
||||
void TidalService::LoginError(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
if (!error.isEmpty()) login_errors_ << error;
|
||||
|
||||
QString error_html;
|
||||
for (const QString &error : login_errors_) {
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
error_html += error + "<br />";
|
||||
}
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
emit LoginFailure(error);
|
||||
emit LoginComplete(false, error);
|
||||
emit LoginFailure(error_html);
|
||||
emit LoginComplete(false, error_html);
|
||||
|
||||
return error;
|
||||
login_errors_.clear();
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
|
@ -61,7 +62,6 @@ class TidalService : public InternetService {
|
|||
static const Song::Source kSource;
|
||||
|
||||
void ReloadSettings();
|
||||
QString CoverCacheDir();
|
||||
|
||||
void Logout();
|
||||
int Search(const QString &query, InternetSearch::SearchType type);
|
||||
|
@ -69,6 +69,8 @@ class TidalService : public InternetService {
|
|||
|
||||
const int max_login_attempts() { return kLoginAttempts; }
|
||||
|
||||
Application *app() { return app_; }
|
||||
|
||||
const bool oauth() { return oauth_; }
|
||||
QString client_id() { return client_id_; }
|
||||
QString api_token() { return api_token_; }
|
||||
|
@ -132,6 +134,7 @@ class TidalService : public InternetService {
|
|||
private slots:
|
||||
void StartAuthorisation();
|
||||
void AuthorisationUrlReceived(const QUrl &url);
|
||||
void HandleLoginSSLErrors(QList<QSslError> ssl_errors);
|
||||
void AccessTokenRequestFinished(QNetworkReply *reply);
|
||||
void SendLogin();
|
||||
void HandleAuthReply(QNetworkReply *reply);
|
||||
|
@ -160,7 +163,7 @@ class TidalService : public InternetService {
|
|||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
void SendSearch();
|
||||
QString LoginError(QString error, QVariant debug = QVariant());
|
||||
void LoginError(const QString &error = QString(), const QVariant &debug = QVariant());
|
||||
|
||||
static const char *kApiTokenB64;
|
||||
static const char *kOAuthUrl;
|
||||
|
@ -240,6 +243,8 @@ class TidalService : public InternetService {
|
|||
|
||||
QList<TidalStreamURLRequest*> stream_url_requests_;
|
||||
|
||||
QStringList login_errors_;
|
||||
|
||||
};
|
||||
|
||||
#endif // TIDALSERVICE_H
|
||||
|
|
|
@ -147,37 +147,35 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
|||
disconnect(reply_, 0, nullptr, 0);
|
||||
reply_->deleteLater();
|
||||
|
||||
QString error;
|
||||
|
||||
QByteArray data = GetReplyData(reply_, error, true);
|
||||
QByteArray data = GetReplyData(reply_, true);
|
||||
if (data.isEmpty()) {
|
||||
reply_ = nullptr;
|
||||
if (!authenticated() && login_sent() && tries_ <= 1) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
reply_ = nullptr;
|
||||
|
||||
//qLog(Debug) << "Tidal:" << data;
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("trackId")) {
|
||||
error = Error("Invalid Json reply, stream missing trackId.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error("Invalid Json reply, stream missing trackId.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
int track_id(json_obj["trackId"].toInt());
|
||||
if (track_id != song_id_) {
|
||||
error = Error("Incorrect track ID returned.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error("Incorrect track ID returned.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -209,8 +207,8 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
|||
QString filepath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/tidalstreams";
|
||||
QString filename = "tidal-" + QString::number(song_id_) + ".xml";
|
||||
if (!QDir().mkpath(filepath)) {
|
||||
error = Error(QString("Failed to create directory %1.").arg(filepath), json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error(QString("Failed to create directory %1.").arg(filepath), json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
QUrl url("file://" + filepath + "/" + filename);
|
||||
|
@ -218,8 +216,8 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
|||
if (file.exists())
|
||||
file.remove();
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
error = Error(QString("Failed to open file %1 for writing.").arg(url.toLocalFile()), json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error(QString("Failed to open file %1 for writing.").arg(url.toLocalFile()), json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
file.write(data_manifest);
|
||||
|
@ -231,15 +229,15 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
|||
|
||||
else {
|
||||
|
||||
json_obj = ExtractJsonObj(data_manifest, error);
|
||||
json_obj = ExtractJsonObj(data_manifest);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("mimeType")) {
|
||||
error = Error("Invalid Json reply, stream url reply manifest is missing mimeType.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error("Invalid Json reply, stream url reply manifest is missing mimeType.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -260,8 +258,8 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
|||
if (json_obj.contains("urls")) {
|
||||
QJsonValue json_urls = json_obj["urls"];
|
||||
if (!json_urls.isArray()) {
|
||||
error = Error("Invalid Json reply, urls is not an array.", json_urls);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
Error("Invalid Json reply, urls is not an array.", json_urls);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
QJsonArray json_array_urls = json_urls.toArray();
|
||||
|
@ -275,11 +273,22 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
|||
}
|
||||
|
||||
if (urls.isEmpty()) {
|
||||
error = Error("Missing stream urls.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, filetype, -1, -1, -1, error);
|
||||
Error("Missing stream urls.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, filetype, -1, -1, -1, ErrorsToHTML(errors_));
|
||||
return;
|
||||
}
|
||||
|
||||
emit StreamURLFinished(original_url_, urls.first(), filetype, -1, -1, -1);
|
||||
|
||||
}
|
||||
|
||||
void TidalStreamURLRequest::Error(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
errors_ << error;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/song.h"
|
||||
|
@ -60,12 +62,15 @@ class TidalStreamURLRequest : public TidalBaseRequest {
|
|||
void StreamURLReceived();
|
||||
|
||||
private:
|
||||
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||
|
||||
TidalService *service_;
|
||||
QNetworkReply *reply_;
|
||||
QUrl original_url_;
|
||||
int song_id_;
|
||||
int tries_;
|
||||
bool need_login_;
|
||||
QStringList errors_;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/systemtrayicon.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
|
||||
const char *OSD::kSettingsGroup = "OSD";
|
||||
|
||||
|
@ -68,7 +68,7 @@ OSD::OSD(SystemTrayIcon *tray_icon, Application *app, QObject *parent)
|
|||
pretty_popup_(new OSDPretty(OSDPretty::Mode_Popup))
|
||||
{
|
||||
|
||||
connect(app_->current_art_loader(), SIGNAL(ThumbnailLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
|
||||
connect(app_->current_albumcover_loader(), SIGNAL(ThumbnailLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
|
||||
|
||||
ReloadSettings();
|
||||
Init();
|
||||
|
@ -114,18 +114,18 @@ void OSD::ReloadPrettyOSDSettings() {
|
|||
|
||||
void OSD::ReshowCurrentSong() {
|
||||
force_show_next_ = true;
|
||||
AlbumArtLoaded(last_song_, last_image_uri_, last_image_);
|
||||
AlbumCoverLoaded(last_song_, last_image_uri_, last_image_);
|
||||
}
|
||||
|
||||
void OSD::AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image) {
|
||||
void OSD::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
// Don't change tray icon details if it's a preview
|
||||
if (!preview_mode_ && tray_icon_)
|
||||
tray_icon_->SetNowPlaying(song, uri);
|
||||
tray_icon_->SetNowPlaying(song, cover_url);
|
||||
|
||||
last_song_ = song;
|
||||
last_image_ = image;
|
||||
last_image_uri_ = uri;
|
||||
last_image_uri_ = cover_url;
|
||||
|
||||
QStringList message_parts;
|
||||
QString summary;
|
||||
|
@ -187,7 +187,7 @@ void OSD::Paused() {
|
|||
|
||||
void OSD::Resumed() {
|
||||
if (show_on_resume_) {
|
||||
AlbumArtLoaded(last_song_, last_image_uri_, last_image_);
|
||||
AlbumCoverLoaded(last_song_, last_image_uri_, last_image_);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -373,7 +373,8 @@ void OSD::ShowPreview(const Behaviour type, const QString &line1, const QString
|
|||
|
||||
// We want to reload the settings, but we can't do this here because the cover art loading is asynch
|
||||
preview_mode_ = true;
|
||||
AlbumArtLoaded(song, QString(), QImage());
|
||||
AlbumCoverLoaded(song, QUrl(), QImage());
|
||||
|
||||
}
|
||||
|
||||
void OSD::SetPrettyOSDToggleMode(bool toggle) {
|
||||
|
|
|
@ -104,7 +104,7 @@ class OSD : public QObject {
|
|||
#if defined(HAVE_DBUS)
|
||||
void CallFinished(QDBusPendingCallWatcher *watcher);
|
||||
#endif
|
||||
void AlbumArtLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
@ -128,7 +128,7 @@ class OSD : public QObject {
|
|||
OSDPretty *pretty_popup_;
|
||||
|
||||
Song last_song_;
|
||||
QString last_image_uri_;
|
||||
QUrl last_image_uri_;
|
||||
QImage last_image_;
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
#include "core/application.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#include "playingwidget.h"
|
||||
|
||||
using std::unique_ptr;
|
||||
|
@ -131,13 +131,12 @@ PlayingWidget::PlayingWidget(QWidget *parent)
|
|||
|
||||
PlayingWidget::~PlayingWidget() {}
|
||||
|
||||
void PlayingWidget::SetApplication(Application *app, AlbumCoverChoiceController *album_cover_choice_controller) {
|
||||
void PlayingWidget::Init(Application *app, AlbumCoverChoiceController *album_cover_choice_controller) {
|
||||
|
||||
app_ = app;
|
||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song, QString, QImage)), SLOT(AlbumArtLoaded(Song, QString, QImage)));
|
||||
|
||||
album_cover_choice_controller_ = album_cover_choice_controller;
|
||||
album_cover_choice_controller_->SetApplication(app_);
|
||||
album_cover_choice_controller_->Init(app_);
|
||||
QList<QAction*> cover_actions = album_cover_choice_controller_->GetAllActions();
|
||||
cover_actions.append(album_cover_choice_controller_->search_cover_auto_action());
|
||||
menu_->addActions(cover_actions);
|
||||
|
@ -152,7 +151,6 @@ void PlayingWidget::SetApplication(Application *app, AlbumCoverChoiceController
|
|||
connect(above_statusbar_action_, SIGNAL(toggled(bool)), SLOT(ShowAboveStatusBar(bool)));
|
||||
|
||||
connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()), this, SLOT(AutomaticCoverSearchDone()));
|
||||
connect(album_cover_choice_controller_->search_cover_auto_action(), SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));
|
||||
|
||||
}
|
||||
|
||||
|
@ -270,7 +268,7 @@ void PlayingWidget::SongChanged(const Song &song) {
|
|||
song_ = song;
|
||||
}
|
||||
|
||||
void PlayingWidget::AlbumArtLoaded(const Song &song, const QString &, const QImage &image) {
|
||||
void PlayingWidget::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (!playing_ || song.id() != song_playing_.id() || song.url() != song_playing_.url() || song.effective_albumartist() != song_playing_.effective_albumartist() || song.effective_album() != song_playing_.effective_album() || song.title() != song_playing_.title()) return;
|
||||
if (timeline_fade_->state() == QTimeLine::Running && image == image_original_) return;
|
||||
|
@ -279,7 +277,6 @@ void PlayingWidget::AlbumArtLoaded(const Song &song, const QString &, const QIma
|
|||
downloading_covers_ = false;
|
||||
song_ = song;
|
||||
SetImage(image);
|
||||
GetCoverAutomatically();
|
||||
|
||||
}
|
||||
|
||||
|
@ -495,28 +492,15 @@ void PlayingWidget::dropEvent(QDropEvent *e) {
|
|||
|
||||
}
|
||||
|
||||
void PlayingWidget::GetCoverAutomatically() {
|
||||
void PlayingWidget::SearchCoverInProgress() {
|
||||
|
||||
// Search for cover automatically?
|
||||
bool search =
|
||||
album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
|
||||
!song_.has_manually_unset_cover() &&
|
||||
song_.art_automatic().isEmpty() &&
|
||||
song_.art_manual().isEmpty() &&
|
||||
!song_.effective_albumartist().isEmpty() &&
|
||||
!song_.effective_album().isEmpty();
|
||||
downloading_covers_ = true;
|
||||
|
||||
if (search) {
|
||||
downloading_covers_ = true;
|
||||
// This is done in mainwindow instead to avoid searching multiple times (ContextView & PlayingWidget)
|
||||
// album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||
|
||||
// Show a spinner animation
|
||||
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
||||
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
||||
spinner_animation_->start();
|
||||
update();
|
||||
}
|
||||
// Show a spinner animation
|
||||
spinner_animation_.reset(new QMovie(":/pictures/spinner.gif", QByteArray(), this));
|
||||
connect(spinner_animation_.get(), SIGNAL(updated(const QRect&)), SLOT(update()));
|
||||
spinner_animation_->start();
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
|
@ -527,7 +511,3 @@ void PlayingWidget::AutomaticCoverSearchDone() {
|
|||
update();
|
||||
|
||||
}
|
||||
|
||||
void PlayingWidget::SearchCoverAutomatically() {
|
||||
GetCoverAutomatically();
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue