parent
82d10dd7cb
commit
5aaa5231b8
|
@ -170,6 +170,7 @@ set(SOURCES
|
||||||
dialogs/addstreamdialog.cpp
|
dialogs/addstreamdialog.cpp
|
||||||
dialogs/userpassdialog.cpp
|
dialogs/userpassdialog.cpp
|
||||||
dialogs/deleteconfirmationdialog.cpp
|
dialogs/deleteconfirmationdialog.cpp
|
||||||
|
dialogs/lastfmimportdialog.cpp
|
||||||
|
|
||||||
widgets/autoexpandingtreeview.cpp
|
widgets/autoexpandingtreeview.cpp
|
||||||
widgets/busyindicator.cpp
|
widgets/busyindicator.cpp
|
||||||
|
@ -223,6 +224,7 @@ set(SOURCES
|
||||||
scrobbler/lastfmscrobbler.cpp
|
scrobbler/lastfmscrobbler.cpp
|
||||||
scrobbler/librefmscrobbler.cpp
|
scrobbler/librefmscrobbler.cpp
|
||||||
scrobbler/listenbrainzscrobbler.cpp
|
scrobbler/listenbrainzscrobbler.cpp
|
||||||
|
scrobbler/lastfmimport.cpp
|
||||||
|
|
||||||
organize/organize.cpp
|
organize/organize.cpp
|
||||||
organize/organizeformat.cpp
|
organize/organizeformat.cpp
|
||||||
|
@ -369,6 +371,7 @@ set(HEADERS
|
||||||
dialogs/addstreamdialog.h
|
dialogs/addstreamdialog.h
|
||||||
dialogs/userpassdialog.h
|
dialogs/userpassdialog.h
|
||||||
dialogs/deleteconfirmationdialog.h
|
dialogs/deleteconfirmationdialog.h
|
||||||
|
dialogs/lastfmimportdialog.h
|
||||||
|
|
||||||
widgets/autoexpandingtreeview.h
|
widgets/autoexpandingtreeview.h
|
||||||
widgets/busyindicator.h
|
widgets/busyindicator.h
|
||||||
|
@ -420,6 +423,7 @@ set(HEADERS
|
||||||
scrobbler/lastfmscrobbler.h
|
scrobbler/lastfmscrobbler.h
|
||||||
scrobbler/librefmscrobbler.h
|
scrobbler/librefmscrobbler.h
|
||||||
scrobbler/listenbrainzscrobbler.h
|
scrobbler/listenbrainzscrobbler.h
|
||||||
|
scrobbler/lastfmimport.h
|
||||||
|
|
||||||
organize/organize.h
|
organize/organize.h
|
||||||
organize/organizedialog.h
|
organize/organizedialog.h
|
||||||
|
@ -472,6 +476,7 @@ set(UI
|
||||||
dialogs/trackselectiondialog.ui
|
dialogs/trackselectiondialog.ui
|
||||||
dialogs/addstreamdialog.ui
|
dialogs/addstreamdialog.ui
|
||||||
dialogs/userpassdialog.ui
|
dialogs/userpassdialog.ui
|
||||||
|
dialogs/lastfmimportdialog.ui
|
||||||
|
|
||||||
widgets/trackslider.ui
|
widgets/trackslider.ui
|
||||||
widgets/fileview.ui
|
widgets/fileview.ui
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
|
||||||
const char *SCollection::kSongsTable = "songs";
|
const char *SCollection::kSongsTable = "songs";
|
||||||
const char *SCollection::kDirsTable = "directories";
|
const char *SCollection::kDirsTable = "directories";
|
||||||
|
@ -110,6 +111,9 @@ void SCollection::Init() {
|
||||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
|
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
|
||||||
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
|
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
|
||||||
|
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(UpdateLastPlayed(QString, QString, QString, int)), backend_, SLOT(UpdateLastPlayed(QString, QString, QString, int)));
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(UpdatePlayCount(QString, QString, int)), backend_, SLOT(UpdatePlayCount(QString, QString, int)));
|
||||||
|
|
||||||
// This will start the watcher checking for updates
|
// This will start the watcher checking for updates
|
||||||
backend_->LoadDirectoriesAsync();
|
backend_->LoadDirectoriesAsync();
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
#include "core/database.h"
|
#include "core/database.h"
|
||||||
#include "core/scopedtransaction.h"
|
#include "core/scopedtransaction.h"
|
||||||
|
|
||||||
|
@ -1297,3 +1298,79 @@ void CollectionBackend::DeleteAll() {
|
||||||
emit DatabaseReset();
|
emit DatabaseReset();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SongList CollectionBackend::GetSongsBy(const QString &artist, const QString &album, const QString &title) {
|
||||||
|
|
||||||
|
QMutexLocker l(db_->Mutex());
|
||||||
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
SongList songs;
|
||||||
|
QSqlQuery q(db);
|
||||||
|
if (album.isEmpty()) {
|
||||||
|
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(songs_table_));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND album = :album COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(songs_table_));
|
||||||
|
}
|
||||||
|
q.bindValue(":artist", artist);
|
||||||
|
if (!album.isEmpty()) q.bindValue(":album", album);
|
||||||
|
q.bindValue(":title", title);
|
||||||
|
q.exec();
|
||||||
|
if (db_->CheckErrors(q)) return SongList();
|
||||||
|
while (q.next()) {
|
||||||
|
Song song(source_);
|
||||||
|
song.InitFromQuery(q, true);
|
||||||
|
songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
return songs;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed) {
|
||||||
|
|
||||||
|
SongList songs = GetSongsBy(artist, album, title);
|
||||||
|
if (songs.isEmpty()) {
|
||||||
|
qLog(Debug) << "Could not find a matching song in the database for" << artist << album << title;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMutexLocker l(db_->Mutex());
|
||||||
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
QSqlQuery q(db);
|
||||||
|
q.prepare(QString("UPDATE %1 SET lastplayed = :lastplayed WHERE ROWID = :id").arg(songs_table_));
|
||||||
|
q.bindValue(":lastplayed", lastplayed);
|
||||||
|
q.bindValue(":id", song.id());
|
||||||
|
q.exec();
|
||||||
|
if (db_->CheckErrors(q)) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit SongsStatisticsChanged(SongList() << songs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &title, const int playcount) {
|
||||||
|
|
||||||
|
SongList songs = GetSongsBy(artist, QString(), title);
|
||||||
|
if (songs.isEmpty()) {
|
||||||
|
qLog(Debug) << "Could not find a matching song in the database for" << artist << title;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMutexLocker l(db_->Mutex());
|
||||||
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
QSqlQuery q(db);
|
||||||
|
q.prepare(QString("UPDATE %1 SET playcount = :playcount WHERE ROWID = :id").arg(songs_table_));
|
||||||
|
q.bindValue(":playcount", playcount);
|
||||||
|
q.bindValue(":id", song.id());
|
||||||
|
q.exec();
|
||||||
|
if (db_->CheckErrors(q)) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit SongsStatisticsChanged(SongList() << songs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -205,6 +205,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
void ResetStatistics(const int id);
|
void ResetStatistics(const int id);
|
||||||
void SongPathChanged(const Song &song, const QFileInfo &new_file);
|
void SongPathChanged(const Song &song, const QFileInfo &new_file);
|
||||||
|
|
||||||
|
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
||||||
|
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed);
|
||||||
|
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void DirectoryDiscovered(const Directory &dir, const SubdirectoryList &subdirs);
|
void DirectoryDiscovered(const Directory &dir, const SubdirectoryList &subdirs);
|
||||||
void DirectoryDeleted(const Directory &dir);
|
void DirectoryDeleted(const Directory &dir);
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
#include "lyrics/chartlyricsprovider.h"
|
#include "lyrics/chartlyricsprovider.h"
|
||||||
|
|
||||||
#include "scrobbler/audioscrobbler.h"
|
#include "scrobbler/audioscrobbler.h"
|
||||||
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
|
||||||
#include "internet/internetservices.h"
|
#include "internet/internetservices.h"
|
||||||
|
|
||||||
|
@ -160,6 +161,7 @@ class ApplicationImpl {
|
||||||
return internet_services;
|
return internet_services;
|
||||||
}),
|
}),
|
||||||
scrobbler_([=]() { return new AudioScrobbler(app, app); }),
|
scrobbler_([=]() { return new AudioScrobbler(app, app); }),
|
||||||
|
lastfm_import_([=]() { return new LastFMImport(app); }),
|
||||||
|
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
moodbar_loader_([=]() { return new MoodbarLoader(app, app); }),
|
moodbar_loader_([=]() { return new MoodbarLoader(app, app); }),
|
||||||
|
@ -187,6 +189,7 @@ class ApplicationImpl {
|
||||||
Lazy<LyricsProviders> lyrics_providers_;
|
Lazy<LyricsProviders> lyrics_providers_;
|
||||||
Lazy<InternetServices> internet_services_;
|
Lazy<InternetServices> internet_services_;
|
||||||
Lazy<AudioScrobbler> scrobbler_;
|
Lazy<AudioScrobbler> scrobbler_;
|
||||||
|
Lazy<LastFMImport> lastfm_import_;
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
Lazy<MoodbarLoader> moodbar_loader_;
|
Lazy<MoodbarLoader> moodbar_loader_;
|
||||||
Lazy<MoodbarController> moodbar_controller_;
|
Lazy<MoodbarController> moodbar_controller_;
|
||||||
|
@ -315,6 +318,7 @@ PlaylistBackend *Application::playlist_backend() const { return p_->playlist_bac
|
||||||
PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); }
|
PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); }
|
||||||
InternetServices *Application::internet_services() const { return p_->internet_services_.get(); }
|
InternetServices *Application::internet_services() const { return p_->internet_services_.get(); }
|
||||||
AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); }
|
AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); }
|
||||||
|
LastFMImport *Application::lastfm_import() const { return p_->lastfm_import_.get(); }
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
MoodbarController *Application::moodbar_controller() const { return p_->moodbar_controller_.get(); }
|
MoodbarController *Application::moodbar_controller() const { return p_->moodbar_controller_.get(); }
|
||||||
MoodbarLoader *Application::moodbar_loader() const { return p_->moodbar_loader_.get(); }
|
MoodbarLoader *Application::moodbar_loader() const { return p_->moodbar_loader_.get(); }
|
||||||
|
|
|
@ -56,6 +56,7 @@ class CurrentAlbumCoverLoader;
|
||||||
class CoverProviders;
|
class CoverProviders;
|
||||||
class LyricsProviders;
|
class LyricsProviders;
|
||||||
class AudioScrobbler;
|
class AudioScrobbler;
|
||||||
|
class LastFMImport;
|
||||||
class InternetServices;
|
class InternetServices;
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
class MoodbarController;
|
class MoodbarController;
|
||||||
|
@ -93,6 +94,7 @@ class Application : public QObject {
|
||||||
LyricsProviders *lyrics_providers() const;
|
LyricsProviders *lyrics_providers() const;
|
||||||
|
|
||||||
AudioScrobbler *scrobbler() const;
|
AudioScrobbler *scrobbler() const;
|
||||||
|
LastFMImport *lastfm_import() const;
|
||||||
|
|
||||||
InternetServices *internet_services() const;
|
InternetServices *internet_services() const;
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,7 @@
|
||||||
#include "dialogs/edittagdialog.h"
|
#include "dialogs/edittagdialog.h"
|
||||||
#include "dialogs/addstreamdialog.h"
|
#include "dialogs/addstreamdialog.h"
|
||||||
#include "dialogs/deleteconfirmationdialog.h"
|
#include "dialogs/deleteconfirmationdialog.h"
|
||||||
|
#include "dialogs/lastfmimportdialog.h"
|
||||||
#include "organize/organizedialog.h"
|
#include "organize/organizedialog.h"
|
||||||
#include "widgets/fancytabwidget.h"
|
#include "widgets/fancytabwidget.h"
|
||||||
#include "widgets/playingwidget.h"
|
#include "widgets/playingwidget.h"
|
||||||
|
@ -169,6 +170,7 @@
|
||||||
#include "internet/internetsearchview.h"
|
#include "internet/internetsearchview.h"
|
||||||
|
|
||||||
#include "scrobbler/audioscrobbler.h"
|
#include "scrobbler/audioscrobbler.h"
|
||||||
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
|
||||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||||
# include "musicbrainz/tagfetcher.h"
|
# include "musicbrainz/tagfetcher.h"
|
||||||
|
@ -257,6 +259,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Tidal), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)),
|
tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Tidal), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)),
|
||||||
#endif
|
#endif
|
||||||
|
lastfm_import_dialog_(new LastFMImportDialog(app_->lastfm_import(), this)),
|
||||||
collection_show_all_(nullptr),
|
collection_show_all_(nullptr),
|
||||||
collection_show_duplicates_(nullptr),
|
collection_show_duplicates_(nullptr),
|
||||||
collection_show_untagged_(nullptr),
|
collection_show_untagged_(nullptr),
|
||||||
|
@ -417,6 +420,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
||||||
ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh"));
|
ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh"));
|
||||||
ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh"));
|
ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh"));
|
||||||
ui_->action_settings->setIcon(IconLoader::Load("configure"));
|
ui_->action_settings->setIcon(IconLoader::Load("configure"));
|
||||||
|
ui_->action_import_data_from_last_fm->setIcon(IconLoader::Load("scrobble"));
|
||||||
|
|
||||||
// Scrobble
|
// Scrobble
|
||||||
|
|
||||||
|
@ -457,6 +461,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
||||||
connect(ui_->action_auto_complete_tags, SIGNAL(triggered()), SLOT(AutoCompleteTags()));
|
connect(ui_->action_auto_complete_tags, SIGNAL(triggered()), SLOT(AutoCompleteTags()));
|
||||||
#endif
|
#endif
|
||||||
connect(ui_->action_settings, SIGNAL(triggered()), SLOT(OpenSettingsDialog()));
|
connect(ui_->action_settings, SIGNAL(triggered()), SLOT(OpenSettingsDialog()));
|
||||||
|
connect(ui_->action_import_data_from_last_fm, SIGNAL(triggered()), lastfm_import_dialog_, SLOT(show()));
|
||||||
connect(ui_->action_toggle_show_sidebar, SIGNAL(toggled(bool)), SLOT(ToggleSidebar(bool)));
|
connect(ui_->action_toggle_show_sidebar, SIGNAL(toggled(bool)), SLOT(ToggleSidebar(bool)));
|
||||||
connect(ui_->action_about_strawberry, SIGNAL(triggered()), SLOT(ShowAboutDialog()));
|
connect(ui_->action_about_strawberry, SIGNAL(triggered()), SLOT(ShowAboutDialog()));
|
||||||
connect(ui_->action_about_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
|
connect(ui_->action_about_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
|
||||||
|
@ -822,6 +827,12 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
||||||
LoveButtonVisibilityChanged(app_->scrobbler()->LoveButton());
|
LoveButtonVisibilityChanged(app_->scrobbler()->LoveButton());
|
||||||
ScrobblingEnabledChanged(app_->scrobbler()->IsEnabled());
|
ScrobblingEnabledChanged(app_->scrobbler()->IsEnabled());
|
||||||
|
|
||||||
|
// Last.fm ImportData
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(Finished()), lastfm_import_dialog_, SLOT(Finished()));
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(FinishedWithError(QString)), lastfm_import_dialog_, SLOT(FinishedWithError(QString)));
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(UpdateTotal(int, int)), lastfm_import_dialog_, SLOT(UpdateTotal(int, int)));
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(UpdateProgress(int, int)), lastfm_import_dialog_, SLOT(UpdateProgress(int, int)));
|
||||||
|
|
||||||
// Load settings
|
// Load settings
|
||||||
qLog(Debug) << "Loading settings";
|
qLog(Debug) << "Loading settings";
|
||||||
settings_.beginGroup(kSettingsGroup);
|
settings_.beginGroup(kSettingsGroup);
|
||||||
|
|
|
@ -96,6 +96,7 @@ class InternetTabsView;
|
||||||
class Windows7ThumbBar;
|
class Windows7ThumbBar;
|
||||||
#endif
|
#endif
|
||||||
class AddStreamDialog;
|
class AddStreamDialog;
|
||||||
|
class LastFMImportDialog;
|
||||||
|
|
||||||
class MainWindow : public QMainWindow, public PlatformInterface {
|
class MainWindow : public QMainWindow, public PlatformInterface {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -324,6 +325,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||||
InternetSongsView *subsonic_view_;
|
InternetSongsView *subsonic_view_;
|
||||||
InternetTabsView *tidal_view_;
|
InternetTabsView *tidal_view_;
|
||||||
|
|
||||||
|
LastFMImportDialog *lastfm_import_dialog_;
|
||||||
|
|
||||||
QAction *collection_show_all_;
|
QAction *collection_show_all_;
|
||||||
QAction *collection_show_duplicates_;
|
QAction *collection_show_duplicates_;
|
||||||
QAction *collection_show_untagged_;
|
QAction *collection_show_untagged_;
|
||||||
|
|
|
@ -511,6 +511,7 @@
|
||||||
<addaction name="action_abort_collection_scan"/>
|
<addaction name="action_abort_collection_scan"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_settings"/>
|
<addaction name="action_settings"/>
|
||||||
|
<addaction name="action_import_data_from_last_fm"/>
|
||||||
<addaction name="action_console"/>
|
<addaction name="action_console"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_toggle_show_sidebar"/>
|
<addaction name="action_toggle_show_sidebar"/>
|
||||||
|
@ -844,6 +845,11 @@
|
||||||
<string>Show sidebar</string>
|
<string>Show sidebar</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_import_data_from_last_fm">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import data from last.fm...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2020, 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QShowEvent>
|
||||||
|
#include <QCloseEvent>
|
||||||
|
|
||||||
|
#include "lastfmimportdialog.h"
|
||||||
|
#include "ui_lastfmimportdialog.h"
|
||||||
|
|
||||||
|
#include "core/iconloader.h"
|
||||||
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
|
||||||
|
LastFMImportDialog::LastFMImportDialog(LastFMImport *lastfm_import, QWidget *parent) : QDialog(parent),
|
||||||
|
ui_(new Ui_LastFMImportDialog),
|
||||||
|
lastfm_import_(lastfm_import),
|
||||||
|
finished_(false),
|
||||||
|
playcount_total_(0),
|
||||||
|
lastplayed_total_(0)
|
||||||
|
{
|
||||||
|
|
||||||
|
ui_->setupUi(this);
|
||||||
|
|
||||||
|
setWindowIcon(IconLoader::Load("scrobble"));
|
||||||
|
|
||||||
|
ui_->stackedWidget->setCurrentWidget(ui_->page_start);
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
connect(ui_->button_close, SIGNAL(clicked()), SLOT(hide()));
|
||||||
|
connect(ui_->button_go, SIGNAL(clicked()), SLOT(Start()));
|
||||||
|
connect(ui_->button_cancel, SIGNAL(clicked()), SLOT(Cancel()));
|
||||||
|
|
||||||
|
connect(ui_->checkbox_last_played, SIGNAL(stateChanged(int)), SLOT(UpdateGoButtonState()));
|
||||||
|
connect(ui_->checkbox_playcounts, SIGNAL(stateChanged(int)), SLOT(UpdateGoButtonState()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LastFMImportDialog::~LastFMImportDialog() { delete ui_; }
|
||||||
|
|
||||||
|
void LastFMImportDialog::showEvent(QShowEvent*) {
|
||||||
|
|
||||||
|
if (ui_->stackedWidget->currentWidget() == ui_->page_start) {
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::closeEvent(QCloseEvent*) {
|
||||||
|
|
||||||
|
if (ui_->stackedWidget->currentWidget() == ui_->page_progress && finished_) {
|
||||||
|
finished_ = false;
|
||||||
|
Reset();
|
||||||
|
ui_->stackedWidget->setCurrentWidget(ui_->page_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::Start() {
|
||||||
|
|
||||||
|
if (ui_->stackedWidget->currentWidget() == ui_->page_start && (ui_->checkbox_last_played->isChecked() || ui_->checkbox_playcounts->isChecked())) {
|
||||||
|
ui_->stackedWidget->setCurrentWidget(ui_->page_progress);
|
||||||
|
ui_->button_go->hide();
|
||||||
|
ui_->button_cancel->show();
|
||||||
|
ui_->label_progress_top->setText(tr("Receiving initial data from last.fm..."));
|
||||||
|
lastfm_import_->ImportData(ui_->checkbox_last_played->isChecked(), ui_->checkbox_playcounts->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::Cancel() {
|
||||||
|
|
||||||
|
if (ui_->stackedWidget->currentWidget() == ui_->page_progress) {
|
||||||
|
lastfm_import_->AbortAll();
|
||||||
|
ui_->stackedWidget->setCurrentWidget(ui_->page_start);
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::Reset() {
|
||||||
|
|
||||||
|
ui_->button_go->show();
|
||||||
|
ui_->button_cancel->hide();
|
||||||
|
|
||||||
|
playcount_total_ = 0;
|
||||||
|
lastplayed_total_ = 0;
|
||||||
|
|
||||||
|
ui_->progressbar->setValue(0);
|
||||||
|
ui_->label_progress_top->clear();
|
||||||
|
ui_->label_progress_bottom->clear();
|
||||||
|
|
||||||
|
UpdateGoButtonState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::UpdateTotal(const int lastplayed_total, const int playcount_total) {
|
||||||
|
|
||||||
|
if (ui_->stackedWidget->currentWidget() != ui_->page_progress) return;
|
||||||
|
|
||||||
|
playcount_total_ = playcount_total;
|
||||||
|
lastplayed_total_ = lastplayed_total;
|
||||||
|
|
||||||
|
if (lastplayed_total > 0 && playcount_total > 0) {
|
||||||
|
ui_->label_progress_top->setText(tr("Receiving playcount for %1 songs and last played for %2 songs.").arg(playcount_total).arg(lastplayed_total));
|
||||||
|
}
|
||||||
|
else if (lastplayed_total > 0) {
|
||||||
|
ui_->label_progress_top->setText(tr("Receiving last played for %1 songs.").arg(lastplayed_total));
|
||||||
|
}
|
||||||
|
else if (playcount_total > 0) {
|
||||||
|
ui_->label_progress_top->setText(tr("Receiving playcounts for %1 songs.").arg(playcount_total));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui_->label_progress_top->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_->label_progress_bottom->clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::UpdateProgress(const int lastplayed_received, const int playcount_received) {
|
||||||
|
|
||||||
|
if (ui_->stackedWidget->currentWidget() != ui_->page_progress) return;
|
||||||
|
|
||||||
|
ui_->progressbar->setValue(static_cast<int>(static_cast<float>(playcount_received + lastplayed_received) / static_cast<float>(playcount_total_ + lastplayed_total_) * 100.0));
|
||||||
|
|
||||||
|
if (lastplayed_received > 0 && playcount_received > 0) {
|
||||||
|
ui_->label_progress_bottom->setText(tr("Playcounts for %1 songs and last played for %2 songs received.").arg(playcount_received).arg(lastplayed_received));
|
||||||
|
}
|
||||||
|
else if (lastplayed_received > 0) {
|
||||||
|
ui_->label_progress_bottom->setText(tr("Last played for %1 songs received.").arg(lastplayed_received));
|
||||||
|
}
|
||||||
|
else if (playcount_received > 0) {
|
||||||
|
ui_->label_progress_bottom->setText(tr("Playcounts for %1 songs received.").arg(playcount_received));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui_->label_progress_bottom->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::Finished() {
|
||||||
|
|
||||||
|
ui_->button_cancel->hide();
|
||||||
|
finished_ = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::FinishedWithError(const QString &error) {
|
||||||
|
|
||||||
|
Finished();
|
||||||
|
ui_->label_progress_bottom->setText(error);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImportDialog::UpdateGoButtonState() {
|
||||||
|
ui_->button_go->setEnabled(ui_->checkbox_last_played->isChecked() || ui_->checkbox_playcounts->isChecked());
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2020, 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LASTFMIMPORTDIALOG_H
|
||||||
|
#define LASTFMIMPORTDIALOG_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "ui_lastfmimportdialog.h"
|
||||||
|
|
||||||
|
class QShowEvent;
|
||||||
|
class QCloseEvent;
|
||||||
|
class LastFMImport;
|
||||||
|
|
||||||
|
class LastFMImportDialog : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LastFMImportDialog(LastFMImport *lastfm_import, QWidget *parent = nullptr);
|
||||||
|
~LastFMImportDialog() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showEvent(QShowEvent*);
|
||||||
|
void closeEvent(QCloseEvent*);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void Start();
|
||||||
|
void Cancel();
|
||||||
|
void UpdateGoButtonState();
|
||||||
|
|
||||||
|
void UpdateTotal(const int lastplayed_total, const int playcount_total);
|
||||||
|
void UpdateProgress(const int lastplayed_received, const int playcount_received);
|
||||||
|
void Finished();
|
||||||
|
void FinishedWithError(const QString &error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui_LastFMImportDialog *ui_;
|
||||||
|
LastFMImport *lastfm_import_;
|
||||||
|
|
||||||
|
bool finished_;
|
||||||
|
int playcount_total_;
|
||||||
|
int lastplayed_total_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LASTFMIMPORTDIALOG_H
|
|
@ -0,0 +1,137 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>LastFMImportDialog</class>
|
||||||
|
<widget class="QDialog" name="LastFMImportDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>520</width>
|
||||||
|
<height>249</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Import data from last.fm</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
|
<widget class="QWidget" name="page_start">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_start_top">
|
||||||
|
<property name="text">
|
||||||
|
<string>Choose data to import from last.fm</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkbox_last_played">
|
||||||
|
<property name="text">
|
||||||
|
<string>Last played</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkbox_playcounts">
|
||||||
|
<property name="text">
|
||||||
|
<string>Play counts</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_start_bottom">
|
||||||
|
<property name="text">
|
||||||
|
<string>Warning: Play counts and last played from last.fm will completely replace the same data for the matched songs. Play counts will replace the data based on artist and song title for the same albums! Please backup your database before you start.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_progress">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_progress_top">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressbar">
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_progress_bottom">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_done"/>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="layout_buttons">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="button_go">
|
||||||
|
<property name="text">
|
||||||
|
<string>Go!</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="button_close">
|
||||||
|
<property name="text">
|
||||||
|
<string>Close</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="button_cancel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cancel</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,588 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2020, 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
|
#include "core/network.h"
|
||||||
|
#include "core/timeconstants.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
|
||||||
|
#include "lastfmimport.h"
|
||||||
|
|
||||||
|
#include "scrobblingapi20.h"
|
||||||
|
#include "lastfmscrobbler.h"
|
||||||
|
|
||||||
|
const int LastFMImport::kRequestsDelay = 2000;
|
||||||
|
|
||||||
|
LastFMImport::LastFMImport(QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
network_(new NetworkAccessManager(this)),
|
||||||
|
timer_flush_requests_(new QTimer(this)),
|
||||||
|
lastplayed_(false),
|
||||||
|
playcount_(false),
|
||||||
|
playcount_total_(0),
|
||||||
|
lastplayed_total_(0),
|
||||||
|
playcount_received_(0),
|
||||||
|
lastplayed_received_(0) {
|
||||||
|
|
||||||
|
timer_flush_requests_->setInterval(kRequestsDelay);
|
||||||
|
timer_flush_requests_->setSingleShot(false);
|
||||||
|
connect(timer_flush_requests_, SIGNAL(timeout()), this, SLOT(FlushRequests()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LastFMImport::~LastFMImport() {
|
||||||
|
|
||||||
|
AbortAll();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::AbortAll() {
|
||||||
|
|
||||||
|
while (!replies_.isEmpty()) {
|
||||||
|
QNetworkReply *reply = replies_.takeFirst();
|
||||||
|
disconnect(reply, nullptr, this, nullptr);
|
||||||
|
reply->abort();
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
playcount_total_ = 0;
|
||||||
|
lastplayed_total_ = 0;
|
||||||
|
playcount_received_ = 0;
|
||||||
|
lastplayed_received_ = 0;
|
||||||
|
|
||||||
|
recent_tracks_requests_.clear();
|
||||||
|
top_tracks_requests_.clear();
|
||||||
|
timer_flush_requests_->stop();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::ReloadSettings() {
|
||||||
|
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(LastFMScrobbler::kSettingsGroup);
|
||||||
|
username_ = s.value("username").toString();
|
||||||
|
s.endGroup();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *LastFMImport::CreateRequest(const ParamList &request_params) {
|
||||||
|
|
||||||
|
ParamList params = ParamList()
|
||||||
|
<< Param("api_key", ScrobblingAPI20::kApiKey)
|
||||||
|
<< Param("user", username_)
|
||||||
|
<< Param("lang", QLocale().name().left(2).toLower())
|
||||||
|
<< Param("format", "json")
|
||||||
|
<< request_params;
|
||||||
|
|
||||||
|
std::sort(params.begin(), params.end());
|
||||||
|
|
||||||
|
QUrlQuery url_query;
|
||||||
|
for (const Param ¶m : params) {
|
||||||
|
url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url(LastFMScrobbler::kApiUrl);
|
||||||
|
url.setQuery(url_query);
|
||||||
|
QNetworkRequest req(url);
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||||
|
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||||
|
#else
|
||||||
|
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
|
#endif
|
||||||
|
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
QNetworkReply *reply = network_->get(req);
|
||||||
|
replies_ << reply;
|
||||||
|
|
||||||
|
//qLog(Debug) << "Sending request" << url_query.toString(QUrl::FullyDecoded);
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray LastFMImport::GetReplyData(QNetworkReply *reply) {
|
||||||
|
|
||||||
|
QByteArray data;
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||||
|
data = reply->readAll();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||||
|
// This is a network error, there is nothing more to do.
|
||||||
|
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 json_error;
|
||||||
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||||
|
int error_code = -1;
|
||||||
|
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 = QString("%1 (%2)").arg(error_message).arg(error_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject LastFMImport::ExtractJsonObj(const QByteArray &data) {
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||||
|
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
Error("Reply from server missing Json data.", data);
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||||
|
Error("Received empty Json document.", json_doc);
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
if (!json_doc.isObject()) {
|
||||||
|
Error("Json document is not an object.", json_doc);
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
QJsonObject json_obj = json_doc.object();
|
||||||
|
if (json_obj.isEmpty()) {
|
||||||
|
Error("Received empty Json object.", json_doc);
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_obj;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::ImportData(const bool lastplayed, const bool playcount) {
|
||||||
|
|
||||||
|
if (!lastplayed && !playcount) return;
|
||||||
|
|
||||||
|
ReloadSettings();
|
||||||
|
|
||||||
|
if (username_.isEmpty()) {
|
||||||
|
Error(tr("Missing username, please login to last.fm first!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbortAll();
|
||||||
|
|
||||||
|
lastplayed_ = lastplayed;
|
||||||
|
playcount_ = playcount;
|
||||||
|
|
||||||
|
if (lastplayed) AddGetRecentTracksRequest(0);
|
||||||
|
if (playcount) AddGetTopTracksRequest(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::FlushRequests() {
|
||||||
|
|
||||||
|
if (!recent_tracks_requests_.isEmpty()) {
|
||||||
|
SendGetRecentTracksRequest(recent_tracks_requests_.dequeue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!top_tracks_requests_.isEmpty()) {
|
||||||
|
SendGetTopTracksRequest(top_tracks_requests_.dequeue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer_flush_requests_->stop();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::AddGetRecentTracksRequest(const int page) {
|
||||||
|
|
||||||
|
recent_tracks_requests_.enqueue(GetRecentTracksRequest(page));
|
||||||
|
|
||||||
|
if (!timer_flush_requests_->isActive()) {
|
||||||
|
timer_flush_requests_->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::SendGetRecentTracksRequest(GetRecentTracksRequest request) {
|
||||||
|
|
||||||
|
ParamList params = ParamList() << Param("method", "user.getRecentTracks");
|
||||||
|
|
||||||
|
if (request.page == 0) {
|
||||||
|
params << Param("page", "1");
|
||||||
|
params << Param("limit", "1");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params << Param("page", QString::number(request.page));
|
||||||
|
params << Param("limit", "500");
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = CreateRequest(params);
|
||||||
|
connect(reply, &QNetworkReply::finished, [=] { GetRecentTracksRequestFinished(reply, request.page); });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::GetRecentTracksRequestFinished(QNetworkReply *reply, const int page) {
|
||||||
|
|
||||||
|
if (!replies_.contains(reply)) return;
|
||||||
|
replies_.removeAll(reply);
|
||||||
|
disconnect(reply, nullptr, this, nullptr);
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
QByteArray data = GetReplyData(reply);
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json_obj = ExtractJsonObj(data);
|
||||||
|
if (json_obj.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_obj.contains("error") && json_obj.contains("message")) {
|
||||||
|
int error_code = json_obj["error"].toInt();
|
||||||
|
QString error_message = json_obj["message"].toString();
|
||||||
|
QString error_reason = QString("%1 (%2)").arg(error_message).arg(error_code);
|
||||||
|
Error(error_reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj.contains("recenttracks")) {
|
||||||
|
Error("JSON reply from server is missing recenttracks.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj["recenttracks"].isObject()) {
|
||||||
|
Error("Failed to pase JSON: recenttracks is not an object!", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
json_obj = json_obj["recenttracks"].toObject();
|
||||||
|
|
||||||
|
if (!json_obj.contains("@attr")) {
|
||||||
|
Error("JSON reply from server is missing @attr.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj.contains("track")) {
|
||||||
|
Error("JSON reply from server is missing track.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj["@attr"].isObject()) {
|
||||||
|
Error("Failed to pase JSON: @attr is not an object.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj["track"].isArray()) {
|
||||||
|
Error("Failed to pase JSON: track is not an object.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj_attr = json_obj["@attr"].toObject();
|
||||||
|
|
||||||
|
if (!obj_attr.contains("page")) {
|
||||||
|
Error("Failed to pase JSON: attr object is missing page.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!obj_attr.contains("totalPages")) {
|
||||||
|
Error("Failed to pase JSON: attr object is missing totalPages.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!obj_attr.contains("total")) {
|
||||||
|
Error("Failed to pase JSON: attr object is missing total.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int total = obj_attr["total"].toString().toInt();
|
||||||
|
int pages = obj_attr["totalPages"].toString().toInt();
|
||||||
|
|
||||||
|
if (page == 0) {
|
||||||
|
lastplayed_total_ = total;
|
||||||
|
UpdateTotal();
|
||||||
|
AddGetRecentTracksRequest(1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
QJsonArray array_track = json_obj["track"].toArray();
|
||||||
|
|
||||||
|
for (const QJsonValue &value_track : array_track) {
|
||||||
|
|
||||||
|
++lastplayed_received_;
|
||||||
|
|
||||||
|
if (!value_track.isObject()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QJsonObject obj_track = value_track.toObject();
|
||||||
|
if (!obj_track.contains("artist") ||
|
||||||
|
!obj_track.contains("album") ||
|
||||||
|
!obj_track.contains("name") ||
|
||||||
|
!obj_track.contains("date") ||
|
||||||
|
!obj_track["artist"].isObject() ||
|
||||||
|
!obj_track["album"].isObject() ||
|
||||||
|
!obj_track["date"].isObject()
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj_artist = obj_track["artist"].toObject();
|
||||||
|
QJsonObject obj_album = obj_track["album"].toObject();
|
||||||
|
QJsonObject obj_date = obj_track["date"].toObject();
|
||||||
|
|
||||||
|
if (!obj_artist.contains("#text") || !obj_album.contains("#text") || !obj_date.contains("#text")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString artist = obj_artist["#text"].toString();
|
||||||
|
QString album = obj_album["#text"].toString();
|
||||||
|
QString date = obj_date["#text"].toString();
|
||||||
|
QString title = obj_track["name"].toString();
|
||||||
|
QDateTime datetime = QDateTime::fromString(date, "dd MMM yyyy, hh:mm");
|
||||||
|
|
||||||
|
emit UpdateLastPlayed(artist, album, title, datetime.toSecsSinceEpoch());
|
||||||
|
UpdateProgress();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == 1) {
|
||||||
|
for (int i = 2 ; i <= pages ; ++i) {
|
||||||
|
AddGetRecentTracksRequest(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FinishCheck();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::AddGetTopTracksRequest(const int page) {
|
||||||
|
|
||||||
|
top_tracks_requests_.enqueue(GetTopTracksRequest(page));
|
||||||
|
|
||||||
|
if (!timer_flush_requests_->isActive()) {
|
||||||
|
timer_flush_requests_->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::SendGetTopTracksRequest(GetTopTracksRequest request) {
|
||||||
|
|
||||||
|
ParamList params = ParamList() << Param("method", "user.getTopTracks");
|
||||||
|
|
||||||
|
if (request.page == 0) {
|
||||||
|
params << Param("page", "1");
|
||||||
|
params << Param("limit", "1");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params << Param("page", QString::number(request.page));
|
||||||
|
params << Param("limit", "500");
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = CreateRequest(params);
|
||||||
|
connect(reply, &QNetworkReply::finished, [=] { GetTopTracksRequestFinished(reply, request.page); });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::GetTopTracksRequestFinished(QNetworkReply *reply, const int page) {
|
||||||
|
|
||||||
|
if (!replies_.contains(reply)) return;
|
||||||
|
replies_.removeAll(reply);
|
||||||
|
disconnect(reply, nullptr, this, nullptr);
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
QByteArray data = GetReplyData(reply);
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json_obj = ExtractJsonObj(data);
|
||||||
|
if (json_obj.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_obj.contains("error") && json_obj.contains("message")) {
|
||||||
|
int error_code = json_obj["error"].toInt();
|
||||||
|
QString error_message = json_obj["message"].toString();
|
||||||
|
QString error_reason = QString("%1 (%2)").arg(error_message).arg(error_code);
|
||||||
|
Error(error_reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj.contains("toptracks")) {
|
||||||
|
Error("JSON reply from server is missing toptracks.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj["toptracks"].isObject()) {
|
||||||
|
Error("Failed to pase JSON: toptracks is not an object!", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
json_obj = json_obj["toptracks"].toObject();
|
||||||
|
|
||||||
|
if (!json_obj.contains("@attr")) {
|
||||||
|
Error("JSON reply from server is missing @attr.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj.contains("track")) {
|
||||||
|
Error("JSON reply from server is missing track.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj["@attr"].isObject()) {
|
||||||
|
Error("Failed to pase JSON: @attr is not an object.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj["track"].isArray()) {
|
||||||
|
Error("Failed to pase JSON: track is not an object.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj_attr = json_obj["@attr"].toObject();
|
||||||
|
|
||||||
|
if (!obj_attr.contains("page")) {
|
||||||
|
Error("Failed to pase JSON: attr object is missing page.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!obj_attr.contains("totalPages")) {
|
||||||
|
Error("Failed to pase JSON: attr object is missing page.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!obj_attr.contains("total")) {
|
||||||
|
Error("Failed to pase JSON: attr object is missing total.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pages = obj_attr["totalPages"].toString().toInt();
|
||||||
|
int total = obj_attr["total"].toString().toInt();
|
||||||
|
|
||||||
|
if (page == 0) {
|
||||||
|
playcount_total_ = total;
|
||||||
|
UpdateTotal();
|
||||||
|
AddGetTopTracksRequest(1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
QJsonArray array_track = json_obj["track"].toArray();
|
||||||
|
for (const QJsonValue &value_track : array_track) {
|
||||||
|
|
||||||
|
++playcount_received_;
|
||||||
|
|
||||||
|
if (!value_track.isObject()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj_track = value_track.toObject();
|
||||||
|
if (!obj_track.contains("artist") ||
|
||||||
|
!obj_track.contains("name") ||
|
||||||
|
!obj_track.contains("playcount") ||
|
||||||
|
!obj_track["artist"].isObject()
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj_artist = obj_track["artist"].toObject();
|
||||||
|
if (!obj_artist.contains("name")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString artist = obj_artist["name"].toString();
|
||||||
|
QString title = obj_track["name"].toString();
|
||||||
|
int playcount = obj_track["playcount"].toString().toInt();
|
||||||
|
|
||||||
|
if (playcount <= 0) continue;
|
||||||
|
|
||||||
|
emit UpdatePlayCount(artist, title, playcount);
|
||||||
|
UpdateProgress();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == 1) {
|
||||||
|
for (int i = 2 ; i <= pages ; ++i) {
|
||||||
|
AddGetTopTracksRequest(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FinishCheck();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::UpdateTotal() {
|
||||||
|
|
||||||
|
if ((!playcount_ || playcount_total_ > 0) && (!lastplayed_ || lastplayed_total_ > 0))
|
||||||
|
emit UpdateTotal(lastplayed_total_, playcount_total_);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::UpdateProgress() {
|
||||||
|
emit UpdateProgress(lastplayed_received_, playcount_received_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::FinishCheck() {
|
||||||
|
if (replies_.isEmpty() && recent_tracks_requests_.isEmpty() && top_tracks_requests_.isEmpty()) emit Finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LastFMImport::Error(const QString &error, const QVariant &debug) {
|
||||||
|
|
||||||
|
qLog(Error) << error;
|
||||||
|
if (debug.isValid()) qLog(Debug) << debug;
|
||||||
|
|
||||||
|
emit FinishedWithError(error);
|
||||||
|
|
||||||
|
AbortAll();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2020, 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LASTFMIMPORT_H
|
||||||
|
#define LASTFMIMPORT_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QQueue>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
class QTimer;
|
||||||
|
class QNetworkReply;
|
||||||
|
|
||||||
|
class NetworkAccessManager;
|
||||||
|
|
||||||
|
class LastFMImport : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LastFMImport(QObject *parent = nullptr);
|
||||||
|
~LastFMImport() override;
|
||||||
|
|
||||||
|
void ReloadSettings();
|
||||||
|
void ImportData(const bool lastplayed = true, const bool playcount = true);
|
||||||
|
void AbortAll();
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef QPair<QString, QString> Param;
|
||||||
|
typedef QList<Param> ParamList;
|
||||||
|
|
||||||
|
struct GetRecentTracksRequest {
|
||||||
|
explicit GetRecentTracksRequest(const int _page) : page(_page) {}
|
||||||
|
int page;
|
||||||
|
};
|
||||||
|
struct GetTopTracksRequest {
|
||||||
|
explicit GetTopTracksRequest(const int _page) : page(_page) {}
|
||||||
|
int page;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
QNetworkReply *CreateRequest(const ParamList &request_params);
|
||||||
|
QByteArray GetReplyData(QNetworkReply *reply);
|
||||||
|
QJsonObject ExtractJsonObj(const QByteArray &data);
|
||||||
|
|
||||||
|
void AddGetRecentTracksRequest(const int page = 0);
|
||||||
|
void AddGetTopTracksRequest(const int page = 0);
|
||||||
|
|
||||||
|
void SendGetRecentTracksRequest(GetRecentTracksRequest request);
|
||||||
|
void SendGetTopTracksRequest(GetTopTracksRequest request);
|
||||||
|
|
||||||
|
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||||
|
|
||||||
|
void UpdateTotal();
|
||||||
|
void UpdateProgress();
|
||||||
|
|
||||||
|
void FinishCheck();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void UpdatePlayCount(QString, QString, int);
|
||||||
|
void UpdateLastPlayed(QString, QString, QString, int);
|
||||||
|
void UpdateTotal(int, int);
|
||||||
|
void UpdateProgress(int, int);
|
||||||
|
void Finished();
|
||||||
|
void FinishedWithError(QString);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void FlushRequests();
|
||||||
|
void GetRecentTracksRequestFinished(QNetworkReply *reply, const int page);
|
||||||
|
void GetTopTracksRequestFinished(QNetworkReply *reply, const int page);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const int kRequestsDelay;
|
||||||
|
|
||||||
|
NetworkAccessManager *network_;
|
||||||
|
QTimer *timer_flush_requests_;
|
||||||
|
|
||||||
|
QString username_;
|
||||||
|
bool lastplayed_;
|
||||||
|
bool playcount_;
|
||||||
|
int playcount_total_;
|
||||||
|
int lastplayed_total_;
|
||||||
|
int playcount_received_;
|
||||||
|
int lastplayed_received_;
|
||||||
|
QQueue<GetRecentTracksRequest> recent_tracks_requests_;
|
||||||
|
QQueue<GetTopTracksRequest> top_tracks_requests_;
|
||||||
|
QList<QNetworkReply*> replies_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LASTFMIMPORT_H
|
|
@ -42,13 +42,13 @@ class LastFMScrobbler : public ScrobblingAPI20 {
|
||||||
|
|
||||||
static const char *kName;
|
static const char *kName;
|
||||||
static const char *kSettingsGroup;
|
static const char *kSettingsGroup;
|
||||||
|
static const char *kApiUrl;
|
||||||
|
|
||||||
NetworkAccessManager *network() const override { return network_; }
|
NetworkAccessManager *network() const override { return network_; }
|
||||||
ScrobblerCache *cache() const override { return cache_; }
|
ScrobblerCache *cache() const override { return cache_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const char *kAuthUrl;
|
static const char *kAuthUrl;
|
||||||
static const char *kApiUrl;
|
|
||||||
static const char *kCacheFile;
|
static const char *kCacheFile;
|
||||||
|
|
||||||
QString settings_group_;
|
QString settings_group_;
|
||||||
|
|
|
@ -1013,7 +1013,7 @@ void ScrobblingAPI20::Error(const QString &error, const QVariant &debug) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ScrobblingAPI20::ErrorString(const ScrobbleErrorCode error) const {
|
QString ScrobblingAPI20::ErrorString(const ScrobbleErrorCode error) {
|
||||||
|
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case ScrobbleErrorCode::NoError:
|
case ScrobbleErrorCode::NoError:
|
||||||
|
|
|
@ -47,6 +47,7 @@ class ScrobblingAPI20 : public ScrobblerService {
|
||||||
explicit ScrobblingAPI20(const QString &name, const QString &settings_group, const QString &auth_url, const QString &api_url, const bool batch, Application *app, QObject *parent = nullptr);
|
explicit ScrobblingAPI20(const QString &name, const QString &settings_group, const QString &auth_url, const QString &api_url, const bool batch, Application *app, QObject *parent = nullptr);
|
||||||
~ScrobblingAPI20() override;
|
~ScrobblingAPI20() override;
|
||||||
|
|
||||||
|
static const char *kApiKey;
|
||||||
static const char *kRedirectUrl;
|
static const char *kRedirectUrl;
|
||||||
|
|
||||||
void ReloadSettings() override;
|
void ReloadSettings() override;
|
||||||
|
@ -118,7 +119,6 @@ class ScrobblingAPI20 : public ScrobblerService {
|
||||||
RateLimitExceeded = 29,
|
RateLimitExceeded = 29,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char *kApiKey;
|
|
||||||
static const char *kSecret;
|
static const char *kSecret;
|
||||||
static const int kScrobblesPerRequest;
|
static const int kScrobblesPerRequest;
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ class ScrobblingAPI20 : public ScrobblerService {
|
||||||
void AuthError(const QString &error);
|
void AuthError(const QString &error);
|
||||||
void SendSingleScrobble(ScrobblerCacheItemPtr item);
|
void SendSingleScrobble(ScrobblerCacheItemPtr item);
|
||||||
void Error(const QString &error, const QVariant &debug = QVariant()) override;
|
void Error(const QString &error, const QVariant &debug = QVariant()) override;
|
||||||
QString ErrorString(const ScrobbleErrorCode error) const;
|
static QString ErrorString(const ScrobbleErrorCode error);
|
||||||
void DoSubmit() override;
|
void DoSubmit() override;
|
||||||
void CheckScrobblePrevSong();
|
void CheckScrobblePrevSong();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue