diff --git a/CMakeLists.txt b/CMakeLists.txt index 89c816300..8714493a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -326,8 +326,6 @@ optional_component(TRANSLATIONS ON "Translations" DEPENDS "Qt5LinguistTools" Qt5LinguistTools_FOUND ) -optional_component(TIDAL ON "Tidal support") -optional_component(QOBUZ ON "Qobuz support") optional_component(SUBSONIC ON "Subsonic support") optional_component(MOODBAR ON "Moodbar" diff --git a/README.md b/README.md index 12eda6889..a1dceb899 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,15 @@ Strawberry is a music player and music collection organizer. It is a fork of Cle * Advanced audio output and device configuration for bit-perfect playback on Linux * Edit tags on music files * Fetch tags from MusicBrainz - * Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Deezer](https://www.deezer.com/) and [Tidal](https://tidal.com/) + * Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/) and [Deezer](https://www.deezer.com/) * Song lyrics from [AudD](https://audd.io/), [lyrics.ovh](https://lyrics.ovh/) and [lololyrics.com](https://www.lololyrics.com/) * Support for multiple backends * Audio analyzer * Audio equalizer * Transfer music to iPod, iPhone, MTP or mass-storage USB player - * Subsonic streaming support - * Unofficial streaming support for [Tidal](https://tidal.com/) and [Qobuz](https://www.qobuz.com/) * Scrobbler with support for [Last.fm](https://www.last.fm/), [Libre.fm](https://libre.fm/) and [ListenBrainz](https://listenbrainz.org/) - -**Tidal and Qobuz streaming in Strawberry is unofficial. You need an official API token (or App ID/Secret) to use it, we can not provide API tokens, or help getting them. Tidal will not work with Tidal Masters (MQA), because MQA is a proprietary format in lossy quality without an open source decoder, we can't support it.** + * Subsonic streaming support + It has so far been tested to work on Linux, OpenBSD and Windows. diff --git a/data/data.qrc b/data/data.qrc index 001450bd6..b8dac0813 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -10,6 +10,7 @@ schema/schema-7.sql schema/schema-8.sql schema/schema-9.sql + schema/schema-10.sql schema/device-schema.sql style/strawberry.css html/playing-tooltip-plain.html diff --git a/data/icons.qrc b/data/icons.qrc index 242c2d300..ff491cc7a 100644 --- a/data/icons.qrc +++ b/data/icons.qrc @@ -89,8 +89,6 @@ icons/128x128/moodbar.png icons/128x128/love.png icons/128x128/subsonic.png - icons/128x128/tidal.png - icons/128x128/qobuz.png icons/64x64/albums.png icons/64x64/alsa.png icons/64x64/application-exit.png @@ -181,8 +179,6 @@ icons/64x64/moodbar.png icons/64x64/love.png icons/64x64/subsonic.png - icons/64x64/tidal.png - icons/64x64/qobuz.png icons/48x48/albums.png icons/48x48/alsa.png icons/48x48/application-exit.png @@ -276,8 +272,6 @@ icons/48x48/moodbar.png icons/48x48/love.png icons/48x48/subsonic.png - icons/48x48/tidal.png - icons/48x48/qobuz.png icons/32x32/albums.png icons/32x32/alsa.png icons/32x32/application-exit.png @@ -371,8 +365,6 @@ icons/32x32/moodbar.png icons/32x32/love.png icons/32x32/subsonic.png - icons/32x32/tidal.png - icons/32x32/qobuz.png icons/22x22/albums.png icons/22x22/alsa.png icons/22x22/application-exit.png @@ -466,7 +458,5 @@ icons/22x22/moodbar.png icons/22x22/love.png icons/22x22/subsonic.png - icons/22x22/tidal.png - icons/22x22/qobuz.png diff --git a/data/icons/128x128/qobuz.png b/data/icons/128x128/qobuz.png deleted file mode 100644 index 4b6b17756..000000000 Binary files a/data/icons/128x128/qobuz.png and /dev/null differ diff --git a/data/icons/128x128/tidal.png b/data/icons/128x128/tidal.png deleted file mode 100644 index 5b7e2f11e..000000000 Binary files a/data/icons/128x128/tidal.png and /dev/null differ diff --git a/data/icons/22x22/qobuz.png b/data/icons/22x22/qobuz.png deleted file mode 100644 index 09834a5f1..000000000 Binary files a/data/icons/22x22/qobuz.png and /dev/null differ diff --git a/data/icons/22x22/tidal.png b/data/icons/22x22/tidal.png deleted file mode 100644 index 496105541..000000000 Binary files a/data/icons/22x22/tidal.png and /dev/null differ diff --git a/data/icons/32x32/qobuz.png b/data/icons/32x32/qobuz.png deleted file mode 100644 index c9c6b8a32..000000000 Binary files a/data/icons/32x32/qobuz.png and /dev/null differ diff --git a/data/icons/32x32/tidal.png b/data/icons/32x32/tidal.png deleted file mode 100644 index c206ac0a3..000000000 Binary files a/data/icons/32x32/tidal.png and /dev/null differ diff --git a/data/icons/48x48/qobuz.png b/data/icons/48x48/qobuz.png deleted file mode 100644 index 08af18ceb..000000000 Binary files a/data/icons/48x48/qobuz.png and /dev/null differ diff --git a/data/icons/48x48/tidal.png b/data/icons/48x48/tidal.png deleted file mode 100644 index fb0143432..000000000 Binary files a/data/icons/48x48/tidal.png and /dev/null differ diff --git a/data/icons/64x64/qobuz.png b/data/icons/64x64/qobuz.png deleted file mode 100644 index 4c7f31b4e..000000000 Binary files a/data/icons/64x64/qobuz.png and /dev/null differ diff --git a/data/icons/64x64/tidal.png b/data/icons/64x64/tidal.png deleted file mode 100644 index cbd33703d..000000000 Binary files a/data/icons/64x64/tidal.png and /dev/null differ diff --git a/data/icons/full/qobuz.png b/data/icons/full/qobuz.png deleted file mode 100644 index c7ad97fdb..000000000 Binary files a/data/icons/full/qobuz.png and /dev/null differ diff --git a/data/icons/full/tidal.png b/data/icons/full/tidal.png deleted file mode 100644 index 316831c69..000000000 Binary files a/data/icons/full/tidal.png and /dev/null differ diff --git a/data/schema/schema-10.sql b/data/schema/schema-10.sql new file mode 100644 index 000000000..971f59f14 --- /dev/null +++ b/data/schema/schema-10.sql @@ -0,0 +1,25 @@ +DROP TABLE IF EXISTS tidal_artists_songs; + +DROP TABLE IF EXISTS tidal_albums_songs; + +DROP TABLE IF EXISTS tidal_songs; + +DROP TABLE IF EXISTS qobuz_artists_songs; + +DROP TABLE IF EXISTS qobuz_albums_songs; + +DROP TABLE IF EXISTS qobuz_songs; + +DROP TABLE IF EXISTS tidal_artists_songs_fts; + +DROP TABLE IF EXISTS tidal_albums_songs_fts; + +DROP TABLE IF EXISTS tidal_songs_fts; + +DROP TABLE IF EXISTS qobuz_artists_songs_fts; + +DROP TABLE IF EXISTS qobuz_albums_songs_fts; + +DROP TABLE IF EXISTS qobuz_songs_fts; + +UPDATE schema_version SET version=10; diff --git a/data/schema/schema.sql b/data/schema/schema.sql index 4d54de7cd..d21148e09 100644 --- a/data/schema/schema.sql +++ b/data/schema/schema.sql @@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version ( DELETE FROM schema_version; -INSERT INTO schema_version (version) VALUES (9); +INSERT INTO schema_version (version) VALUES (10); CREATE TABLE IF NOT EXISTS directories ( path TEXT NOT NULL, @@ -74,177 +74,6 @@ CREATE TABLE IF NOT EXISTS songs ( ); -CREATE TABLE IF NOT EXISTS tidal_artists_songs ( - - title TEXT NOT NULL, - album TEXT NOT NULL, - artist TEXT NOT NULL, - albumartist TEXT NOT NULL, - track INTEGER NOT NULL DEFAULT -1, - disc INTEGER NOT NULL DEFAULT -1, - year INTEGER NOT NULL DEFAULT -1, - originalyear INTEGER NOT NULL DEFAULT 0, - genre TEXT NOT NULL, - compilation INTEGER NOT NULL DEFAULT -1, - composer TEXT NOT NULL, - performer TEXT NOT NULL, - grouping TEXT NOT NULL, - comment TEXT NOT NULL, - lyrics TEXT NOT NULL, - - artist_id INTEGER NOT NULL DEFAULT -1, - album_id TEXT NOT NULL, - song_id INTEGER NOT NULL DEFAULT -1, - - beginning INTEGER NOT NULL DEFAULT 0, - length INTEGER NOT NULL DEFAULT 0, - - bitrate INTEGER NOT NULL DEFAULT -1, - samplerate INTEGER NOT NULL DEFAULT -1, - bitdepth INTEGER NOT NULL DEFAULT -1, - - source INTEGER NOT NULL DEFAULT 0, - directory_id INTEGER NOT NULL DEFAULT -1, - url TEXT NOT NULL, - filetype INTEGER NOT NULL DEFAULT 0, - filesize INTEGER NOT NULL DEFAULT -1, - mtime INTEGER NOT NULL DEFAULT -1, - ctime INTEGER NOT NULL DEFAULT -1, - unavailable INTEGER DEFAULT 0, - - playcount INTEGER NOT NULL DEFAULT 0, - skipcount INTEGER NOT NULL DEFAULT 0, - lastplayed INTEGER NOT NULL DEFAULT -1, - - compilation_detected INTEGER DEFAULT 0, - compilation_on INTEGER NOT NULL DEFAULT 0, - compilation_off INTEGER NOT NULL DEFAULT 0, - compilation_effective INTEGER NOT NULL DEFAULT 0, - - art_automatic TEXT, - art_manual TEXT, - - effective_albumartist TEXT, - effective_originalyear INTEGER NOT NULL DEFAULT 0, - - cue_path TEXT - -); - -CREATE TABLE IF NOT EXISTS tidal_albums_songs ( - - title TEXT NOT NULL, - album TEXT NOT NULL, - artist TEXT NOT NULL, - albumartist TEXT NOT NULL, - track INTEGER NOT NULL DEFAULT -1, - disc INTEGER NOT NULL DEFAULT -1, - year INTEGER NOT NULL DEFAULT -1, - originalyear INTEGER NOT NULL DEFAULT 0, - genre TEXT NOT NULL, - compilation INTEGER NOT NULL DEFAULT -1, - composer TEXT NOT NULL, - performer TEXT NOT NULL, - grouping TEXT NOT NULL, - comment TEXT NOT NULL, - lyrics TEXT NOT NULL, - - artist_id INTEGER NOT NULL DEFAULT -1, - album_id TEXT NOT NULL, - song_id INTEGER NOT NULL DEFAULT -1, - - beginning INTEGER NOT NULL DEFAULT 0, - length INTEGER NOT NULL DEFAULT 0, - - bitrate INTEGER NOT NULL DEFAULT -1, - samplerate INTEGER NOT NULL DEFAULT -1, - bitdepth INTEGER NOT NULL DEFAULT -1, - - source INTEGER NOT NULL DEFAULT 0, - directory_id INTEGER NOT NULL DEFAULT -1, - url TEXT NOT NULL, - filetype INTEGER NOT NULL DEFAULT 0, - filesize INTEGER NOT NULL DEFAULT -1, - mtime INTEGER NOT NULL DEFAULT -1, - ctime INTEGER NOT NULL DEFAULT -1, - unavailable INTEGER DEFAULT 0, - - playcount INTEGER NOT NULL DEFAULT 0, - skipcount INTEGER NOT NULL DEFAULT 0, - lastplayed INTEGER NOT NULL DEFAULT -1, - - compilation_detected INTEGER DEFAULT 0, - compilation_on INTEGER NOT NULL DEFAULT 0, - compilation_off INTEGER NOT NULL DEFAULT 0, - compilation_effective INTEGER NOT NULL DEFAULT 0, - - art_automatic TEXT, - art_manual TEXT, - - effective_albumartist TEXT, - effective_originalyear INTEGER NOT NULL DEFAULT 0, - - cue_path TEXT - -); - -CREATE TABLE IF NOT EXISTS tidal_songs ( - - title TEXT NOT NULL, - album TEXT NOT NULL, - artist TEXT NOT NULL, - albumartist TEXT NOT NULL, - track INTEGER NOT NULL DEFAULT -1, - disc INTEGER NOT NULL DEFAULT -1, - year INTEGER NOT NULL DEFAULT -1, - originalyear INTEGER NOT NULL DEFAULT 0, - genre TEXT NOT NULL, - compilation INTEGER NOT NULL DEFAULT -1, - composer TEXT NOT NULL, - performer TEXT NOT NULL, - grouping TEXT NOT NULL, - comment TEXT NOT NULL, - lyrics TEXT NOT NULL, - - artist_id INTEGER NOT NULL DEFAULT -1, - album_id TEXT NOT NULL, - song_id INTEGER NOT NULL DEFAULT -1, - - beginning INTEGER NOT NULL DEFAULT 0, - length INTEGER NOT NULL DEFAULT 0, - - bitrate INTEGER NOT NULL DEFAULT -1, - samplerate INTEGER NOT NULL DEFAULT -1, - bitdepth INTEGER NOT NULL DEFAULT -1, - - source INTEGER NOT NULL DEFAULT 0, - directory_id INTEGER NOT NULL DEFAULT -1, - url TEXT NOT NULL, - filetype INTEGER NOT NULL DEFAULT 0, - filesize INTEGER NOT NULL DEFAULT -1, - mtime INTEGER NOT NULL DEFAULT -1, - ctime INTEGER NOT NULL DEFAULT -1, - unavailable INTEGER DEFAULT 0, - - playcount INTEGER NOT NULL DEFAULT 0, - skipcount INTEGER NOT NULL DEFAULT 0, - lastplayed INTEGER NOT NULL DEFAULT -1, - - compilation_detected INTEGER DEFAULT 0, - compilation_on INTEGER NOT NULL DEFAULT 0, - compilation_off INTEGER NOT NULL DEFAULT 0, - compilation_effective INTEGER NOT NULL DEFAULT 0, - - art_automatic TEXT, - art_manual TEXT, - - effective_albumartist TEXT, - effective_originalyear INTEGER NOT NULL DEFAULT 0, - - cue_path TEXT - -); - CREATE TABLE IF NOT EXISTS subsonic_songs ( title TEXT NOT NULL, @@ -302,177 +131,6 @@ CREATE TABLE IF NOT EXISTS subsonic_songs ( ); -CREATE TABLE IF NOT EXISTS qobuz_artists_songs ( - - title TEXT NOT NULL, - album TEXT NOT NULL, - artist TEXT NOT NULL, - albumartist TEXT NOT NULL, - track INTEGER NOT NULL DEFAULT -1, - disc INTEGER NOT NULL DEFAULT -1, - year INTEGER NOT NULL DEFAULT -1, - originalyear INTEGER NOT NULL DEFAULT 0, - genre TEXT NOT NULL, - compilation INTEGER NOT NULL DEFAULT -1, - composer TEXT NOT NULL, - performer TEXT NOT NULL, - grouping TEXT NOT NULL, - comment TEXT NOT NULL, - lyrics TEXT NOT NULL, - - artist_id INTEGER NOT NULL DEFAULT -1, - album_id TEXT NOT NULL, - song_id INTEGER NOT NULL DEFAULT -1, - - beginning INTEGER NOT NULL DEFAULT 0, - length INTEGER NOT NULL DEFAULT 0, - - bitrate INTEGER NOT NULL DEFAULT -1, - samplerate INTEGER NOT NULL DEFAULT -1, - bitdepth INTEGER NOT NULL DEFAULT -1, - - source INTEGER NOT NULL DEFAULT 0, - directory_id INTEGER NOT NULL DEFAULT -1, - url TEXT NOT NULL, - filetype INTEGER NOT NULL DEFAULT 0, - filesize INTEGER NOT NULL DEFAULT -1, - mtime INTEGER NOT NULL DEFAULT -1, - ctime INTEGER NOT NULL DEFAULT -1, - unavailable INTEGER DEFAULT 0, - - playcount INTEGER NOT NULL DEFAULT 0, - skipcount INTEGER NOT NULL DEFAULT 0, - lastplayed INTEGER NOT NULL DEFAULT -1, - - compilation_detected INTEGER DEFAULT 0, - compilation_on INTEGER NOT NULL DEFAULT 0, - compilation_off INTEGER NOT NULL DEFAULT 0, - compilation_effective INTEGER NOT NULL DEFAULT 0, - - art_automatic TEXT, - art_manual TEXT, - - effective_albumartist TEXT, - effective_originalyear INTEGER NOT NULL DEFAULT 0, - - cue_path TEXT - -); - -CREATE TABLE IF NOT EXISTS qobuz_albums_songs ( - - title TEXT NOT NULL, - album TEXT NOT NULL, - artist TEXT NOT NULL, - albumartist TEXT NOT NULL, - track INTEGER NOT NULL DEFAULT -1, - disc INTEGER NOT NULL DEFAULT -1, - year INTEGER NOT NULL DEFAULT -1, - originalyear INTEGER NOT NULL DEFAULT 0, - genre TEXT NOT NULL, - compilation INTEGER NOT NULL DEFAULT -1, - composer TEXT NOT NULL, - performer TEXT NOT NULL, - grouping TEXT NOT NULL, - comment TEXT NOT NULL, - lyrics TEXT NOT NULL, - - artist_id INTEGER NOT NULL DEFAULT -1, - album_id TEXT NOT NULL, - song_id INTEGER NOT NULL DEFAULT -1, - - beginning INTEGER NOT NULL DEFAULT 0, - length INTEGER NOT NULL DEFAULT 0, - - bitrate INTEGER NOT NULL DEFAULT -1, - samplerate INTEGER NOT NULL DEFAULT -1, - bitdepth INTEGER NOT NULL DEFAULT -1, - - source INTEGER NOT NULL DEFAULT 0, - directory_id INTEGER NOT NULL DEFAULT -1, - url TEXT NOT NULL, - filetype INTEGER NOT NULL DEFAULT 0, - filesize INTEGER NOT NULL DEFAULT -1, - mtime INTEGER NOT NULL DEFAULT -1, - ctime INTEGER NOT NULL DEFAULT -1, - unavailable INTEGER DEFAULT 0, - - playcount INTEGER NOT NULL DEFAULT 0, - skipcount INTEGER NOT NULL DEFAULT 0, - lastplayed INTEGER NOT NULL DEFAULT -1, - - compilation_detected INTEGER DEFAULT 0, - compilation_on INTEGER NOT NULL DEFAULT 0, - compilation_off INTEGER NOT NULL DEFAULT 0, - compilation_effective INTEGER NOT NULL DEFAULT 0, - - art_automatic TEXT, - art_manual TEXT, - - effective_albumartist TEXT, - effective_originalyear INTEGER NOT NULL DEFAULT 0, - - cue_path TEXT - -); - -CREATE TABLE IF NOT EXISTS qobuz_songs ( - - title TEXT NOT NULL, - album TEXT NOT NULL, - artist TEXT NOT NULL, - albumartist TEXT NOT NULL, - track INTEGER NOT NULL DEFAULT -1, - disc INTEGER NOT NULL DEFAULT -1, - year INTEGER NOT NULL DEFAULT -1, - originalyear INTEGER NOT NULL DEFAULT 0, - genre TEXT NOT NULL, - compilation INTEGER NOT NULL DEFAULT -1, - composer TEXT NOT NULL, - performer TEXT NOT NULL, - grouping TEXT NOT NULL, - comment TEXT NOT NULL, - lyrics TEXT NOT NULL, - - artist_id INTEGER NOT NULL DEFAULT -1, - album_id TEXT NOT NULL, - song_id INTEGER NOT NULL DEFAULT -1, - - beginning INTEGER NOT NULL DEFAULT 0, - length INTEGER NOT NULL DEFAULT 0, - - bitrate INTEGER NOT NULL DEFAULT -1, - samplerate INTEGER NOT NULL DEFAULT -1, - bitdepth INTEGER NOT NULL DEFAULT -1, - - source INTEGER NOT NULL DEFAULT 0, - directory_id INTEGER NOT NULL DEFAULT -1, - url TEXT NOT NULL, - filetype INTEGER NOT NULL DEFAULT 0, - filesize INTEGER NOT NULL DEFAULT -1, - mtime INTEGER NOT NULL DEFAULT -1, - ctime INTEGER NOT NULL DEFAULT -1, - unavailable INTEGER DEFAULT 0, - - playcount INTEGER NOT NULL DEFAULT 0, - skipcount INTEGER NOT NULL DEFAULT 0, - lastplayed INTEGER NOT NULL DEFAULT -1, - - compilation_detected INTEGER DEFAULT 0, - compilation_on INTEGER NOT NULL DEFAULT 0, - compilation_off INTEGER NOT NULL DEFAULT 0, - compilation_effective INTEGER NOT NULL DEFAULT 0, - - art_automatic TEXT, - art_manual TEXT, - - effective_albumartist TEXT, - effective_originalyear INTEGER NOT NULL DEFAULT 0, - - cue_path TEXT - -); - CREATE TABLE IF NOT EXISTS playlists ( name TEXT NOT NULL, @@ -585,51 +243,6 @@ CREATE VIRTUAL TABLE IF NOT EXISTS songs_fts USING fts5( ); -CREATE VIRTUAL TABLE IF NOT EXISTS tidal_artists_songs_fts USING fts5( - - ftstitle, - ftsalbum, - ftsartist, - ftsalbumartist, - ftscomposer, - ftsperformer, - ftsgrouping, - ftsgenre, - ftscomment, - tokenize = "unicode61 remove_diacritics 0" - -); - -CREATE VIRTUAL TABLE IF NOT EXISTS tidal_albums_songs_fts USING fts5( - - ftstitle, - ftsalbum, - ftsartist, - ftsalbumartist, - ftscomposer, - ftsperformer, - ftsgrouping, - ftsgenre, - ftscomment, - tokenize = "unicode61 remove_diacritics 0" - -); - -CREATE VIRTUAL TABLE IF NOT EXISTS tidal_songs_fts USING fts5( - - ftstitle, - ftsalbum, - ftsartist, - ftsalbumartist, - ftscomposer, - ftsperformer, - ftsgrouping, - ftsgenre, - ftscomment, - tokenize = "unicode61 remove_diacritics 0" - -); - CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts5( ftstitle, @@ -645,51 +258,6 @@ CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts5( ); -CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts5( - - ftstitle, - ftsalbum, - ftsartist, - ftsalbumartist, - ftscomposer, - ftsperformer, - ftsgrouping, - ftsgenre, - ftscomment, - tokenize = "unicode61 remove_diacritics 0" - -); - -CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts5( - - ftstitle, - ftsalbum, - ftsartist, - ftsalbumartist, - ftscomposer, - ftsperformer, - ftsgrouping, - ftsgenre, - ftscomment, - tokenize = "unicode61 remove_diacritics 0" - -); - -CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts5( - - ftstitle, - ftsalbum, - ftsartist, - ftsalbumartist, - ftscomposer, - ftsperformer, - ftsgrouping, - ftsgenre, - ftscomment, - tokenize = "unicode61 remove_diacritics 0" - -); - CREATE VIRTUAL TABLE IF NOT EXISTS playlist_items_fts_ USING fts5( ftstitle, diff --git a/debian/control b/debian/control index 11d55ee14..2e6acbaa3 100644 --- a/debian/control +++ b/debian/control @@ -55,13 +55,13 @@ Description: Audio player and music collection organizer - Advanced audio output and device configuration for bit-perfect playback on Linux - Edit tags on music files - Fetch tags from MusicBrainz - - Album cover art from Lastfm, Musicbrainz, Discogs, Deezer and Tidal + - Album cover art from Lastfm, Musicbrainz, Discogs and Deezer - Song lyrics from AudD, lyrics.ovh and lololyrics.com - Support for multiple backends - Audio analyzer - Audio equalizer - Transfer music to iPod, iPhone, MTP or mass-storage USB player - - Streaming support for Tidal, Qobuz and Subsonic - Scrobbler with support for Last.fm, Libre.fm and ListenBrainz + - Streaming support for Subsonic . It is a fork of Clementine. The name is inspired by the band Strawbs. diff --git a/debian/copyright b/debian/copyright index 8cb570695..6d615810d 100644 --- a/debian/copyright +++ b/debian/copyright @@ -44,14 +44,10 @@ Files: src/core/main.h src/settings/backendsettingspage.h src/settings/scrobblersettingspage.cpp src/settings/scrobblersettingspage.h - src/settings/tidalsettingspage.cpp - src/settings/tidalsettingspage.h src/covermanager/lastfmcoverprovider.cpp src/covermanager/lastfmcoverprovider.h src/covermanager/musicbrainzcoverprovider.cpp src/covermanager/musicbrainzcoverprovider.h - src/covermanager/tidalcoverprovider.cpp - src/covermanager/tidalcoverprovider.h src/covermanager/deezercoverprovider.cpp src/covermanager/deezercoverprovider.h src/globalshortcuts/globalshortcutbackend-system.cpp @@ -64,8 +60,6 @@ Files: src/core/main.h src/globalshortcuts/keymapper_win.h src/lyrics/* src/scrobbler/* - src/tidal/* - src/qobuz/* src/subsonic/* src/transcoder/transcoderoptionswavpack.cpp src/transcoder/transcoderoptionswavpack.h diff --git a/dist/man/strawberry.1 b/dist/man/strawberry.1 index 340995e21..791f15efb 100644 --- a/dist/man/strawberry.1 +++ b/dist/man/strawberry.1 @@ -25,7 +25,7 @@ Features: .br - Fetch tags from MusicBrainz .br -- Album cover art from Lastfm, Musicbrainz, Discogs, Deezer and Tidal +- Album cover art from Lastfm, Musicbrainz, Discogs and Deezer .br - Song lyrics from AudD, lyrics.ovh and lololyrics.com .br @@ -37,7 +37,7 @@ Features: .br - Transfer music to iPod, iPhone, MTP or mass-storage USB player .br -- Streaming from Tidal, Qobuz and Subsonic +- Streaming from Subsonic .TP It is a fork of Clementine. The name is inspired by the band Strawbs. .SH OPTIONS diff --git a/dist/rpm/strawberry.spec.in b/dist/rpm/strawberry.spec.in index eef19bef7..129951ce4 100644 --- a/dist/rpm/strawberry.spec.in +++ b/dist/rpm/strawberry.spec.in @@ -104,14 +104,14 @@ Features: - Advanced audio output and device configuration for bit-perfect playback on Linux - Edit tags on music files - Fetch tags from MusicBrainz - - Album cover art from Last.fm, Musicbrainz, Discogs, Deezer and Tidal + - Album cover art from Last.fm, Musicbrainz, Discogs and Deezer - Song lyrics from AudD, lyrics.ovh and lololyrics.com - Support for multiple backends - Audio analyzer - Audio equalizer - Transfer music to iPod, iPhone, MTP or mass-storage USB player - - Streaming support for Tidal, Qobuz and Subsonic - Scrobbler with support for Last.fm, Libre.fm and ListenBrainz + - Streaming support for Subsonic %prep %setup -qn %{name}-@STRAWBERRY_VERSION_PACKAGE@ diff --git a/dist/unix/org.strawberrymusicplayer.strawberry.appdata.xml b/dist/unix/org.strawberrymusicplayer.strawberry.appdata.xml index 83740ff08..41a1a6a0d 100644 --- a/dist/unix/org.strawberrymusicplayer.strawberry.appdata.xml +++ b/dist/unix/org.strawberrymusicplayer.strawberry.appdata.xml @@ -31,8 +31,8 @@
  • Support for multiple backends
  • Audio analyzer and equalizer
  • Transfer music to iPod, iPhone, MTP or mass-storage USB player
  • -
  • Streaming support for Tidal, Qobuz and Subsonic
  • Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
  • +
  • Streaming support for Subsonic
  • diff --git a/dist/unix/org.strawberrymusicplayer.strawberry.desktop b/dist/unix/org.strawberrymusicplayer.strawberry.desktop index c687087af..0e1538758 100755 --- a/dist/unix/org.strawberrymusicplayer.strawberry.desktop +++ b/dist/unix/org.strawberrymusicplayer.strawberry.desktop @@ -10,5 +10,5 @@ Icon=strawberry Terminal=false Categories=AudioVideo;Player;Qt;Audio; StartupNotify=false -MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal; +MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf StartupWMClass=strawberry diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f308f332b..e9dd0db50 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -906,50 +906,6 @@ optional_source(WIN32 core/windows7thumbbar.h ) -optional_source(HAVE_TIDAL - SOURCES - tidal/tidalservice.cpp - tidal/tidalurlhandler.cpp - tidal/tidalbaserequest.cpp - tidal/tidalrequest.cpp - tidal/tidalstreamurlrequest.cpp - tidal/tidalfavoriterequest.cpp - settings/tidalsettingspage.cpp - covermanager/tidalcoverprovider.cpp - HEADERS - tidal/tidalservice.h - tidal/tidalurlhandler.h - tidal/tidalbaserequest.h - tidal/tidalrequest.h - tidal/tidalstreamurlrequest.h - tidal/tidalfavoriterequest.h - settings/tidalsettingspage.h - covermanager/tidalcoverprovider.h - UI - settings/tidalsettingspage.ui -) - -optional_source(HAVE_QOBUZ - SOURCES - qobuz/qobuzservice.cpp - qobuz/qobuzurlhandler.cpp - qobuz/qobuzbaserequest.cpp - qobuz/qobuzrequest.cpp - qobuz/qobuzstreamurlrequest.cpp - qobuz/qobuzfavoriterequest.cpp - settings/qobuzsettingspage.cpp - HEADERS - qobuz/qobuzservice.h - qobuz/qobuzurlhandler.h - qobuz/qobuzbaserequest.h - qobuz/qobuzrequest.h - qobuz/qobuzstreamurlrequest.h - qobuz/qobuzfavoriterequest.h - settings/qobuzsettingspage.h - UI - settings/qobuzsettingspage.ui -) - optional_source(HAVE_SUBSONIC SOURCES subsonic/subsonicservice.cpp diff --git a/src/config.h.in b/src/config.h.in index a474395cd..1755932e5 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -47,9 +47,7 @@ #cmakedefine HAVE_PHONON #cmakedefine XINE_ANALYZER -#cmakedefine HAVE_TIDAL #cmakedefine HAVE_SUBSONIC -#cmakedefine HAVE_QOBUZ #cmakedefine HAVE_MOODBAR diff --git a/src/core/application.cpp b/src/core/application.cpp index 645e6bb72..df2ec261e 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -67,15 +67,6 @@ #include "internet/internetservices.h" #include "internet/internetsearch.h" -#ifdef HAVE_TIDAL -# include "tidal/tidalservice.h" -# include "covermanager/tidalcoverprovider.h" -#endif - -#ifdef HAVE_QOBUZ -# include "qobuz/qobuzservice.h" -#endif - #ifdef HAVE_SUBSONIC # include "subsonic/subsonicservice.h" #endif @@ -121,9 +112,6 @@ class ApplicationImpl { cover_providers->AddProvider(new DiscogsCoverProvider(app, app)); cover_providers->AddProvider(new MusicbrainzCoverProvider(app, app)); cover_providers->AddProvider(new DeezerCoverProvider(app, app)); -#ifdef HAVE_TIDAL - cover_providers->AddProvider(new TidalCoverProvider(app, app)); -#endif return cover_providers; }), album_cover_loader_([=]() { @@ -141,23 +129,11 @@ class ApplicationImpl { }), internet_services_([=]() { InternetServices *internet_services = new InternetServices(app); -#ifdef HAVE_TIDAL - internet_services->AddService(new TidalService(app, internet_services)); -#endif -#ifdef HAVE_QOBUZ - internet_services->AddService(new QobuzService(app, internet_services)); -#endif #ifdef HAVE_SUBSONIC internet_services->AddService(new SubsonicService(app, internet_services)); #endif return internet_services; }), -#ifdef HAVE_TIDAL - tidal_search_([=]() { return new InternetSearch(app, Song::Source_Tidal, app); }), -#endif -#ifdef HAVE_QOBUZ - qobuz_search_([=]() { return new InternetSearch(app, Song::Source_Qobuz, app); }), -#endif scrobbler_([=]() { return new AudioScrobbler(app, app); }), #ifdef HAVE_MOODBAR @@ -185,12 +161,6 @@ class ApplicationImpl { Lazy current_albumcover_loader_; Lazy lyrics_providers_; Lazy internet_services_; -#ifdef HAVE_TIDAL - Lazy tidal_search_; -#endif -#ifdef HAVE_QOBUZ - Lazy qobuz_search_; -#endif Lazy scrobbler_; #ifdef HAVE_MOODBAR Lazy moodbar_loader_; @@ -319,12 +289,6 @@ LyricsProviders *Application::lyrics_providers() const { return p_->lyrics_provi PlaylistBackend *Application::playlist_backend() const { return p_->playlist_backend_.get(); } PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); } InternetServices *Application::internet_services() const { return p_->internet_services_.get(); } -#ifdef HAVE_TIDAL -InternetSearch *Application::tidal_search() const { return p_->tidal_search_.get(); } -#endif -#ifdef HAVE_QOBUZ -InternetSearch *Application::qobuz_search() const { return p_->qobuz_search_.get(); } -#endif AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); } #ifdef HAVE_MOODBAR MoodbarController *Application::moodbar_controller() const { return p_->moodbar_controller_.get(); } diff --git a/src/core/application.h b/src/core/application.h index d6f44ab79..51862f523 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -97,12 +97,6 @@ class Application : public QObject { AudioScrobbler *scrobbler() const; InternetServices *internet_services() const; -#ifdef HAVE_TIDAL - InternetSearch *tidal_search() const; -#endif -#ifdef HAVE_QOBUZ - InternetSearch *qobuz_search() const; -#endif #ifdef HAVE_MOODBAR MoodbarController *moodbar_controller() const; @@ -114,7 +108,7 @@ class Application : public QObject { QThread *MoveToNewThread(QObject *object); void MoveToThread(QObject *object, QThread *thread); -private slots: + private slots: void ExitReceived(); public slots: @@ -122,7 +116,7 @@ private slots: void ReloadSettings(); void OpenSettingsDialogAtPage(SettingsDialog::Page page); -signals: + signals: void ErrorAdded(const QString &message); void SettingsChanged(); void SettingsDialogRequested(SettingsDialog::Page page); diff --git a/src/core/database.cpp b/src/core/database.cpp index 31734b29a..307a78031 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -54,7 +54,7 @@ #include "scopedtransaction.h" const char *Database::kDatabaseFilename = "strawberry.db"; -const int Database::kSchemaVersion = 9; +const int Database::kSchemaVersion = 10; const char *Database::kMagicAllSongsTables = "%allsongstables"; int Database::sNextConnectionId = 1; diff --git a/src/core/iconmapper.h b/src/core/iconmapper.h index 162732767..313770768 100644 --- a/src/core/iconmapper.h +++ b/src/core/iconmapper.h @@ -105,7 +105,6 @@ static const QMap iconmapper_ = { { "moodbar", { {"preferences-desktop-icons"}, 0, 0 } }, { "nvidia", { {}, 0, 0 } }, { "pulseaudio", { {}, 0, 0 } }, - { "qobuz", { {}, 0, 0 } }, { "realtek", { {}, 0, 0 } }, { "scrobble-disabled", { {}, 0, 0 } }, { "scrobble", { {}, 0, 0 } }, @@ -116,7 +115,6 @@ static const QMap iconmapper_ = { { "star", { {}, 0, 0 } }, { "strawberry", { {}, 0, 0 } }, { "subsonic", { {}, 0, 0 } }, - { "tidal", { {}, 0, 0 } }, { "tools-wizard", { {}, 0, 0 } }, { "view-choose", { {}, 0, 0 } }, { "view-fullscreen", { {}, 0, 0 } }, diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index 95b06aa36..b6e117fd1 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -144,13 +144,6 @@ #include "settings/behavioursettingspage.h" #include "settings/backendsettingspage.h" #include "settings/playlistsettingspage.h" -#ifdef HAVE_TIDAL -# include "tidal/tidalservice.h" -# include "settings/tidalsettingspage.h" -#endif -#ifdef HAVE_QOBUZ -# include "settings/qobuzsettingspage.h" -#endif #ifdef HAVE_SUBSONIC # include "settings/subsonicsettingspage.h" #endif @@ -230,12 +223,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co dialog->SetDestinationModel(app->collection()->model()->directory_model()); return dialog; }), -#ifdef HAVE_TIDAL - tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Tidal), app_->tidal_search(), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)), -#endif -#ifdef HAVE_QOBUZ - qobuz_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Qobuz), app_->qobuz_search(), QobuzSettingsPage::kSettingsGroup, SettingsDialog::Page_Qobuz, this)), -#endif #ifdef HAVE_SUBSONIC subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)), #endif @@ -288,12 +275,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co #ifndef Q_OS_WIN ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device"), tr("Devices")); #endif -#ifdef HAVE_TIDAL - ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal"), tr("Tidal")); -#endif -#ifdef HAVE_QOBUZ - ui_->tabs->AddTab(qobuz_view_, "qobuz", IconLoader::Load("qobuz"), tr("Qobuz")); -#endif #ifdef HAVE_SUBSONIC ui_->tabs->AddTab(subsonic_view_, "subsonic", IconLoader::Load("subsonic"), tr("Subsonic")); #endif @@ -578,22 +559,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co collection_view_->filter()->AddMenuAction(separator); collection_view_->filter()->AddMenuAction(collection_config_action); -#ifdef HAVE_TIDAL - connect(tidal_view_->artists_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); - 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*))); - if (TidalService *tidalservice = qobject_cast (app_->internet_services()->ServiceBySource(Song::Source_Tidal))) - connect(this, SIGNAL(AuthorisationUrlReceived(const QUrl&)), tidalservice, SLOT(AuthorisationUrlReceived(const QUrl&))); -#endif - -#ifdef HAVE_QOBUZ - connect(qobuz_view_->artists_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); - connect(qobuz_view_->albums_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); - connect(qobuz_view_->songs_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); - connect(qobuz_view_->search_view(), SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); -#endif - #ifdef HAVE_SUBSONIC connect(subsonic_view_->view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); #endif @@ -916,26 +881,6 @@ void MainWindow::ReloadSettings() { } } -#ifdef HAVE_TIDAL - settings.beginGroup(TidalSettingsPage::kSettingsGroup); - bool enable_tidal = settings.value("enabled", false).toBool(); - settings.endGroup(); - if (enable_tidal) - ui_->tabs->EnableTab(tidal_view_); - else - ui_->tabs->DisableTab(tidal_view_); -#endif - -#ifdef HAVE_QOBUZ - settings.beginGroup(QobuzSettingsPage::kSettingsGroup); - bool enable_qobuz = settings.value("enabled", false).toBool(); - settings.endGroup(); - if (enable_qobuz) - ui_->tabs->EnableTab(qobuz_view_); - else - ui_->tabs->DisableTab(qobuz_view_); -#endif - #ifdef HAVE_SUBSONIC settings.beginGroup(SubsonicSettingsPage::kSettingsGroup); bool enable_subsonic = settings.value("enabled", false).toBool(); @@ -966,12 +911,6 @@ void MainWindow::ReloadAllSettings() { album_cover_choice_controller_->ReloadSettings(); if (cover_manager_.get()) cover_manager_->ReloadSettings(); context_view_->ReloadSettings(); -#ifdef HAVE_TIDAL - tidal_view_->ReloadSettings(); -#endif -#ifdef HAVE_QOBUZ - qobuz_view_->ReloadSettings(); -#endif #ifdef HAVE_SUBSONIC subsonic_view_->ReloadSettings(); #endif @@ -2043,14 +1982,6 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) { if (!options.urls().empty()) { -#ifdef HAVE_TIDAL - for (const QUrl url : options.urls()) { - if (url.scheme() == "tidal" && url.host() == "login") { - emit AuthorisationUrlReceived(url); - return; - } - } -#endif MimeData *data = new MimeData; data->setUrls(options.urls()); // Behaviour depends on command line options, so set it here diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h index 38d6dea60..24d2e8719 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -315,8 +315,6 @@ class MainWindow : public QMainWindow, public PlatformInterface { std::unique_ptr track_selection_dialog_; PlaylistItemList autocomplete_tag_items_; - InternetTabsView *tidal_view_; - InternetTabsView *qobuz_view_; InternetSongsView *subsonic_view_; QAction *collection_show_all_; diff --git a/src/covermanager/tidalcoverprovider.cpp b/src/covermanager/tidalcoverprovider.cpp deleted file mode 100644 index 562515bbe..000000000 --- a/src/covermanager/tidalcoverprovider.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/application.h" -#include "core/closure.h" -#include "core/network.h" -#include "core/logging.h" -#include "core/song.h" -#include "internet/internetservices.h" -#include "tidal/tidalservice.h" -#include "albumcoverfetcher.h" -#include "coverprovider.h" -#include "tidalcoverprovider.h" - -const char *TidalCoverProvider::kApiUrl = "https://api.tidalhifi.com/v1"; -const char *TidalCoverProvider::kResourcesUrl = "https://resources.tidal.com"; -const int TidalCoverProvider::kLimit = 10; - -TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) : - CoverProvider("Tidal", 2.0, true, app, parent), - service_(app->internet_services()->Service()), - network_(new NetworkAccessManager(this)) { - -} - -bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) { - - if (!service_ || !service_->authenticated()) return false; - - 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; - -} - -void TidalCoverProvider::CancelSearch(int id) { Q_UNUSED(id); } - -QNetworkReply *TidalCoverProvider::CreateRequest(const QString &ressource_name, const ParamList ¶ms_supplied) { - - const ParamList params = ParamList() << params_supplied - << Param("countryCode", service_->country_code()); - - QUrlQuery url_query; - 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); - } - - QUrl url(kApiUrl + QString("/") + ressource_name); - url.setQuery(url_query); - QNetworkRequest req(url); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -#endif - req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - 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; - -} - -QByteArray TidalCoverProvider::GetReplyData(QNetworkReply *reply, QString &error) { - - 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 = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error())); - } - else { - // 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; - 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(); - error = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status); - } - } - 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(error); - } - return QByteArray(); - } - - return data; - -} - -QJsonObject TidalCoverProvider::ExtractJsonObj(QByteArray &data, QString &error) { - - 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); - return QJsonObject(); - } - - if (json_doc.isEmpty()) { - error = Error("Received empty Json document.", data); - return QJsonObject(); - } - - if (!json_doc.isObject()) { - error = 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); - return QJsonObject(); - } - - return json_obj; - -} - -QJsonValue TidalCoverProvider::ExtractItems(QByteArray &data, QString &error) { - - QJsonObject json_obj = ExtractJsonObj(data, error); - if (json_obj.isEmpty()) return QJsonValue(); - return ExtractItems(json_obj, error); - -} - -QJsonValue TidalCoverProvider::ExtractItems(QJsonObject &json_obj, QString &error) { - - if (!json_obj.contains("items")) { - error = Error("Json reply is missing items.", json_obj); - return QJsonArray(); - } - QJsonValue json_items = json_obj["items"]; - return json_items; - -} - -void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) { - - reply->deleteLater(); - - CoverSearchResults results; - QString error; - - QByteArray data = GetReplyData(reply, error); - if (data.isEmpty()) { - emit SearchFinished(id, results); - return; - } - - QJsonObject json_obj = ExtractJsonObj(data, error); - if (json_obj.isEmpty()) { - emit SearchFinished(id, results); - return; - } - - QJsonValue json_value = ExtractItems(json_obj, error); - if (!json_value.isArray()) { - emit SearchFinished(id, results); - return; - } - QJsonArray json_items = json_value.toArray(); - if (json_items.isEmpty()) { - emit SearchFinished(id, results); - return; - } - - for (const QJsonValue &value : json_items) { - if (!value.isObject()) { - Error("Invalid Json reply, item not a object.", value); - continue; - } - QJsonObject json_obj = value.toObject(); - - if (!json_obj.contains("artist") || !json_obj.contains("type") || !json_obj.contains("id") || !json_obj.contains("title") || !json_obj.contains("cover")) { - Error("Invalid Json reply, item missing id, type, album or cover.", json_obj); - continue; - } - QString album = json_obj["title"].toString(); - QString cover = json_obj["cover"].toString(); - - QJsonValue json_value_artist = json_obj["artist"]; - if (!json_value_artist.isObject()) { - Error("Invalid Json reply, item artist is not a object.", json_value_artist); - continue; - } - QJsonObject json_artist = json_value_artist.toObject(); - if (!json_artist.contains("name")) { - Error("Invalid Json reply, item artist missing name.", json_artist); - continue; - } - QString artist = json_artist["name"].toString(); - - album.remove(Song::kAlbumRemoveDisc); - album.remove(Song::kAlbumRemoveMisc); - - cover = cover.replace("-", "/"); - QUrl cover_url (QString("%1/images/%2/%3.jpg").arg(kResourcesUrl).arg(cover).arg("1280x1280")); - - CoverSearchResult cover_result; - cover_result.artist = artist; - cover_result.album = album; - cover_result.image_url = cover_url; - results << cover_result; - - } - emit SearchFinished(id, results); - -} - -QString TidalCoverProvider::Error(QString error, QVariant debug) { - qLog(Error) << "Tidal:" << error; - if (debug.isValid()) qLog(Debug) << debug; - return error; -} diff --git a/src/covermanager/tidalcoverprovider.h b/src/covermanager/tidalcoverprovider.h deleted file mode 100644 index 9eb3ee2e4..000000000 --- a/src/covermanager/tidalcoverprovider.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef TIDALCOVERPROVIDER_H -#define TIDALCOVERPROVIDER_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coverprovider.h" - -class QNetworkAccessManager; -class QNetworkReply; -class Application; -class TidalService; - -class TidalCoverProvider : public CoverProvider { - Q_OBJECT - - public: - explicit TidalCoverProvider(Application *app, QObject *parent = nullptr); - bool StartSearch(const QString &artist, const QString &album, const int id); - void CancelSearch(int id); - - private slots: - void HandleSearchReply(QNetworkReply *reply, const int id); - - private: - typedef QPair Param; - typedef QList ParamList; - typedef QPair EncodedParam; - static const char *kApiUrl; - static const char *kResourcesUrl; - static const int kLimit; - - 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); - QJsonValue ExtractItems(QJsonObject &json_obj, QString &error); - QString Error(QString error, QVariant debug = QVariant()); - - TidalService *service_; - QNetworkAccessManager *network_; - -}; - -#endif // TIDALCOVERPROVIDER_H diff --git a/src/qobuz/qobuzbaserequest.cpp b/src/qobuz/qobuzbaserequest.cpp deleted file mode 100644 index dcb74c196..000000000 --- a/src/qobuz/qobuzbaserequest.cpp +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/network.h" -#include "qobuzservice.h" -#include "qobuzbaserequest.h" - -const char *QobuzBaseRequest::kApiUrl = "https://www.qobuz.com/api.json/0.2"; - -QobuzBaseRequest::QobuzBaseRequest(QobuzService *service, NetworkAccessManager *network, QObject *parent) : - QObject(parent), - service_(service), - network_(network) - {} - -QobuzBaseRequest::~QobuzBaseRequest() {} - -QNetworkReply *QobuzBaseRequest::CreateRequest(const QString &ressource_name, const QList ¶ms_provided) { - - ParamList params = ParamList() << params_provided - << Param("app_id", app_id()); - - std::sort(params.begin(), params.end()); - - QUrlQuery url_query; - for (const Param& param : params) { - EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - url_query.addQueryItem(encoded_param.first, encoded_param.second); - } - - QUrl url(kApiUrl + QString("/") + ressource_name); - url.setQuery(url_query); - QNetworkRequest req(url); - req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - req.setRawHeader("X-App-Id", app_id().toUtf8()); - if (authenticated()) - req.setRawHeader("X-User-Auth-Token", user_auth_token().toUtf8()); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -#endif - - QNetworkReply *reply = network_->get(req); - connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(HandleSSLErrors(QList))); - - //qLog(Debug) << "Qobuz: Sending request" << url; - - return reply; - -} - -void QobuzBaseRequest::HandleSSLErrors(QList ssl_errors) { - - for (QSslError &ssl_error : ssl_errors) { - Error(ssl_error.errorString()); - } - -} - -QByteArray QobuzBaseRequest::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 { - // 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); - if (parse_error.error == QJsonParseError::NoError && !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(); - error = QString("%1 (%2)").arg(message).arg(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 QobuzBaseRequest::ExtractJsonObj(QByteArray &data) { - - QJsonParseError json_error; - QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error); - - if (json_error.error != QJsonParseError::NoError) { - Error("Reply from server missing Json data.", data); - return QJsonObject(); - } - - if (json_doc.isEmpty()) { - Error("Received empty Json document.", data); - 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; - -} - -QJsonValue QobuzBaseRequest::ExtractItems(QByteArray &data) { - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) return QJsonValue(); - return ExtractItems(json_obj); - -} - -QJsonValue QobuzBaseRequest::ExtractItems(QJsonObject &json_obj) { - - if (!json_obj.contains("items")) { - Error("Json reply is missing items.", json_obj); - return QJsonArray(); - } - QJsonValue json_items = json_obj["items"]; - return json_items; - -} - -QString QobuzBaseRequest::ErrorsToHTML(const QStringList &errors) { - - QString error_html; - for (const QString &error : errors) { - error_html += error + "
    "; - } - return error_html; - -} diff --git a/src/qobuz/qobuzbaserequest.h b/src/qobuz/qobuzbaserequest.h deleted file mode 100644 index aea92e31b..000000000 --- a/src/qobuz/qobuzbaserequest.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef QOBUZBASEREQUEST_H -#define QOBUZBASEREQUEST_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/song.h" -#include "qobuzservice.h" - -class QNetworkReply; -class NetworkAccessManager; - -class QobuzBaseRequest : public QObject { - Q_OBJECT - - public: - - enum QueryType { - QueryType_None, - QueryType_Artists, - QueryType_Albums, - QueryType_Songs, - QueryType_SearchArtists, - QueryType_SearchAlbums, - QueryType_SearchSongs, - QueryType_StreamURL, - }; - - QobuzBaseRequest(QobuzService *service, NetworkAccessManager *network, QObject *parent); - ~QobuzBaseRequest(); - - typedef QPair Param; - typedef QList ParamList; - - typedef QPair EncodedParam; - typedef QList EncodedParamList; - - QNetworkReply *CreateRequest(const QString &ressource_name, const QList ¶ms_provided); - QByteArray GetReplyData(QNetworkReply *reply); - QJsonObject ExtractJsonObj(QByteArray &data); - QJsonValue ExtractItems(QByteArray &data); - QJsonValue ExtractItems(QJsonObject &json_obj); - - 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(); } - QString app_secret() { return service_->app_secret(); } - QString username() { return service_->username(); } - QString password() { return service_->password(); } - int format() { return service_->format(); } - int artistssearchlimit() { return service_->artistssearchlimit(); } - int albumssearchlimit() { return service_->albumssearchlimit(); } - int songssearchlimit() { return service_->songssearchlimit(); } - - qint64 user_id() { return service_->user_id(); } - QString user_auth_token() { return service_->user_auth_token(); } - QString device_id() { return service_->device_id(); } - qint64 credential_id() { return service_->credential_id(); } - - bool authenticated() { return service_->authenticated(); } - bool login_sent() { return service_->login_sent(); } - int max_login_attempts() { return service_->max_login_attempts(); } - int login_attempts() { return service_->login_attempts(); } - - private slots: - void HandleSSLErrors(QList ssl_errors); - - private: - - static const char *kApiUrl; - - QobuzService *service_; - NetworkAccessManager *network_; - -}; - -#endif // QOBUZBASEREQUEST_H diff --git a/src/qobuz/qobuzfavoriterequest.cpp b/src/qobuz/qobuzfavoriterequest.cpp deleted file mode 100644 index 1b479a4b7..000000000 --- a/src/qobuz/qobuzfavoriterequest.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/logging.h" -#include "core/network.h" -#include "core/closure.h" -#include "core/song.h" -#include "qobuzservice.h" -#include "qobuzbaserequest.h" -#include "qobuzfavoriterequest.h" - -QobuzFavoriteRequest::QobuzFavoriteRequest(QobuzService *service, NetworkAccessManager *network, QObject *parent) - : QobuzBaseRequest(service, network, parent), - service_(service), - network_(network) {} - -QobuzFavoriteRequest::~QobuzFavoriteRequest() { - - while (!replies_.isEmpty()) { - QNetworkReply *reply = replies_.takeFirst(); - disconnect(reply, 0, this, 0); - reply->abort(); - reply->deleteLater(); - } - -} - -QString QobuzFavoriteRequest::FavoriteText(const FavoriteType type) { - - switch (type) { - case FavoriteType_Artists: - return "artists"; - case FavoriteType_Albums: - return "albums"; - case FavoriteType_Songs: - default: - return "tracks"; - } - -} - -void QobuzFavoriteRequest::AddArtists(const SongList &songs) { - AddFavorites(FavoriteType_Artists, songs); -} - -void QobuzFavoriteRequest::AddAlbums(const SongList &songs) { - AddFavorites(FavoriteType_Albums, songs); -} - -void QobuzFavoriteRequest::AddSongs(const SongList &songs) { - AddFavorites(FavoriteType_Songs, songs); -} - -void QobuzFavoriteRequest::AddFavorites(const FavoriteType type, const SongList &songs) { - - if (songs.isEmpty()) return; - - QString text; - switch (type) { - case FavoriteType_Artists: - text = "artist_ids"; - break; - case FavoriteType_Albums: - text = "album_ids"; - break; - case FavoriteType_Songs: - text = "track_ids"; - break; - } - - QStringList ids_list; - for (const Song &song : songs) { - QString id; - switch (type) { - case FavoriteType_Artists: - if (song.artist_id() <= 0) continue; - id = QString::number(song.artist_id()); - break; - case FavoriteType_Albums: - if (song.album_id().isEmpty()) continue; - id = song.album_id(); - break; - case FavoriteType_Songs: - if (song.song_id() <= 0) continue; - id = QString::number(song.song_id()); - break; - } - if (id.isEmpty()) continue; - if (!ids_list.contains(id)) { - ids_list << id; - } - } - if (ids_list.isEmpty()) return; - - QString ids = ids_list.join(','); - - typedef QPair EncodedParam; - - ParamList params = ParamList() << Param("app_id", app_id()) - << Param("user_auth_token", user_auth_token()) - << Param(text, ids); - - QUrlQuery url_query; - for (const Param& param : params) { - EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - url_query.addQueryItem(encoded_param.first, encoded_param.second); - } - - QNetworkReply *reply = CreateRequest("favorite/create", params); - NewClosure(reply, SIGNAL(finished()), this, SLOT(AddFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs); - replies_ << reply; - -} - -void QobuzFavoriteRequest::AddFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs) { - - if (replies_.contains(reply)) { - replies_.removeAll(reply); - reply->deleteLater(); - } - else { - return; - } - - QByteArray data = GetReplyData(reply); - - if (reply->error() != QNetworkReply::NoError) { - return; - } - - qLog(Debug) << "Qobuz:" << songs.count() << "songs added to" << FavoriteText(type) << "favorites."; - - switch (type) { - case FavoriteType_Artists: - emit ArtistsAdded(songs); - break; - case FavoriteType_Albums: - emit AlbumsAdded(songs); - break; - case FavoriteType_Songs: - emit SongsAdded(songs); - break; - } - -} - -void QobuzFavoriteRequest::RemoveArtists(const SongList &songs) { - RemoveFavorites(FavoriteType_Artists, songs); -} - -void QobuzFavoriteRequest::RemoveAlbums(const SongList &songs) { - RemoveFavorites(FavoriteType_Albums, songs); -} - -void QobuzFavoriteRequest::RemoveSongs(const SongList &songs) { - RemoveFavorites(FavoriteType_Songs, songs); -} - -void QobuzFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongList &songs) { - - if (songs.isEmpty()) return; - - QString text; - switch (type) { - case FavoriteType_Artists: - text = "artist_ids"; - break; - case FavoriteType_Albums: - text = "album_ids"; - break; - case FavoriteType_Songs: - text = "track_ids"; - break; - } - - QStringList ids_list; - for (const Song &song : songs) { - QString id; - switch (type) { - case FavoriteType_Artists: - if (song.artist_id() <= 0) continue; - id = QString::number(song.artist_id()); - break; - case FavoriteType_Albums: - if (song.album_id().isEmpty()) continue; - id = song.album_id(); - break; - case FavoriteType_Songs: - if (song.song_id() <= 0) continue; - id = QString::number(song.song_id()); - break; - } - if (id.isEmpty()) continue; - if (!ids_list.contains(id)) { - ids_list << id; - } - } - if (ids_list.isEmpty()) return; - - QString ids = ids_list.join(','); - - ParamList params = ParamList() << Param("app_id", app_id()) - << Param("user_auth_token", user_auth_token()) - << Param(text, ids); - - QUrlQuery url_query; - 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); - } - - QNetworkReply *reply = CreateRequest("favorite/delete", params); - NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoveFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs); - replies_ << reply; - -} - -void QobuzFavoriteRequest::RemoveFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs) { - - if (replies_.contains(reply)) { - replies_.removeAll(reply); - reply->deleteLater(); - } - else { - return; - } - - QByteArray data = GetReplyData(reply); - if (reply->error() != QNetworkReply::NoError) { - return; - } - - qLog(Debug) << "Qobuz:" << songs.count() << "songs removed from" << FavoriteText(type) << "favorites."; - - switch (type) { - case FavoriteType_Artists: - emit ArtistsRemoved(songs); - break; - case FavoriteType_Albums: - emit AlbumsRemoved(songs); - break; - case FavoriteType_Songs: - emit SongsRemoved(songs); - break; - } - -} - -void QobuzFavoriteRequest::Error(const QString &error, const QVariant &debug) { - - qLog(Error) << "Qobuz:" << error; - if (debug.isValid()) qLog(Debug) << debug; - -} diff --git a/src/qobuz/qobuzfavoriterequest.h b/src/qobuz/qobuzfavoriterequest.h deleted file mode 100644 index d176c833e..000000000 --- a/src/qobuz/qobuzfavoriterequest.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef QOBUZFAVORITEREQUEST_H -#define QOBUZFAVORITEREQUEST_H - -#include "config.h" - -#include -#include -#include -#include - -#include "qobuzbaserequest.h" -#include "core/song.h" - -class QNetworkReply; -class QobuzService; -class NetworkAccessManager; - -class QobuzFavoriteRequest : public QobuzBaseRequest { - Q_OBJECT - - public: - QobuzFavoriteRequest(QobuzService *service, NetworkAccessManager *network, QObject *parent); - ~QobuzFavoriteRequest(); - - enum FavoriteType { - FavoriteType_Artists, - FavoriteType_Albums, - FavoriteType_Songs - }; - - signals: - void ArtistsAdded(const SongList &songs); - void AlbumsAdded(const SongList &songs); - void SongsAdded(const SongList &songs); - void ArtistsRemoved(const SongList &songs); - void AlbumsRemoved(const SongList &songs); - void SongsRemoved(const SongList &songs); - - private slots: - void AddArtists(const SongList &songs); - void AddAlbums(const SongList &songs); - void AddSongs(const SongList &songs); - - void RemoveArtists(const SongList &songs); - void RemoveAlbums(const SongList &songs); - void RemoveSongs(const SongList &songs); - - void AddFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs); - 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); - - QobuzService *service_; - NetworkAccessManager *network_; - QList replies_; - -}; - -#endif // QOBUZFAVORITEREQUEST_H diff --git a/src/qobuz/qobuzrequest.cpp b/src/qobuz/qobuzrequest.cpp deleted file mode 100644 index f45697567..000000000 --- a/src/qobuz/qobuzrequest.cpp +++ /dev/null @@ -1,1265 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/closure.h" -#include "core/logging.h" -#include "core/network.h" -#include "core/song.h" -#include "core/timeconstants.h" -#include "core/application.h" -#include "covermanager/albumcoverloader.h" -#include "qobuzservice.h" -#include "qobuzurlhandler.h" -#include "qobuzbaserequest.h" -#include "qobuzrequest.h" - -const int QobuzRequest::kMaxConcurrentArtistsRequests = 3; -const int QobuzRequest::kMaxConcurrentAlbumsRequests = 3; -const int QobuzRequest::kMaxConcurrentSongsRequests = 3; -const int QobuzRequest::kMaxConcurrentArtistAlbumsRequests = 3; -const int QobuzRequest::kMaxConcurrentAlbumSongsRequests = 3; -const int QobuzRequest::kMaxConcurrentAlbumCoverRequests = 1; - -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), - finished_(false), - artists_requests_active_(0), - artists_total_(0), - artists_received_(0), - albums_requests_active_(0), - songs_requests_active_(0), - artist_albums_requests_active_(0), - artist_albums_requested_(0), - artist_albums_received_(0), - album_songs_requests_active_(0), - album_songs_requested_(0), - album_songs_received_(0), - album_covers_requests_active_(), - album_covers_requested_(0), - album_covers_received_(0), - no_results_(false) {} - -QobuzRequest::~QobuzRequest() { - - while (!replies_.isEmpty()) { - QNetworkReply *reply = replies_.takeFirst(); - disconnect(reply, 0, this, 0); - if (reply->isRunning()) reply->abort(); - reply->deleteLater(); - } - - while (!album_cover_replies_.isEmpty()) { - QNetworkReply *reply = album_cover_replies_.takeFirst(); - disconnect(reply, 0, this, 0); - if (reply->isRunning()) reply->abort(); - reply->deleteLater(); - } - -} - -void QobuzRequest::Process() { - - switch (type_) { - case QueryType::QueryType_Artists: - GetArtists(); - break; - case QueryType::QueryType_Albums: - GetAlbums(); - break; - case QueryType::QueryType_Songs: - GetSongs(); - break; - case QueryType::QueryType_SearchArtists: - ArtistsSearch(); - break; - case QueryType::QueryType_SearchAlbums: - AlbumsSearch(); - break; - case QueryType::QueryType_SearchSongs: - SongsSearch(); - break; - default: - Error("Invalid query type."); - break; - } - -} - -void QobuzRequest::Search(const int query_id, const QString &search_text) { - query_id_ = query_id; - search_text_ = search_text; -} - -void QobuzRequest::GetArtists() { - - emit UpdateStatus(query_id_, tr("Retrieving artists...")); - emit UpdateProgress(query_id_, 0); - AddArtistsRequest(); - -} - -void QobuzRequest::AddArtistsRequest(const int offset, const int limit) { - - Request request; - request.limit = limit; - request.offset = offset; - artists_requests_queue_.enqueue(request); - if (artists_requests_active_ < kMaxConcurrentArtistsRequests) FlushArtistsRequests(); - -} - -void QobuzRequest::FlushArtistsRequests() { - - while (!artists_requests_queue_.isEmpty() && artists_requests_active_ < kMaxConcurrentArtistsRequests) { - - Request request = artists_requests_queue_.dequeue(); - ++artists_requests_active_; - - ParamList params; - if (type_ == QueryType_Artists) { - params << Param("type", "artists"); - params << Param("user_auth_token", user_auth_token()); - } - else if (type_ == QueryType_SearchArtists) params << Param("query", search_text_); - if (request.limit > 0) params << Param("limit", QString::number(request.limit)); - if (request.offset > 0) params << Param("offset", QString::number(request.offset)); - QNetworkReply *reply = nullptr; - if (type_ == QueryType_Artists) { - reply = CreateRequest(QString("favorite/getUserFavorites"), params); - } - else if (type_ == QueryType_SearchArtists) { - reply = CreateRequest("artist/search", params); - } - if (!reply) continue; - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistsReplyReceived(QNetworkReply*, const int, const int)), reply, request.limit, request.offset); - - } - -} - -void QobuzRequest::GetAlbums() { - - emit UpdateStatus(query_id_, tr("Retrieving albums...")); - emit UpdateProgress(query_id_, 0); - AddAlbumsRequest(); - -} - -void QobuzRequest::AddAlbumsRequest(const int offset, const int limit) { - - Request request; - request.limit = limit; - request.offset = offset; - albums_requests_queue_.enqueue(request); - if (albums_requests_active_ < kMaxConcurrentAlbumsRequests) FlushAlbumsRequests(); - -} - -void QobuzRequest::FlushAlbumsRequests() { - - while (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) { - - Request request = albums_requests_queue_.dequeue(); - ++albums_requests_active_; - - ParamList params; - if (type_ == QueryType_Albums) { - params << Param("type", "albums"); - params << Param("user_auth_token", user_auth_token()); - } - else if (type_ == QueryType_SearchAlbums) params << Param("query", search_text_); - if (request.limit > 0) params << Param("limit", QString::number(request.limit)); - if (request.offset > 0) params << Param("offset", QString::number(request.offset)); - QNetworkReply *reply = nullptr; - if (type_ == QueryType_Albums) { - reply = CreateRequest(QString("favorite/getUserFavorites"), params); - } - else if (type_ == QueryType_SearchAlbums) { - reply = CreateRequest("album/search", params); - } - if (!reply) continue; - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReplyReceived(QNetworkReply*, const int, const int)), reply, request.limit, request.offset); - - } - -} - -void QobuzRequest::GetSongs() { - - emit UpdateStatus(query_id_, tr("Retrieving songs...")); - emit UpdateProgress(query_id_, 0); - AddSongsRequest(); - -} - -void QobuzRequest::AddSongsRequest(const int offset, const int limit) { - - Request request; - request.limit = limit; - request.offset = offset; - songs_requests_queue_.enqueue(request); - if (songs_requests_active_ < kMaxConcurrentSongsRequests) FlushSongsRequests(); - -} - -void QobuzRequest::FlushSongsRequests() { - - while (!songs_requests_queue_.isEmpty() && songs_requests_active_ < kMaxConcurrentSongsRequests) { - - Request request = songs_requests_queue_.dequeue(); - ++songs_requests_active_; - - ParamList params; - if (type_ == QueryType_Songs) { - params << Param("type", "tracks"); - params << Param("user_auth_token", user_auth_token()); - } - else if (type_ == QueryType_SearchSongs) params << Param("query", search_text_); - if (request.limit > 0) params << Param("limit", QString::number(request.limit)); - if (request.offset > 0) params << Param("offset", QString::number(request.offset)); - QNetworkReply *reply = nullptr; - if (type_ == QueryType_Songs) { - reply = CreateRequest(QString("favorite/getUserFavorites"), params); - } - else if (type_ == QueryType_SearchSongs) { - reply = CreateRequest("track/search", params); - } - if (!reply) continue; - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(SongsReplyReceived(QNetworkReply*, const int, const int)), reply, request.limit, request.offset); - - } - -} - -void QobuzRequest::ArtistsSearch() { - - emit UpdateStatus(query_id_, tr("Searching...")); - emit UpdateProgress(query_id_, 0); - AddArtistsSearchRequest(); - -} - -void QobuzRequest::AddArtistsSearchRequest(const int offset) { - - AddArtistsRequest(offset, service_->artistssearchlimit()); - -} - -void QobuzRequest::AlbumsSearch() { - - emit UpdateStatus(query_id_, tr("Searching...")); - emit UpdateProgress(query_id_, 0); - AddAlbumsSearchRequest(); - -} - -void QobuzRequest::AddAlbumsSearchRequest(const int offset) { - - AddAlbumsRequest(offset, service_->albumssearchlimit()); - -} - -void QobuzRequest::SongsSearch() { - - emit UpdateStatus(query_id_, tr("Searching...")); - emit UpdateProgress(query_id_, 0); - AddSongsSearchRequest(); - -} - -void QobuzRequest::AddSongsSearchRequest(const int offset) { - - AddSongsRequest(offset, service_->songssearchlimit()); - -} - -void QobuzRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) { - - if (!replies_.contains(reply)) return; - replies_.removeAll(reply); - reply->deleteLater(); - - QByteArray data = GetReplyData(reply); - - --artists_requests_active_; - - if (finished_) return; - - if (data.isEmpty()) { - ArtistsFinishCheck(); - return; - } - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) { - ArtistsFinishCheck(); - return; - } - - if (!json_obj.contains("artists")) { - ArtistsFinishCheck(); - Error("Json object is missing artists.", json_obj); - return; - } - QJsonValue json_artists = json_obj["artists"]; - if (!json_artists.isObject()) { - Error("Json artists is not an object.", json_obj); - ArtistsFinishCheck(); - return; - } - QJsonObject json_obj_artists = json_artists.toObject(); - - if (!json_obj_artists.contains("limit") || - !json_obj_artists.contains("offset") || - !json_obj_artists.contains("total") || - !json_obj_artists.contains("items")) { - ArtistsFinishCheck(); - Error("Json artists object is missing values.", json_obj); - return; - } - //int limit = json_obj_artists["limit"].toInt(); - int offset = json_obj_artists["offset"].toInt(); - int artists_total = json_obj_artists["total"].toInt(); - - if (offset_requested == 0) { - artists_total_ = artists_total; - } - else if (artists_total != artists_total_) { - Error(QString("total returned does not match previous total! %1 != %2").arg(artists_total).arg(artists_total_)); - ArtistsFinishCheck(); - return; - } - - if (offset != offset_requested) { - Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested)); - ArtistsFinishCheck(); - return; - } - - if (offset_requested == 0) { - emit ProgressSetMaximum(query_id_, artists_total_); - emit UpdateProgress(query_id_, artists_received_); - } - - QJsonValue json_value = ExtractItems(json_obj_artists); - if (!json_value.isArray()) { - ArtistsFinishCheck(); - return; - } - - QJsonArray json_items = json_value.toArray(); - if (json_items.isEmpty()) { // Empty array means no results - if (offset_requested == 0) no_results_ = true; - ArtistsFinishCheck(); - return; - } - - int artists_received = 0; - for (const QJsonValue &value : json_items) { - - ++artists_received; - - if (!value.isObject()) { - Error("Invalid Json reply, item not a object.", value); - continue; - } - QJsonObject json_obj = value.toObject(); - - if (json_obj.contains("item")) { - QJsonValue json_item = json_obj["item"]; - if (!json_item.isObject()) { - Error("Invalid Json reply, item not a object.", json_item); - continue; - } - json_obj = json_item.toObject(); - } - - if (!json_obj.contains("id") || !json_obj.contains("name")) { - Error("Invalid Json reply, item missing id or album.", json_obj); - continue; - } - - qint64 artist_id = json_obj["id"].toInt(); - if (artist_albums_requests_pending_.contains(artist_id)) continue; - artist_albums_requests_pending_.append(artist_id); - - } - artists_received_ += artists_received; - - if (offset_requested != 0) emit UpdateProgress(query_id_, artists_received_); - - ArtistsFinishCheck(limit_requested, offset, artists_received); - -} - -void QobuzRequest::ArtistsFinishCheck(const int limit, const int offset, const int artists_received) { - - if (finished_) return; - - if ((limit == 0 || limit > artists_received) && artists_received_ < artists_total_) { - int offset_next = offset + artists_received; - if (offset_next > 0 && offset_next < artists_total_) { - if (type_ == QueryType_Artists) AddArtistsRequest(offset_next); - else if (type_ == QueryType_SearchArtists) AddArtistsSearchRequest(offset_next); - } - } - - if (!artists_requests_queue_.isEmpty() && artists_requests_active_ < kMaxConcurrentArtistsRequests) FlushArtistsRequests(); - - if (artists_requests_queue_.isEmpty() && artists_requests_active_ <= 0) { // Artist query is finished, get all albums for all artists. - - // Get artist albums - for (qint64 artist_id : artist_albums_requests_pending_) { - AddArtistAlbumsRequest(artist_id); - ++artist_albums_requested_; - } - artist_albums_requests_pending_.clear(); - - if (artist_albums_requested_ > 0) { - if (artist_albums_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving albums for %1 artist...").arg(artist_albums_requested_)); - else emit UpdateStatus(query_id_, tr("Retrieving albums for %1 artists...").arg(artist_albums_requested_)); - emit ProgressSetMaximum(query_id_, artist_albums_requested_); - emit UpdateProgress(query_id_, 0); - } - - } - - FinishCheck(); - -} - -void QobuzRequest::AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) { - --albums_requests_active_; - AlbumsReceived(reply, 0, limit_requested, offset_requested); - if (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) FlushAlbumsRequests(); -} - -void QobuzRequest::AddArtistAlbumsRequest(const qint64 artist_id, const int offset) { - - Request request; - request.artist_id = artist_id; - request.offset = offset; - artist_albums_requests_queue_.enqueue(request); - if (artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) FlushArtistAlbumsRequests(); - -} - -void QobuzRequest::FlushArtistAlbumsRequests() { - - while (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) { - - Request request = artist_albums_requests_queue_.dequeue(); - ++artist_albums_requests_active_; - - ParamList params = ParamList() << Param("artist_id", QString::number(request.artist_id)) - << Param("extra", "albums"); - - if (request.offset > 0) params << Param("offset", QString::number(request.offset)); - QNetworkReply *reply = CreateRequest(QString("artist/get"), params); - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistAlbumsReplyReceived(QNetworkReply*, const qint64, const int)), reply, request.artist_id, request.offset); - - } - -} - -void QobuzRequest::ArtistAlbumsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const int offset_requested) { - - --artist_albums_requests_active_; - ++artist_albums_received_; - emit UpdateProgress(query_id_, artist_albums_received_); - AlbumsReceived(reply, artist_id, 0, offset_requested); - if (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) FlushArtistAlbumsRequests(); - -} - -void QobuzRequest::AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_requested, const int limit_requested, const int offset_requested) { - - if (!replies_.contains(reply)) return; - replies_.removeAll(reply); - reply->deleteLater(); - - QByteArray data = GetReplyData(reply); - - if (finished_) return; - - if (data.isEmpty()) { - AlbumsFinishCheck(artist_id_requested); - return; - } - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) { - AlbumsFinishCheck(artist_id_requested); - return; - } - - qint64 album_artist_id = artist_id_requested; - if (json_obj.contains("id")) { - album_artist_id = json_obj["id"].toInt(); - } - QString album_artist; - if (json_obj.contains("name")) { - album_artist = json_obj["name"].toString(); - } - - if (album_artist_id != artist_id_requested) { - AlbumsFinishCheck(artist_id_requested); - Error("Artist id returned does not match artist id requested.", json_obj); - return; - } - - if (!json_obj.contains("albums")) { - AlbumsFinishCheck(artist_id_requested); - Error("Json object is missing albums.", json_obj); - return; - } - QJsonValue json_albums = json_obj["albums"]; - if (!json_albums.isObject()) { - Error("Json albums is not an object.", json_obj); - AlbumsFinishCheck(artist_id_requested); - return; - } - QJsonObject json_obj_albums = json_albums.toObject(); - - if (!json_obj_albums.contains("limit") || - !json_obj_albums.contains("offset") || - !json_obj_albums.contains("total") || - !json_obj_albums.contains("items")) { - AlbumsFinishCheck(artist_id_requested); - Error("Json albums object is missing values.", json_obj); - return; - } - - //int limit = json_obj["limit"].toInt(); - int offset = json_obj["offset"].toInt(); - int albums_total = json_obj["total"].toInt(); - - if (offset != offset_requested) { - Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested)); - AlbumsFinishCheck(artist_id_requested); - return; - } - - QJsonValue json_value = ExtractItems(json_obj_albums); - if (!json_value.isArray()) { - AlbumsFinishCheck(artist_id_requested); - return; - } - QJsonArray json_items = json_value.toArray(); - if (json_items.isEmpty()) { - if ((type_ == QueryType_Albums || type_ == QueryType_SearchAlbums) && offset_requested == 0) { - no_results_ = true; - } - AlbumsFinishCheck(artist_id_requested); - return; - } - - int albums_received = 0; - for (const QJsonValue &value : json_items) { - - ++albums_received; - - if (!value.isObject()) { - Error("Invalid Json reply, item not a object.", value); - continue; - } - QJsonObject json_obj = value.toObject(); - - if (!json_obj.contains("artist") || !json_obj.contains("title") || !json_obj.contains("id")) { - Error("Invalid Json reply, item missing artist, title or id.", json_obj); - continue; - } - - QString album_id = json_obj["id"].toString(); - if (album_songs_requests_pending_.contains(album_id)) continue; - - QString album = json_obj["title"].toString(); - - QJsonValue json_value_artist = json_obj["artist"]; - if (!json_value_artist.isObject()) { - Error("Invalid Json reply, item artist is not a object.", json_value_artist); - continue; - } - QJsonObject json_artist = json_value_artist.toObject(); - if (!json_artist.contains("id") || !json_artist.contains("name")) { - Error("Invalid Json reply, item artist missing id or name.", json_artist); - continue; - } - - qint64 artist_id = json_artist["id"].toInt(); - QString artist = json_artist["name"].toString(); - if (artist_id_requested != 0 && artist_id != artist_id_requested) { - qLog(Debug) << "Skipping artist" << "artist" << artist << artist_id << "does not match album artist" << album_artist_id << album_artist; - continue; - } - - Request request; - request.artist_id = artist_id; - request.album_id = album_id; - request.album_artist = artist; - request.album = album; - album_songs_requests_pending_.insert(album_id, request); - - } - - AlbumsFinishCheck(artist_id_requested, limit_requested, offset, albums_total, albums_received); - -} - -void QobuzRequest::AlbumsFinishCheck(const qint64 artist_id, const int limit, const int offset, const int albums_total, const int albums_received) { - - if (finished_) return; - - if (limit == 0 || limit > albums_received) { - int offset_next = offset + albums_received; - if (offset_next > 0 && offset_next < albums_total) { - switch (type_) { - case QueryType_Albums: - AddAlbumsRequest(offset_next); - break; - case QueryType_SearchAlbums: - AddAlbumsSearchRequest(offset_next); - break; - case QueryType_Artists: - case QueryType_SearchArtists: - AddArtistAlbumsRequest(artist_id, offset_next); - break; - default: - break; - } - } - } - - if ( - albums_requests_queue_.isEmpty() && - albums_requests_active_ <= 0 && - artist_albums_requests_queue_.isEmpty() && - artist_albums_requests_active_ <= 0 - ) { // Artist albums query is finished, get all songs for all albums. - - // Get songs for all the albums. - - QHash ::iterator i; - for (i = album_songs_requests_pending_.begin() ; i != album_songs_requests_pending_.end() ; ++i) { - Request request = i.value(); - AddAlbumSongsRequest(request.artist_id, request.album_id, request.album_artist, request.album); - } - album_songs_requests_pending_.clear(); - - if (album_songs_requested_ > 0) { - if (album_songs_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving songs for %1 album...").arg(album_songs_requested_)); - else emit UpdateStatus(query_id_, tr("Retrieving songs for %1 albums...").arg(album_songs_requested_)); - emit ProgressSetMaximum(query_id_, album_songs_requested_); - emit UpdateProgress(query_id_, 0); - } - } - - FinishCheck(); - -} - -void QobuzRequest::SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) { - - --songs_requests_active_; - SongsReceived(reply, 0, 0, limit_requested, offset_requested); - -} - -void QobuzRequest::AddAlbumSongsRequest(const qint64 artist_id, const QString &album_id, const QString &album_artist, const QString &album, const int offset) { - - Request request; - request.artist_id = artist_id; - request.album_id = album_id; - request.album_artist = album_artist; - request.album = album; - request.offset = offset; - album_songs_requests_queue_.enqueue(request); - ++album_songs_requested_; - if (album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests(); - -} - -void QobuzRequest::FlushAlbumSongsRequests() { - - while (!album_songs_requests_queue_.isEmpty() && album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) { - - Request request = album_songs_requests_queue_.dequeue(); - ++album_songs_requests_active_; - ParamList params = ParamList() << Param("album_id", request.album_id); - if (request.offset > 0) params << Param("offset", QString::number(request.offset)); - QNetworkReply *reply = CreateRequest(QString("album/get"), params); - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumSongsReplyReceived(QNetworkReply*, const qint64, const QString&, const int, const QString&, const QString&)), reply, request.artist_id, request.album_id, request.offset, request.album_artist, request.album); - - } - -} - -void QobuzRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const QString &album_id, const int offset_requested, const QString &album_artist, const QString &album) { - - --album_songs_requests_active_; - ++album_songs_received_; - if (offset_requested == 0) { - emit UpdateProgress(query_id_, album_songs_received_); - } - SongsReceived(reply, artist_id, album_id, 0, offset_requested, album_artist, album); - -} - -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) { - - if (!replies_.contains(reply)) return; - replies_.removeAll(reply); - reply->deleteLater(); - - QByteArray data = GetReplyData(reply); - - if (finished_) return; - - if (data.isEmpty()) { - SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist_requested, album_requested); - return; - } - - 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; - } - - if (!json_obj.contains("tracks")) { - Error("Json object is missing tracks.", json_obj); - SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist_requested, album_requested); - return; - } - - qint64 artist_id = artist_id_requested; - QString album_artist = album_artist_requested; - QString album_id = album_id_requested; - QString album = album_requested; - QUrl cover_url; - - if (json_obj.contains("id")) { - album_id = json_obj["id"].toString(); - } - - if (json_obj.contains("title")) { - album = json_obj["title"].toString(); - } - - if (json_obj.contains("artist")) { - QJsonValue json_artist = json_obj["artist"]; - if (!json_artist.isObject()) { - Error("Invalid Json reply, album artist is not a object.", json_artist); - SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist, album); - return; - } - QJsonObject json_obj_artist = json_artist.toObject(); - if (!json_obj_artist.contains("id") || !json_obj_artist.contains("name")) { - Error("Invalid Json reply, album artist is missing id or name.", json_obj_artist); - SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist, album); - return; - } - artist_id = json_obj_artist["id"].toInt(); - album_artist = json_obj_artist["name"].toString(); - } - - if (json_obj.contains("image")) { - QJsonValue json_image = json_obj["image"]; - if (!json_image.isObject()) { - Error("Invalid Json reply, album image is not a object.", json_image); - SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist, album); - return; - } - QJsonObject json_obj_image = json_image.toObject(); - if (!json_obj_image.contains("large")) { - Error("Invalid Json reply, album image is missing large.", json_obj_image); - SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist, album); - return; - } - QString album_image = json_obj_image["large"].toString(); - if (!album_image.isEmpty()) { - cover_url = QUrl(album_image); - } - } - - QJsonValue json_tracks = json_obj["tracks"]; - if (!json_tracks.isObject()) { - Error("Json tracks is not an object.", json_obj); - SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist, album); - return; - } - QJsonObject json_obj_tracks = json_tracks.toObject(); - - if (!json_obj_tracks.contains("limit") || - !json_obj_tracks.contains("offset") || - !json_obj_tracks.contains("total") || - !json_obj_tracks.contains("items")) { - SongsFinishCheck(artist_id_requested, album_id_requested, limit_requested, offset_requested, 0, 0, album_artist, album); - Error("Json songs object is missing values.", json_obj); - return; - } - - //int limit = json_obj_tracks["limit"].toInt(); - int offset = json_obj_tracks["offset"].toInt(); - int songs_total = json_obj_tracks["total"].toInt(); - - if (offset != offset_requested) { - Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested)); - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist, album); - return; - } - - 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; - } - - QJsonArray json_items = json_value.toArray(); - if (json_items.isEmpty()) { - if ((type_ == QueryType_Songs || type_ == QueryType_SearchSongs) && offset_requested == 0) { - no_results_ = true; - } - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist, album); - return; - } - - bool compilation = false; - bool multidisc = false; - SongList songs; - int songs_received = 0; - for (const QJsonValue &value : json_items) { - - if (!value.isObject()) { - Error("Invalid Json reply, track is not a object.", value); - continue; - } - QJsonObject json_obj = value.toObject(); - - ++songs_received; - Song song(Song::Source_Qobuz); - ParseSong(song, json_obj, artist_id, album_id, album_artist, album, cover_url); - if (!song.is_valid()) continue; - if (song.disc() >= 2) multidisc = true; - if (song.is_compilation()) compilation = true; - songs << song; - } - - for (Song &song : songs) { - if (compilation) song.set_compilation_detected(true); - if (multidisc) { - QString album_full(QString("%1 - (Disc %2)").arg(song.album()).arg(song.disc())); - song.set_album(album_full); - } - songs_ << song; - } - - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, songs_received, album_artist, album); - -} - -void QobuzRequest::SongsFinishCheck(const qint64 artist_id, const QString &album_id, const int limit, const int offset, const int songs_total, const int songs_received, const QString &album_artist, const QString &album) { - - if (finished_) return; - - if (limit == 0 || limit > songs_received) { - int offset_next = offset + songs_received; - if (offset_next > 0 && offset_next < songs_total) { - switch (type_) { - case QueryType_Songs: - AddSongsRequest(offset_next); - break; - case QueryType_SearchSongs: - AddSongsSearchRequest(offset_next); - break; - case QueryType_Artists: - case QueryType_SearchArtists: - case QueryType_Albums: - case QueryType_SearchAlbums: - AddAlbumSongsRequest(artist_id, album_id, album_artist, album, offset_next); - break; - default: - break; - } - } - } - - if (!songs_requests_queue_.isEmpty() && songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests(); - if (!album_songs_requests_queue_.isEmpty() && album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests(); - - if ( - service_->download_album_covers() && - IsQuery() && - songs_requests_queue_.isEmpty() && - songs_requests_active_ <= 0 && - album_songs_requests_queue_.isEmpty() && - album_songs_requests_active_ <= 0 && - album_cover_requests_queue_.isEmpty() && - album_covers_received_ <= 0 && - album_covers_requests_sent_.isEmpty() && - album_songs_received_ >= album_songs_requested_ - ) { - GetAlbumCovers(); - } - - FinishCheck(); - -} - -int QobuzRequest::ParseSong(Song &song, const QJsonObject &json_obj, qint64 artist_id, QString album_id, QString album_artist, QString album, QUrl cover_url) { - - if ( - !json_obj.contains("id") || - !json_obj.contains("title") || - !json_obj.contains("track_number") || - !json_obj.contains("duration") || - !json_obj.contains("copyright") || - !json_obj.contains("streamable") - ) { - Error("Invalid Json reply, track is missing one or more values.", json_obj); - return -1; - } - - qint64 song_id = json_obj["id"].toInt(); - QString title = json_obj["title"].toString(); - int track = json_obj["track_number"].toInt(); - QString copyright = json_obj["copyright"].toString(); - quint64 duration = json_obj["duration"].toInt() * kNsecPerSec; - //bool streamable = json_obj["streamable"].toBool(); - QString composer; - QString performer; - - if (json_obj.contains("album")) { - - QJsonValue json_album = json_obj["album"]; - if (!json_album.isObject()) { - Error("Invalid Json reply, album is not an object.", json_album); - return -1; - } - QJsonObject json_obj_album = json_album.toObject(); - - if (json_obj_album.contains("id")) { - album_id = json_obj_album["id"].toString(); - } - - if (json_obj_album.contains("title")) { - album = json_obj_album["title"].toString(); - } - - if (json_obj_album.contains("artist")) { - QJsonValue json_artist = json_obj_album["artist"]; - if (!json_artist.isObject()) { - Error("Invalid Json reply, album artist is not a object.", json_artist); - return -1; - } - QJsonObject json_obj_artist = json_artist.toObject(); - if (!json_obj_artist.contains("id") || !json_obj_artist.contains("name")) { - Error("Invalid Json reply, album artist is missing id or name.", json_obj_artist); - return -1; - } - artist_id = json_obj_artist["id"].toInt(); - album_artist = json_obj_artist["name"].toString(); - } - - if (json_obj_album.contains("image")) { - QJsonValue json_image = json_obj_album["image"]; - if (!json_image.isObject()) { - Error("Invalid Json reply, album image is not a object.", json_image); - return -1; - } - QJsonObject json_obj_image = json_image.toObject(); - if (!json_obj_image.contains("large")) { - Error("Invalid Json reply, album image is missing large.", json_obj_image); - return -1; - } - QString album_image = json_obj_image["large"].toString(); - if (!album_image.isEmpty()) { - cover_url = QUrl(album_image); - } - } - } - - if (json_obj.contains("composer")) { - QJsonValue json_composer = json_obj["composer"]; - if (!json_composer.isObject()) { - Error("Invalid Json reply, track composer is not a object.", json_composer); - return -1; - } - QJsonObject json_obj_composer = json_composer.toObject(); - if (!json_obj_composer.contains("id") || !json_obj_composer.contains("name")) { - Error("Invalid Json reply, track composer is missing id or name.", json_obj_composer); - return -1; - } - composer = json_obj_composer["name"].toString(); - } - - if (json_obj.contains("performer")) { - QJsonValue json_performer = json_obj["performer"]; - if (!json_performer.isObject()) { - Error("Invalid Json reply, track performer is not a object.", json_performer); - return -1; - } - QJsonObject json_obj_performer = json_performer.toObject(); - if (!json_obj_performer.contains("id") || !json_obj_performer.contains("name")) { - Error("Invalid Json reply, track performer is missing id or name.", json_obj_performer); - return -1; - } - performer = json_obj_performer["name"].toString(); - } - - //if (!streamable) { - //Warn(QString("Song %1 %2 %3 is not streamable").arg(album_artist).arg(album).arg(title)); - //} - - QUrl url; - url.setScheme(url_handler_->scheme()); - url.setPath(QString::number(song_id)); - - title.remove(Song::kTitleRemoveMisc); - - //qLog(Debug) << "id" << song_id << "track" << track << "title" << title << "album" << album << "album artist" << album_artist << cover_url << streamable << url; - - song.set_source(Song::Source_Qobuz); - song.set_song_id(song_id); - song.set_album_id(album_id); - song.set_artist_id(artist_id); - song.set_album(album); - song.set_artist(album_artist); - song.set_title(title); - song.set_track(track); - song.set_url(url); - song.set_length_nanosec(duration); - song.set_art_automatic(cover_url); - song.set_comment(copyright); - song.set_directory_id(0); - song.set_filetype(Song::FileType_Stream); - song.set_filesize(0); - song.set_mtime(0); - song.set_ctime(0); - song.set_valid(true); - - return song_id; - -} - -void QobuzRequest::GetAlbumCovers() { - - for (Song &song : songs_) { - AddAlbumCoverRequest(song); - } - FlushAlbumCoverRequests(); - - if (album_covers_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving album cover for %1 album...").arg(album_covers_requested_)); - else emit UpdateStatus(query_id_, tr("Retrieving album covers for %1 albums...").arg(album_covers_requested_)); - emit ProgressSetMaximum(query_id_, album_covers_requested_); - emit UpdateProgress(query_id_, 0); - -} - -void QobuzRequest::AddAlbumCoverRequest(Song &song) { - - QUrl cover_url(song.art_automatic()); - if (!cover_url.isValid()) return; - - if (album_covers_requests_sent_.contains(cover_url)) { - album_covers_requests_sent_.insertMulti(cover_url, &song); - return; - } - - AlbumCoverRequest request; - 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(cover_url, &song); - ++album_covers_requested_; - - album_cover_requests_queue_.enqueue(request); - -} - -void QobuzRequest::FlushAlbumCoverRequests() { - - while (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests) { - - AlbumCoverRequest request = album_cover_requests_queue_.dequeue(); - ++album_covers_requests_active_; - - QNetworkRequest req(request.url); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -#endif - QNetworkReply *reply = network_->get(req); - album_cover_replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumCoverReceived(QNetworkReply*, const QUrl&, const QString&)), reply, request.url, request.filename); - - } - -} - -void QobuzRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_url, const QString &filename) { - - if (album_cover_replies_.contains(reply)) { - album_cover_replies_.removeAll(reply); - reply->deleteLater(); - } - else { - AlbumCoverFinishCheck(); - return; - } - - --album_covers_requests_active_; - ++album_covers_received_; - - if (finished_) return; - - emit UpdateProgress(query_id_, album_covers_received_); - - if (!album_covers_requests_sent_.contains(cover_url)) { - AlbumCoverFinishCheck(); - return; - } - - if (reply->error() != QNetworkReply::NoError) { - Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error())); - album_covers_requests_sent_.remove(cover_url); - AlbumCoverFinishCheck(); - return; - } - - QByteArray data = reply->readAll(); - if (data.isEmpty()) { - Error(QString("Received empty image data for %1").arg(cover_url.toString())); - album_covers_requests_sent_.remove(cover_url); - AlbumCoverFinishCheck(); - return; - } - - QImage image; - if (image.loadFromData(data)) { - - if (image.save(filename, "JPG")) { - while (album_covers_requests_sent_.contains(cover_url)) { - Song *song = album_covers_requests_sent_.take(cover_url); - song->set_art_automatic(QUrl::fromLocalFile(filename)); - } - } - - } - else { - album_covers_requests_sent_.remove(cover_url); - Error(QString("Error decoding image data from %1").arg(cover_url.toString())); - } - - AlbumCoverFinishCheck(); - -} - -void QobuzRequest::AlbumCoverFinishCheck() { - - if (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests) - FlushAlbumCoverRequests(); - - FinishCheck(); - -} - -void QobuzRequest::FinishCheck() { - - if ( - !finished_ && - albums_requests_queue_.isEmpty() && - artists_requests_queue_.isEmpty() && - songs_requests_queue_.isEmpty() && - artist_albums_requests_queue_.isEmpty() && - album_songs_requests_queue_.isEmpty() && - album_cover_requests_queue_.isEmpty() && - artist_albums_requests_pending_.isEmpty() && - album_songs_requests_pending_.isEmpty() && - album_covers_requests_sent_.isEmpty() && - artists_requests_active_ <= 0 && - albums_requests_active_ <= 0 && - songs_requests_active_ <= 0 && - artist_albums_requests_active_ <= 0 && - artist_albums_received_ >= artist_albums_requested_ && - album_songs_requests_active_ <= 0 && - album_songs_received_ >= album_songs_requested_ && - album_covers_requests_active_ <= 0 && - album_covers_received_ >= album_covers_requested_ - ) { - finished_ = true; - if (no_results_ && songs_.isEmpty()) { - if (IsSearch()) - emit Results(query_id_, SongList(), tr("No match.")); - else - emit Results(query_id_, SongList(), QString()); - } - else { - if (songs_.isEmpty() && errors_.isEmpty()) - emit Results(query_id_, songs_, tr("Unknown error")); - else - emit Results(query_id_, songs_, ErrorsToHTML(errors_)); - } - } - -} - -void QobuzRequest::Error(const QString &error, const QVariant &debug) { - - if (!error.isEmpty()) { - errors_ << error; - qLog(Error) << "Qobuz:" << error; - } - if (debug.isValid()) qLog(Debug) << debug; - FinishCheck(); - -} - -void QobuzRequest::Warn(const QString &error, const QVariant &debug) { - - qLog(Error) << "Qobuz:" << error; - if (debug.isValid()) qLog(Debug) << debug; - -} - diff --git a/src/qobuz/qobuzrequest.h b/src/qobuz/qobuzrequest.h deleted file mode 100644 index ec9a74769..000000000 --- a/src/qobuz/qobuzrequest.h +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef QOBUZREQUEST_H -#define QOBUZREQUEST_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/song.h" -#include "qobuzbaserequest.h" - -class QNetworkReply; -class Application; -class NetworkAccessManager; -class QobuzService; -class QobuzUrlHandler; - -class QobuzRequest : public QobuzBaseRequest { - Q_OBJECT - - public: - - QobuzRequest(QobuzService *service, QobuzUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QueryType type, QObject *parent); - ~QobuzRequest(); - - void ReloadSettings(); - - void Process(); - void Search(const int search_id, const QString &search_text); - - signals: - void Login(); - void Login(const QString &username, const QString &password, const QString &token); - void LoginSuccess(); - void LoginFailure(QString failure_reason); - void Results(const int id, const SongList &songs, const QString &error); - void UpdateStatus(const int id, const QString &text); - void ProgressSetMaximum(const int id, const int max); - void UpdateProgress(const int id, const int max); - void StreamURLFinished(const QUrl original_url, const QUrl url, const Song::FileType, QString error = QString()); - - private slots: - - void ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); - - void AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); - void AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_requested, const int limit_requested, const int offset_requested); - - void SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); - void 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 = QString(), const QString &album_requested = QString()); - - void ArtistAlbumsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const int offset_requested); - void AlbumSongsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const QString &album_id, const int offset_requested, const QString &album_artist, const QString &album); - void AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_url, const QString &filename); - - private: - typedef QPair Param; - typedef QList ParamList; - - struct Request { - qint64 artist_id = 0; - QString album_id = 0; - qint64 song_id = 0; - int offset = 0; - int limit = 0; - QString album_artist; - QString album; - }; - struct AlbumCoverRequest { - QUrl url; - QString filename; - }; - - bool IsQuery() { return (type_ == QueryType_Artists || type_ == QueryType_Albums || type_ == QueryType_Songs); } - bool IsSearch() { return (type_ == QueryType_SearchArtists || type_ == QueryType_SearchAlbums || type_ == QueryType_SearchSongs); } - - void GetArtists(); - void GetAlbums(); - void GetSongs(); - - void ArtistsSearch(); - void AlbumsSearch(); - void SongsSearch(); - - void AddArtistsRequest(const int offset = 0, const int limit = 0); - void AddArtistsSearchRequest(const int offset = 0); - void FlushArtistsRequests(); - void AddAlbumsRequest(const int offset = 0, const int limit = 0); - void AddAlbumsSearchRequest(const int offset = 0); - void FlushAlbumsRequests(); - void AddSongsRequest(const int offset = 0, const int limit = 0); - void AddSongsSearchRequest(const int offset = 0); - void FlushSongsRequests(); - - void ArtistsFinishCheck(const int limit = 0, const int offset = 0, const int artists_received = 0); - void AlbumsFinishCheck(const qint64 artist_id, const int limit = 0, const int offset = 0, const int albums_total = 0, const int albums_received = 0); - void SongsFinishCheck(const qint64 artist_id, const QString &album_id, const int limit, const int offset, const int songs_total, const int songs_received, const QString &album_artist, const QString &album); - - void AddArtistAlbumsRequest(const qint64 artist_id, const int offset = 0); - void FlushArtistAlbumsRequests(); - - void AddAlbumSongsRequest(const qint64 artist_id, const QString &album_id, const QString &album_artist, const QString &album, const int offset = 0); - void FlushAlbumSongsRequests(); - - int ParseSong(Song &song, const QJsonObject &json_obj, qint64 artist_id, QString album_id, QString album_artist, QString album, QUrl cover_url); - - QString AlbumCoverFileName(const Song &song); - - void GetAlbumCovers(); - void AddAlbumCoverRequest(Song &song); - void FlushAlbumCoverRequests(); - void AlbumCoverFinishCheck(); - - void FinishCheck(); - 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; - static const int kMaxConcurrentSongsRequests; - static const int kMaxConcurrentArtistAlbumsRequests; - static const int kMaxConcurrentAlbumSongsRequests; - static const int kMaxConcurrentAlbumCoverRequests; - - QobuzService *service_; - QobuzUrlHandler *url_handler_; - Application *app_; - NetworkAccessManager *network_; - - QueryType type_; - int query_id_; - QString search_text_; - - bool finished_; - - QQueue artists_requests_queue_; - QQueue albums_requests_queue_; - QQueue songs_requests_queue_; - - QQueue artist_albums_requests_queue_; - QQueue album_songs_requests_queue_; - QQueue album_cover_requests_queue_; - - QList artist_albums_requests_pending_; - QHash album_songs_requests_pending_; - QMultiMap album_covers_requests_sent_; - - int artists_requests_active_; - int artists_total_; - int artists_received_; - - int albums_requests_active_; - int songs_requests_active_; - - int artist_albums_requests_active_; - int artist_albums_requested_; - int artist_albums_received_; - - int album_songs_requests_active_; - int album_songs_requested_; - int album_songs_received_; - - int album_covers_requests_active_; - int album_covers_requested_; - int album_covers_received_; - - SongList songs_; - QStringList errors_; - bool no_results_; - QList replies_; - QList album_cover_replies_; - -}; - -#endif // QOBUZREQUEST_H diff --git a/src/qobuz/qobuzservice.cpp b/src/qobuz/qobuzservice.cpp deleted file mode 100644 index 930ab7958..000000000 --- a/src/qobuz/qobuzservice.cpp +++ /dev/null @@ -1,765 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/application.h" -#include "core/player.h" -#include "core/closure.h" -#include "core/logging.h" -#include "core/network.h" -#include "core/database.h" -#include "core/song.h" -#include "core/utilities.h" -#include "internet/internetsearch.h" -#include "collection/collectionbackend.h" -#include "collection/collectionmodel.h" -#include "qobuzservice.h" -#include "qobuzurlhandler.h" -#include "qobuzbaserequest.h" -#include "qobuzrequest.h" -#include "qobuzfavoriterequest.h" -#include "qobuzstreamurlrequest.h" -#include "settings/settingsdialog.h" -#include "settings/qobuzsettingspage.h" - -using std::shared_ptr; - -const Song::Source QobuzService::kSource = Song::Source_Qobuz; -const char *QobuzService::kAuthUrl = "https://www.qobuz.com/api.json/0.2/user/login"; -const int QobuzService::kLoginAttempts = 2; -const int QobuzService::kTimeResetLoginAttempts = 60000; - -const char *QobuzService::kArtistsSongsTable = "qobuz_artists_songs"; -const char *QobuzService::kAlbumsSongsTable = "qobuz_albums_songs"; -const char *QobuzService::kSongsTable = "qobuz_songs"; - -const char *QobuzService::kArtistsSongsFtsTable = "qobuz_artists_songs_fts"; -const char *QobuzService::kAlbumsSongsFtsTable = "qobuz_albums_songs_fts"; -const char *QobuzService::kSongsFtsTable = "qobuz_songs_fts"; - -QobuzService::QobuzService(Application *app, QObject *parent) - : InternetService(Song::Source_Qobuz, "Qobuz", "qobuz", app, parent), - app_(app), - network_(new NetworkAccessManager(this)), - url_handler_(new QobuzUrlHandler(app, this)), - artists_collection_backend_(nullptr), - albums_collection_backend_(nullptr), - songs_collection_backend_(nullptr), - artists_collection_model_(nullptr), - albums_collection_model_(nullptr), - songs_collection_model_(nullptr), - artists_collection_sort_model_(new QSortFilterProxyModel(this)), - albums_collection_sort_model_(new QSortFilterProxyModel(this)), - songs_collection_sort_model_(new QSortFilterProxyModel(this)), - timer_search_delay_(new QTimer(this)), - timer_login_attempt_(new QTimer(this)), - favorite_request_(new QobuzFavoriteRequest(this, network_, this)), - format_(0), - search_delay_(1500), - artistssearchlimit_(1), - albumssearchlimit_(1), - songssearchlimit_(1), - download_album_covers_(true), - user_id_(-1), - credential_id_(-1), - pending_search_id_(0), - next_pending_search_id_(1), - search_id_(0), - login_sent_(false), - login_attempts_(0) - { - - app->player()->RegisterUrlHandler(url_handler_); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) - network_->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); -#endif - - // Backends - - artists_collection_backend_ = new CollectionBackend(); - artists_collection_backend_->moveToThread(app_->database()->thread()); - artists_collection_backend_->Init(app_->database(), Song::Source_Qobuz, kArtistsSongsTable, QString(), QString(), kArtistsSongsFtsTable); - - albums_collection_backend_ = new CollectionBackend(); - albums_collection_backend_->moveToThread(app_->database()->thread()); - albums_collection_backend_->Init(app_->database(), Song::Source_Qobuz, kAlbumsSongsTable, QString(), QString(), kAlbumsSongsFtsTable); - - songs_collection_backend_ = new CollectionBackend(); - songs_collection_backend_->moveToThread(app_->database()->thread()); - songs_collection_backend_->Init(app_->database(), Song::Source_Qobuz, kSongsTable, QString(), QString(), kSongsFtsTable); - - artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this); - albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this); - songs_collection_model_ = new CollectionModel(songs_collection_backend_, app_, this); - - artists_collection_sort_model_->setSourceModel(artists_collection_model_); - artists_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - artists_collection_sort_model_->setDynamicSortFilter(true); - artists_collection_sort_model_->setSortLocaleAware(true); - artists_collection_sort_model_->sort(0); - - albums_collection_sort_model_->setSourceModel(albums_collection_model_); - albums_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - albums_collection_sort_model_->setDynamicSortFilter(true); - albums_collection_sort_model_->setSortLocaleAware(true); - albums_collection_sort_model_->sort(0); - - songs_collection_sort_model_->setSourceModel(songs_collection_model_); - songs_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - songs_collection_sort_model_->setDynamicSortFilter(true); - songs_collection_sort_model_->setSortLocaleAware(true); - songs_collection_sort_model_->sort(0); - - // Search - - timer_search_delay_->setSingleShot(true); - connect(timer_search_delay_, SIGNAL(timeout()), SLOT(StartSearch())); - - timer_login_attempt_->setSingleShot(true); - connect(timer_login_attempt_, SIGNAL(timeout()), SLOT(ResetLoginAttempts())); - - connect(this, SIGNAL(Login()), SLOT(SendLogin())); - connect(this, SIGNAL(Login(QString, QString, QString)), SLOT(SendLogin(QString, QString, QString))); - - connect(this, SIGNAL(AddArtists(const SongList&)), favorite_request_, SLOT(AddArtists(const SongList&))); - connect(this, SIGNAL(AddAlbums(const SongList&)), favorite_request_, SLOT(AddAlbums(const SongList&))); - connect(this, SIGNAL(AddSongs(const SongList&)), favorite_request_, SLOT(AddSongs(const SongList&))); - - connect(this, SIGNAL(RemoveArtists(const SongList&)), favorite_request_, SLOT(RemoveArtists(const SongList&))); - connect(this, SIGNAL(RemoveAlbums(const SongList&)), favorite_request_, SLOT(RemoveAlbums(const SongList&))); - connect(this, SIGNAL(RemoveSongs(const SongList&)), favorite_request_, SLOT(RemoveSongs(const SongList&))); - - connect(favorite_request_, SIGNAL(ArtistsAdded(const SongList&)), artists_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); - connect(favorite_request_, SIGNAL(AlbumsAdded(const SongList&)), albums_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); - connect(favorite_request_, SIGNAL(SongsAdded(const SongList&)), songs_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); - - connect(favorite_request_, SIGNAL(ArtistsRemoved(const SongList&)), artists_collection_backend_, SLOT(DeleteSongs(const SongList&))); - connect(favorite_request_, SIGNAL(AlbumsRemoved(const SongList&)), albums_collection_backend_, SLOT(DeleteSongs(const SongList&))); - connect(favorite_request_, SIGNAL(SongsRemoved(const SongList&)), songs_collection_backend_, SLOT(DeleteSongs(const SongList&))); - - ReloadSettings(); - -} - -QobuzService::~QobuzService() { - - while (!stream_url_requests_.isEmpty()) { - QobuzStreamURLRequest *stream_url_req = stream_url_requests_.takeFirst(); - disconnect(stream_url_req, 0, this, 0); - stream_url_req->deleteLater(); - } - - artists_collection_backend_->deleteLater(); - albums_collection_backend_->deleteLater(); - songs_collection_backend_->deleteLater(); - -} - -void QobuzService::Exit() { - - wait_for_exit_ << artists_collection_backend_ << albums_collection_backend_ << songs_collection_backend_; - - connect(artists_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); - connect(albums_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); - connect(songs_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); - - artists_collection_backend_->ExitAsync(); - albums_collection_backend_->ExitAsync(); - songs_collection_backend_->ExitAsync(); - -} - -void QobuzService::ExitReceived() { - - QObject *obj = static_cast(sender()); - disconnect(obj, 0, this, 0); - qLog(Debug) << obj << "successfully exited."; - wait_for_exit_.removeAll(obj); - if (wait_for_exit_.isEmpty()) emit ExitFinished(); - -} - -void QobuzService::ShowConfig() { - app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Qobuz); -} - -void QobuzService::ReloadSettings() { - - QSettings s; - s.beginGroup(QobuzSettingsPage::kSettingsGroup); - - app_id_ = s.value("app_id").toString(); - app_secret_ = s.value("app_secret").toString(); - - username_ = s.value("username").toString(); - QByteArray password = s.value("password").toByteArray(); - if (password.isEmpty()) password_.clear(); - else password_ = QString::fromUtf8(QByteArray::fromBase64(password)); - - format_ = s.value("format", 27).toInt(); - search_delay_ = s.value("searchdelay", 1500).toInt(); - artistssearchlimit_ = s.value("artistssearchlimit", 4).toInt(); - albumssearchlimit_ = s.value("albumssearchlimit", 10).toInt(); - songssearchlimit_ = s.value("songssearchlimit", 10).toInt(); - download_album_covers_ = s.value("downloadalbumcovers", true).toBool(); - - user_id_ = s.value("user_id").toInt(); - device_id_ = s.value("device_id").toString(); - user_auth_token_ = s.value("user_auth_token").toString(); - - s.endGroup(); - -} - -void QobuzService::SendLogin() { - SendLogin(app_id_, username_, password_); -} - -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_; - if (timer_login_attempt_->isActive()) timer_login_attempt_->stop(); - timer_login_attempt_->setInterval(kTimeResetLoginAttempts); - timer_login_attempt_->start(); - - const ParamList params = ParamList() << Param("app_id", app_id) - << Param("username", username) - << Param("password", password) - << Param("device_manufacturer_id", Utilities::MacAddress()); - - QUrlQuery url_query; - 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); - } - - QUrl url(kAuthUrl); - QNetworkRequest req(url); - - req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8(); - QNetworkReply *reply = network_->post(req, query); - - connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(HandleLoginSSLErrors(QList))); - NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply); - - qLog(Debug) << "Qobuz: Sending request" << url << query; - -} - -void QobuzService::HandleLoginSSLErrors(QList 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 || 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 "status", "code" and "message" - then use that instead. - QByteArray data(reply->readAll()); - 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.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(); - login_errors_ << QString("%1 (%2)").arg(message).arg(code); - } - } - 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(); - return; - } - } - - login_errors_.clear(); - - QByteArray data(reply->readAll()); - QJsonParseError json_error; - QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error); - - if (json_error.error != QJsonParseError::NoError) { - LoginError("Authentication reply from server missing Json data."); - return; - } - - if (json_doc.isEmpty()) { - LoginError("Authentication reply from server has empty Json document."); - return; - } - - if (!json_doc.isObject()) { - LoginError("Authentication reply from server has Json document that is not an object.", json_doc); - return; - } - - QJsonObject json_obj = json_doc.object(); - if (json_obj.isEmpty()) { - LoginError("Authentication reply from server has empty Json object.", json_doc); - return; - } - - if (!json_obj.contains("user_auth_token")) { - LoginError("Authentication reply from server is missing user_auth_token", json_obj); - return; - } - user_auth_token_ = json_obj["user_auth_token"].toString(); - - if (!json_obj.contains("user")) { - LoginError("Authentication reply from server is missing user", json_obj); - return; - } - QJsonValue json_user = json_obj["user"]; - if (!json_user.isObject()) { - LoginError("Authentication reply user is not a object", json_obj); - return; - } - QJsonObject json_obj_user = json_user.toObject(); - - if (!json_obj_user.contains("id")) { - LoginError("Authentication reply from server is missing user id", json_obj_user); - return; - } - user_id_ = json_obj_user["id"].toInt(); - - if (!json_obj_user.contains("device")) { - LoginError("Authentication reply from server is missing user device", json_obj_user); - return; - } - QJsonValue json_device = json_obj_user["device"]; - if (!json_device.isObject()) { - LoginError("Authentication reply from server user device is not a object", json_device); - return; - } - QJsonObject json_obj_device = json_device.toObject(); - - if (!json_obj_device.contains("device_manufacturer_id")) { - LoginError("Authentication reply from server device is missing device_manufacturer_id", json_obj_device); - return; - } - device_id_ = json_obj_device["device_manufacturer_id"].toString(); - - if (!json_obj_user.contains("credential")) { - LoginError("Authentication reply from server is missing user credential", json_obj_user); - return; - } - QJsonValue json_credential = json_obj_user["credential"]; - if (!json_credential.isObject()) { - LoginError("Authentication reply from serve userr credential is not a object", json_device); - return; - } - QJsonObject json_obj_credential = json_credential.toObject(); - - if (!json_obj_credential.contains("id")) { - LoginError("Authentication reply user credential from server is missing user credential id", json_obj_device); - return; - } - credential_id_ = json_obj_credential["id"].toInt(); - - QSettings s; - s.beginGroup(QobuzSettingsPage::kSettingsGroup); - s.setValue("user_auth_token", user_auth_token_); - s.setValue("user_id", user_id_); - s.setValue("credential_id", credential_id_); - s.setValue("device_id", device_id_); - s.endGroup(); - - qLog(Debug) << "Qobuz: Login successful" << "user id" << user_id_ << "user auth token" << user_auth_token_ << "device id" << device_id_; - - login_attempts_ = 0; - if (timer_login_attempt_->isActive()) timer_login_attempt_->stop(); - - emit LoginComplete(true); - emit LoginSuccess(); - -} - -void QobuzService::Logout() { - - user_auth_token_.clear(); - device_id_.clear(); - user_id_ = -1; - credential_id_ = -1; - - QSettings s; - s.beginGroup(QobuzSettingsPage::kSettingsGroup); - s.remove("user_id"); - s.remove("credential_id"); - s.remove("device_id"); - s.remove("user_auth_token"); - s.endGroup(); - -} - -void QobuzService::ResetLoginAttempts() { - login_attempts_ = 0; -} - -void QobuzService::TryLogin() { - - if (authenticated() || login_sent_) return; - - if (login_attempts_ >= kLoginAttempts) { - emit LoginComplete(false, tr("Maximum number of login attempts reached.")); - return; - } - if (app_id_.isEmpty()) { - emit LoginComplete(false, tr("Missing Qobuz app ID.")); - return; - } - if (username_.isEmpty()) { - emit LoginComplete(false, tr("Missing Qobuz username.")); - return; - } - if (password_.isEmpty()) { - emit LoginComplete(false, tr("Missing Qobuz password.")); - return; - } - - emit Login(); - -} - -void QobuzService::ResetArtistsRequest() { - - if (artists_request_.get()) { - disconnect(artists_request_.get(), 0, this, 0); - disconnect(this, 0, artists_request_.get(), 0); - artists_request_.reset(); - } - -} - -void QobuzService::GetArtists() { - - if (app_id().isEmpty()) { - emit ArtistsResults(SongList(), tr("Missing Qobuz app ID.")); - return; - } - - if (!authenticated()) { - emit ArtistsResults(SongList(), tr("Not authenticated with Qobuz.")); - return; - } - - ResetArtistsRequest(); - - 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&))); - connect(artists_request_.get(), SIGNAL(ProgressSetMaximum(const int, const int)), SLOT(ArtistsProgressSetMaximumReceived(const int, const int))); - connect(artists_request_.get(), SIGNAL(UpdateProgress(const int, const int)), SLOT(ArtistsUpdateProgressReceived(const int, const int))); - - artists_request_->Process(); - -} - -void QobuzService::ArtistsResultsReceived(const int id, const SongList &songs, const QString &error) { - Q_UNUSED(id); - emit ArtistsResults(songs, error); -} - -void QobuzService::ArtistsUpdateStatusReceived(const int id, const QString &text) { - Q_UNUSED(id); - emit ArtistsUpdateStatus(text); -} - -void QobuzService::ArtistsProgressSetMaximumReceived(const int id, const int max) { - Q_UNUSED(id); - emit ArtistsProgressSetMaximum(max); -} - -void QobuzService::ArtistsUpdateProgressReceived(const int id, const int progress) { - Q_UNUSED(id); - emit ArtistsUpdateProgress(progress); -} - -void QobuzService::ResetAlbumsRequest() { - - if (albums_request_.get()) { - disconnect(albums_request_.get(), 0, this, 0); - disconnect(this, 0, albums_request_.get(), 0); - albums_request_.reset(); - } - -} - -void QobuzService::GetAlbums() { - - if (app_id().isEmpty()) { - emit AlbumsResults(SongList(), tr("Missing Qobuz app ID.")); - return; - } - - if (!authenticated()) { - emit AlbumsResults(SongList(), tr("Not authenticated with Qobuz.")); - return; - } - - ResetAlbumsRequest(); - 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))); - connect(albums_request_.get(), SIGNAL(UpdateProgress(const int, const int)), SLOT(AlbumsUpdateProgressReceived(const int, const int))); - - albums_request_->Process(); - -} - -void QobuzService::AlbumsResultsReceived(const int id, const SongList &songs, const QString &error) { - Q_UNUSED(id); - emit AlbumsResults(songs, error); -} - -void QobuzService::AlbumsUpdateStatusReceived(const int id, const QString &text) { - Q_UNUSED(id); - emit AlbumsUpdateStatus(text); -} - -void QobuzService::AlbumsProgressSetMaximumReceived(const int id, const int max) { - Q_UNUSED(id); - emit AlbumsProgressSetMaximum(max); -} - -void QobuzService::AlbumsUpdateProgressReceived(const int id, const int progress) { - Q_UNUSED(id); - emit AlbumsUpdateProgress(progress); -} - -void QobuzService::ResetSongsRequest() { - - if (songs_request_.get()) { - disconnect(songs_request_.get(), 0, this, 0); - disconnect(this, 0, songs_request_.get(), 0); - songs_request_.reset(); - } - -} - -void QobuzService::GetSongs() { - - if (app_id().isEmpty()) { - emit SongsResults(SongList(), tr("Missing Qobuz app ID.")); - return; - } - - if (!authenticated()) { - emit SongsResults(SongList(), tr("Not authenticated with Qobuz.")); - return; - } - - ResetSongsRequest(); - 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))); - connect(songs_request_.get(), SIGNAL(UpdateProgress(const int, const int)), SLOT(SongsUpdateProgressReceived(const int, const int))); - - songs_request_->Process(); - -} - -void QobuzService::SongsResultsReceived(const int id, const SongList &songs, const QString &error) { - Q_UNUSED(id); - emit SongsResults(songs, error); -} - -void QobuzService::SongsUpdateStatusReceived(const int id, const QString &text) { - Q_UNUSED(id); - emit SongsUpdateStatus(text); -} - -void QobuzService::SongsProgressSetMaximumReceived(const int id, const int max) { - Q_UNUSED(id); - emit SongsProgressSetMaximum(max); -} - -void QobuzService::SongsUpdateProgressReceived(const int id, const int progress) { - Q_UNUSED(id); - emit SongsUpdateProgress(progress); -} - -int QobuzService::Search(const QString &text, InternetSearch::SearchType type) { - - pending_search_id_ = next_pending_search_id_; - pending_search_text_ = text; - pending_search_type_ = type; - - next_pending_search_id_++; - - if (text.isEmpty()) { - timer_search_delay_->stop(); - return pending_search_id_; - } - timer_search_delay_->setInterval(search_delay_); - timer_search_delay_->start(); - - return pending_search_id_; - -} - -void QobuzService::StartSearch() { - - search_id_ = pending_search_id_; - search_text_ = pending_search_text_; - - if (app_id_.isEmpty()) { // App ID is the only thing needed to search. - emit SearchResults(search_id_, SongList(), tr("Missing Qobuz app ID.")); - return; - } - - SendSearch(); - -} - -void QobuzService::CancelSearch() { -} - -void QobuzService::SendSearch() { - - QobuzBaseRequest::QueryType type; - - switch (pending_search_type_) { - case InternetSearch::SearchType_Artists: - type = QobuzBaseRequest::QueryType_SearchArtists; - break; - case InternetSearch::SearchType_Albums: - type = QobuzBaseRequest::QueryType_SearchAlbums; - break; - case InternetSearch::SearchType_Songs: - type = QobuzBaseRequest::QueryType_SearchSongs; - break; - default: - //Error("Invalid search type."); - return; - } - - 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&))); - connect(search_request_.get(), SIGNAL(ProgressSetMaximum(const int, const int)), SIGNAL(SearchProgressSetMaximum(const int, const int))); - connect(search_request_.get(), SIGNAL(UpdateProgress(const int, const int)), SIGNAL(SearchUpdateProgress(const int, const int))); - - search_request_->Search(search_id_, search_text_); - search_request_->Process(); - -} - -void QobuzService::SearchResultsReceived(const int id, const SongList &songs, const QString &error) { - emit SearchResults(id, songs, error); -} - -void QobuzService::GetStreamURL(const QUrl &url) { - - if (app_id().isEmpty() || app_secret().isEmpty()) { // Don't check for login here, because we allow automatic login. - emit StreamURLFinished(url, url, Song::FileType_Stream, -1, -1, -1, tr("Missing Qobuz app ID or secret.")); - return; - } - - QobuzStreamURLRequest *stream_url_req = new QobuzStreamURLRequest(this, network_, url, this); - stream_url_requests_ << stream_url_req; - - connect(stream_url_req, SIGNAL(TryLogin()), this, SLOT(TryLogin())); - connect(stream_url_req, SIGNAL(StreamURLFinished(const QUrl&, const QUrl&, const Song::FileType, const int, const int, const qint64, QString)), this, SLOT(HandleStreamURLFinished(const QUrl&, const QUrl&, const Song::FileType, const int, const int, const qint64, QString))); - connect(this, SIGNAL(LoginComplete(const bool, QString)), stream_url_req, SLOT(LoginComplete(const bool, QString))); - - stream_url_req->Process(); - -} - -void QobuzService::HandleStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) { - - QobuzStreamURLRequest *stream_url_req = qobject_cast(sender()); - if (!stream_url_req || !stream_url_requests_.contains(stream_url_req)) return; - stream_url_req->deleteLater(); - stream_url_requests_.removeAll(stream_url_req); - - emit StreamURLFinished(original_url, stream_url, filetype, samplerate, bit_depth, duration, error); - -} - -void QobuzService::LoginError(const QString &error, const QVariant &debug) { - - if (!error.isEmpty()) login_errors_ << error; - - QString error_html; - for (const QString &error : login_errors_) { - qLog(Error) << "Qobuz:" << error; - error_html += error + "
    "; - } - if (debug.isValid()) qLog(Debug) << debug; - - emit LoginFailure(error_html); - emit LoginComplete(false, error_html); - - login_errors_.clear(); - -} diff --git a/src/qobuz/qobuzservice.h b/src/qobuz/qobuzservice.h deleted file mode 100644 index d45343dfa..000000000 --- a/src/qobuz/qobuzservice.h +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef QOBUZSERVICE_H -#define QOBUZSERVICE_H - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/song.h" -#include "internet/internetservice.h" -#include "internet/internetsearch.h" - -class QTimer; -class QNetworkReply; -class QSortFilterProxyModel; -class Application; -class NetworkAccessManager; -class QobuzUrlHandler; -class QobuzRequest; -class QobuzFavoriteRequest; -class QobuzStreamURLRequest; -class CollectionBackend; -class CollectionModel; - -using std::shared_ptr; - -class QobuzService : public InternetService { - Q_OBJECT - - public: - QobuzService(Application *app, QObject *parent); - ~QobuzService(); - - static const Song::Source kSource; - - void Exit(); - void ReloadSettings(); - - void Logout(); - int Search(const QString &query, InternetSearch::SearchType type); - void CancelSearch(); - - 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_; } - QString password() { return password_; } - int format() { return format_; } - int search_delay() { return search_delay_; } - int artistssearchlimit() { return artistssearchlimit_; } - int albumssearchlimit() { return albumssearchlimit_; } - int songssearchlimit() { return songssearchlimit_; } - bool download_album_covers() { return download_album_covers_; } - - QString user_auth_token() { return user_auth_token_; } - qint64 user_id() { return user_id_; } - QString device_id() { return device_id_; } - qint64 credential_id() { return credential_id_; } - - bool authenticated() { return (!app_id_.isEmpty() && !app_secret_.isEmpty() && !user_auth_token_.isEmpty()); } - bool login_sent() { return login_sent_; } - bool login_attempts() { return login_attempts_; } - - void GetStreamURL(const QUrl &url); - - CollectionBackend *artists_collection_backend() { return artists_collection_backend_; } - CollectionBackend *albums_collection_backend() { return albums_collection_backend_; } - CollectionBackend *songs_collection_backend() { return songs_collection_backend_; } - - CollectionModel *artists_collection_model() { return artists_collection_model_; } - CollectionModel *albums_collection_model() { return albums_collection_model_; } - CollectionModel *songs_collection_model() { return songs_collection_model_; } - - QSortFilterProxyModel *artists_collection_sort_model() { return artists_collection_sort_model_; } - QSortFilterProxyModel *albums_collection_sort_model() { return albums_collection_sort_model_; } - QSortFilterProxyModel *songs_collection_sort_model() { return songs_collection_sort_model_; } - - enum QueryType { - QueryType_Artists, - QueryType_Albums, - QueryType_Songs, - QueryType_SearchArtists, - QueryType_SearchAlbums, - QueryType_SearchSongs, - }; - - signals: - - public slots: - void ShowConfig(); - void TryLogin(); - void SendLogin(const QString &app_id, const QString &username, const QString &password); - void GetArtists(); - void GetAlbums(); - void GetSongs(); - void ResetArtistsRequest(); - void ResetAlbumsRequest(); - void ResetSongsRequest(); - - private slots: - void ExitReceived(); - void SendLogin(); - void HandleLoginSSLErrors(QList ssl_errors); - void HandleAuthReply(QNetworkReply *reply); - void ResetLoginAttempts(); - void StartSearch(); - void ArtistsResultsReceived(const int id, const SongList &songs, const QString &error); - void AlbumsResultsReceived(const int id, const SongList &songs, const QString &error); - void SongsResultsReceived(const int id, const SongList &songs, const QString &error); - void SearchResultsReceived(const int id, const SongList &songs, const QString &error); - void ArtistsUpdateStatusReceived(const int id, const QString &text); - void AlbumsUpdateStatusReceived(const int id, const QString &text); - void SongsUpdateStatusReceived(const int id, const QString &text); - void ArtistsProgressSetMaximumReceived(const int id, const int max); - void AlbumsProgressSetMaximumReceived(const int id, const int max); - void SongsProgressSetMaximumReceived(const int id, const int max); - void ArtistsUpdateProgressReceived(const int id, const int progress); - void AlbumsUpdateProgressReceived(const int id, const int progress); - void SongsUpdateProgressReceived(const int id, const int progress); - void HandleStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error); - - private: - typedef QPair Param; - typedef QList ParamList; - - typedef QPair EncodedParam; - typedef QList EncodedParamList; - - void SendSearch(); - void LoginError(const QString &error = QString(), const QVariant &debug = QVariant()); - - static const char *kAuthUrl; - static const int kLoginAttempts; - static const int kTimeResetLoginAttempts; - - static const char *kArtistsSongsTable; - static const char *kAlbumsSongsTable; - static const char *kSongsTable; - - static const char *kArtistsSongsFtsTable; - static const char *kAlbumsSongsFtsTable; - static const char *kSongsFtsTable; - - Application *app_; - NetworkAccessManager *network_; - QobuzUrlHandler *url_handler_; - - CollectionBackend *artists_collection_backend_; - CollectionBackend *albums_collection_backend_; - CollectionBackend *songs_collection_backend_; - - CollectionModel *artists_collection_model_; - CollectionModel *albums_collection_model_; - CollectionModel *songs_collection_model_; - - QSortFilterProxyModel *artists_collection_sort_model_; - QSortFilterProxyModel *albums_collection_sort_model_; - QSortFilterProxyModel *songs_collection_sort_model_; - - QTimer *timer_search_delay_; - QTimer *timer_login_attempt_; - - std::shared_ptr artists_request_; - std::shared_ptr albums_request_; - std::shared_ptr songs_request_; - std::shared_ptr search_request_; - QobuzFavoriteRequest *favorite_request_; - - QString app_id_; - QString app_secret_; - QString username_; - QString password_; - int format_; - int search_delay_; - int artistssearchlimit_; - int albumssearchlimit_; - int songssearchlimit_; - bool download_album_covers_; - - qint64 user_id_; - QString user_auth_token_; - QString device_id_; - qint64 credential_id_; - - int pending_search_id_; - int next_pending_search_id_; - QString pending_search_text_; - InternetSearch::SearchType pending_search_type_; - - int search_id_; - QString search_text_; - bool login_sent_; - int login_attempts_; - - QList stream_url_requests_; - - QStringList login_errors_; - - QList wait_for_exit_; - -}; - -#endif // QOBUZSERVICE_H diff --git a/src/qobuz/qobuzstreamurlrequest.cpp b/src/qobuz/qobuzstreamurlrequest.cpp deleted file mode 100644 index b8b342541..000000000 --- a/src/qobuz/qobuzstreamurlrequest.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/logging.h" -#include "core/network.h" -#include "core/song.h" -#include "core/timeconstants.h" -#include "qobuzservice.h" -#include "qobuzbaserequest.h" -#include "qobuzstreamurlrequest.h" - -QobuzStreamURLRequest::QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent) - : QobuzBaseRequest(service, network, parent), - service_(service), - reply_(nullptr), - original_url_(original_url), - song_id_(original_url.path().toInt()), - tries_(0), - need_login_(false) {} - -QobuzStreamURLRequest::~QobuzStreamURLRequest() { - - if (reply_) { - disconnect(reply_, 0, this, 0); - if (reply_->isRunning()) reply_->abort(); - reply_->deleteLater(); - } - -} - -void QobuzStreamURLRequest::LoginComplete(const bool success, const QString &error) { - - if (!need_login_) return; - need_login_ = false; - - if (!success) { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error); - return; - } - - Process(); - -} - -void QobuzStreamURLRequest::Process() { - - if (app_id().isEmpty() || app_secret().isEmpty()) { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Missing Qobuz app ID or secret.")); - return; - } - - if (!authenticated()) { - need_login_ = true; - emit TryLogin(); - return; - } - GetStreamURL(); - -} - -void QobuzStreamURLRequest::Cancel() { - - if (reply_ && reply_->isRunning()) { - reply_->abort(); - } - else { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Cancelled.")); - } - -} - -void QobuzStreamURLRequest::GetStreamURL() { - - ++tries_; - - if (reply_) { - disconnect(reply_, 0, this, 0); - if (reply_->isRunning()) reply_->abort(); - reply_->deleteLater(); - } - - QByteArray appid = app_id().toUtf8(); - QByteArray secret_decoded = QByteArray::fromBase64(app_secret().toUtf8()); - QString secret; - for (int x = 0, y = 0; x < secret_decoded.length(); ++x , ++y) { - if (y == appid.length()) y = 0; - secret.append(QChar(secret_decoded[x] ^ appid[y])); - } - - quint64 timestamp = QDateTime::currentDateTime().toTime_t(); - - ParamList params_to_sign = ParamList() << Param("format_id", QString::number(format())) - << Param("track_id", QString::number(song_id_)); - - std::sort(params_to_sign.begin(), params_to_sign.end()); - - QString data_to_sign; - data_to_sign += "trackgetFileUrl"; - for (const Param ¶m : params_to_sign) { - data_to_sign += param.first + param.second; - } - data_to_sign += QString::number(timestamp); - data_to_sign += secret.toUtf8(); - - QByteArray const digest = QCryptographicHash::hash(data_to_sign.toUtf8(), QCryptographicHash::Md5); - QString signature = QString::fromLatin1(digest.toHex()).rightJustified(32, '0').toLower(); - - ParamList params = params_to_sign; - params << Param("request_ts", QString::number(timestamp)); - params << Param("request_sig", signature); - params << Param("user_auth_token", user_auth_token()); - - std::sort(params.begin(), params.end()); - - reply_ = CreateRequest(QString("track/getFileUrl"), params); - connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived())); - -} - -void QobuzStreamURLRequest::StreamURLReceived() { - - if (!reply_) return; - - QByteArray data = GetReplyData(reply_); - - disconnect(reply_, 0, this, 0); - reply_->deleteLater(); - reply_ = nullptr; - - if (data.isEmpty()) { - if (!authenticated() && login_sent() && tries_ <= 1) { - need_login_ = true; - return; - } - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - - if (!json_obj.contains("track_id")) { - Error("Invalid Json reply, stream url is missing track_id.", json_obj); - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - - int track_id = json_obj["track_id"].toInt(); - if (track_id != song_id_) { - Error("Incorrect track ID returned.", json_obj); - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - - if (!json_obj.contains("mime_type") || !json_obj.contains("url")) { - 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, errors_.first()); - return; - } - - QUrl url(json_obj["url"].toString()); - QString mimetype = json_obj["mime_type"].toString(); - - Song::FileType filetype(Song::FileType_Unknown); - QMimeDatabase mimedb; - for (QString suffix : mimedb.mimeTypeForName(mimetype.toUtf8()).suffixes()) { - filetype = Song::FiletypeByExtension(suffix); - if (filetype != Song::FileType_Unknown) break; - } - if (filetype == Song::FileType_Unknown) { - qLog(Debug) << "Qobuz: Unknown mimetype" << mimetype; - filetype = Song::FileType_Stream; - } - - if (!url.isValid()) { - Error("Returned stream url is invalid.", json_obj); - emit StreamURLFinished(original_url_, original_url_, filetype, -1, -1, -1, errors_.first()); - return; - } - - qint64 duration = -1; - if (json_obj.contains("duration")) { - duration = json_obj["duration"].toDouble() * kNsecPerSec; - } - int samplerate = -1; - if (json_obj.contains("sampling_rate")) { - samplerate = json_obj["sampling_rate"].toDouble() * 1000; - } - int bit_depth = -1; - if (json_obj.contains("bit_depth")) { - bit_depth = json_obj["bit_depth"].toDouble(); - } - - 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; - -} - diff --git a/src/qobuz/qobuzstreamurlrequest.h b/src/qobuz/qobuzstreamurlrequest.h deleted file mode 100644 index 568d7f537..000000000 --- a/src/qobuz/qobuzstreamurlrequest.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef QOBUZSTREAMURLREQUEST_H -#define QOBUZSTREAMURLREQUEST_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "core/song.h" -#include "qobuzbaserequest.h" - -class QNetworkReply; -class NetworkAccessManager; -class QobuzService; - -class QobuzStreamURLRequest : public QobuzBaseRequest { - Q_OBJECT - - public: - QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent); - ~QobuzStreamURLRequest(); - - void GetStreamURL(); - void Process(); - void NeedLogin() { need_login_ = true; } - void Cancel(); - - QUrl original_url() { return original_url_; } - int song_id() { return song_id_; } - bool need_login() { return need_login_; } - - signals: - void TryLogin(); - void StreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString()); - - private slots: - void LoginComplete(const bool success, const QString &error = QString()); - 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_; - -}; - -#endif // QOBUZSTREAMURLREQUEST_H diff --git a/src/qobuz/qobuzurlhandler.cpp b/src/qobuz/qobuzurlhandler.cpp deleted file mode 100644 index e4a3d27c9..000000000 --- a/src/qobuz/qobuzurlhandler.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include -#include -#include - -#include "core/application.h" -#include "core/taskmanager.h" -#include "core/song.h" -#include "qobuz/qobuzservice.h" -#include "qobuzurlhandler.h" - -QobuzUrlHandler::QobuzUrlHandler(Application *app, QobuzService *service) : - UrlHandler(service), - app_(app), - service_(service), - task_id_(-1) - { - - connect(service, SIGNAL(StreamURLFinished(const QUrl&, const QUrl&, const Song::FileType, const int, const int, const qint64, QString)), this, SLOT(GetStreamURLFinished(const QUrl&, const QUrl&, const Song::FileType, const int, const int, const qint64, QString))); - -} - -UrlHandler::LoadResult QobuzUrlHandler::StartLoading(const QUrl &url) { - - LoadResult ret(url); - if (task_id_ != -1) return ret; - task_id_ = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme())); - service_->GetStreamURL(url); - ret.type_ = LoadResult::WillLoadAsynchronously; - return ret; - -} - -void QobuzUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) { - - if (task_id_ == -1) return; - CancelTask(); - if (error.isEmpty()) - emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration)); - else - emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, stream_url, filetype, -1, -1, -1, error)); - -} - -void QobuzUrlHandler::CancelTask() { - app_->task_manager()->SetTaskFinished(task_id_); - task_id_ = -1; -} diff --git a/src/qobuz/qobuzurlhandler.h b/src/qobuz/qobuzurlhandler.h deleted file mode 100644 index 2650f5a23..000000000 --- a/src/qobuz/qobuzurlhandler.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef QOBUZURLHANDLER_H -#define QOBUZURLHANDLER_H - -#include -#include -#include -#include - -#include "core/urlhandler.h" -#include "core/song.h" -#include "qobuz/qobuzservice.h" - -class Application; - -class QobuzUrlHandler : public UrlHandler { - Q_OBJECT - - public: - QobuzUrlHandler(Application *app, QobuzService *service); - - QString scheme() const { return service_->url_scheme(); } - LoadResult StartLoading(const QUrl &url); - - void CancelTask(); - - private slots: - void GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString()); - - private: - Application *app_; - QobuzService *service_; - int task_id_; - -}; - -#endif diff --git a/src/settings/qobuzsettingspage.cpp b/src/settings/qobuzsettingspage.cpp deleted file mode 100644 index 846df5d2e..000000000 --- a/src/settings/qobuzsettingspage.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "settingsdialog.h" -#include "qobuzsettingspage.h" -#include "ui_qobuzsettingspage.h" -#include "core/application.h" -#include "core/iconloader.h" -#include "widgets/loginstatewidget.h" -#include "internet/internetservices.h" -#include "qobuz/qobuzservice.h" - -const char *QobuzSettingsPage::kSettingsGroup = "Qobuz"; - -QobuzSettingsPage::QobuzSettingsPage(SettingsDialog *parent) - : SettingsPage(parent), - ui_(new Ui::QobuzSettingsPage), - service_(dialog()->app()->internet_services()->Service()) { - - ui_->setupUi(this); - setWindowIcon(IconLoader::Load("qobuz")); - - connect(ui_->button_login, SIGNAL(clicked()), SLOT(LoginClicked())); - connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked())); - - connect(this, SIGNAL(Login(QString, QString, QString)), service_, SLOT(SendLogin(QString, QString, QString))); - - connect(service_, SIGNAL(LoginFailure(QString)), SLOT(LoginFailure(QString))); - connect(service_, SIGNAL(LoginSuccess()), SLOT(LoginSuccess())); - - dialog()->installEventFilter(this); - - ui_->format->addItem("MP3 320", 5); - ui_->format->addItem("FLAC Lossless", 6); - ui_->format->addItem("FLAC Hi-Res <= 96kHz", 7); - ui_->format->addItem("FLAC Hi-Res > 96kHz", 27); - -} - -QobuzSettingsPage::~QobuzSettingsPage() { delete ui_; } - -void QobuzSettingsPage::Load() { - - QSettings s; - - s.beginGroup(kSettingsGroup); - ui_->enable->setChecked(s.value("enabled", false).toBool()); - ui_->app_id->setText(s.value("app_id").toString()); - ui_->app_secret->setText(s.value("app_secret").toString()); - - ui_->username->setText(s.value("username").toString()); - QByteArray password = s.value("password").toByteArray(); - if (password.isEmpty()) ui_->password->clear(); - else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password))); - - dialog()->ComboBoxLoadFromSettings(s, ui_->format, "format", 27); - ui_->searchdelay->setValue(s.value("searchdelay", 1500).toInt()); - ui_->artistssearchlimit->setValue(s.value("artistssearchlimit", 4).toInt()); - ui_->albumssearchlimit->setValue(s.value("albumssearchlimit", 10).toInt()); - ui_->songssearchlimit->setValue(s.value("songssearchlimit", 10).toInt()); - ui_->checkbox_download_album_covers->setChecked(s.value("downloadalbumcovers", true).toBool()); - - s.endGroup(); - - if (service_->authenticated()) ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn); - -} - -void QobuzSettingsPage::Save() { - - QSettings s; - s.beginGroup(kSettingsGroup); - s.setValue("enabled", ui_->enable->isChecked()); - s.setValue("app_id", ui_->app_id->text()); - s.setValue("app_secret", ui_->app_secret->text()); - - s.setValue("username", ui_->username->text()); - s.setValue("password", QString::fromUtf8(ui_->password->text().toUtf8().toBase64())); - - s.setValue("format", ui_->format->itemData(ui_->format->currentIndex())); - s.setValue("searchdelay", ui_->searchdelay->value()); - s.setValue("artistssearchlimit", ui_->artistssearchlimit->value()); - s.setValue("albumssearchlimit", ui_->albumssearchlimit->value()); - s.setValue("songssearchlimit", ui_->songssearchlimit->value()); - s.setValue("downloadalbumcovers", ui_->checkbox_download_album_covers->isChecked()); - s.endGroup(); - - service_->ReloadSettings(); - -} - -void QobuzSettingsPage::LoginClicked() { - - if (ui_->app_id->text().isEmpty() || ui_->username->text().isEmpty() || ui_->password->text().isEmpty()) { - QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing app id, username or password.")); - return; - } - emit Login(ui_->app_id->text(), ui_->username->text(), ui_->password->text()); - ui_->button_login->setEnabled(false); - -} - -bool QobuzSettingsPage::eventFilter(QObject *object, QEvent *event) { - - if (object == dialog() && event->type() == QEvent::Enter) { - ui_->button_login->setEnabled(true); - return false; - } - - return SettingsPage::eventFilter(object, event); - -} - -void QobuzSettingsPage::LogoutClicked() { - service_->Logout(); - ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut); - ui_->button_login->setEnabled(true); -} - -void QobuzSettingsPage::LoginSuccess() { - if (!this->isVisible()) return; - ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn); - ui_->button_login->setEnabled(true); -} - -void QobuzSettingsPage::LoginFailure(QString failure_reason) { - if (!this->isVisible()) return; - QMessageBox::warning(this, tr("Authentication failed"), failure_reason); -} diff --git a/src/settings/qobuzsettingspage.h b/src/settings/qobuzsettingspage.h deleted file mode 100644 index 21db0a4f6..000000000 --- a/src/settings/qobuzsettingspage.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2019, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef QOBUZSETTINGSPAGE_H -#define QOBUZSETTINGSPAGE_H - -#include -#include - -#include "settings/settingspage.h" - -class QEvent; -class SettingsDialog; -class QobuzService; -class Ui_QobuzSettingsPage; - -class QobuzSettingsPage : public SettingsPage { - Q_OBJECT - - public: - explicit QobuzSettingsPage(SettingsDialog* parent = nullptr); - ~QobuzSettingsPage(); - - static const char *kSettingsGroup; - - void Load(); - void Save(); - - bool eventFilter(QObject *object, QEvent *event); - - signals: - void Login(); - void Login(const QString &username, const QString &password, const QString &token); - - private slots: - void LoginClicked(); - void LogoutClicked(); - void LoginSuccess(); - void LoginFailure(QString failure_reason); - - private: - Ui_QobuzSettingsPage* ui_; - QobuzService *service_; -}; - -#endif diff --git a/src/settings/qobuzsettingspage.ui b/src/settings/qobuzsettingspage.ui deleted file mode 100644 index 6967b321e..000000000 --- a/src/settings/qobuzsettingspage.ui +++ /dev/null @@ -1,293 +0,0 @@ - - - QobuzSettingsPage - - - - 0 - 0 - 715 - 836 - - - - Qobuz - - - - - - Enable - - - - - - - - 0 - 0 - - - - Authentication - - - - - - - 150 - 0 - - - - App ID - - - - - - - - - - Username - - - - - - - - - - - - - - Password - - - - - - - QLineEdit::Password - - - - - - - App Secret - - - - - - - - - - - - - Login - - - - - - - - - - Preferences - - - - - - Audio format - - - - - - - - - - Search delay - - - - - - - ms - - - 0 - - - 10000 - - - 50 - - - 1500 - - - - - - - Artists search limit - - - - - - - 1 - - - 100 - - - 50 - - - - - - - Albums search limit - - - - - - - 1 - - - 1000 - - - 50 - - - - - - - Songs search limit - - - - - - - 1 - - - 1000 - - - 50 - - - - - - - Download album covers - - - - - - - - - - Qt::Vertical - - - - 20 - 30 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 64 - 64 - - - - - 64 - 64 - - - - :/icons/64x64/qobuz.png - - - - - - - - - - LoginStateWidget - QWidget -
    widgets/loginstatewidget.h
    - 1 -
    -
    - - enable - app_id - app_secret - username - password - button_login - format - searchdelay - artistssearchlimit - albumssearchlimit - songssearchlimit - checkbox_download_album_covers - - - - - - -
    diff --git a/src/settings/settingsdialog.cpp b/src/settings/settingsdialog.cpp index c68c9c5d7..09eac1f88 100644 --- a/src/settings/settingsdialog.cpp +++ b/src/settings/settingsdialog.cpp @@ -67,12 +67,6 @@ #ifdef HAVE_MOODBAR # include "moodbarsettingspage.h" #endif -#ifdef HAVE_TIDAL -# include "tidalsettingspage.h" -#endif -#ifdef HAVE_QOBUZ -# include "qobuzsettingspage.h" -#endif #ifdef HAVE_SUBSONIC # include "subsonicsettingspage.h" #endif @@ -149,15 +143,9 @@ SettingsDialog::SettingsDialog(Application *app, QWidget *parent) AddPage(Page_Moodbar, new MoodbarSettingsPage(this), iface); #endif -#if defined(HAVE_TIDAL) || defined(HAVE_SUBSONIC) || defined(HAVE_QOBUZ) +#if defined(HAVE_SUBSONIC) QTreeWidgetItem *streaming = AddCategory(tr("Streaming")); #endif -#ifdef HAVE_TIDAL - AddPage(Page_Tidal, new TidalSettingsPage(this), streaming); -#endif -#ifdef HAVE_QOBUZ - AddPage(Page_Qobuz, new QobuzSettingsPage(this), streaming); -#endif #ifdef HAVE_SUBSONIC AddPage(Page_Subsonic, new SubsonicSettingsPage(this), streaming); #endif diff --git a/src/settings/settingsdialog.h b/src/settings/settingsdialog.h index 993bfc303..5de9c55b7 100644 --- a/src/settings/settingsdialog.h +++ b/src/settings/settingsdialog.h @@ -84,9 +84,7 @@ class SettingsDialog : public QDialog { Page_Proxy, Page_Scrobbler, Page_Moodbar, - Page_Tidal, - Page_Subsonic, - Page_Qobuz, + Page_Subsonic }; enum Role { diff --git a/src/settings/tidalsettingspage.cpp b/src/settings/tidalsettingspage.cpp deleted file mode 100644 index 841cacea7..000000000 --- a/src/settings/tidalsettingspage.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "settingsdialog.h" -#include "tidalsettingspage.h" -#include "ui_tidalsettingspage.h" -#include "core/application.h" -#include "core/iconloader.h" -#include "internet/internetservices.h" -#include "tidal/tidalservice.h" -#include "widgets/loginstatewidget.h" - -const char *TidalSettingsPage::kSettingsGroup = "Tidal"; - -TidalSettingsPage::TidalSettingsPage(SettingsDialog *parent) - : SettingsPage(parent), - ui_(new Ui::TidalSettingsPage), - service_(dialog()->app()->internet_services()->Service()) { - - ui_->setupUi(this); - setWindowIcon(IconLoader::Load("tidal")); - - connect(ui_->button_login, SIGNAL(clicked()), SLOT(LoginClicked())); - connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked())); - connect(ui_->oauth, SIGNAL(toggled(bool)), SLOT(OAuthClicked(bool))); - - connect(this, SIGNAL(Login()), service_, SLOT(StartAuthorisation())); - connect(this, SIGNAL(Login(const QString&, const QString&, const QString&)), service_, SLOT(SendLogin(const QString&, const QString&, const QString&))); - - connect(service_, SIGNAL(LoginFailure(const QString&)), SLOT(LoginFailure(const QString&))); - connect(service_, SIGNAL(LoginSuccess()), SLOT(LoginSuccess())); - - dialog()->installEventFilter(this); - - ui_->quality->addItem("Low", "LOW"); - ui_->quality->addItem("High", "HIGH"); - ui_->quality->addItem("Lossless", "LOSSLESS"); - ui_->quality->addItem("Hi resolution", "HI_RES"); - - ui_->coversize->addItem("160x160", "160x160"); - ui_->coversize->addItem("320x320", "320x320"); - ui_->coversize->addItem("640x640", "640x640"); - ui_->coversize->addItem("750x750", "750x750"); - ui_->coversize->addItem("1280x1280", "1280x1280"); - - ui_->streamurl->addItem("streamurl", StreamUrlMethod_StreamUrl); - ui_->streamurl->addItem("urlpostpaywall", StreamUrlMethod_UrlPostPaywall); - ui_->streamurl->addItem("playbackinfopostpaywall", StreamUrlMethod_PlaybackInfoPostPaywall); - -} - -TidalSettingsPage::~TidalSettingsPage() { delete ui_; } - -void TidalSettingsPage::Load() { - - QSettings s; - - s.beginGroup(kSettingsGroup); - ui_->enable->setChecked(s.value("enabled", false).toBool()); - ui_->oauth->setChecked(s.value("oauth", false).toBool()); - - ui_->client_id->setText(s.value("client_id").toString()); - ui_->api_token->setText(s.value("api_token").toString()); - - ui_->username->setText(s.value("username").toString()); - QByteArray password = s.value("password").toByteArray(); - if (password.isEmpty()) ui_->password->clear(); - else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password))); - - dialog()->ComboBoxLoadFromSettings(s, ui_->quality, "quality", "HIGH"); - ui_->searchdelay->setValue(s.value("searchdelay", 1500).toInt()); - ui_->artistssearchlimit->setValue(s.value("artistssearchlimit", 4).toInt()); - ui_->albumssearchlimit->setValue(s.value("albumssearchlimit", 10).toInt()); - ui_->songssearchlimit->setValue(s.value("songssearchlimit", 10).toInt()); - ui_->checkbox_fetchalbums->setChecked(s.value("fetchalbums", false).toBool()); - ui_->checkbox_download_album_covers->setChecked(s.value("downloadalbumcovers", true).toBool()); - dialog()->ComboBoxLoadFromSettings(s, ui_->coversize, "coversize", "320x320"); - - StreamUrlMethod stream_url = static_cast(s.value("streamurl").toInt()); - int i = ui_->streamurl->findData(stream_url); - if (i == -1) i = ui_->streamurl->findData(StreamUrlMethod_StreamUrl); - ui_->streamurl->setCurrentIndex(i); - - s.endGroup(); - - OAuthClicked(ui_->oauth->isChecked()); - if (service_->authenticated()) ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn); - -} - -void TidalSettingsPage::Save() { - - QSettings s; - s.beginGroup(kSettingsGroup); - s.setValue("enabled", ui_->enable->isChecked()); - s.setValue("oauth", ui_->oauth->isChecked()); - s.setValue("client_id", ui_->client_id->text()); - s.setValue("api_token", ui_->api_token->text()); - - s.setValue("username", ui_->username->text()); - s.setValue("password", QString::fromUtf8(ui_->password->text().toUtf8().toBase64())); - - s.setValue("quality", ui_->quality->itemData(ui_->quality->currentIndex())); - s.setValue("searchdelay", ui_->searchdelay->value()); - s.setValue("artistssearchlimit", ui_->artistssearchlimit->value()); - s.setValue("albumssearchlimit", ui_->albumssearchlimit->value()); - s.setValue("songssearchlimit", ui_->songssearchlimit->value()); - s.setValue("fetchalbums", ui_->checkbox_fetchalbums->isChecked()); - s.setValue("downloadalbumcovers", ui_->checkbox_download_album_covers->isChecked()); - s.setValue("coversize", ui_->coversize->itemData(ui_->coversize->currentIndex())); - s.setValue("streamurl", ui_->streamurl->itemData(ui_->streamurl->currentIndex())); - s.endGroup(); - - service_->ReloadSettings(); - -} - -void TidalSettingsPage::LoginClicked() { - - if (ui_->oauth->isChecked()) { - if (ui_->client_id->text().isEmpty()) { - QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing Tidal client ID.")); - return; - } - emit Login(); - } - else { - if (ui_->username->text().isEmpty() || ui_->password->text().isEmpty()) { - QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing username or password.")); - return; - } - emit Login(ui_->api_token->text(), ui_->username->text(), ui_->password->text()); - } - ui_->button_login->setEnabled(false); - -} - -bool TidalSettingsPage::eventFilter(QObject *object, QEvent *event) { - - if (object == dialog() && event->type() == QEvent::Enter) { - ui_->button_login->setEnabled(true); - return false; - } - - return SettingsPage::eventFilter(object, event); - -} - -void TidalSettingsPage::OAuthClicked(const bool enabled) { - - ui_->client_id->setEnabled(enabled); - ui_->api_token->setEnabled(!enabled); - ui_->username->setEnabled(!enabled); - ui_->password->setEnabled(!enabled); - -} - -void TidalSettingsPage::LogoutClicked() { - service_->Logout(); - ui_->button_login->setEnabled(true); - ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut); -} - -void TidalSettingsPage::LoginSuccess() { - if (!this->isVisible()) return; - ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn); - ui_->button_login->setEnabled(true); -} - -void TidalSettingsPage::LoginFailure(const QString &failure_reason) { - if (!this->isVisible()) return; - QMessageBox::warning(this, tr("Authentication failed"), failure_reason); - ui_->button_login->setEnabled(true); -} diff --git a/src/settings/tidalsettingspage.h b/src/settings/tidalsettingspage.h deleted file mode 100644 index 87cd6a6cf..000000000 --- a/src/settings/tidalsettingspage.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef TIDALSETTINGSPAGE_H -#define TIDALSETTINGSPAGE_H - -#include "config.h" - -#include -#include - -#include "settings/settingspage.h" - -class QEvent; -class TidalService; -class SettingsDialog; -class Ui_TidalSettingsPage; - -class TidalSettingsPage : public SettingsPage { - Q_OBJECT - - public: - explicit TidalSettingsPage(SettingsDialog* parent = nullptr); - ~TidalSettingsPage(); - - static const char *kSettingsGroup; - - enum StreamUrlMethod { - StreamUrlMethod_StreamUrl, - StreamUrlMethod_UrlPostPaywall, - StreamUrlMethod_PlaybackInfoPostPaywall, - }; - - void Load(); - void Save(); - - bool eventFilter(QObject *object, QEvent *event); - - signals: - void Login(); - void Login(const QString &api_token, const QString &username, const QString &password); - - private slots: - void OAuthClicked(const bool enabled); - void LoginClicked(); - void LogoutClicked(); - void LoginSuccess(); - void LoginFailure(const QString &failure_reason); - - private: - Ui_TidalSettingsPage* ui_; - TidalService *service_; -}; - -#endif diff --git a/src/settings/tidalsettingspage.ui b/src/settings/tidalsettingspage.ui deleted file mode 100644 index 2541762e8..000000000 --- a/src/settings/tidalsettingspage.ui +++ /dev/null @@ -1,344 +0,0 @@ - - - TidalSettingsPage - - - - 0 - 0 - 715 - 836 - - - - Tidal - - - - - - Enable - - - - - - - - 0 - 0 - - - - Authentication - - - - - - Use OAuth - - - - - - - - 150 - 0 - - - - Client ID - - - - - - - - - - - 150 - 0 - - - - API Token - - - - - - - - 200 - 0 - - - - - - - - Username - - - - - - - - - - - - - - Password - - - - - - - QLineEdit::Password - - - - - - - - - - Login - - - - - - - - - - Preferences - - - - - - Audio quality - - - - - - - - - - Search delay - - - - - - - ms - - - 0 - - - 10000 - - - 50 - - - 1500 - - - - - - - Artists search limit - - - - - - - 1 - - - 100 - - - 50 - - - - - - - Albums search limit - - - - - - - 1 - - - 1000 - - - 50 - - - - - - - Songs search limit - - - - - - - Download album covers - - - - - - - Fetch entire albums when searching songs - - - - - - - - - - Album cover size - - - - - - - 1 - - - 1000 - - - 50 - - - - - - - Stream URL method - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 30 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 64 - 64 - - - - - 64 - 64 - - - - :/icons/64x64/tidal.png - - - - - - - - - - LoginStateWidget - QWidget -
    widgets/loginstatewidget.h
    - 1 -
    -
    - - enable - oauth - client_id - api_token - username - password - button_login - quality - searchdelay - artistssearchlimit - albumssearchlimit - songssearchlimit - checkbox_download_album_covers - checkbox_fetchalbums - coversize - streamurl - - - - - - -
    diff --git a/src/tidal/tidalbaserequest.cpp b/src/tidal/tidalbaserequest.cpp deleted file mode 100644 index 57ca9947b..000000000 --- a/src/tidal/tidalbaserequest.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/logging.h" -#include "core/network.h" -#include "tidalservice.h" -#include "tidalbaserequest.h" - -const char *TidalBaseRequest::kApiUrl = "https://api.tidalhifi.com/v1"; - -TidalBaseRequest::TidalBaseRequest(TidalService *service, NetworkAccessManager *network, QObject *parent) : - QObject(parent), - service_(service), - network_(network) - {} - -TidalBaseRequest::~TidalBaseRequest() {} - -QNetworkReply *TidalBaseRequest::CreateRequest(const QString &ressource_name, const QList ¶ms_provided) { - - ParamList params = ParamList() << params_provided - << Param("countryCode", country_code()); - - QUrlQuery url_query; - for (const Param& param : params) { - EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - url_query.addQueryItem(encoded_param.first, encoded_param.second); - } - - QUrl url(kApiUrl + QString("/") + ressource_name); - url.setQuery(url_query); - QNetworkRequest req(url); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -#endif - req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8()); - if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8()); - - QNetworkReply *reply = network_->get(req); - connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(HandleSSLErrors(QList))); - - //qLog(Debug) << "Tidal: Sending request" << url; - - return reply; - -} - -void TidalBaseRequest::HandleSSLErrors(QList ssl_errors) { - - for (QSslError &ssl_error : ssl_errors) { - Error(ssl_error.errorString()); - } - -} - -QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, const bool send_login) { - - 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 { - // See if there is Json data containing "status" and "userMessage" - then use that instead. - data = reply->readAll(); - QString error; - QJsonParseError json_error; - QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error); - int status = 0; - int sub_status = 0; - 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(); - error = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status); - } - } - 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:" << error; - qLog(Info) << "Tidal:" << "Attempting to login."; - NeedLogin(); - emit service_->Login(); - } - else { - Error(error); - } - } - else { - Error(error); - } - } - return QByteArray(); - } - - return data; - -} - -QJsonObject TidalBaseRequest::ExtractJsonObj(QByteArray &data) { - - QJsonParseError json_error; - QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error); - - if (json_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.", data); - 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; - -} - -QJsonValue TidalBaseRequest::ExtractItems(QByteArray &data) { - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) return QJsonValue(); - return ExtractItems(json_obj); - -} - -QJsonValue TidalBaseRequest::ExtractItems(QJsonObject &json_obj) { - - if (!json_obj.contains("items")) { - Error("Json reply is missing items.", json_obj); - return QJsonArray(); - } - QJsonValue json_items = json_obj["items"]; - return json_items; - -} - -QString TidalBaseRequest::ErrorsToHTML(const QStringList &errors) { - - QString error_html; - for (const QString &error : errors) { - error_html += error + "
    "; - } - return error_html; - -} diff --git a/src/tidal/tidalbaserequest.h b/src/tidal/tidalbaserequest.h deleted file mode 100644 index c18c8ddee..000000000 --- a/src/tidal/tidalbaserequest.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef TIDALBASEREQUEST_H -#define TIDALBASEREQUEST_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "tidalservice.h" - -class QNetworkReply; -class NetworkAccessManager; - -class TidalBaseRequest : public QObject { - Q_OBJECT - - public: - - enum QueryType { - QueryType_None, - QueryType_Artists, - QueryType_Albums, - QueryType_Songs, - QueryType_SearchArtists, - QueryType_SearchAlbums, - QueryType_SearchSongs, - QueryType_StreamURL, - }; - - TidalBaseRequest(TidalService *service, NetworkAccessManager *network, QObject *parent); - ~TidalBaseRequest(); - - typedef QPair Param; - typedef QList ParamList; - - typedef QPair EncodedParam; - typedef QList EncodedParamList; - - QNetworkReply *CreateRequest(const QString &ressource_name, const QList ¶ms_provided); - QByteArray GetReplyData(QNetworkReply *reply, const bool send_login); - QJsonObject ExtractJsonObj(QByteArray &data); - QJsonValue ExtractItems(QByteArray &data); - QJsonValue ExtractItems(QJsonObject &json_obj); - - virtual void Error(const QString &error, const QVariant &debug = QVariant()) = 0; - QString ErrorsToHTML(const QStringList &errors); - - QString api_url() { return QString(kApiUrl); } - bool oauth() { return service_->oauth(); } - QString client_id() { return service_->client_id(); } - QString api_token() { return service_->api_token(); } - quint64 user_id() { return service_->user_id(); } - QString country_code() { return service_->country_code(); } - QString username() { return service_->username(); } - QString password() { return service_->password(); } - QString quality() { return service_->quality(); } - int artistssearchlimit() { return service_->artistssearchlimit(); } - int albumssearchlimit() { return service_->albumssearchlimit(); } - int songssearchlimit() { return service_->songssearchlimit(); } - - QString access_token() { return service_->access_token(); } - QString session_id() { return service_->session_id(); } - - bool authenticated() { return service_->authenticated(); } - bool login_sent() { return service_->login_sent(); } - int max_login_attempts() { return service_->max_login_attempts(); } - int login_attempts() { return service_->login_attempts(); } - - virtual void NeedLogin() = 0; - - private slots: - void HandleSSLErrors(QList ssl_errors); - - private: - - static const char *kApiUrl; - - TidalService *service_; - NetworkAccessManager *network_; - -}; - -#endif // TIDALBASEREQUEST_H diff --git a/src/tidal/tidalfavoriterequest.cpp b/src/tidal/tidalfavoriterequest.cpp deleted file mode 100644 index 7676f74b2..000000000 --- a/src/tidal/tidalfavoriterequest.cpp +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/logging.h" -#include "core/network.h" -#include "core/closure.h" -#include "core/song.h" -#include "tidalservice.h" -#include "tidalbaserequest.h" -#include "tidalfavoriterequest.h" - -TidalFavoriteRequest::TidalFavoriteRequest(TidalService *service, NetworkAccessManager *network, QObject *parent) - : TidalBaseRequest(service, network, parent), - service_(service), - network_(network), - need_login_(false) {} - -TidalFavoriteRequest::~TidalFavoriteRequest() { - - while (!replies_.isEmpty()) { - QNetworkReply *reply = replies_.takeFirst(); - disconnect(reply, 0, this, 0); - reply->abort(); - reply->deleteLater(); - } - -} - -QString TidalFavoriteRequest::FavoriteText(const FavoriteType type) { - - switch (type) { - case FavoriteType_Artists: - return "artists"; - case FavoriteType_Albums: - return "albums"; - case FavoriteType_Songs: - default: - return "tracks"; - } - -} - -void TidalFavoriteRequest::AddArtists(const SongList &songs) { - AddFavorites(FavoriteType_Artists, songs); -} - -void TidalFavoriteRequest::AddAlbums(const SongList &songs) { - AddFavorites(FavoriteType_Albums, songs); -} - -void TidalFavoriteRequest::AddSongs(const SongList &songs) { - AddFavorites(FavoriteType_Songs, songs); -} - -void TidalFavoriteRequest::AddFavorites(const FavoriteType type, const SongList &songs) { - - if (songs.isEmpty()) return; - - QString text; - switch (type) { - case FavoriteType_Artists: - text = "artistIds"; - break; - case FavoriteType_Albums: - text = "albumIds"; - break; - case FavoriteType_Songs: - text = "trackIds"; - break; - } - - QStringList ids_list; - for (const Song &song : songs) { - QString id; - switch (type) { - case FavoriteType_Artists: - if (song.artist_id() <= 0) continue; - id = QString::number(song.artist_id()); - break; - case FavoriteType_Albums: - if (song.album_id().isEmpty()) continue; - id = song.album_id(); - break; - case FavoriteType_Songs: - if (song.song_id() <= 0) continue; - id = QString::number(song.song_id()); - break; - } - if (id.isEmpty()) continue; - if (!ids_list.contains(id)) { - ids_list << id; - } - } - if (ids_list.isEmpty()) return; - - QString ids = ids_list.join(','); - - typedef QPair EncodedParam; - - ParamList params = ParamList() << Param("countryCode", country_code()) - << Param(text, ids); - - QUrlQuery url_query; - for (const Param& param : params) { - EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - url_query.addQueryItem(encoded_param.first, encoded_param.second); - } - - QUrl url(api_url() + QString("/") + "users/" + QString::number(service_->user_id()) + "/favorites/" + FavoriteText(type)); - QNetworkRequest req(url); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -#endif - req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8()); - if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8()); - QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8(); - QNetworkReply *reply = network_->post(req, query); - NewClosure(reply, SIGNAL(finished()), this, SLOT(AddFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs); - replies_ << reply; - - qLog(Debug) << "Tidal: Sending request" << url << query; - -} - -void TidalFavoriteRequest::AddFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs) { - - if (replies_.contains(reply)) { - replies_.removeAll(reply); - reply->deleteLater(); - } - else { - return; - } - - QString error; - QByteArray data = GetReplyData(reply, false); - - if (reply->error() != QNetworkReply::NoError) { - return; - } - - qLog(Debug) << "Tidal:" << songs.count() << "songs added to" << FavoriteText(type) << "favorites."; - - switch (type) { - case FavoriteType_Artists: - emit ArtistsAdded(songs); - break; - case FavoriteType_Albums: - emit AlbumsAdded(songs); - break; - case FavoriteType_Songs: - emit SongsAdded(songs); - break; - } - -} - -void TidalFavoriteRequest::RemoveArtists(const SongList &songs) { - RemoveFavorites(FavoriteType_Artists, songs); -} - -void TidalFavoriteRequest::RemoveAlbums(const SongList &songs) { - RemoveFavorites(FavoriteType_Albums, songs); -} - -void TidalFavoriteRequest::RemoveSongs(const SongList &songs) { - RemoveFavorites(FavoriteType_Songs, songs); -} - -void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongList songs) { - - if (songs.isEmpty()) return; - - QList ids; - QMultiMap songs_map; - for (const Song &song : songs) { - qint64 id = -1; - switch (type) { - case FavoriteType_Artists: - if (song.artist_id() <= 0) continue; - id = song.artist_id(); - break; - case FavoriteType_Albums: - if (song.album_id().isEmpty()) continue; - id = song.album_id().toLongLong(); - break; - case FavoriteType_Songs: - if (song.song_id() <= 0) continue; - id = song.song_id(); - break; - } - if (!ids.contains(id)) ids << id; - songs_map.insertMulti(id, song); - } - - for (int id : ids) { - SongList songs_list = songs_map.values(id); - RemoveFavorites(type, id, songs_list); - } - -} - -void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const int id, const SongList &songs) { - - ParamList params = ParamList() << Param("countryCode", country_code()); - - QUrlQuery url_query; - for (const Param& param : params) { - EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - url_query.addQueryItem(encoded_param.first, encoded_param.second); - } - - QUrl url(api_url() + QString("/") + "users/" + QString::number(service_->user_id()) + "/favorites/" + FavoriteText(type) + QString("/") + QString::number(id)); - url.setQuery(url_query); - QNetworkRequest req(url); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -#endif - req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8()); - if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8()); - QNetworkReply *reply = network_->deleteResource(req); - NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoveFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs); - replies_ << reply; - - qLog(Debug) << "Tidal: Sending request" << url << "with" << songs.count() << "songs"; - -} - -void TidalFavoriteRequest::RemoveFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs) { - - if (replies_.contains(reply)) { - replies_.removeAll(reply); - reply->deleteLater(); - } - else { - return; - } - - QString error; - QByteArray data = GetReplyData(reply, false); - if (reply->error() != QNetworkReply::NoError) { - return; - } - - qLog(Debug) << "Tidal:" << songs.count() << "songs removed from" << FavoriteText(type) << "favorites."; - - switch (type) { - case FavoriteType_Artists: - emit ArtistsRemoved(songs); - break; - case FavoriteType_Albums: - emit AlbumsRemoved(songs); - break; - case FavoriteType_Songs: - emit SongsRemoved(songs); - break; - } - -} - -void TidalFavoriteRequest::Error(const QString &error, const QVariant &debug) { - - qLog(Error) << "Tidal:" << error; - if (debug.isValid()) qLog(Debug) << debug; - -} diff --git a/src/tidal/tidalfavoriterequest.h b/src/tidal/tidalfavoriterequest.h deleted file mode 100644 index 34b7994ce..000000000 --- a/src/tidal/tidalfavoriterequest.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef TIDALFAVORITEREQUEST_H -#define TIDALFAVORITEREQUEST_H - -#include "config.h" - -#include -#include -#include -#include - -#include "tidalbaserequest.h" -#include "core/song.h" - -class QNetworkReply; -class TidalService; -class NetworkAccessManager; - -class TidalFavoriteRequest : public TidalBaseRequest { - Q_OBJECT - - public: - TidalFavoriteRequest(TidalService *service, NetworkAccessManager *network, QObject *parent); - ~TidalFavoriteRequest(); - - enum FavoriteType { - FavoriteType_Artists, - FavoriteType_Albums, - FavoriteType_Songs - }; - - bool need_login() { return need_login_; } - - void NeedLogin() { need_login_ = true; } - - signals: - void ArtistsAdded(const SongList &songs); - void AlbumsAdded(const SongList &songs); - void SongsAdded(const SongList &songs); - void ArtistsRemoved(const SongList &songs); - void AlbumsRemoved(const SongList &songs); - void SongsRemoved(const SongList &songs); - - private slots: - void AddArtists(const SongList &songs); - void AddAlbums(const SongList &songs); - void AddSongs(const SongList &songs); - - void RemoveArtists(const SongList &songs); - void RemoveAlbums(const SongList &songs); - void RemoveSongs(const SongList &songs); - - void AddFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs); - 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); - void RemoveFavorites(const FavoriteType type, const int id, const SongList &songs); - - TidalService *service_; - NetworkAccessManager *network_; - QList replies_; - bool need_login_; - -}; - -#endif // TIDALFAVORITEREQUEST_H diff --git a/src/tidal/tidalrequest.cpp b/src/tidal/tidalrequest.cpp deleted file mode 100644 index ede81c354..000000000 --- a/src/tidal/tidalrequest.cpp +++ /dev/null @@ -1,1215 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/closure.h" -#include "core/logging.h" -#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 "tidalbaserequest.h" -#include "tidalrequest.h" - -const char *TidalRequest::kResourcesUrl = "https://resources.tidal.com"; -const int TidalRequest::kMaxConcurrentArtistsRequests = 3; -const int TidalRequest::kMaxConcurrentAlbumsRequests = 3; -const int TidalRequest::kMaxConcurrentSongsRequests = 3; -const int TidalRequest::kMaxConcurrentArtistAlbumsRequests = 3; -const int TidalRequest::kMaxConcurrentAlbumSongsRequests = 3; -const int TidalRequest::kMaxConcurrentAlbumCoverRequests = 1; - -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()), - coversize_(service_->coversize()), - query_id_(-1), - finished_(false), - artists_requests_active_(0), - artists_total_(0), - artists_received_(0), - albums_requests_active_(0), - songs_requests_active_(0), - artist_albums_requests_active_(0), - artist_albums_requested_(0), - artist_albums_received_(0), - album_songs_requests_active_(0), - album_songs_requested_(0), - album_songs_received_(0), - album_covers_requests_active_(), - album_covers_requested_(0), - album_covers_received_(0), - need_login_(false), - no_results_(false) {} - -TidalRequest::~TidalRequest() { - - while (!replies_.isEmpty()) { - QNetworkReply *reply = replies_.takeFirst(); - disconnect(reply, 0, this, 0); - if (reply->isRunning()) reply->abort(); - reply->deleteLater(); - } - - while (!album_cover_replies_.isEmpty()) { - QNetworkReply *reply = album_cover_replies_.takeFirst(); - disconnect(reply, 0, this, 0); - if (reply->isRunning()) reply->abort(); - reply->deleteLater(); - } - -} - -void TidalRequest::LoginComplete(const bool success, QString error) { - - if (!need_login_) return; - need_login_ = false; - - if (!success) { - Error(error); - return; - } - - Process(); - -} - -void TidalRequest::Process() { - - if (!service_->authenticated()) { - emit UpdateStatus(query_id_, tr("Authenticating...")); - need_login_ = true; - service_->TryLogin(); - return; - } - - switch (type_) { - case QueryType::QueryType_Artists: - GetArtists(); - break; - case QueryType::QueryType_Albums: - GetAlbums(); - break; - case QueryType::QueryType_Songs: - GetSongs(); - break; - case QueryType::QueryType_SearchArtists: - ArtistsSearch(); - break; - case QueryType::QueryType_SearchAlbums: - AlbumsSearch(); - break; - case QueryType::QueryType_SearchSongs: - SongsSearch(); - break; - default: - Error("Invalid query type."); - break; - } - -} - -void TidalRequest::Search(const int query_id, const QString &search_text) { - query_id_ = query_id; - search_text_ = search_text; -} - -void TidalRequest::GetArtists() { - - emit UpdateStatus(query_id_, tr("Retrieving artists...")); - emit UpdateProgress(query_id_, 0); - AddArtistsRequest(); - -} - -void TidalRequest::AddArtistsRequest(const int offset, const int limit) { - - Request request; - request.limit = limit; - request.offset = offset; - artists_requests_queue_.enqueue(request); - if (artists_requests_active_ < kMaxConcurrentArtistsRequests) FlushArtistsRequests(); - -} - -void TidalRequest::FlushArtistsRequests() { - - while (!artists_requests_queue_.isEmpty() && artists_requests_active_ < kMaxConcurrentArtistsRequests) { - - Request request = artists_requests_queue_.dequeue(); - ++artists_requests_active_; - - ParamList parameters; - if (type_ == QueryType_SearchArtists) parameters << Param("query", search_text_); - if (request.limit > 0) parameters << Param("limit", QString::number(request.limit)); - if (request.offset > 0) parameters << Param("offset", QString::number(request.offset)); - QNetworkReply *reply; - if (type_ == QueryType_Artists) { - reply = CreateRequest(QString("users/%1/favorites/artists").arg(service_->user_id()), parameters); - } - if (type_ == QueryType_SearchArtists) { - reply = CreateRequest("search/artists", parameters); - } - if (!reply) continue; - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistsReplyReceived(QNetworkReply*, const int, const int)), reply, request.limit, request.offset); - - } - -} - -void TidalRequest::GetAlbums() { - - emit UpdateStatus(query_id_, tr("Retrieving albums...")); - emit UpdateProgress(query_id_, 0); - AddAlbumsRequest(); - -} - -void TidalRequest::AddAlbumsRequest(const int offset, const int limit) { - - Request request; - request.limit = limit; - request.offset = offset; - albums_requests_queue_.enqueue(request); - if (albums_requests_active_ < kMaxConcurrentAlbumsRequests) FlushAlbumsRequests(); - -} - -void TidalRequest::FlushAlbumsRequests() { - - while (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) { - - Request request = albums_requests_queue_.dequeue(); - ++albums_requests_active_; - - ParamList parameters; - if (type_ == QueryType_SearchAlbums) parameters << Param("query", search_text_); - if (request.limit > 0) parameters << Param("limit", QString::number(request.limit)); - if (request.offset > 0) parameters << Param("offset", QString::number(request.offset)); - QNetworkReply *reply; - if (type_ == QueryType_Albums) { - reply = CreateRequest(QString("users/%1/favorites/albums").arg(service_->user_id()), parameters); - } - if (type_ == QueryType_SearchAlbums) { - reply = CreateRequest("search/albums", parameters); - } - if (!reply) continue; - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReplyReceived(QNetworkReply*, const int, const int)), reply, request.limit, request.offset); - - } - -} - -void TidalRequest::GetSongs() { - - emit UpdateStatus(query_id_, tr("Retrieving songs...")); - emit UpdateProgress(query_id_, 0); - AddSongsRequest(); - -} - -void TidalRequest::AddSongsRequest(const int offset, const int limit) { - - Request request; - request.limit = limit; - request.offset = offset; - songs_requests_queue_.enqueue(request); - if (songs_requests_active_ < kMaxConcurrentSongsRequests) FlushSongsRequests(); - -} - -void TidalRequest::FlushSongsRequests() { - - while (!songs_requests_queue_.isEmpty() && songs_requests_active_ < kMaxConcurrentSongsRequests) { - - Request request = songs_requests_queue_.dequeue(); - ++songs_requests_active_; - - ParamList parameters; - if (type_ == QueryType_SearchSongs) parameters << Param("query", search_text_); - if (request.limit > 0) parameters << Param("limit", QString::number(request.limit)); - if (request.offset > 0) parameters << Param("offset", QString::number(request.offset)); - QNetworkReply *reply; - if (type_ == QueryType_Songs) { - reply = CreateRequest(QString("users/%1/favorites/tracks").arg(service_->user_id()), parameters); - } - if (type_ == QueryType_SearchSongs) { - reply = CreateRequest("search/tracks", parameters); - } - if (!reply) continue; - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(SongsReplyReceived(QNetworkReply*, const int, const int)), reply, request.limit, request.offset); - - } - -} - -void TidalRequest::ArtistsSearch() { - - emit UpdateStatus(query_id_, tr("Searching...")); - emit UpdateProgress(query_id_, 0); - AddArtistsSearchRequest(); - -} - -void TidalRequest::AddArtistsSearchRequest(const int offset) { - - AddArtistsRequest(offset, service_->artistssearchlimit()); - -} - -void TidalRequest::AlbumsSearch() { - - emit UpdateStatus(query_id_, tr("Searching...")); - emit UpdateProgress(query_id_, 0); - AddAlbumsSearchRequest(); - -} - -void TidalRequest::AddAlbumsSearchRequest(const int offset) { - - AddAlbumsRequest(offset, service_->albumssearchlimit()); - -} - -void TidalRequest::SongsSearch() { - - emit UpdateStatus(query_id_, tr("Searching...")); - emit UpdateProgress(query_id_, 0); - AddSongsSearchRequest(); - -} - -void TidalRequest::AddSongsSearchRequest(const int offset) { - - AddSongsRequest(offset, service_->songssearchlimit()); - -} - -void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) { - - if (!replies_.contains(reply)) return; - replies_.removeAll(reply); - reply->deleteLater(); - - QByteArray data = GetReplyData(reply, (offset_requested == 0)); - - --artists_requests_active_; - - if (finished_) return; - - if (data.isEmpty()) { - ArtistsFinishCheck(); - return; - } - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) { - ArtistsFinishCheck(); - return; - } - - if (!json_obj.contains("limit") || - !json_obj.contains("offset") || - !json_obj.contains("totalNumberOfItems") || - !json_obj.contains("items")) { - ArtistsFinishCheck(); - Error("Json object missing values.", json_obj); - return; - } - //int limit = json_obj["limit"].toInt(); - int offset = json_obj["offset"].toInt(); - int artists_total = json_obj["totalNumberOfItems"].toInt(); - - if (offset_requested == 0) { - artists_total_ = artists_total; - } - else if (artists_total != artists_total_) { - Error(QString("totalNumberOfItems returned does not match previous totalNumberOfItems! %1 != %2").arg(artists_total).arg(artists_total_)); - ArtistsFinishCheck(); - return; - } - - if (offset != offset_requested) { - Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested)); - ArtistsFinishCheck(); - return; - } - - if (offset_requested == 0) { - emit ProgressSetMaximum(query_id_, artists_total_); - emit UpdateProgress(query_id_, artists_received_); - } - - QJsonValue json_value = ExtractItems(json_obj); - if (!json_value.isArray()) { - ArtistsFinishCheck(); - return; - } - - QJsonArray json_items = json_value.toArray(); - if (json_items.isEmpty()) { // Empty array means no results - if (offset_requested == 0) no_results_ = true; - ArtistsFinishCheck(); - return; - } - - int artists_received = 0; - for (const QJsonValue &value : json_items) { - - ++artists_received; - - if (!value.isObject()) { - Error("Invalid Json reply, item not a object.", value); - continue; - } - QJsonObject json_obj = value.toObject(); - - if (json_obj.contains("item")) { - QJsonValue json_item = json_obj["item"]; - if (!json_item.isObject()) { - Error("Invalid Json reply, item not a object.", json_item); - continue; - } - json_obj = json_item.toObject(); - } - - if (!json_obj.contains("id") || !json_obj.contains("name")) { - Error("Invalid Json reply, item missing id or album.", json_obj); - continue; - } - - qint64 artist_id = json_obj["id"].toInt(); - if (artist_albums_requests_pending_.contains(artist_id)) continue; - artist_albums_requests_pending_.append(artist_id); - - } - artists_received_ += artists_received; - - if (offset_requested != 0) emit UpdateProgress(query_id_, artists_received_); - - ArtistsFinishCheck(limit_requested, offset, artists_received); - -} - -void TidalRequest::ArtistsFinishCheck(const int limit, const int offset, const int artists_received) { - - if (finished_) return; - - if ((limit == 0 || limit > artists_received) && artists_received_ < artists_total_) { - int offset_next = offset + artists_received; - if (offset_next > 0 && offset_next < artists_total_) { - if (type_ == QueryType_Artists) AddArtistsRequest(offset_next); - else if (type_ == QueryType_SearchArtists) AddArtistsSearchRequest(offset_next); - } - } - - if (!artists_requests_queue_.isEmpty() && artists_requests_active_ < kMaxConcurrentArtistsRequests) FlushArtistsRequests(); - - if (artists_requests_queue_.isEmpty() && artists_requests_active_ <= 0) { // Artist query is finished, get all albums for all artists. - - // Get artist albums - for (qint64 artist_id : artist_albums_requests_pending_) { - AddArtistAlbumsRequest(artist_id); - ++artist_albums_requested_; - } - artist_albums_requests_pending_.clear(); - - if (artist_albums_requested_ > 0) { - if (artist_albums_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving albums for %1 artist...").arg(artist_albums_requested_)); - else emit UpdateStatus(query_id_, tr("Retrieving albums for %1 artists...").arg(artist_albums_requested_)); - emit ProgressSetMaximum(query_id_, artist_albums_requested_); - emit UpdateProgress(query_id_, 0); - } - - } - - FinishCheck(); - -} - -void TidalRequest::AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) { - --albums_requests_active_; - AlbumsReceived(reply, 0, limit_requested, offset_requested, (offset_requested == 0)); - if (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) FlushAlbumsRequests(); -} - -void TidalRequest::AddArtistAlbumsRequest(const qint64 artist_id, const int offset) { - - Request request; - request.artist_id = artist_id; - request.offset = offset; - artist_albums_requests_queue_.enqueue(request); - if (artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) FlushArtistAlbumsRequests(); - -} - -void TidalRequest::FlushArtistAlbumsRequests() { - - while (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) { - - Request request = artist_albums_requests_queue_.dequeue(); - ++artist_albums_requests_active_; - - ParamList parameters; - if (request.offset > 0) parameters << Param("offset", QString::number(request.offset)); - QNetworkReply *reply = CreateRequest(QString("artists/%1/albums").arg(request.artist_id), parameters); - NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistAlbumsReplyReceived(QNetworkReply*, const qint64, const int)), reply, request.artist_id, request.offset); - replies_ << reply; - - } - -} - -void TidalRequest::ArtistAlbumsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const int offset_requested) { - - --artist_albums_requests_active_; - ++artist_albums_received_; - emit UpdateProgress(query_id_, artist_albums_received_); - AlbumsReceived(reply, artist_id, 0, offset_requested, false); - if (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) FlushArtistAlbumsRequests(); - -} - -void TidalRequest::AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_requested, const int limit_requested, const int offset_requested, const bool auto_login) { - - if (!replies_.contains(reply)) return; - replies_.removeAll(reply); - reply->deleteLater(); - - QByteArray data = GetReplyData(reply, auto_login); - - if (finished_) return; - - if (data.isEmpty()) { - AlbumsFinishCheck(artist_id_requested); - return; - } - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) { - AlbumsFinishCheck(artist_id_requested); - return; - } - - if (!json_obj.contains("limit") || - !json_obj.contains("offset") || - !json_obj.contains("totalNumberOfItems") || - !json_obj.contains("items")) { - Error("Json object missing values.", json_obj); - AlbumsFinishCheck(artist_id_requested); - return; - } - - //int limit = json_obj["limit"].toInt(); - int offset = json_obj["offset"].toInt(); - int albums_total = json_obj["totalNumberOfItems"].toInt(); - - if (offset != offset_requested) { - Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested)); - AlbumsFinishCheck(artist_id_requested); - return; - } - - QJsonValue json_value = ExtractItems(json_obj); - if (!json_value.isArray()) { - AlbumsFinishCheck(artist_id_requested); - return; - } - QJsonArray json_items = json_value.toArray(); - if (json_items.isEmpty()) { - if ((type_ == QueryType_Albums || type_ == QueryType_SearchAlbums || (type_ == QueryType_SearchSongs && fetchalbums_)) && offset_requested == 0) { - no_results_ = true; - } - AlbumsFinishCheck(artist_id_requested); - return; - } - - int albums_received = 0; - for (const QJsonValue &value : json_items) { - - ++albums_received; - - if (!value.isObject()) { - Error("Invalid Json reply, item not a object.", value); - continue; - } - QJsonObject json_obj = value.toObject(); - - if (json_obj.contains("item")) { - QJsonValue json_item = json_obj["item"]; - if (!json_item.isObject()) { - Error("Invalid Json reply, item not a object.", json_item); - continue; - } - json_obj = json_item.toObject(); - } - - qint64 album_id = 0; - QString album; - if (json_obj.contains("type")) { // This was a albums request or search - if (!json_obj.contains("id") || !json_obj.contains("title")) { - Error("Invalid Json reply, item is missing ID or title.", json_obj); - continue; - } - album_id = json_obj["id"].toInt(); - album = json_obj["title"].toString(); - } - else if (json_obj.contains("album")) { // This was a tracks request or search - QJsonValue json_value_album = json_obj["album"]; - if (!json_value_album.isObject()) { - Error("Invalid Json reply, item album is not a object.", json_value_album); - continue; - } - QJsonObject json_album = json_value_album.toObject(); - if (!json_album.contains("id") || !json_album.contains("title")) { - Error("Invalid Json reply, item album is missing ID or title.", json_album); - continue; - } - album_id = json_album["id"].toInt(); - album = json_album["title"].toString(); - - } - else { - Error("Invalid Json reply, item missing type or album.", json_obj); - continue; - } - - if (album_songs_requests_pending_.contains(album_id)) continue; - - if (!json_obj.contains("artist") || !json_obj.contains("title") || !json_obj.contains("audioQuality")) { - Error("Invalid Json reply, item missing artist, title or audioQuality.", json_obj); - continue; - } - QJsonValue json_value_artist = json_obj["artist"]; - if (!json_value_artist.isObject()) { - Error("Invalid Json reply, item artist is not a object.", json_value_artist); - continue; - } - QJsonObject json_artist = json_value_artist.toObject(); - if (!json_artist.contains("id") || !json_artist.contains("name")) { - Error("Invalid Json reply, item artist missing id or name.", json_artist); - continue; - } - - qint64 artist_id = json_artist["id"].toInt(); - QString artist = json_artist["name"].toString(); - - QString quality = json_obj["audioQuality"].toString(); - QString copyright = json_obj["copyright"].toString(); - - //qLog(Debug) << "Tidal:" << artist << album << quality << copyright; - - Request request; - if (artist_id_requested == 0) { - request.artist_id = artist_id; - } - else { - request.artist_id = artist_id_requested; - } - request.album_id = album_id; - request.album_artist = artist; - album_songs_requests_pending_.insert(album_id, request); - - } - - AlbumsFinishCheck(artist_id_requested, limit_requested, offset, albums_total, albums_received); - -} - -void TidalRequest::AlbumsFinishCheck(const qint64 artist_id, const int limit, const int offset, const int albums_total, const int albums_received) { - - if (finished_) return; - - if (limit == 0 || limit > albums_received) { - int offset_next = offset + albums_received; - if (offset_next > 0 && offset_next < albums_total) { - switch (type_) { - case QueryType_Albums: - AddAlbumsRequest(offset_next); - break; - case QueryType_SearchAlbums: - AddAlbumsSearchRequest(offset_next); - break; - case QueryType_Artists: - case QueryType_SearchArtists: - AddArtistAlbumsRequest(artist_id, offset_next); - break; - default: - break; - } - } - } - - if ( - albums_requests_queue_.isEmpty() && - albums_requests_active_ <= 0 && - artist_albums_requests_queue_.isEmpty() && - artist_albums_requests_active_ <= 0 - ) { // Artist albums query is finished, get all songs for all albums. - - // Get songs for all the albums. - - QHash ::iterator i; - for (i = album_songs_requests_pending_.begin() ; i != album_songs_requests_pending_.end() ; ++i) { - Request request = i.value(); - AddAlbumSongsRequest(request.artist_id, request.album_id, request.album_artist); - } - album_songs_requests_pending_.clear(); - - if (album_songs_requested_ > 0) { - if (album_songs_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving songs for %1 album...").arg(album_songs_requested_)); - else emit UpdateStatus(query_id_, tr("Retrieving songs for %1 albums...").arg(album_songs_requested_)); - emit ProgressSetMaximum(query_id_, album_songs_requested_); - emit UpdateProgress(query_id_, 0); - } - } - - FinishCheck(); - -} - -void TidalRequest::SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) { - - --songs_requests_active_; - if (type_ == QueryType_SearchSongs && fetchalbums_) { - AlbumsReceived(reply, 0, limit_requested, offset_requested, (offset_requested == 0)); - } - else { - SongsReceived(reply, 0, 0, limit_requested, offset_requested, (offset_requested == 0)); - } - -} - -void TidalRequest::AddAlbumSongsRequest(const qint64 artist_id, const qint64 album_id, const QString &album_artist, const int offset) { - - Request request; - request.artist_id = artist_id; - request.album_id = album_id; - request.album_artist = album_artist; - request.offset = offset; - album_songs_requests_queue_.enqueue(request); - ++album_songs_requested_; - if (album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests(); - -} - -void TidalRequest::FlushAlbumSongsRequests() { - - while (!album_songs_requests_queue_.isEmpty() && album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) { - - Request request = album_songs_requests_queue_.dequeue(); - ++album_songs_requests_active_; - ParamList parameters; - if (request.offset > 0) parameters << Param("offset", QString::number(request.offset)); - QNetworkReply *reply = CreateRequest(QString("albums/%1/tracks").arg(request.album_id), parameters); - replies_ << reply; - NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumSongsReplyReceived(QNetworkReply*, const qint64, const qint64, const int, const QString&)), reply, request.artist_id, request.album_id, request.offset, request.album_artist); - - } - -} - -void TidalRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const qint64 album_id, const int offset_requested, const QString &album_artist) { - - --album_songs_requests_active_; - ++album_songs_received_; - if (offset_requested == 0) { - emit UpdateProgress(query_id_, album_songs_received_); - } - SongsReceived(reply, artist_id, album_id, 0, offset_requested, false, album_artist); - -} - -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) { - - if (!replies_.contains(reply)) return; - replies_.removeAll(reply); - reply->deleteLater(); - - QByteArray data = GetReplyData(reply, auto_login); - - if (finished_) return; - - if (data.isEmpty()) { - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist); - return; - } - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) { - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist); - return; - } - - if (!json_obj.contains("limit") || - !json_obj.contains("offset") || - !json_obj.contains("totalNumberOfItems") || - !json_obj.contains("items")) { - Error("Json object missing values.", json_obj); - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist); - return; - } - - //int limit = json_obj["limit"].toInt(); - int offset = json_obj["offset"].toInt(); - int songs_total = json_obj["totalNumberOfItems"].toInt(); - - if (offset != offset_requested) { - Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested)); - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist); - return; - } - - 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; - } - - QJsonArray json_items = json_value.toArray(); - if (json_items.isEmpty()) { - if ((type_ == QueryType_Songs || type_ == QueryType_SearchSongs) && offset_requested == 0) { - no_results_ = true; - } - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist); - return; - } - - bool compilation = false; - bool multidisc = false; - SongList songs; - int songs_received = 0; - for (const QJsonValue &value : json_items) { - - if (!value.isObject()) { - Error("Invalid Json reply, track is not a object.", value); - continue; - } - QJsonObject json_obj = value.toObject(); - - if (json_obj.contains("item")) { - QJsonValue json_item = json_obj["item"]; - if (!json_item.isObject()) { - Error("Invalid Json reply, item not a object.", json_item); - continue; - } - json_obj = json_item.toObject(); - } - - ++songs_received; - Song song(Song::Source_Tidal); - ParseSong(song, json_obj, artist_id, album_id, album_artist); - if (!song.is_valid()) continue; - if (song.disc() >= 2) multidisc = true; - if (song.is_compilation()) compilation = true; - songs << song; - } - - for (Song &song : songs) { - if (compilation) song.set_compilation_detected(true); - if (multidisc) { - QString album_full(QString("%1 - (Disc %2)").arg(song.album()).arg(song.disc())); - song.set_album(album_full); - } - songs_ << song; - } - - SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, songs_received, album_artist); - -} - -void TidalRequest::SongsFinishCheck(const qint64 artist_id, const qint64 album_id, const int limit, const int offset, const int songs_total, const int songs_received, const QString &album_artist) { - - if (finished_) return; - - if (limit == 0 || limit > songs_received) { - int offset_next = offset + songs_received; - if (offset_next > 0 && offset_next < songs_total) { - switch (type_) { - case QueryType_Songs: - AddSongsRequest(offset_next); - break; - case QueryType_SearchSongs: - // If artist_id and album_id isn't zero it means that it's a songs search where we fetch all albums too. So fallthrough. - if (artist_id == 0 && album_id == 0) { - AddSongsSearchRequest(offset_next); - break; - } - // fallthrough - case QueryType_Artists: - case QueryType_SearchArtists: - case QueryType_Albums: - case QueryType_SearchAlbums: - AddAlbumSongsRequest(artist_id, album_id, album_artist, offset_next); - break; - default: - break; - } - } - } - - if (!songs_requests_queue_.isEmpty() && songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests(); - if (!album_songs_requests_queue_.isEmpty() && album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests(); - - if ( - service_->download_album_covers() && - IsQuery() && - songs_requests_queue_.isEmpty() && - songs_requests_active_ <= 0 && - album_songs_requests_queue_.isEmpty() && - album_songs_requests_active_ <= 0 && - album_cover_requests_queue_.isEmpty() && - album_covers_received_ <= 0 && - album_covers_requests_sent_.isEmpty() && - album_songs_received_ >= album_songs_requested_ - ) { - GetAlbumCovers(); - } - - FinishCheck(); - -} - -int TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const qint64 artist_id_requested, const qint64 album_id_requested, const QString &album_artist) { - - Q_UNUSED(artist_id_requested); - - if ( - !json_obj.contains("album") || - !json_obj.contains("allowStreaming") || - !json_obj.contains("artist") || - !json_obj.contains("artists") || - !json_obj.contains("audioQuality") || - !json_obj.contains("duration") || - !json_obj.contains("id") || - !json_obj.contains("streamReady") || - !json_obj.contains("title") || - !json_obj.contains("trackNumber") || - !json_obj.contains("url") || - !json_obj.contains("volumeNumber") || - !json_obj.contains("copyright") - ) { - Error("Invalid Json reply, track is missing one or more values.", json_obj); - return -1; - } - - QJsonValue json_value_artist = json_obj["artist"]; - QJsonValue json_value_album = json_obj["album"]; - QJsonValue json_duration = json_obj["duration"]; - QJsonArray json_artists = json_obj["artists"].toArray(); - - qint64 song_id = json_obj["id"].toInt(); - - QString title = json_obj["title"].toString(); - QString urlstr = json_obj["url"].toString(); - int track = json_obj["trackNumber"].toInt(); - int disc = json_obj["volumeNumber"].toInt(); - bool allow_streaming = json_obj["allowStreaming"].toBool(); - bool stream_ready = json_obj["streamReady"].toBool(); - QString copyright = json_obj["copyright"].toString(); - - if (!json_value_artist.isObject()) { - Error("Invalid Json reply, track artist is not a object.", json_value_artist); - return -1; - } - QJsonObject json_artist = json_value_artist.toObject(); - if (!json_artist.contains("id") || !json_artist.contains("name")) { - Error("Invalid Json reply, track artist is missing id or name.", json_artist); - return -1; - } - qint64 artist_id = json_artist["id"].toInt(); - QString artist = json_artist["name"].toString(); - - if (!json_value_album.isObject()) { - Error("Invalid Json reply, track album is not a object.", json_value_album); - return -1; - } - QJsonObject json_album = json_value_album.toObject(); - if (!json_album.contains("id") || !json_album.contains("title") || !json_album.contains("cover")) { - Error("Invalid Json reply, track album is missing id, title or cover.", json_album); - return -1; - } - qint64 album_id = json_album["id"].toInt(); - if (album_id_requested != 0 && album_id_requested != album_id) { - Error("Invalid Json reply, track album id is wrong.", json_album); - return -1; - } - QString album = json_album["title"].toString(); - QString cover = json_album["cover"].toString(); - - if (!allow_streaming) { - Warn(QString("Song %1 %2 %3 is not allowStreaming").arg(artist).arg(album).arg(title)); - } - - if (!stream_ready) { - Warn(QString("Song %1 %2 %3 is not streamReady").arg(artist).arg(album).arg(title)); - } - - QUrl url; - url.setScheme(url_handler_->scheme()); - url.setPath(QString::number(song_id)); - - QVariant q_duration = json_duration.toVariant(); - quint64 duration = 0; - if (q_duration.isValid() && (q_duration.type() == QVariant::Int || q_duration.type() == QVariant::Double)) { - duration = q_duration.toLongLong() * kNsecPerSec; - } - else { - Error("Invalid duration for song.", json_duration); - return -1; - } - - cover = cover.replace("-", "/"); - QUrl cover_url (QString("%1/images/%2/%3.jpg").arg(kResourcesUrl).arg(cover).arg(coversize_)); - - title.remove(Song::kTitleRemoveMisc); - - //qLog(Debug) << "id" << song_id << "track" << track << "disc" << disc << "title" << title << "album" << album << "album artist" << album_artist << "artist" << artist << cover << allow_streaming << url; - - song.set_source(Song::Source_Tidal); - song.set_song_id(song_id); - song.set_album_id(album_id); - song.set_artist_id(artist_id); - if (album_artist != artist) song.set_albumartist(album_artist); - song.set_album(album); - song.set_artist(artist); - song.set_title(title); - song.set_track(track); - song.set_disc(disc); - song.set_url(url); - song.set_length_nanosec(duration); - song.set_art_automatic(cover_url); - song.set_comment(copyright); - song.set_directory_id(0); - song.set_filetype(Song::FileType_Stream); - song.set_filesize(0); - song.set_mtime(0); - song.set_ctime(0); - song.set_valid(true); - - return song_id; - -} - -void TidalRequest::GetAlbumCovers() { - - for (Song &song : songs_) { - AddAlbumCoverRequest(song); - } - FlushAlbumCoverRequests(); - - if (album_covers_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving album cover for %1 album...").arg(album_covers_requested_)); - else emit UpdateStatus(query_id_, tr("Retrieving album covers for %1 albums...").arg(album_covers_requested_)); - emit ProgressSetMaximum(query_id_, album_covers_requested_); - emit UpdateProgress(query_id_, 0); - -} - -void TidalRequest::AddAlbumCoverRequest(Song &song) { - - 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 = 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); - -} - -void TidalRequest::FlushAlbumCoverRequests() { - - while (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests) { - - AlbumCoverRequest request = album_cover_requests_queue_.dequeue(); - ++album_covers_requests_active_; - - QNetworkRequest req(request.url); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -#endif - QNetworkReply *reply = network_->get(req); - album_cover_replies_ << reply; - 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, const QString &filename) { - - if (album_cover_replies_.contains(reply)) { - album_cover_replies_.removeAll(reply); - reply->deleteLater(); - } - else { - AlbumCoverFinishCheck(); - return; - } - - --album_covers_requests_active_; - ++album_covers_received_; - - if (finished_) return; - - emit UpdateProgress(query_id_, album_covers_received_); - - if (!album_covers_requests_sent_.contains(album_id)) { - AlbumCoverFinishCheck(); - return; - } - - if (reply->error() != QNetworkReply::NoError) { - Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error())); - album_covers_requests_sent_.remove(album_id); - AlbumCoverFinishCheck(); - return; - } - - QByteArray data = reply->readAll(); - if (data.isEmpty()) { - Error(QString("Received empty image data for %1").arg(url.toString())); - album_covers_requests_sent_.remove(album_id); - AlbumCoverFinishCheck(); - return; - } - - QImage image; - if (image.loadFromData(data)) { - - 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(QUrl::fromLocalFile(filename)); - } - } - - } - else { - album_covers_requests_sent_.remove(album_id); - Error(QString("Error decoding image data from %1").arg(url.toString())); - } - - AlbumCoverFinishCheck(); - -} - -void TidalRequest::AlbumCoverFinishCheck() { - - if (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests) - FlushAlbumCoverRequests(); - - FinishCheck(); - -} - -void TidalRequest::FinishCheck() { - - if ( - !finished_ && - !need_login_ && - albums_requests_queue_.isEmpty() && - artists_requests_queue_.isEmpty() && - songs_requests_queue_.isEmpty() && - artist_albums_requests_queue_.isEmpty() && - album_songs_requests_queue_.isEmpty() && - album_cover_requests_queue_.isEmpty() && - artist_albums_requests_pending_.isEmpty() && - album_songs_requests_pending_.isEmpty() && - album_covers_requests_sent_.isEmpty() && - artists_requests_active_ <= 0 && - albums_requests_active_ <= 0 && - songs_requests_active_ <= 0 && - artist_albums_requests_active_ <= 0 && - artist_albums_received_ >= artist_albums_requested_ && - album_songs_requests_active_ <= 0 && - album_songs_received_ >= album_songs_requested_ && - album_covers_requested_ <= album_covers_received_ && - album_covers_requests_active_ <= 0 && - album_covers_received_ >= album_covers_requested_ - ) { - finished_ = true; - if (no_results_ && songs_.isEmpty()) { - if (IsSearch()) - emit Results(query_id_, SongList(), tr("No match.")); - else - emit Results(query_id_, SongList(), QString()); - } - else { - if (songs_.isEmpty() && errors_.isEmpty()) - emit Results(query_id_, songs_, tr("Unknown error")); - else - emit Results(query_id_, songs_, ErrorsToHTML(errors_)); - } - } - -} - -void TidalRequest::Error(const QString &error, const QVariant &debug) { - - if (!error.isEmpty()) { - errors_ << error; - qLog(Error) << "Tidal:" << error; - } - - if (debug.isValid()) qLog(Debug) << debug; - - FinishCheck(); - -} - -void TidalRequest::Warn(QString error, QVariant debug) { - - qLog(Error) << "Tidal:" << error; - if (debug.isValid()) qLog(Debug) << debug; - -} - diff --git a/src/tidal/tidalrequest.h b/src/tidal/tidalrequest.h deleted file mode 100644 index 385f4d479..000000000 --- a/src/tidal/tidalrequest.h +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef TIDALREQUEST_H -#define TIDALREQUEST_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/song.h" -#include "tidalbaserequest.h" - -class QNetworkReply; -class Application; -class NetworkAccessManager; -class TidalService; -class TidalUrlHandler; - -class TidalRequest : public TidalBaseRequest { - Q_OBJECT - - public: - - TidalRequest(TidalService *service, TidalUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QueryType type, QObject *parent); - ~TidalRequest(); - - void ReloadSettings(); - - void Process(); - void NeedLogin() { need_login_ = true; } - void Search(const int search_id, const QString &search_text); - - signals: - void Login(); - void Login(const QString &username, const QString &password, const QString &token); - void LoginSuccess(); - void LoginFailure(QString failure_reason); - void Results(const int id, const SongList &songs, const QString &error); - void UpdateStatus(const int id, const QString &text); - void ProgressSetMaximum(const int id, const int max); - void UpdateProgress(const int id, const int max); - void StreamURLFinished(const QUrl original_url, const QUrl url, const Song::FileType, QString error = QString()); - - private slots: - void LoginComplete(const bool success, QString error = QString()); - - void ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); - - void AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); - void AlbumsReceived(QNetworkReply *reply, const qint64 artist_id_requested, const int limit_requested, const int offset_requested, const bool auto_login); - - void SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); - void SongsReceived(QNetworkReply *reply, const qint64 artist_id, const qint64 album_id, const int limit_requested, const int offset_requested, const bool auto_login = false, const QString &album_artist = QString()); - - 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, const QString &filename); - - private: - typedef QPair Param; - typedef QList ParamList; - - struct Request { - qint64 artist_id = 0; - qint64 album_id = 0; - qint64 song_id = 0; - int offset = 0; - int limit = 0; - QString album_artist; - }; - struct AlbumCoverRequest { - qint64 artist_id = 0; - QString album_id = 0; - QUrl url; - QString filename; - }; - - bool IsQuery() { return (type_ == QueryType_Artists || type_ == QueryType_Albums || type_ == QueryType_Songs); } - bool IsSearch() { return (type_ == QueryType_SearchArtists || type_ == QueryType_SearchAlbums || type_ == QueryType_SearchSongs); } - - void GetArtists(); - void GetAlbums(); - void GetSongs(); - - void ArtistsSearch(); - void AlbumsSearch(); - void SongsSearch(); - - void AddArtistsRequest(const int offset = 0, const int limit = 0); - void AddArtistsSearchRequest(const int offset = 0); - void FlushArtistsRequests(); - void AddAlbumsRequest(const int offset = 0, const int limit = 0); - void AddAlbumsSearchRequest(const int offset = 0); - void FlushAlbumsRequests(); - void AddSongsRequest(const int offset = 0, const int limit = 0); - void AddSongsSearchRequest(const int offset = 0); - void FlushSongsRequests(); - - void ArtistsFinishCheck(const int limit = 0, const int offset = 0, const int artists_received = 0); - void AlbumsFinishCheck(const qint64 artist_id, const int limit = 0, const int offset = 0, const int albums_total = 0, const int albums_received = 0); - void SongsFinishCheck(const qint64 artist_id, const qint64 album_id, const int limit, const int offset, const int songs_total, const int songs_received, const QString &album_artist); - - void AddArtistAlbumsRequest(const qint64 artist_id, const int offset = 0); - void FlushArtistAlbumsRequests(); - - void AddAlbumSongsRequest(const qint64 artist_id, const qint64 album_id, const QString &album_artist, const int offset = 0); - 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()); - - void GetAlbumCovers(); - void AddAlbumCoverRequest(Song &song); - void FlushAlbumCoverRequests(); - void AlbumCoverFinishCheck(); - - void FinishCheck(); - void Warn(QString error, QVariant debug = QVariant()); - void Error(const QString &error, const QVariant &debug = QVariant()); - - static const char *kResourcesUrl; - static const int kMaxConcurrentArtistsRequests; - static const int kMaxConcurrentAlbumsRequests; - static const int kMaxConcurrentSongsRequests; - static const int kMaxConcurrentArtistAlbumsRequests; - static const int kMaxConcurrentAlbumSongsRequests; - static const int kMaxConcurrentAlbumCoverRequests; - - TidalService *service_; - TidalUrlHandler *url_handler_; - Application *app_; - NetworkAccessManager *network_; - - QueryType type_; - bool fetchalbums_; - QString coversize_; - - int query_id_; - QString search_text_; - - bool finished_; - - QQueue artists_requests_queue_; - QQueue albums_requests_queue_; - QQueue songs_requests_queue_; - - QQueue artist_albums_requests_queue_; - QQueue album_songs_requests_queue_; - QQueue album_cover_requests_queue_; - - QList artist_albums_requests_pending_; - QHash album_songs_requests_pending_; - QMultiMap album_covers_requests_sent_; - - int artists_requests_active_; - int artists_total_; - int artists_received_; - - int albums_requests_active_; - int songs_requests_active_; - - int artist_albums_requests_active_; - int artist_albums_requested_; - int artist_albums_received_; - - int album_songs_requests_active_; - int album_songs_requested_; - int album_songs_received_; - - int album_covers_requests_active_; - int album_covers_requested_; - int album_covers_received_; - - SongList songs_; - QStringList errors_; - bool need_login_; - bool no_results_; - QList replies_; - QList album_cover_replies_; - -}; - -#endif // TIDALREQUEST_H diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp deleted file mode 100644 index 1369b62df..000000000 --- a/src/tidal/tidalservice.cpp +++ /dev/null @@ -1,970 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/application.h" -#include "core/player.h" -#include "core/closure.h" -#include "core/logging.h" -#include "core/network.h" -#include "core/database.h" -#include "core/song.h" -#include "core/utilities.h" -#include "internet/internetsearch.h" -#include "collection/collectionbackend.h" -#include "collection/collectionmodel.h" -#include "tidalservice.h" -#include "tidalurlhandler.h" -#include "tidalbaserequest.h" -#include "tidalrequest.h" -#include "tidalfavoriterequest.h" -#include "tidalstreamurlrequest.h" -#include "settings/settingsdialog.h" -#include "settings/tidalsettingspage.h" - -using std::shared_ptr; - -const Song::Source TidalService::kSource = Song::Source_Tidal; -const char *TidalService::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng=="; -const char *TidalService::kOAuthUrl = "https://login.tidal.com/authorize"; -const char *TidalService::kOAuthAccessTokenUrl = "https://login.tidal.com/oauth2/token"; -const char *TidalService::kOAuthRedirectUrl = "tidal://login/auth"; -const char *TidalService::kAuthUrl = "https://api.tidalhifi.com/v1/login/username"; -const int TidalService::kLoginAttempts = 2; -const int TidalService::kTimeResetLoginAttempts = 60000; - -const char *TidalService::kArtistsSongsTable = "tidal_artists_songs"; -const char *TidalService::kAlbumsSongsTable = "tidal_albums_songs"; -const char *TidalService::kSongsTable = "tidal_songs"; - -const char *TidalService::kArtistsSongsFtsTable = "tidal_artists_songs_fts"; -const char *TidalService::kAlbumsSongsFtsTable = "tidal_albums_songs_fts"; -const char *TidalService::kSongsFtsTable = "tidal_songs_fts"; - -TidalService::TidalService(Application *app, QObject *parent) - : InternetService(Song::Source_Tidal, "Tidal", "tidal", app, parent), - app_(app), - network_(new NetworkAccessManager(this)), - url_handler_(new TidalUrlHandler(app, this)), - artists_collection_backend_(nullptr), - albums_collection_backend_(nullptr), - songs_collection_backend_(nullptr), - artists_collection_model_(nullptr), - albums_collection_model_(nullptr), - songs_collection_model_(nullptr), - artists_collection_sort_model_(new QSortFilterProxyModel(this)), - albums_collection_sort_model_(new QSortFilterProxyModel(this)), - songs_collection_sort_model_(new QSortFilterProxyModel(this)), - timer_search_delay_(new QTimer(this)), - timer_login_attempt_(new QTimer(this)), - favorite_request_(new TidalFavoriteRequest(this, network_, this)), - user_id_(0), - search_delay_(1500), - artistssearchlimit_(1), - albumssearchlimit_(1), - songssearchlimit_(1), - fetchalbums_(true), - download_album_covers_(true), - pending_search_id_(0), - next_pending_search_id_(1), - search_id_(0), - login_sent_(false), - login_attempts_(0) - { - - app->player()->RegisterUrlHandler(url_handler_); - - // Backends - - artists_collection_backend_ = new CollectionBackend(); - artists_collection_backend_->moveToThread(app_->database()->thread()); - artists_collection_backend_->Init(app_->database(), Song::Source_Tidal, kArtistsSongsTable, QString(), QString(), kArtistsSongsFtsTable); - - albums_collection_backend_ = new CollectionBackend(); - albums_collection_backend_->moveToThread(app_->database()->thread()); - albums_collection_backend_->Init(app_->database(), Song::Source_Tidal, kAlbumsSongsTable, QString(), QString(), kAlbumsSongsFtsTable); - - songs_collection_backend_ = new CollectionBackend(); - songs_collection_backend_->moveToThread(app_->database()->thread()); - songs_collection_backend_->Init(app_->database(), Song::Source_Tidal, kSongsTable, QString(), QString(), kSongsFtsTable); - - artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this); - albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this); - songs_collection_model_ = new CollectionModel(songs_collection_backend_, app_, this); - - artists_collection_sort_model_->setSourceModel(artists_collection_model_); - artists_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - artists_collection_sort_model_->setDynamicSortFilter(true); - artists_collection_sort_model_->setSortLocaleAware(true); - artists_collection_sort_model_->sort(0); - - albums_collection_sort_model_->setSourceModel(albums_collection_model_); - albums_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - albums_collection_sort_model_->setDynamicSortFilter(true); - albums_collection_sort_model_->setSortLocaleAware(true); - albums_collection_sort_model_->sort(0); - - songs_collection_sort_model_->setSourceModel(songs_collection_model_); - songs_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - songs_collection_sort_model_->setDynamicSortFilter(true); - songs_collection_sort_model_->setSortLocaleAware(true); - songs_collection_sort_model_->sort(0); - - // Search - - timer_search_delay_->setSingleShot(true); - connect(timer_search_delay_, SIGNAL(timeout()), SLOT(StartSearch())); - - timer_login_attempt_->setSingleShot(true); - connect(timer_login_attempt_, SIGNAL(timeout()), SLOT(ResetLoginAttempts())); - - connect(this, SIGNAL(Login()), SLOT(SendLogin())); - connect(this, SIGNAL(Login(QString, QString, QString)), SLOT(SendLogin(QString, QString, QString))); - - connect(this, SIGNAL(AddArtists(const SongList&)), favorite_request_, SLOT(AddArtists(const SongList&))); - connect(this, SIGNAL(AddAlbums(const SongList&)), favorite_request_, SLOT(AddAlbums(const SongList&))); - connect(this, SIGNAL(AddSongs(const SongList&)), favorite_request_, SLOT(AddSongs(const SongList&))); - - connect(this, SIGNAL(RemoveArtists(const SongList&)), favorite_request_, SLOT(RemoveArtists(const SongList&))); - connect(this, SIGNAL(RemoveAlbums(const SongList&)), favorite_request_, SLOT(RemoveAlbums(const SongList&))); - connect(this, SIGNAL(RemoveSongs(const SongList&)), favorite_request_, SLOT(RemoveSongs(const SongList&))); - - connect(favorite_request_, SIGNAL(ArtistsAdded(const SongList&)), artists_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); - connect(favorite_request_, SIGNAL(AlbumsAdded(const SongList&)), albums_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); - connect(favorite_request_, SIGNAL(SongsAdded(const SongList&)), songs_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); - - connect(favorite_request_, SIGNAL(ArtistsRemoved(const SongList&)), artists_collection_backend_, SLOT(DeleteSongs(const SongList&))); - connect(favorite_request_, SIGNAL(AlbumsRemoved(const SongList&)), albums_collection_backend_, SLOT(DeleteSongs(const SongList&))); - connect(favorite_request_, SIGNAL(SongsRemoved(const SongList&)), songs_collection_backend_, SLOT(DeleteSongs(const SongList&))); - - ReloadSettings(); - -} - -TidalService::~TidalService() { - - while (!stream_url_requests_.isEmpty()) { - TidalStreamURLRequest *stream_url_req = stream_url_requests_.takeFirst(); - disconnect(stream_url_req, 0, this, 0); - stream_url_req->deleteLater(); - } - - artists_collection_backend_->deleteLater(); - albums_collection_backend_->deleteLater(); - songs_collection_backend_->deleteLater(); - -} - -void TidalService::Exit() { - - wait_for_exit_ << artists_collection_backend_ << albums_collection_backend_ << songs_collection_backend_; - - connect(artists_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); - connect(albums_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); - connect(songs_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); - - artists_collection_backend_->ExitAsync(); - albums_collection_backend_->ExitAsync(); - songs_collection_backend_->ExitAsync(); - -} - -void TidalService::ExitReceived() { - - QObject *obj = static_cast(sender()); - disconnect(obj, 0, this, 0); - qLog(Debug) << obj << "successfully exited."; - wait_for_exit_.removeAll(obj); - if (wait_for_exit_.isEmpty()) emit ExitFinished(); - -} - -void TidalService::ShowConfig() { - app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Tidal); -} - -void TidalService::ReloadSettings() { - - QSettings s; - s.beginGroup(TidalSettingsPage::kSettingsGroup); - - oauth_ = s.value("oauth", false).toBool(); - client_id_ = s.value("client_id").toString(); - api_token_ = s.value("api_token").toString(); - if (api_token_.isEmpty()) api_token_ = QString::fromUtf8(QByteArray::fromBase64(kApiTokenB64)); - - username_ = s.value("username").toString(); - QByteArray password = s.value("password").toByteArray(); - if (password.isEmpty()) password_.clear(); - else password_ = QString::fromUtf8(QByteArray::fromBase64(password)); - - quality_ = s.value("quality", "LOSSLESS").toString(); - search_delay_ = s.value("searchdelay", 1500).toInt(); - artistssearchlimit_ = s.value("artistssearchlimit", 4).toInt(); - albumssearchlimit_ = s.value("albumssearchlimit", 10).toInt(); - songssearchlimit_ = s.value("songssearchlimit", 10).toInt(); - fetchalbums_ = s.value("fetchalbums", false).toBool(); - coversize_ = s.value("coversize", "320x320").toString(); - download_album_covers_ = s.value("downloadalbumcovers", true).toBool(); - stream_url_method_ = static_cast(s.value("streamurl").toInt()); - - user_id_ = s.value("user_id").toInt(); - country_code_ = s.value("country_code", "US").toString(); - access_token_ = s.value("access_token").toString(); - refresh_token_ = s.value("refresh_token").toString(); - session_id_ = s.value("session_id").toString(); - expiry_time_ = s.value("expiry_time").toDateTime(); - - s.endGroup(); - -} - -void TidalService::StartAuthorisation() { - - login_sent_ = true; - ++login_attempts_; - if (timer_login_attempt_->isActive()) timer_login_attempt_->stop(); - timer_login_attempt_->setInterval(kTimeResetLoginAttempts); - timer_login_attempt_->start(); - - code_verifier_ = Utilities::CryptographicRandomString(44); - code_challenge_ = QString(QCryptographicHash::hash(code_verifier_.toUtf8(), QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding)); - - if (code_challenge_.lastIndexOf(QChar('=')) == code_challenge_.length() - 1) { - code_challenge_.chop(1); - } - - const ParamList params = ParamList() << Param("response_type", "code") - << Param("code_challenge", code_challenge_) - << Param("code_challenge_method", "S256") - << Param("redirect_uri", kOAuthRedirectUrl) - << Param("client_id", client_id_) - << Param("scope", "r_usr w_usr"); - - QUrlQuery url_query; - 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); - } - - QUrl url = QUrl(kOAuthUrl); - url.setQuery(url_query); - QDesktopServices::openUrl(url); - -} - -void TidalService::AuthorisationUrlReceived(const QUrl &url) { - - qLog(Debug) << "Tidal: Authorisation URL Received" << url; - - QUrlQuery url_query(url); - - if (url_query.hasQueryItem("token_type") && url_query.hasQueryItem("expires_in") && url_query.hasQueryItem("access_token")) { - - access_token_ = url_query.queryItemValue("access_token").toUtf8(); - int expires_in = url_query.queryItemValue("expires_in").toInt(); - expiry_time_ = QDateTime::currentDateTime().addSecs(expires_in - 120); - session_id_.clear(); - - QSettings s; - s.beginGroup(TidalSettingsPage::kSettingsGroup); - s.setValue("access_token", access_token_); - s.setValue("expiry_time", expiry_time_); - s.remove("refresh_token"); - s.remove("session_id"); - s.endGroup(); - - login_attempts_ = 0; - if (timer_login_attempt_->isActive()) timer_login_attempt_->stop(); - - emit LoginComplete(true); - emit LoginSuccess(); - } - - else if (url_query.hasQueryItem("code") && url_query.hasQueryItem("state")) { - - QString code = url_query.queryItemValue("code"); - QString state = url_query.queryItemValue("state"); - - const ParamList params = ParamList() << Param("code", code) - << Param("client_id", client_id_) - << Param("grant_type", "authorization_code") - << Param("redirect_uri", kOAuthRedirectUrl) - << Param("scope", "r_usr w_usr") - << Param("code_verifier", code_verifier_); - - QUrlQuery url_query; - 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); - } - - 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)), this, SLOT(HandleLoginSSLErrors(QList))); - NewClosure(reply, SIGNAL(finished()), this, SLOT(AccessTokenRequestFinished(QNetworkReply*)), reply); - - } - - else { - - LoginError(tr("Reply from Tidal is missing query items.")); - return; - } - -} - -void TidalService::HandleLoginSSLErrors(QList 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 || 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 "status" and "userMessage" then use that instead. - QByteArray data(reply->readAll()); - 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.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(); - login_errors_ << QString("Authentication failure: %1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status); - } - } - 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(); - return; - } - } - - QByteArray data(reply->readAll()); - QJsonParseError json_error; - QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error); - - if (json_error.error != QJsonParseError::NoError) { - LoginError("Authentication reply from server missing Json data."); - return; - } - - if (json_doc.isNull() || json_doc.isEmpty()) { - LoginError("Authentication reply from server has empty Json document."); - return; - } - - if (!json_doc.isObject()) { - LoginError("Authentication reply from server has Json document that is not an object.", json_doc); - return; - } - - QJsonObject json_obj = json_doc.object(); - if (json_obj.isEmpty()) { - LoginError("Authentication reply from server has empty Json object.", json_doc); - return; - } - - if (!json_obj.contains("access_token") || - !json_obj.contains("refresh_token") || - !json_obj.contains("expires_in") || - !json_obj.contains("user") - ) { - LoginError("Authentication reply from server is missing access_token, refresh_token, expires_in or user", json_obj); - return; - } - - access_token_ = json_obj["access_token"].toString(); - refresh_token_ = json_obj["refresh_token"].toString(); - int expires_in = json_obj["expires_in"].toInt(); - expiry_time_ = QDateTime::currentDateTime().addSecs(expires_in - 120); - - QJsonValue json_user = json_obj["user"]; - if (!json_user.isObject()) { - LoginError("Authentication reply from server has Json user that is not an object.", json_doc); - return; - } - QJsonObject json_obj_user = json_user.toObject(); - if (json_obj_user.isEmpty()) { - LoginError("Authentication reply from server has empty Json user object.", json_doc); - return; - } - - country_code_ = json_obj_user["countryCode"].toString(); - user_id_ = json_obj_user["userId"].toInt(); - session_id_.clear(); - - QSettings s; - s.beginGroup(TidalSettingsPage::kSettingsGroup); - s.setValue("access_token", access_token_); - s.setValue("refresh_token", refresh_token_); - s.setValue("expiry_time", expiry_time_); - s.setValue("country_code", country_code_); - s.setValue("user_id", user_id_); - s.remove("session_id"); - s.endGroup(); - - qLog(Debug) << "Tidal: Login successful" << "user id" << user_id_ << "access token" << access_token_; - - login_attempts_ = 0; - if (timer_login_attempt_->isActive()) timer_login_attempt_->stop(); - - emit LoginComplete(true); - emit LoginSuccess(); - -} - -void TidalService::SendLogin() { - SendLogin(api_token_, username_, password_); -} - -void TidalService::SendLogin(const QString &api_token, const QString &username, const QString &password) { - - login_sent_ = true; - ++login_attempts_; - if (timer_login_attempt_->isActive()) timer_login_attempt_->stop(); - timer_login_attempt_->setInterval(kTimeResetLoginAttempts); - timer_login_attempt_->start(); - - const ParamList params = ParamList() << Param("token", (api_token.isEmpty() ? api_token_ : api_token)) - << Param("username", username) - << Param("password", password) - << Param("clientVersion", "2.2.1--7"); - - QUrlQuery url_query; - 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); - } - - QUrl url(kAuthUrl); - QNetworkRequest req(url); - - req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - req.setRawHeader("X-Tidal-Token", (api_token.isEmpty() ? api_token_.toUtf8() : api_token.toUtf8())); - - QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8(); - QNetworkReply *reply = network_->post(req, query); - connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(HandleLoginSSLErrors(QList))); - NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply); - - //qLog(Debug) << "Tidal: Sending request" << url << query; - -} - -void TidalService::HandleAuthReply(QNetworkReply *reply) { - - reply->deleteLater(); - - login_sent_ = false; - - 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 { - // 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); - 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(); - login_errors_ << QString("Authentication failure: %1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status); - } - } - 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(); - login_errors_.clear(); - return; - } - } - - login_errors_.clear(); - - QByteArray data(reply->readAll()); - QJsonParseError json_error; - QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error); - - if (json_error.error != QJsonParseError::NoError) { - LoginError("Authentication reply from server missing Json data."); - return; - } - - if (json_doc.isNull() || json_doc.isEmpty()) { - LoginError("Authentication reply from server has empty Json document."); - return; - } - - if (!json_doc.isObject()) { - LoginError("Authentication reply from server has Json document that is not an object.", json_doc); - return; - } - - QJsonObject json_obj = json_doc.object(); - if (json_obj.isEmpty()) { - LoginError("Authentication reply from server has empty Json object.", json_doc); - return; - } - - if (!json_obj.contains("userId") || !json_obj.contains("sessionId") || !json_obj.contains("countryCode") ) { - LoginError("Authentication reply from server is missing userId, sessionId or countryCode", json_obj); - return; - } - - country_code_ = json_obj["countryCode"].toString(); - session_id_ = json_obj["sessionId"].toString(); - user_id_ = json_obj["userId"].toInt(); - access_token_.clear(); - refresh_token_.clear(); - expiry_time_ = QDateTime(); - - QSettings s; - s.beginGroup(TidalSettingsPage::kSettingsGroup); - s.remove("access_token"); - s.remove("refresh_token"); - s.remove("expiry_time"); - s.setValue("user_id", user_id_); - s.setValue("session_id", session_id_); - s.setValue("country_code", country_code_); - s.endGroup(); - - qLog(Debug) << "Tidal: Login successful" << "user id" << user_id_ << "session id" << session_id_ << "country code" << country_code_; - - login_attempts_ = 0; - if (timer_login_attempt_->isActive()) timer_login_attempt_->stop(); - - emit LoginComplete(true); - emit LoginSuccess(); - -} - -void TidalService::Logout() { - - access_token_.clear(); - session_id_.clear(); - expiry_time_ = QDateTime(); - - QSettings s; - s.beginGroup(TidalSettingsPage::kSettingsGroup); - s.remove("user_id"); - s.remove("country_code"); - s.remove("access_token"); - s.remove("session_id"); - s.remove("expiry_time"); - s.endGroup(); - -} - -void TidalService::ResetLoginAttempts() { - login_attempts_ = 0; -} - -void TidalService::TryLogin() { - - if (authenticated() || login_sent_) return; - - if (api_token_.isEmpty()) { - emit LoginComplete(false, tr("Missing Tidal API token.")); - return; - } - if (username_.isEmpty()) { - emit LoginComplete(false, tr("Missing Tidal username.")); - return; - } - if (password_.isEmpty()) { - emit LoginComplete(false, tr("Missing Tidal password.")); - return; - } - if (login_attempts_ >= kLoginAttempts) { - emit LoginComplete(false, tr("Not authenticated with Tidal and reached maximum number of login attempts.")); - return; - } - - emit Login(); - -} - -void TidalService::ResetArtistsRequest() { - - if (artists_request_.get()) { - disconnect(artists_request_.get(), 0, this, 0); - disconnect(this, 0, artists_request_.get(), 0); - artists_request_.reset(); - } - -} - -void TidalService::GetArtists() { - - if (!authenticated()) { - if (oauth_) { - emit ArtistsResults(SongList(), tr("Not authenticated with Tidal.")); - ShowConfig(); - return; - } - else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { - emit ArtistsResults(SongList(), tr("Missing Tidal API token, username or passord.")); - ShowConfig(); - return; - } - } - - ResetArtistsRequest(); - - 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&))); - connect(artists_request_.get(), SIGNAL(ProgressSetMaximum(const int, const int)), SLOT(ArtistsProgressSetMaximumReceived(const int, const int))); - connect(artists_request_.get(), SIGNAL(UpdateProgress(const int, const int)), SLOT(ArtistsUpdateProgressReceived(const int, const int))); - connect(this, SIGNAL(LoginComplete(const bool, QString)), artists_request_.get(), SLOT(LoginComplete(const bool, QString))); - - artists_request_->Process(); - -} - -void TidalService::ArtistsResultsReceived(const int id, const SongList &songs, const QString &error) { - Q_UNUSED(id); - emit ArtistsResults(songs, error); -} - -void TidalService::ArtistsUpdateStatusReceived(const int id, const QString &text) { - Q_UNUSED(id); - emit ArtistsUpdateStatus(text); -} - -void TidalService::ArtistsProgressSetMaximumReceived(const int id, const int max) { - Q_UNUSED(id); - emit ArtistsProgressSetMaximum(max); -} - -void TidalService::ArtistsUpdateProgressReceived(const int id, const int progress) { - Q_UNUSED(id); - emit ArtistsUpdateProgress(progress); -} - -void TidalService::ResetAlbumsRequest() { - - if (albums_request_.get()) { - disconnect(albums_request_.get(), 0, this, 0); - disconnect(this, 0, albums_request_.get(), 0); - albums_request_.reset(); - } - -} - -void TidalService::GetAlbums() { - - if (!authenticated()) { - if (oauth_) { - emit AlbumsResults(SongList(), tr("Not authenticated with Tidal.")); - ShowConfig(); - return; - } - else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { - emit AlbumsResults(SongList(), tr("Missing Tidal API token, username or passord.")); - ShowConfig(); - return; - } - } - - ResetAlbumsRequest(); - 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))); - connect(albums_request_.get(), SIGNAL(UpdateProgress(const int, const int)), SLOT(AlbumsUpdateProgressReceived(const int, const int))); - connect(this, SIGNAL(LoginComplete(const bool, const QString&)), albums_request_.get(), SLOT(LoginComplete(const bool, const QString&))); - - albums_request_->Process(); - -} - -void TidalService::AlbumsResultsReceived(const int id, const SongList &songs, const QString &error) { - Q_UNUSED(id); - emit AlbumsResults(songs, error); -} - -void TidalService::AlbumsUpdateStatusReceived(const int id, const QString &text) { - Q_UNUSED(id); - emit AlbumsUpdateStatus(text); -} - -void TidalService::AlbumsProgressSetMaximumReceived(const int id, const int max) { - Q_UNUSED(id); - emit AlbumsProgressSetMaximum(max); -} - -void TidalService::AlbumsUpdateProgressReceived(const int id, const int progress) { - Q_UNUSED(id); - emit AlbumsUpdateProgress(progress); -} - -void TidalService::ResetSongsRequest() { - - if (songs_request_.get()) { - disconnect(songs_request_.get(), 0, this, 0); - disconnect(this, 0, songs_request_.get(), 0); - songs_request_.reset(); - } - -} - -void TidalService::GetSongs() { - - if (!authenticated()) { - if (oauth_) { - emit SongsResults(SongList(), tr("Not authenticated with Tidal.")); - ShowConfig(); - return; - } - else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { - emit SongsResults(SongList(), tr("Missing Tidal API token, username or passord.")); - ShowConfig(); - return; - } - } - - ResetSongsRequest(); - 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))); - connect(songs_request_.get(), SIGNAL(UpdateProgress(const int, const int)), SLOT(SongsUpdateProgressReceived(const int, const int))); - connect(this, SIGNAL(LoginComplete(const bool, const QString&)), songs_request_.get(), SLOT(LoginComplete(const bool, const QString&))); - - songs_request_->Process(); - -} - -void TidalService::SongsResultsReceived(const int id, const SongList &songs, const QString &error) { - Q_UNUSED(id); - emit SongsResults(songs, error); -} - -void TidalService::SongsUpdateStatusReceived(const int id, const QString &text) { - Q_UNUSED(id); - emit SongsUpdateStatus(text); -} - -void TidalService::SongsProgressSetMaximumReceived(const int id, const int max) { - Q_UNUSED(id); - emit SongsProgressSetMaximum(max); -} - -void TidalService::SongsUpdateProgressReceived(const int id, const int progress) { - Q_UNUSED(id); - emit SongsUpdateProgress(progress); -} - -int TidalService::Search(const QString &text, InternetSearch::SearchType type) { - - pending_search_id_ = next_pending_search_id_; - pending_search_text_ = text; - pending_search_type_ = type; - - next_pending_search_id_++; - - if (text.isEmpty()) { - timer_search_delay_->stop(); - return pending_search_id_; - } - timer_search_delay_->setInterval(search_delay_); - timer_search_delay_->start(); - - return pending_search_id_; - -} - -void TidalService::StartSearch() { - - if (!authenticated()) { - if (oauth_) { - emit SearchResults(pending_search_id_, SongList(), tr("Not authenticated with Tidal.")); - ShowConfig(); - return; - } - else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { - emit SearchResults(pending_search_id_, SongList(), tr("Missing Tidal API token, username or passord.")); - ShowConfig(); - return; - } - } - - search_id_ = pending_search_id_; - search_text_ = pending_search_text_; - - SendSearch(); - -} - -void TidalService::CancelSearch() { -} - -void TidalService::SendSearch() { - - TidalBaseRequest::QueryType type; - - switch (pending_search_type_) { - case InternetSearch::SearchType_Artists: - type = TidalBaseRequest::QueryType_SearchArtists; - break; - case InternetSearch::SearchType_Albums: - type = TidalBaseRequest::QueryType_SearchAlbums; - break; - case InternetSearch::SearchType_Songs: - type = TidalBaseRequest::QueryType_SearchSongs; - break; - default: - //Error("Invalid search type."); - return; - } - - 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&))); - connect(search_request_.get(), SIGNAL(ProgressSetMaximum(const int, const int)), SIGNAL(SearchProgressSetMaximum(const int, const int))); - connect(search_request_.get(), SIGNAL(UpdateProgress(const int, const int)), SIGNAL(SearchUpdateProgress(const int, const int))); - connect(this, SIGNAL(LoginComplete(const bool, const QString&)), search_request_.get(), SLOT(LoginComplete(const bool, const QString&))); - - search_request_->Search(search_id_, search_text_); - search_request_->Process(); - -} - -void TidalService::SearchResultsReceived(const int id, const SongList &songs, const QString &error) { - emit SearchResults(id, songs, error); -} - -void TidalService::GetStreamURL(const QUrl &url) { - - if (!authenticated()) { - if (oauth_) { - emit StreamURLFinished(url, url, Song::FileType_Stream, -1, -1, -1, tr("Not authenticated with Tidal.")); - return; - } - else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { - emit StreamURLFinished(url, url, Song::FileType_Stream, -1, -1, -1, tr("Missing Tidal API token, username or passord.")); - return; - } - } - - TidalStreamURLRequest *stream_url_req = new TidalStreamURLRequest(this, network_, url, this); - stream_url_requests_ << stream_url_req; - - connect(stream_url_req, SIGNAL(TryLogin()), this, SLOT(TryLogin())); - connect(stream_url_req, SIGNAL(StreamURLFinished(const QUrl&, const QUrl&, const Song::FileType, const int, const int, const qint64, QString)), this, SLOT(HandleStreamURLFinished(const QUrl&, const QUrl&, const Song::FileType, const int, const int, const qint64, QString))); - connect(this, SIGNAL(LoginComplete(const bool, const QString&)), stream_url_req, SLOT(LoginComplete(const bool, QString))); - - stream_url_req->Process(); - -} - -void TidalService::HandleStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) { - - TidalStreamURLRequest *stream_url_req = qobject_cast(sender()); - if (!stream_url_req || !stream_url_requests_.contains(stream_url_req)) return; - stream_url_req->deleteLater(); - stream_url_requests_.removeAll(stream_url_req); - - emit StreamURLFinished(original_url, stream_url, filetype, samplerate, bit_depth, duration, error); - -} - -void TidalService::LoginError(const QString &error, const QVariant &debug) { - - if (!error.isEmpty()) login_errors_ << error; - - QString error_html; - for (const QString &error : login_errors_) { - qLog(Error) << "Tidal:" << error; - error_html += error + "
    "; - } - if (debug.isValid()) qLog(Debug) << debug; - - emit LoginFailure(error_html); - emit LoginComplete(false, error_html); - - login_errors_.clear(); - -} diff --git a/src/tidal/tidalservice.h b/src/tidal/tidalservice.h deleted file mode 100644 index a2a47e477..000000000 --- a/src/tidal/tidalservice.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef TIDALSERVICE_H -#define TIDALSERVICE_H - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/song.h" -#include "internet/internetservice.h" -#include "internet/internetsearch.h" -#include "settings/tidalsettingspage.h" - -class QSortFilterProxyModel; -class QNetworkReply; -class QTimer; - -class Application; -class NetworkAccessManager; -class TidalUrlHandler; -class TidalRequest; -class TidalFavoriteRequest; -class TidalStreamURLRequest; -class CollectionBackend; -class CollectionModel; - -using std::shared_ptr; - -class TidalService : public InternetService { - Q_OBJECT - - public: - TidalService(Application *app, QObject *parent); - ~TidalService(); - - static const Song::Source kSource; - - void Exit(); - void ReloadSettings(); - - void Logout(); - int Search(const QString &query, InternetSearch::SearchType type); - void CancelSearch(); - - int max_login_attempts() { return kLoginAttempts; } - - Application *app() { return app_; } - - bool oauth() { return oauth_; } - QString client_id() { return client_id_; } - QString api_token() { return api_token_; } - quint64 user_id() { return user_id_; } - QString country_code() { return country_code_; } - QString username() { return username_; } - QString password() { return password_; } - QString quality() { return quality_; } - int search_delay() { return search_delay_; } - int artistssearchlimit() { return artistssearchlimit_; } - int albumssearchlimit() { return albumssearchlimit_; } - int songssearchlimit() { return songssearchlimit_; } - bool fetchalbums() { return fetchalbums_; } - QString coversize() { return coversize_; } - bool download_album_covers() { return download_album_covers_; } - TidalSettingsPage::StreamUrlMethod stream_url_method() { return stream_url_method_; } - - QString access_token() { return access_token_; } - QString session_id() { return session_id_; } - - bool authenticated() { return (!access_token_.isEmpty() || !session_id_.isEmpty()); } - bool login_sent() { return login_sent_; } - bool login_attempts() { return login_attempts_; } - - void GetStreamURL(const QUrl &url); - - CollectionBackend *artists_collection_backend() { return artists_collection_backend_; } - CollectionBackend *albums_collection_backend() { return albums_collection_backend_; } - CollectionBackend *songs_collection_backend() { return songs_collection_backend_; } - - CollectionModel *artists_collection_model() { return artists_collection_model_; } - CollectionModel *albums_collection_model() { return albums_collection_model_; } - CollectionModel *songs_collection_model() { return songs_collection_model_; } - - QSortFilterProxyModel *artists_collection_sort_model() { return artists_collection_sort_model_; } - QSortFilterProxyModel *albums_collection_sort_model() { return albums_collection_sort_model_; } - QSortFilterProxyModel *songs_collection_sort_model() { return songs_collection_sort_model_; } - - enum QueryType { - QueryType_Artists, - QueryType_Albums, - QueryType_Songs, - QueryType_SearchArtists, - QueryType_SearchAlbums, - QueryType_SearchSongs, - }; - - signals: - - public slots: - void ShowConfig(); - void TryLogin(); - void SendLogin(const QString &api_token, const QString &username, const QString &password); - void GetArtists(); - void GetAlbums(); - void GetSongs(); - void ResetArtistsRequest(); - void ResetAlbumsRequest(); - void ResetSongsRequest(); - - private slots: - void ExitReceived(); - void StartAuthorisation(); - void AuthorisationUrlReceived(const QUrl &url); - void HandleLoginSSLErrors(QList ssl_errors); - void AccessTokenRequestFinished(QNetworkReply *reply); - void SendLogin(); - void HandleAuthReply(QNetworkReply *reply); - void ResetLoginAttempts(); - void StartSearch(); - void ArtistsResultsReceived(const int id, const SongList &songs, const QString &error); - void AlbumsResultsReceived(const int id, const SongList &songs, const QString &error); - void SongsResultsReceived(const int id, const SongList &songs, const QString &error); - void SearchResultsReceived(const int id, const SongList &songs, const QString &error); - void ArtistsUpdateStatusReceived(const int id, const QString &text); - void AlbumsUpdateStatusReceived(const int id, const QString &text); - void SongsUpdateStatusReceived(const int id, const QString &text); - void ArtistsProgressSetMaximumReceived(const int id, const int max); - void AlbumsProgressSetMaximumReceived(const int id, const int max); - void SongsProgressSetMaximumReceived(const int id, const int max); - void ArtistsUpdateProgressReceived(const int id, const int progress); - void AlbumsUpdateProgressReceived(const int id, const int progress); - void SongsUpdateProgressReceived(const int id, const int progress); - void HandleStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString()); - - private: - typedef QPair Param; - typedef QList ParamList; - - typedef QPair EncodedParam; - typedef QList EncodedParamList; - - void SendSearch(); - void LoginError(const QString &error = QString(), const QVariant &debug = QVariant()); - - static const char *kApiTokenB64; - static const char *kOAuthUrl; - static const char *kOAuthAccessTokenUrl; - static const char *kOAuthRedirectUrl; - static const char *kAuthUrl; - static const int kLoginAttempts; - static const int kTimeResetLoginAttempts; - - static const char *kArtistsSongsTable; - static const char *kAlbumsSongsTable; - static const char *kSongsTable; - - static const char *kArtistsSongsFtsTable; - static const char *kAlbumsSongsFtsTable; - static const char *kSongsFtsTable; - - Application *app_; - NetworkAccessManager *network_; - TidalUrlHandler *url_handler_; - - CollectionBackend *artists_collection_backend_; - CollectionBackend *albums_collection_backend_; - CollectionBackend *songs_collection_backend_; - - CollectionModel *artists_collection_model_; - CollectionModel *albums_collection_model_; - CollectionModel *songs_collection_model_; - - QSortFilterProxyModel *artists_collection_sort_model_; - QSortFilterProxyModel *albums_collection_sort_model_; - QSortFilterProxyModel *songs_collection_sort_model_; - - QTimer *timer_search_delay_; - QTimer *timer_login_attempt_; - - std::shared_ptr artists_request_; - std::shared_ptr albums_request_; - std::shared_ptr songs_request_; - std::shared_ptr search_request_; - TidalFavoriteRequest *favorite_request_; - - bool oauth_; - QString client_id_; - QString api_token_; - quint64 user_id_; - QString country_code_; - QString username_; - QString password_; - QString quality_; - int search_delay_; - int artistssearchlimit_; - int albumssearchlimit_; - int songssearchlimit_; - bool fetchalbums_; - QString coversize_; - bool download_album_covers_; - TidalSettingsPage::StreamUrlMethod stream_url_method_; - - QString access_token_; - QString refresh_token_; - QString session_id_; - QDateTime expiry_time_; - - int pending_search_id_; - int next_pending_search_id_; - QString pending_search_text_; - InternetSearch::SearchType pending_search_type_; - - int search_id_; - QString search_text_; - bool login_sent_; - int login_attempts_; - - QString code_verifier_; - QString code_challenge_; - - QList stream_url_requests_; - - QStringList login_errors_; - - QList wait_for_exit_; - -}; - -#endif // TIDALSERVICE_H diff --git a/src/tidal/tidalstreamurlrequest.cpp b/src/tidal/tidalstreamurlrequest.cpp deleted file mode 100644 index cbbf70abe..000000000 --- a/src/tidal/tidalstreamurlrequest.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/logging.h" -#include "core/network.h" -#include "core/song.h" -#include "settings/tidalsettingspage.h" -#include "tidalservice.h" -#include "tidalbaserequest.h" -#include "tidalstreamurlrequest.h" - -TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent) - : TidalBaseRequest(service, network, parent), - service_(service), - reply_(nullptr), - original_url_(original_url), - song_id_(original_url.path().toInt()), - tries_(0), - need_login_(false) {} - -TidalStreamURLRequest::~TidalStreamURLRequest() { - - if (reply_) { - disconnect(reply_, 0, this, 0); - if (reply_->isRunning()) reply_->abort(); - reply_->deleteLater(); - } - -} - -void TidalStreamURLRequest::LoginComplete(const bool success, QString error) { - - if (!need_login_) return; - need_login_ = false; - - if (!success) { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error); - return; - } - - Process(); - -} - -void TidalStreamURLRequest::Process() { - - if (!authenticated()) { - if (oauth()) { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Not authenticated with Tidal.")); - return; - } - else if (api_token().isEmpty() || username().isEmpty() || password().isEmpty()) { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Missing Tidal API token, username or passord.")); - return; - } - need_login_ = true; - emit TryLogin(); - return; - } - - GetStreamURL(); - -} - -void TidalStreamURLRequest::Cancel() { - - if (reply_ && reply_->isRunning()) { - reply_->abort(); - } - else { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Cancelled.")); - } - -} - -void TidalStreamURLRequest::GetStreamURL() { - - ++tries_; - - if (reply_) { - disconnect(reply_, 0, this, 0); - if (reply_->isRunning()) reply_->abort(); - reply_->deleteLater(); - } - - ParamList params; - - switch (stream_url_method()) { - case TidalSettingsPage::StreamUrlMethod_StreamUrl: - params << Param("soundQuality", quality()); - reply_ = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id_), params); - connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived())); - break; - case TidalSettingsPage::StreamUrlMethod_UrlPostPaywall: - params << Param("audioquality", quality()); - params << Param("playbackmode", "STREAM"); - params << Param("assetpresentation", "FULL"); - params << Param("urlusagemode", "STREAM"); - reply_ = CreateRequest(QString("tracks/%1/urlpostpaywall").arg(song_id_), params); - connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived())); - break; - case TidalSettingsPage::StreamUrlMethod_PlaybackInfoPostPaywall: - params << Param("audioquality", quality()); - params << Param("playbackmode", "STREAM"); - params << Param("assetpresentation", "FULL"); - reply_ = CreateRequest(QString("tracks/%1/playbackinfopostpaywall").arg(song_id_), params); - connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived())); - break; - } - -} - -void TidalStreamURLRequest::StreamURLReceived() { - - if (!reply_) return; - disconnect(reply_, 0, this, 0); - reply_->deleteLater(); - - 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, errors_.first()); - return; - } - reply_ = nullptr; - - //qLog(Debug) << "Tidal:" << data; - - QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - - if (!json_obj.contains("trackId")) { - Error("Invalid Json reply, stream missing trackId.", json_obj); - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - int track_id(json_obj["trackId"].toInt()); - if (track_id != song_id_) { - Error("Incorrect track ID returned.", json_obj); - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - - Song::FileType filetype(Song::FileType_Unknown); - - if (json_obj.contains("codec") || json_obj.contains("codecs")) { - QString codec; - if (json_obj.contains("codec")) codec = json_obj["codec"].toString().toLower(); - if (json_obj.contains("codecs")) codec = json_obj["codecs"].toString().toLower(); - filetype = Song::FiletypeByExtension(codec); - if (filetype == Song::FileType_Unknown) { - qLog(Debug) << "Tidal: Unknown codec" << codec; - filetype = Song::FileType_Stream; - } - } - - QList urls; - - if (json_obj.contains("manifest")) { - - QString manifest(json_obj["manifest"].toString()); - QByteArray data_manifest = QByteArray::fromBase64(manifest.toUtf8()); - - //qLog(Debug) << "Tidal:" << data_manifest; - - QXmlStreamReader xml_reader(data_manifest); - if (xml_reader.readNextStartElement()) { - - QString filepath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/tidalstreams"; - QString filename = "tidal-" + QString::number(song_id_) + ".xml"; - if (!QDir().mkpath(filepath)) { - Error(QString("Failed to create directory %1.").arg(filepath), json_obj); - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - QUrl url("file://" + filepath + "/" + filename); - QFile file(url.toLocalFile()); - if (file.exists()) - file.remove(); - if (!file.open(QIODevice::WriteOnly)) { - 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, errors_.first()); - return; - } - file.write(data_manifest); - file.close(); - - urls << url; - - } - - else { - - json_obj = ExtractJsonObj(data_manifest); - if (json_obj.isEmpty()) { - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - - if (!json_obj.contains("mimeType")) { - 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, errors_.first()); - return; - } - - QString mimetype = json_obj["mimeType"].toString(); - QMimeDatabase mimedb; - for (QString suffix : mimedb.mimeTypeForName(mimetype.toUtf8()).suffixes()) { - filetype = Song::FiletypeByExtension(suffix); - if (filetype != Song::FileType_Unknown) break; - } - if (filetype == Song::FileType_Unknown) { - qLog(Debug) << "Tidal: Unknown mimetype" << mimetype; - filetype = Song::FileType_Stream; - } - } - - } - - if (json_obj.contains("urls")) { - QJsonValue json_urls = json_obj["urls"]; - if (!json_urls.isArray()) { - Error("Invalid Json reply, urls is not an array.", json_urls); - emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first()); - return; - } - QJsonArray json_array_urls = json_urls.toArray(); - for (const QJsonValue &value : json_array_urls) { - urls << QUrl(value.toString()); - } - } - else if (json_obj.contains("url")) { - QUrl new_url(json_obj["url"].toString()); - urls << new_url; - } - - if (urls.isEmpty()) { - Error("Missing stream urls.", json_obj); - emit StreamURLFinished(original_url_, original_url_, filetype, -1, -1, -1, errors_.first()); - 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; - } - -} diff --git a/src/tidal/tidalstreamurlrequest.h b/src/tidal/tidalstreamurlrequest.h deleted file mode 100644 index aacb86f0d..000000000 --- a/src/tidal/tidalstreamurlrequest.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef TIDALSTREAMURLREQUEST_H -#define TIDALSTREAMURLREQUEST_H - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "core/song.h" -#include "tidalservice.h" -#include "tidalbaserequest.h" -#include "settings/tidalsettingspage.h" - -class QNetworkReply; -class NetworkAccessManager; - -class TidalStreamURLRequest : public TidalBaseRequest { - Q_OBJECT - - public: - TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent); - ~TidalStreamURLRequest(); - - void GetStreamURL(); - void Process(); - void NeedLogin() { need_login_ = true; } - void Cancel(); - - bool oauth() { return service_->oauth(); } - TidalSettingsPage::StreamUrlMethod stream_url_method() { return service_->stream_url_method(); } - QUrl original_url() { return original_url_; } - int song_id() { return song_id_; } - bool need_login() { return need_login_; } - - signals: - void TryLogin(); - void StreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString()); - - private slots: - void LoginComplete(const bool success, QString error = QString()); - 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_; - -}; - -#endif // TIDALSTREAMURLREQUEST_H diff --git a/src/tidal/tidalurlhandler.cpp b/src/tidal/tidalurlhandler.cpp deleted file mode 100644 index c49d672ee..000000000 --- a/src/tidal/tidalurlhandler.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#include "config.h" - -#include -#include -#include - -#include "core/application.h" -#include "core/taskmanager.h" -#include "core/song.h" -#include "tidal/tidalservice.h" -#include "tidalurlhandler.h" - -TidalUrlHandler::TidalUrlHandler(Application *app, TidalService *service) : - UrlHandler(service), - app_(app), - service_(service), - task_id_(-1) - { - - connect(service, SIGNAL(StreamURLFinished(const QUrl&, const QUrl&, const Song::FileType, const int, const int, const qint64, QString)), this, SLOT(GetStreamURLFinished(const QUrl&, const QUrl&, const Song::FileType, const int, const int, const qint64, QString))); - -} - -UrlHandler::LoadResult TidalUrlHandler::StartLoading(const QUrl &url) { - - LoadResult ret(url); - if (task_id_ != -1) return ret; - task_id_ = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme())); - service_->GetStreamURL(url); - ret.type_ = LoadResult::WillLoadAsynchronously; - return ret; - -} - -void TidalUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error) { - - if (task_id_ == -1) return; - CancelTask(); - if (error.isEmpty()) - emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration)); - else - emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, stream_url, filetype, -1, -1, -1, error)); - -} - -void TidalUrlHandler::CancelTask() { - app_->task_manager()->SetTaskFinished(task_id_); - task_id_ = -1; -} diff --git a/src/tidal/tidalurlhandler.h b/src/tidal/tidalurlhandler.h deleted file mode 100644 index 519510a6b..000000000 --- a/src/tidal/tidalurlhandler.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Strawberry Music Player - * Copyright 2018, Jonas Kvinge - * - * 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 . - * - */ - -#ifndef TIDALURLHANDLER_H -#define TIDALURLHANDLER_H - -#include "config.h" - -#include -#include -#include -#include - -#include "core/urlhandler.h" -#include "core/song.h" -#include "tidal/tidalservice.h" - -class Application; - -class TidalUrlHandler : public UrlHandler { - Q_OBJECT - - public: - TidalUrlHandler(Application *app, TidalService *service); - - QString scheme() const { return service_->url_scheme(); } - LoadResult StartLoading(const QUrl &url); - - void CancelTask(); - - private slots: - void GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, QString error = QString()); - - private: - Application *app_; - TidalService *service_; - int task_id_; - -}; - -#endif