mirror of
https://github.com/strawberrymusicplayer/strawberry
synced 2025-02-03 10:57:33 +01:00
Add Qobuz support (#181)
This commit is contained in:
parent
dbd2edf442
commit
89252d0dba
@ -345,6 +345,7 @@ optional_component(TRANSLATIONS ON "Translations"
|
||||
)
|
||||
|
||||
optional_component(TIDAL ON "Tidal support")
|
||||
optional_component(QOBUZ ON "Qobuz support")
|
||||
optional_component(SUBSONIC ON "Subsonic support")
|
||||
|
||||
optional_component(MOODBAR ON "Moodbar"
|
||||
|
@ -25,7 +25,7 @@ Strawberry is a music player and music collection organizer. It is a fork of Cle
|
||||
* Audio analyzer
|
||||
* Audio equalizer
|
||||
* Transfer music to iPod, iPhone, MTP or mass-storage USB player
|
||||
* Streaming support for Tidal and Subsonic
|
||||
* Streaming support for Tidal, Qobuz and Subsonic
|
||||
* Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||
|
||||
It has so far been tested to work on Linux, OpenBSD, macOS and Windows.
|
||||
|
@ -7,6 +7,7 @@
|
||||
<file>schema/schema-4.sql</file>
|
||||
<file>schema/schema-5.sql</file>
|
||||
<file>schema/schema-6.sql</file>
|
||||
<file>schema/schema-7.sql</file>
|
||||
<file>schema/device-schema.sql</file>
|
||||
<file>style/strawberry.css</file>
|
||||
<file>html/playing-tooltip-plain.html</file>
|
||||
|
@ -83,12 +83,13 @@
|
||||
<file>icons/128x128/xine.png</file>
|
||||
<file>icons/128x128/zoom-in.png</file>
|
||||
<file>icons/128x128/zoom-out.png</file>
|
||||
<file>icons/128x128/tidal.png</file>
|
||||
<file>icons/128x128/scrobble.png</file>
|
||||
<file>icons/128x128/scrobble-disabled.png</file>
|
||||
<file>icons/128x128/moodbar.png</file>
|
||||
<file>icons/128x128/love.png</file>
|
||||
<file>icons/128x128/subsonic.png</file>
|
||||
<file>icons/128x128/tidal.png</file>
|
||||
<file>icons/128x128/qobuz.png</file>
|
||||
<file>icons/64x64/albums.png</file>
|
||||
<file>icons/64x64/alsa.png</file>
|
||||
<file>icons/64x64/application-exit.png</file>
|
||||
@ -172,12 +173,13 @@
|
||||
<file>icons/64x64/xine.png</file>
|
||||
<file>icons/64x64/zoom-in.png</file>
|
||||
<file>icons/64x64/zoom-out.png</file>
|
||||
<file>icons/64x64/tidal.png</file>
|
||||
<file>icons/64x64/scrobble.png</file>
|
||||
<file>icons/64x64/scrobble-disabled.png</file>
|
||||
<file>icons/64x64/moodbar.png</file>
|
||||
<file>icons/64x64/love.png</file>
|
||||
<file>icons/64x64/subsonic.png</file>
|
||||
<file>icons/64x64/tidal.png</file>
|
||||
<file>icons/64x64/qobuz.png</file>
|
||||
<file>icons/48x48/albums.png</file>
|
||||
<file>icons/48x48/alsa.png</file>
|
||||
<file>icons/48x48/application-exit.png</file>
|
||||
@ -264,12 +266,13 @@
|
||||
<file>icons/48x48/xine.png</file>
|
||||
<file>icons/48x48/zoom-in.png</file>
|
||||
<file>icons/48x48/zoom-out.png</file>
|
||||
<file>icons/48x48/tidal.png</file>
|
||||
<file>icons/48x48/scrobble.png</file>
|
||||
<file>icons/48x48/scrobble-disabled.png</file>
|
||||
<file>icons/48x48/moodbar.png</file>
|
||||
<file>icons/48x48/love.png</file>
|
||||
<file>icons/48x48/subsonic.png</file>
|
||||
<file>icons/48x48/tidal.png</file>
|
||||
<file>icons/48x48/qobuz.png</file>
|
||||
<file>icons/32x32/albums.png</file>
|
||||
<file>icons/32x32/alsa.png</file>
|
||||
<file>icons/32x32/application-exit.png</file>
|
||||
@ -357,12 +360,13 @@
|
||||
<file>icons/32x32/xine.png</file>
|
||||
<file>icons/32x32/zoom-in.png</file>
|
||||
<file>icons/32x32/zoom-out.png</file>
|
||||
<file>icons/32x32/tidal.png</file>
|
||||
<file>icons/32x32/scrobble.png</file>
|
||||
<file>icons/32x32/scrobble-disabled.png</file>
|
||||
<file>icons/32x32/moodbar.png</file>
|
||||
<file>icons/32x32/love.png</file>
|
||||
<file>icons/32x32/subsonic.png</file>
|
||||
<file>icons/32x32/tidal.png</file>
|
||||
<file>icons/32x32/qobuz.png</file>
|
||||
<file>icons/22x22/albums.png</file>
|
||||
<file>icons/22x22/alsa.png</file>
|
||||
<file>icons/22x22/application-exit.png</file>
|
||||
@ -450,11 +454,12 @@
|
||||
<file>icons/22x22/xine.png</file>
|
||||
<file>icons/22x22/zoom-in.png</file>
|
||||
<file>icons/22x22/zoom-out.png</file>
|
||||
<file>icons/22x22/tidal.png</file>
|
||||
<file>icons/22x22/scrobble.png</file>
|
||||
<file>icons/22x22/scrobble-disabled.png</file>
|
||||
<file>icons/22x22/moodbar.png</file>
|
||||
<file>icons/22x22/love.png</file>
|
||||
<file>icons/22x22/subsonic.png</file>
|
||||
<file>icons/22x22/tidal.png</file>
|
||||
<file>icons/22x22/qobuz.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
data/icons/128x128/qobuz.png
Normal file
BIN
data/icons/128x128/qobuz.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
BIN
data/icons/22x22/qobuz.png
Normal file
BIN
data/icons/22x22/qobuz.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 964 B |
BIN
data/icons/32x32/qobuz.png
Normal file
BIN
data/icons/32x32/qobuz.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
data/icons/48x48/qobuz.png
Normal file
BIN
data/icons/48x48/qobuz.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
data/icons/64x64/qobuz.png
Normal file
BIN
data/icons/64x64/qobuz.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
BIN
data/icons/full/qobuz.png
Normal file
BIN
data/icons/full/qobuz.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
@ -76,4 +76,3 @@ CREATE VIRTUAL TABLE device_%deviceid_fts USING fts3(
|
||||
);
|
||||
|
||||
UPDATE devices SET schema_version=0 WHERE ROWID=%deviceid;
|
||||
|
||||
|
217
data/schema/schema-7.sql
Normal file
217
data/schema/schema-7.sql
Normal file
@ -0,0 +1,217 @@
|
||||
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 INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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 INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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 INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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 VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
UPDATE schema_version SET version=7;
|
@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
|
||||
|
||||
DELETE FROM schema_version;
|
||||
|
||||
INSERT INTO schema_version (version) VALUES (6);
|
||||
INSERT INTO schema_version (version) VALUES (7);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS directories (
|
||||
path TEXT NOT NULL,
|
||||
@ -302,6 +302,177 @@ 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 INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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 INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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 INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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,
|
||||
@ -470,6 +641,51 @@ CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts3(
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS playlist_items_fts_ USING fts3(
|
||||
|
||||
ftstitle,
|
||||
@ -500,7 +716,6 @@ CREATE VIRTUAL TABLE IF NOT EXISTS %allsongstables_fts USING fts3(
|
||||
|
||||
);
|
||||
|
||||
|
||||
INSERT INTO songs_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment)
|
||||
SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment FROM songs;
|
||||
|
||||
|
2
debian/control
vendored
2
debian/control
vendored
@ -66,7 +66,7 @@ Description: Audio player and music collection organizer
|
||||
- Audio analyzer
|
||||
- Audio equalizer
|
||||
- Transfer music to iPod, iPhone, MTP or mass-storage USB player
|
||||
- Streaming support for Tidal and Subsonic
|
||||
- Streaming support for Tidal, Qobuz and Subsonic
|
||||
- Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||
.
|
||||
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
||||
|
4
debian/copyright
vendored
4
debian/copyright
vendored
@ -58,9 +58,11 @@ Files: src/core/main.h
|
||||
src/lyrics/*
|
||||
src/scrobbler/*
|
||||
src/tidal/*
|
||||
src/qobuz/*
|
||||
src/subsonic/*
|
||||
src/transcoder/transcoderoptionswavpack.cpp
|
||||
src/transcoder/transcoderoptionswavpack.h
|
||||
Copyright: 2012-2014, 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
Copyright: 2012-2014, 2017-2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/core/main.cpp
|
||||
|
2
dist/man/strawberry.1
vendored
2
dist/man/strawberry.1
vendored
@ -37,7 +37,7 @@ Features:
|
||||
.br
|
||||
- Transfer music to iPod, iPhone, MTP or mass-storage USB player
|
||||
.br
|
||||
- Integrated Tidal support
|
||||
- Streaming from Tidal, Qobuz and Subsonic
|
||||
.TP
|
||||
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
||||
.SH OPTIONS
|
||||
|
2
dist/rpm/strawberry.spec.in
vendored
2
dist/rpm/strawberry.spec.in
vendored
@ -106,7 +106,7 @@ Features:
|
||||
- Audio analyzer
|
||||
- Audio equalizer
|
||||
- Transfer music to iPod, iPhone, MTP or mass-storage USB player
|
||||
- Streaming support for Tidal and Subsonic
|
||||
- Streaming support for Tidal, Qobuz and Subsonic
|
||||
- Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||
|
||||
%prep
|
||||
|
2
dist/unix/org.strawbs.strawberry.appdata.xml
vendored
2
dist/unix/org.strawbs.strawberry.appdata.xml
vendored
@ -34,7 +34,7 @@
|
||||
<li>Audio analyzer</li>
|
||||
<li>Audio equalizer</li>
|
||||
<li>Transfer music to iPod, iPhone, MTP or mass-storage USB player</li>
|
||||
<li>Streaming support for Tidal and Subsonic</li>
|
||||
<li>Streaming support for Tidal, Qobuz and Subsonic</li>
|
||||
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
@ -911,6 +911,27 @@ optional_source(HAVE_TIDAL
|
||||
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
|
||||
|
@ -51,6 +51,7 @@
|
||||
|
||||
#cmakedefine HAVE_TIDAL
|
||||
#cmakedefine HAVE_SUBSONIC
|
||||
#cmakedefine HAVE_QOBUZ
|
||||
|
||||
#cmakedefine HAVE_MOODBAR
|
||||
|
||||
|
@ -71,6 +71,10 @@
|
||||
# include "covermanager/tidalcoverprovider.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_QOBUZ
|
||||
# include "qobuz/qobuzservice.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SUBSONIC
|
||||
# include "subsonic/subsonicservice.h"
|
||||
#endif
|
||||
@ -140,6 +144,9 @@ class ApplicationImpl {
|
||||
#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
|
||||
@ -147,6 +154,9 @@ class ApplicationImpl {
|
||||
}),
|
||||
#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); }),
|
||||
|
||||
@ -177,6 +187,9 @@ class ApplicationImpl {
|
||||
Lazy<InternetServices> internet_services_;
|
||||
#ifdef HAVE_TIDAL
|
||||
Lazy<InternetSearch> tidal_search_;
|
||||
#endif
|
||||
#ifdef HAVE_QOBUZ
|
||||
Lazy<InternetSearch> qobuz_search_;
|
||||
#endif
|
||||
Lazy<AudioScrobbler> scrobbler_;
|
||||
#ifdef HAVE_MOODBAR
|
||||
@ -254,6 +267,9 @@ InternetServices *Application::internet_services() const { return p_->internet_s
|
||||
#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(); }
|
||||
|
@ -102,6 +102,9 @@ class Application : public QObject {
|
||||
#ifdef HAVE_TIDAL
|
||||
InternetSearch *tidal_search() const;
|
||||
#endif
|
||||
#ifdef HAVE_QOBUZ
|
||||
InternetSearch *qobuz_search() const;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_MOODBAR
|
||||
MoodbarController *moodbar_controller() const;
|
||||
|
@ -52,7 +52,7 @@
|
||||
#include "scopedtransaction.h"
|
||||
|
||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||
const int Database::kSchemaVersion = 6;
|
||||
const int Database::kSchemaVersion = 7;
|
||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||
|
||||
int Database::sNextConnectionId = 1;
|
||||
|
@ -138,6 +138,10 @@
|
||||
# include "tidal/tidalservice.h"
|
||||
# include "settings/tidalsettingspage.h"
|
||||
#endif
|
||||
#ifdef HAVE_QOBUZ
|
||||
# include "qobuz/qobuzservice.h"
|
||||
# include "settings/qobuzsettingspage.h"
|
||||
#endif
|
||||
#ifdef HAVE_SUBSONIC
|
||||
# include "subsonic/subsonicservice.h"
|
||||
# include "settings/subsonicsettingspage.h"
|
||||
@ -216,6 +220,9 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||
#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
|
||||
@ -273,6 +280,9 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||
#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
|
||||
@ -566,7 +576,13 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||
TidalService *tidalservice = qobject_cast<TidalService*> (app_->internet_services()->ServiceBySource(Song::Source_Tidal));
|
||||
if (tidalservice)
|
||||
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
|
||||
@ -889,6 +905,16 @@ void MainWindow::ReloadSettings() {
|
||||
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();
|
||||
@ -917,6 +943,9 @@ void MainWindow::ReloadAllSettings() {
|
||||
#ifdef HAVE_TIDAL
|
||||
tidal_view_->ReloadSettings();
|
||||
#endif
|
||||
#ifdef HAVE_QOBUZ
|
||||
qobuz_view_->ReloadSettings();
|
||||
#endif
|
||||
#ifdef HAVE_SUBSONIC
|
||||
subsonic_view_->ReloadSettings();
|
||||
#endif
|
||||
|
@ -314,6 +314,7 @@ signals:
|
||||
#endif
|
||||
|
||||
InternetTabsView *tidal_view_;
|
||||
InternetTabsView *qobuz_view_;
|
||||
InternetSongsView *subsonic_view_;
|
||||
|
||||
QAction *collection_show_all_;
|
||||
|
@ -333,7 +333,7 @@ bool Song::has_cue() const { return !d->cue_path_.isEmpty(); }
|
||||
|
||||
bool Song::is_collection_song() const { return !is_cdda() && !is_stream() && id() != -1; }
|
||||
bool Song::is_metadata_good() const { return !d->title_.isEmpty() && !d->album_.isEmpty() && !d->artist_.isEmpty() && !d->url_.isEmpty() && d->end_ > 0; }
|
||||
bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal || d->source_ == Source_Subsonic; }
|
||||
bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal || d->source_ == Source_Subsonic || d->source_ == Source_Qobuz; }
|
||||
bool Song::is_cdda() const { return d->source_ == Source_CDDA; }
|
||||
|
||||
const QString &Song::error() const { return d->error_; }
|
||||
@ -411,6 +411,7 @@ Song::Source Song::SourceFromURL(const QUrl &url) {
|
||||
else if (url.scheme() == "cdda") return Source_CDDA;
|
||||
else if (url.scheme() == "tidal") return Source_Tidal;
|
||||
else if (url.scheme() == "subsonic") return Source_Subsonic;
|
||||
else if (url.scheme() == "qobuz") return Source_Qobuz;
|
||||
else if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "rtsp") return Source_Stream;
|
||||
else return Source_Unknown;
|
||||
|
||||
@ -426,6 +427,7 @@ QString Song::TextForSource(Source source) {
|
||||
case Song::Source_Stream: return QObject::tr("Stream");
|
||||
case Song::Source_Tidal: return QObject::tr("Tidal");
|
||||
case Song::Source_Subsonic: return QObject::tr("subsonic");
|
||||
case Song::Source_Qobuz: return QObject::tr("qobuz");
|
||||
case Song::Source_Unknown: return QObject::tr("Unknown");
|
||||
}
|
||||
return QObject::tr("Unknown");
|
||||
@ -442,6 +444,7 @@ QIcon Song::IconForSource(Source source) {
|
||||
case Song::Source_Stream: return IconLoader::Load("applications-internet");
|
||||
case Song::Source_Tidal: return IconLoader::Load("tidal");
|
||||
case Song::Source_Subsonic: return IconLoader::Load("subsonic");
|
||||
case Song::Source_Qobuz: return IconLoader::Load("qobuz");
|
||||
case Song::Source_Unknown: return IconLoader::Load("edit-delete");
|
||||
}
|
||||
return IconLoader::Load("edit-delete");
|
||||
|
@ -75,6 +75,7 @@ class Song {
|
||||
Source_Stream = 5,
|
||||
Source_Tidal = 6,
|
||||
Source_Subsonic = 7,
|
||||
Source_Qobuz = 8,
|
||||
};
|
||||
|
||||
// Don't change these values - they're stored in the database, and defined in the tag reader protobuf.
|
||||
|
194
src/qobuz/qobuzbaserequest.cpp
Normal file
194
src/qobuz/qobuzbaserequest.cpp
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "qobuzservice.h"
|
||||
#include "qobuzbaserequest.h"
|
||||
|
||||
const char *QobuzBaseRequest::kApiUrl = "http://www.qobuz.com/api.json/0.2";
|
||||
|
||||
QobuzBaseRequest::QobuzBaseRequest(QobuzService *service, NetworkAccessManager *network, QObject *parent) :
|
||||
QObject(parent),
|
||||
service_(service),
|
||||
network_(network)
|
||||
{}
|
||||
|
||||
QobuzBaseRequest::~QobuzBaseRequest() {
|
||||
|
||||
while (!replies_.isEmpty()) {
|
||||
QNetworkReply *reply = replies_.takeFirst();
|
||||
disconnect(reply, 0, nullptr, 0);
|
||||
if (reply->isRunning()) reply->abort();
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QNetworkReply *QobuzBaseRequest::CreateRequest(const QString &ressource_name, const QList<Param> ¶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");
|
||||
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
replies_ << reply;
|
||||
|
||||
//qLog(Debug) << "Qobuz: Sending request" << url;
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
QByteArray QobuzBaseRequest::GetReplyData(QNetworkReply *reply, QString &error) {
|
||||
|
||||
if (replies_.contains(reply)) {
|
||||
replies_.removeAll(reply);
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
error = Error(QString("Received HTTP code %1").arg(http_code));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (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", "code" and "message" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("code") && json_obj.contains("message")) {
|
||||
QString status = json_obj["status"].toString();
|
||||
int code = json_obj["code"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
error = Error(failure_reason);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
QJsonObject QobuzBaseRequest::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.isNull() || 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 QobuzBaseRequest::ExtractItems(QByteArray &data, QString &error) {
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
if (json_obj.isEmpty()) return QJsonValue();
|
||||
return ExtractItems(json_obj, error);
|
||||
|
||||
}
|
||||
|
||||
QJsonValue QobuzBaseRequest::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;
|
||||
|
||||
}
|
||||
|
||||
QString QobuzBaseRequest::Error(QString error, QVariant debug) {
|
||||
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
return error;
|
||||
|
||||
}
|
108
src/qobuz/qobuzbaserequest.h
Normal file
108
src/qobuz/qobuzbaserequest.h
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QOBUZBASEREQUEST_H
|
||||
#define QOBUZBASEREQUEST_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "internet/internetservices.h"
|
||||
#include "internet/internetservice.h"
|
||||
#include "internet/internetsearch.h"
|
||||
#include "qobuzservice.h"
|
||||
|
||||
class Application;
|
||||
class NetworkAccessManager;
|
||||
class QobuzUrlHandler;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
|
||||
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<QString, QString> Param;
|
||||
typedef QList<Param> ParamList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QJsonObject &json_obj, QString &error);
|
||||
|
||||
virtual QString Error(QString error, QVariant debug = QVariant());
|
||||
|
||||
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(); }
|
||||
|
||||
QString access_token() { return service_->access_token(); }
|
||||
|
||||
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:
|
||||
|
||||
static const char *kApiUrl;
|
||||
|
||||
QobuzService *service_;
|
||||
NetworkAccessManager *network_;
|
||||
QList<QNetworkReply*> replies_;
|
||||
|
||||
};
|
||||
|
||||
#endif // QOBUZBASEREQUEST_H
|
281
src/qobuz/qobuzfavoriterequest.cpp
Normal file
281
src/qobuz/qobuzfavoriterequest.cpp
Normal file
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QMultiMap>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
|
||||
#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, nullptr, 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() <= 0) continue;
|
||||
id = QString::number(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<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
ParamList params = ParamList() << Param("app_id", app_id())
|
||||
<< Param("user_auth_token", access_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;
|
||||
}
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
|
||||
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() <= 0) continue;
|
||||
id = QString::number(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", access_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;
|
||||
}
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
78
src/qobuz/qobuzfavoriterequest.h
Normal file
78
src/qobuz/qobuzfavoriterequest.h
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QOBUZFAVORITEREQUEST_H
|
||||
#define QOBUZFAVORITEREQUEST_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QList>
|
||||
|
||||
#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:
|
||||
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 <QNetworkReply*> replies_;
|
||||
|
||||
};
|
||||
|
||||
#endif // QOBUZFAVORITEREQUEST_H
|
1273
src/qobuz/qobuzrequest.cpp
Normal file
1273
src/qobuz/qobuzrequest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
208
src/qobuz/qobuzrequest.h
Normal file
208
src/qobuz/qobuzrequest.h
Normal file
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QOBUZREQUEST_H
|
||||
#define QOBUZREQUEST_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "qobuzbaserequest.h"
|
||||
|
||||
class NetworkAccessManager;
|
||||
class QobuzService;
|
||||
class QobuzUrlHandler;
|
||||
|
||||
class QobuzRequest : public QobuzBaseRequest {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
QobuzRequest(QobuzService *service, QobuzUrlHandler *url_handler, 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(SongList songs);
|
||||
void SearchResults(int id, SongList songs);
|
||||
void ErrorSignal(QString message);
|
||||
void ErrorSignal(int id, QString message);
|
||||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int max);
|
||||
void UpdateProgress(int max);
|
||||
void StreamURLFinished(const QUrl original_url, const QUrl url, const Song::FileType, QString error = QString());
|
||||
|
||||
private slots:
|
||||
//void LoginComplete(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 int 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 int artist_id_requested, const QString &album_id_requested, const int limit_requested, const int offset_requested, const QString &album_artist_requested = QString());
|
||||
|
||||
void ArtistAlbumsReplyReceived(QNetworkReply *reply, const int artist_id, const int offset_requested);
|
||||
void AlbumSongsReplyReceived(QNetworkReply *reply, const int artist_id, const QString &album_id, const int offset_requested, const QString &album_artist);
|
||||
void AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_url, const QString &filename);
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> ParamList;
|
||||
|
||||
struct Request {
|
||||
int artist_id = 0;
|
||||
QString album_id = 0;
|
||||
int song_id = 0;
|
||||
int offset = 0;
|
||||
int limit = 0;
|
||||
QString album_artist;
|
||||
QString album;
|
||||
};
|
||||
struct AlbumCoverRequest {
|
||||
//int artist_id = 0;
|
||||
QUrl url;
|
||||
QString filename;
|
||||
};
|
||||
|
||||
const bool IsQuery() { return (type_ == QueryType_Artists || type_ == QueryType_Albums || type_ == QueryType_Songs); }
|
||||
const 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 int artist_id, const int limit = 0, const int offset = 0, const int albums_total = 0, const int albums_received = 0);
|
||||
void SongsFinishCheck(const int artist_id, const QString &album_id, const int limit, const int offset, const int songs_total, const int songs_received, const QString &album_artist);
|
||||
|
||||
void AddArtistAlbumsRequest(const int artist_id, const int offset = 0);
|
||||
void FlushArtistAlbumsRequests();
|
||||
|
||||
void AddAlbumSongsRequest(const int artist_id, const QString &album_id, const QString &album_artist, const int offset = 0);
|
||||
void FlushAlbumSongsRequests();
|
||||
|
||||
int ParseSong(Song &song, const QJsonObject &json_obj, int 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(QString error, QVariant debug = QVariant());
|
||||
QString Error(QString error, 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_;
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
QueryType type_;
|
||||
|
||||
int search_id_;
|
||||
QString search_text_;
|
||||
|
||||
bool finished_;
|
||||
|
||||
QQueue<Request> artists_requests_queue_;
|
||||
QQueue<Request> albums_requests_queue_;
|
||||
QQueue<Request> songs_requests_queue_;
|
||||
|
||||
QQueue<Request> artist_albums_requests_queue_;
|
||||
QQueue<Request> album_songs_requests_queue_;
|
||||
QQueue<AlbumCoverRequest> album_cover_requests_queue_;
|
||||
|
||||
QList<int> artist_albums_requests_pending_;
|
||||
QHash<QString, Request> album_songs_requests_pending_;
|
||||
QMultiMap<QUrl, Song*> 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_;
|
||||
QString errors_;
|
||||
bool no_results_;
|
||||
QList<QNetworkReply*> album_cover_replies_;
|
||||
|
||||
};
|
||||
|
||||
#endif // QOBUZREQUEST_H
|
603
src/qobuz/qobuzservice.cpp
Normal file
603
src/qobuz/qobuzservice.cpp
Normal file
@ -0,0 +1,603 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QDesktopServices>
|
||||
#include <QCryptographicHash>
|
||||
#include <QByteArray>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSettings>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#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 "qobuzrequest.h"
|
||||
#include "qobuzfavoriterequest.h"
|
||||
#include "qobuzstreamurlrequest.h"
|
||||
#include "settings/qobuzsettingspage.h"
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
const Song::Source QobuzService::kSource = Song::Source_Qobuz;
|
||||
const char *QobuzService::kAuthUrl = "http://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),
|
||||
cache_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(), kArtistsSongsTable, QString(), QString(), kArtistsSongsFtsTable);
|
||||
|
||||
albums_collection_backend_ = new CollectionBackend();
|
||||
albums_collection_backend_->moveToThread(app_->database()->thread());
|
||||
albums_collection_backend_->Init(app_->database(), kAlbumsSongsTable, QString(), QString(), kAlbumsSongsFtsTable);
|
||||
|
||||
songs_collection_backend_ = new CollectionBackend();
|
||||
songs_collection_backend_->moveToThread(app_->database()->thread());
|
||||
songs_collection_backend_->Init(app_->database(), 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, nullptr, 0);
|
||||
stream_url_req->deleteLater();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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", 5).toInt();
|
||||
albumssearchlimit_ = s.value("albumssearchlimit", 100).toInt();
|
||||
songssearchlimit_ = s.value("songssearchlimit", 100).toInt();
|
||||
cache_album_covers_ = s.value("cachealbumcovers", true).toBool();
|
||||
|
||||
access_token_ = s.value("access_token").toString();
|
||||
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
QString QobuzService::CoverCacheDir() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers";
|
||||
}
|
||||
|
||||
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_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);
|
||||
|
||||
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);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply);
|
||||
|
||||
qLog(Debug) << "Qobuz: Sending request" << url << query;
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::HandleAuthReply(QNetworkReply *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
login_sent_ = false;
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (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);
|
||||
QString failure_reason;
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("code") && json_obj.contains("message")) {
|
||||
QString status = json_obj["status"].toString();
|
||||
int code = json_obj["code"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
LoginError(failure_reason);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code != 200) {
|
||||
LoginError(QString("Received HTTP code %1").arg(http_code));
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data(reply->readAll());
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
|
||||
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("user_auth_token")) {
|
||||
LoginError("Authentication reply from server is missing user_auth_token", json_obj);
|
||||
return;
|
||||
}
|
||||
|
||||
access_token_ = json_obj["user_auth_token"].toString();
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(QobuzSettingsPage::kSettingsGroup);
|
||||
s.setValue("access_token", access_token_);
|
||||
s.endGroup();
|
||||
|
||||
qLog(Debug) << "Qobuz: Login successful" << "access token" << access_token_;
|
||||
|
||||
login_attempts_ = 0;
|
||||
if (timer_login_attempt_->isActive()) timer_login_attempt_->stop();
|
||||
|
||||
emit LoginComplete(true);
|
||||
emit LoginSuccess();
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::Logout() {
|
||||
|
||||
access_token_.clear();
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(QobuzSettingsPage::kSettingsGroup);
|
||||
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, "Maximum number of login attempts reached.");
|
||||
return;
|
||||
}
|
||||
if (app_id_.isEmpty()) {
|
||||
emit LoginComplete(false, "Missing Qobuz app ID.");
|
||||
return;
|
||||
}
|
||||
if (username_.isEmpty()) {
|
||||
emit LoginComplete(false, "Missing Qobuz username.");
|
||||
return;
|
||||
}
|
||||
if (password_.isEmpty()) {
|
||||
emit LoginComplete(false, "Missing Qobuz password.");
|
||||
return;
|
||||
}
|
||||
|
||||
emit Login();
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::ResetArtistsRequest() {
|
||||
|
||||
if (artists_request_.get()) {
|
||||
disconnect(artists_request_.get(), 0, nullptr, 0);
|
||||
disconnect(this, 0, artists_request_.get(), 0);
|
||||
artists_request_.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::GetArtists() {
|
||||
|
||||
ResetArtistsRequest();
|
||||
|
||||
artists_request_.reset(new QobuzRequest(this, url_handler_, network_, QobuzBaseRequest::QueryType_Artists, this));
|
||||
|
||||
connect(artists_request_.get(), SIGNAL(ErrorSignal(QString)), SLOT(ArtistsErrorReceived(QString)));
|
||||
connect(artists_request_.get(), SIGNAL(Results(SongList)), SLOT(ArtistsResultsReceived(SongList)));
|
||||
connect(artists_request_.get(), SIGNAL(UpdateStatus(QString)), SIGNAL(ArtistsUpdateStatus(QString)));
|
||||
connect(artists_request_.get(), SIGNAL(ProgressSetMaximum(int)), SIGNAL(ArtistsProgressSetMaximum(int)));
|
||||
connect(artists_request_.get(), SIGNAL(UpdateProgress(int)), SIGNAL(ArtistsUpdateProgress(int)));
|
||||
connect(this, SIGNAL(LoginComplete(bool, QString)), artists_request_.get(), SLOT(LoginComplete(bool, QString)));
|
||||
|
||||
artists_request_->Process();
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::ArtistsResultsReceived(SongList songs) {
|
||||
|
||||
emit ArtistsResults(songs);
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::ArtistsErrorReceived(QString error) {
|
||||
|
||||
emit ArtistsError(error);
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::ResetAlbumsRequest() {
|
||||
|
||||
if (albums_request_.get()) {
|
||||
disconnect(albums_request_.get(), 0, nullptr, 0);
|
||||
disconnect(this, 0, albums_request_.get(), 0);
|
||||
albums_request_.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::GetAlbums() {
|
||||
|
||||
ResetAlbumsRequest();
|
||||
albums_request_.reset(new QobuzRequest(this, url_handler_, network_, QobuzBaseRequest::QueryType_Albums, this));
|
||||
connect(albums_request_.get(), SIGNAL(ErrorSignal(QString)), SLOT(AlbumsErrorReceived(QString)));
|
||||
connect(albums_request_.get(), SIGNAL(Results(SongList)), SLOT(AlbumsResultsReceived(SongList)));
|
||||
connect(albums_request_.get(), SIGNAL(UpdateStatus(QString)), SIGNAL(AlbumsUpdateStatus(QString)));
|
||||
connect(albums_request_.get(), SIGNAL(ProgressSetMaximum(int)), SIGNAL(AlbumsProgressSetMaximum(int)));
|
||||
connect(albums_request_.get(), SIGNAL(UpdateProgress(int)), SIGNAL(AlbumsUpdateProgress(int)));
|
||||
connect(this, SIGNAL(LoginComplete(bool, QString)), albums_request_.get(), SLOT(LoginComplete(bool, QString)));
|
||||
|
||||
albums_request_->Process();
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::AlbumsResultsReceived(SongList songs) {
|
||||
|
||||
emit AlbumsResults(songs);
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::AlbumsErrorReceived(QString error) {
|
||||
|
||||
emit AlbumsError(error);
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::ResetSongsRequest() {
|
||||
|
||||
if (songs_request_.get()) {
|
||||
disconnect(songs_request_.get(), 0, nullptr, 0);
|
||||
disconnect(this, 0, songs_request_.get(), 0);
|
||||
songs_request_.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::GetSongs() {
|
||||
|
||||
ResetSongsRequest();
|
||||
songs_request_.reset(new QobuzRequest(this, url_handler_, network_, QobuzBaseRequest::QueryType_Songs, this));
|
||||
connect(songs_request_.get(), SIGNAL(ErrorSignal(QString)), SLOT(SongsErrorReceived(QString)));
|
||||
connect(songs_request_.get(), SIGNAL(Results(SongList)), SLOT(SongsResultsReceived(SongList)));
|
||||
connect(songs_request_.get(), SIGNAL(UpdateStatus(QString)), SIGNAL(SongsUpdateStatus(QString)));
|
||||
connect(songs_request_.get(), SIGNAL(ProgressSetMaximum(int)), SIGNAL(SongsProgressSetMaximum(int)));
|
||||
connect(songs_request_.get(), SIGNAL(UpdateProgress(int)), SIGNAL(SongsUpdateProgress(int)));
|
||||
connect(this, SIGNAL(LoginComplete(bool, QString)), songs_request_.get(), SLOT(LoginComplete(bool, QString)));
|
||||
|
||||
songs_request_->Process();
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::SongsResultsReceived(SongList songs) {
|
||||
|
||||
emit SongsResults(songs);
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::SongsErrorReceived(QString error) {
|
||||
|
||||
emit SongsError(error);
|
||||
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
if (app_id_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
|
||||
emit SearchError(pending_search_id_, tr("Not authenticated."));
|
||||
next_pending_search_id_ = 1;
|
||||
ShowConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
search_id_ = pending_search_id_;
|
||||
search_text_ = pending_search_text_;
|
||||
|
||||
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_, network_, type, this));
|
||||
|
||||
connect(search_request_.get(), SIGNAL(SearchResults(int, SongList)), SIGNAL(SearchResults(int, SongList)));
|
||||
connect(search_request_.get(), SIGNAL(ErrorSignal(int, QString)), SIGNAL(SearchError(int, QString)));
|
||||
connect(search_request_.get(), SIGNAL(UpdateStatus(QString)), SIGNAL(SearchUpdateStatus(QString)));
|
||||
connect(search_request_.get(), SIGNAL(ProgressSetMaximum(int)), SIGNAL(SearchProgressSetMaximum(int)));
|
||||
connect(search_request_.get(), SIGNAL(UpdateProgress(int)), SIGNAL(SearchUpdateProgress(int)));
|
||||
connect(this, SIGNAL(LoginComplete(bool, QString)), search_request_.get(), SLOT(LoginComplete(bool, QString)));
|
||||
|
||||
search_request_->Search(search_id_, search_text_);
|
||||
search_request_->Process();
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::GetStreamURL(const QUrl &url) {
|
||||
|
||||
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(QUrl, QUrl, Song::FileType, QString)), this, SLOT(HandleStreamURLFinished(QUrl, QUrl, Song::FileType, QString)));
|
||||
connect(this, SIGNAL(LoginComplete(bool, QString)), stream_url_req, SLOT(LoginComplete(bool, QString)));
|
||||
|
||||
stream_url_req->Process();
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::HandleStreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType filetype, QString error) {
|
||||
|
||||
QobuzStreamURLRequest *stream_url_req = qobject_cast<QobuzStreamURLRequest*>(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, error);
|
||||
|
||||
}
|
||||
|
||||
QString QobuzService::LoginError(QString error, QVariant debug) {
|
||||
|
||||
qLog(Error) << "Qobuz:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
emit LoginFailure(error);
|
||||
emit LoginComplete(false, error);
|
||||
|
||||
return error;
|
||||
|
||||
}
|
212
src/qobuz/qobuzservice.h
Normal file
212
src/qobuz/qobuzservice.h
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QOBUZSERVICE_H
|
||||
#define QOBUZSERVICE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "internet/internetservice.h"
|
||||
#include "internet/internetsearch.h"
|
||||
#include "settings/qobuzsettingspage.h"
|
||||
|
||||
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 ReloadSettings();
|
||||
QString CoverCacheDir();
|
||||
|
||||
void Logout();
|
||||
int Search(const QString &query, InternetSearch::SearchType type);
|
||||
void CancelSearch();
|
||||
|
||||
const int max_login_attempts() { return kLoginAttempts; }
|
||||
|
||||
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 cache_album_covers() { return cache_album_covers_; }
|
||||
|
||||
QString access_token() { return access_token_; }
|
||||
|
||||
const bool authenticated() { return (!app_id_.isEmpty() && !app_secret_.isEmpty() && !access_token_.isEmpty()); }
|
||||
const bool login_sent() { return login_sent_; }
|
||||
const 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 &username, const QString &password, const QString &token);
|
||||
void GetArtists();
|
||||
void GetAlbums();
|
||||
void GetSongs();
|
||||
void ResetArtistsRequest();
|
||||
void ResetAlbumsRequest();
|
||||
void ResetSongsRequest();
|
||||
|
||||
private slots:
|
||||
void SendLogin();
|
||||
void HandleAuthReply(QNetworkReply *reply);
|
||||
void ResetLoginAttempts();
|
||||
void StartSearch();
|
||||
void ArtistsResultsReceived(SongList songs);
|
||||
void ArtistsErrorReceived(QString error);
|
||||
void AlbumsResultsReceived(SongList songs);
|
||||
void AlbumsErrorReceived(QString error);
|
||||
void SongsResultsReceived(SongList songs);
|
||||
void SongsErrorReceived(QString error);
|
||||
void HandleStreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType filetype, QString error = QString());
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> ParamList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
void SendSearch();
|
||||
QString LoginError(QString error, 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<QobuzRequest> artists_request_;
|
||||
std::shared_ptr<QobuzRequest> albums_request_;
|
||||
std::shared_ptr<QobuzRequest> songs_request_;
|
||||
std::shared_ptr<QobuzRequest> 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 cache_album_covers_;
|
||||
|
||||
QString access_token_;
|
||||
|
||||
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<QobuzStreamURLRequest*> stream_url_requests_;
|
||||
|
||||
};
|
||||
|
||||
#endif // QOBUZSERVICE_H
|
213
src/qobuz/qobuzstreamurlrequest.cpp
Normal file
213
src/qobuz/qobuzstreamurlrequest.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QMimeDatabase>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QList>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QDateTime>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonObject>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "settings/qobuzsettingspage.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, nullptr, 0);
|
||||
if (reply_->isRunning()) reply_->abort();
|
||||
reply_->deleteLater();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QobuzStreamURLRequest::LoginComplete(bool success, QString error) {
|
||||
|
||||
if (!need_login_) return;
|
||||
need_login_ = false;
|
||||
|
||||
if (!success) {
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||
return;
|
||||
}
|
||||
|
||||
Process();
|
||||
|
||||
}
|
||||
|
||||
void QobuzStreamURLRequest::Process() {
|
||||
|
||||
if (app_id().isEmpty() || app_secret().isEmpty()) {
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, tr("Missing 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, tr("Cancelled."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QobuzStreamURLRequest::GetStreamURL() {
|
||||
|
||||
++tries_;
|
||||
|
||||
if (reply_) {
|
||||
disconnect(reply_, 0, nullptr, 0);
|
||||
if (reply_->isRunning()) reply_->abort();
|
||||
reply_->deleteLater();
|
||||
}
|
||||
|
||||
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) {
|
||||
EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
|
||||
data_to_sign += param.first;
|
||||
data_to_sign += param.second;
|
||||
}
|
||||
data_to_sign += QString::number(timestamp);
|
||||
data_to_sign += app_secret();
|
||||
|
||||
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", access_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;
|
||||
disconnect(reply_, 0, nullptr, 0);
|
||||
reply_->deleteLater();
|
||||
|
||||
QString error;
|
||||
|
||||
QByteArray data = GetReplyData(reply_, error);
|
||||
if (data.isEmpty()) {
|
||||
reply_ = nullptr;
|
||||
if (!authenticated() && login_sent() && tries_ <= 1) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||
return;
|
||||
}
|
||||
reply_ = nullptr;
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("track_id")) {
|
||||
error = Error("Invalid Json reply, stream url is missing track_id.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||
return;
|
||||
}
|
||||
|
||||
int track_id = json_obj["track_id"].toInt();
|
||||
if (track_id != song_id_) {
|
||||
error = Error("Incorrect track ID returned.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("mime_type") || !json_obj.contains("url")) {
|
||||
error = Error("Invalid Json reply, stream url is missing url or mime_type.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||
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 = Error("Returned stream url is invalid.", json_obj);
|
||||
emit StreamURLFinished(original_url_, original_url_, filetype, error);
|
||||
return;
|
||||
}
|
||||
|
||||
emit StreamURLFinished(original_url_, url, filetype);
|
||||
|
||||
}
|
69
src/qobuz/qobuzstreamurlrequest.h
Normal file
69
src/qobuz/qobuzstreamurlrequest.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QOBUZSTREAMURLREQUEST_H
|
||||
#define QOBUZSTREAMURLREQUEST_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#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, QString error = QString());
|
||||
|
||||
private slots:
|
||||
void LoginComplete(bool success, QString error = QString());
|
||||
void StreamURLReceived();
|
||||
|
||||
private:
|
||||
QobuzService *service_;
|
||||
QNetworkReply *reply_;
|
||||
QUrl original_url_;
|
||||
int song_id_;
|
||||
int tries_;
|
||||
bool need_login_;
|
||||
|
||||
};
|
||||
|
||||
#endif // QOBUZSTREAMURLREQUEST_H
|
68
src/qobuz/qobuzurlhandler.cpp
Normal file
68
src/qobuz/qobuzurlhandler.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.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(QUrl, QUrl, Song::FileType, QString)), this, SLOT(GetStreamURLFinished(QUrl, QUrl, Song::FileType, 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(QUrl original_url, QUrl url, Song::FileType filetype, QString error) {
|
||||
|
||||
if (task_id_ == -1) return;
|
||||
CancelTask();
|
||||
if (error.isEmpty())
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, url, filetype));
|
||||
else
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, url, filetype, -1, error));
|
||||
|
||||
}
|
||||
|
||||
void QobuzUrlHandler::CancelTask() {
|
||||
app_->task_manager()->SetTaskFinished(task_id_);
|
||||
task_id_ = -1;
|
||||
}
|
55
src/qobuz/qobuzurlhandler.h
Normal file
55
src/qobuz/qobuzurlhandler.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QOBUZURLHANDLER_H
|
||||
#define QOBUZURLHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/urlhandler.h"
|
||||
#include "core/song.h"
|
||||
#include "qobuz/qobuzservice.h"
|
||||
|
||||
class Application;
|
||||
class QobuzService;
|
||||
|
||||
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(QUrl original_url, QUrl url, Song::FileType filetype, QString error = QString());
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
QobuzService *service_;
|
||||
int task_id_;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
152
src/settings/qobuzsettingspage.cpp
Normal file
152
src/settings/qobuzsettingspage.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QMessageBox>
|
||||
#include <QEvent>
|
||||
|
||||
#include "qobuzsettingspage.h"
|
||||
#include "ui_qobuzsettingspage.h"
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "internet/internetservices.h"
|
||||
#include "qobuz/qobuzservice.h"
|
||||
#include "qobuz/qobuzstreamurlrequest.h"
|
||||
|
||||
const char *QobuzSettingsPage::kSettingsGroup = "Qobuz";
|
||||
|
||||
QobuzSettingsPage::QobuzSettingsPage(SettingsDialog *parent)
|
||||
: SettingsPage(parent),
|
||||
ui_(new Ui::QobuzSettingsPage),
|
||||
service_(dialog()->app()->internet_services()->Service<QobuzService>()) {
|
||||
|
||||
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", 5).toInt());
|
||||
ui_->albumssearchlimit->setValue(s.value("albumssearchlimit", 100).toInt());
|
||||
ui_->songssearchlimit->setValue(s.value("songssearchlimit", 100).toInt());
|
||||
ui_->checkbox_cache_album_covers->setChecked(s.value("cachealbumcovers", 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("cachealbumcovers", ui_->checkbox_cache_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_->button_login->setEnabled(true);
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut);
|
||||
}
|
||||
|
||||
void QobuzSettingsPage::LoginSuccess() {
|
||||
if (!this->isVisible()) return;
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
|
||||
ui_->button_login->setEnabled(false);
|
||||
}
|
||||
|
||||
void QobuzSettingsPage::LoginFailure(QString failure_reason) {
|
||||
if (!this->isVisible()) return;
|
||||
QMessageBox::warning(this, tr("Authentication failed"), failure_reason);
|
||||
}
|
61
src/settings/qobuzsettingspage.h
Normal file
61
src/settings/qobuzsettingspage.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QOBUZSETTINGSPAGE_H
|
||||
#define QOBUZSETTINGSPAGE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QEvent>
|
||||
|
||||
#include "settings/settingspage.h"
|
||||
|
||||
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
|
297
src/settings/qobuzsettingspage.ui
Normal file
297
src/settings/qobuzsettingspage.ui
Normal file
@ -0,0 +1,297 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QobuzSettingsPage</class>
|
||||
<widget class="QWidget" name="QobuzSettingsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>715</width>
|
||||
<height>836</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Qobuz</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="layout_qobuzsettingspage">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_1">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="credential_group">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Authentication</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="layout_credential_group">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_app_id">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>App ID</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="app_id"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_username">
|
||||
<property name="text">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="username"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_password">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_app_secret">
|
||||
<property name="text">
|
||||
<string>App Secret</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="app_secret"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_login">
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LoginStateWidget" name="login_state" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupbox_preferences">
|
||||
<property name="title">
|
||||
<string>Preferences</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="layout_preferences">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_format">
|
||||
<property name="text">
|
||||
<string>Audio format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="format"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_searchdelay">
|
||||
<property name="text">
|
||||
<string>Search delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="searchdelay">
|
||||
<property name="suffix">
|
||||
<string>ms</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_artistssearchlimit">
|
||||
<property name="text">
|
||||
<string>Artists search limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="artistssearchlimit">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_albumssearchlimit">
|
||||
<property name="text">
|
||||
<string>Albums search limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="albumssearchlimit">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_songssearchlimit">
|
||||
<property name="text">
|
||||
<string>Songs search limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="songssearchlimit">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="checkbox_cache_album_covers">
|
||||
<property name="text">
|
||||
<string>Cache album covers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_middle">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout_bottom">
|
||||
<item>
|
||||
<spacer name="spacer_bottom">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_qobuz">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../data/icons.qrc">:/icons/64x64/qobuz.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LoginStateWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/loginstatewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>username</tabstop>
|
||||
<tabstop>password</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
@ -62,11 +62,14 @@
|
||||
#include "transcodersettingspage.h"
|
||||
#include "networkproxysettingspage.h"
|
||||
#include "scrobblersettingspage.h"
|
||||
#ifdef HAVE_MOODBAR
|
||||
# include "moodbarsettingspage.h"
|
||||
#endif
|
||||
#ifdef HAVE_TIDAL
|
||||
# include "tidalsettingspage.h"
|
||||
#endif
|
||||
#ifdef HAVE_MOODBAR
|
||||
# include "moodbarsettingspage.h"
|
||||
#ifdef HAVE_QOBUZ
|
||||
# include "qobuzsettingspage.h"
|
||||
#endif
|
||||
#ifdef HAVE_SUBSONIC
|
||||
# include "subsonicsettingspage.h"
|
||||
@ -143,12 +146,15 @@ SettingsDialog::SettingsDialog(Application *app, QWidget *parent)
|
||||
AddPage(Page_Moodbar, new MoodbarSettingsPage(this), iface);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_TIDAL) || defined(HAVE_SUBSONIC)
|
||||
#if defined(HAVE_TIDAL) || defined(HAVE_SUBSONIC) || defined(HAVE_QOBUZ)
|
||||
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
|
||||
@ -320,9 +326,20 @@ void SettingsDialog::CurrentItemChanged(QTreeWidgetItem *item) {
|
||||
|
||||
}
|
||||
|
||||
void SettingsDialog::ComboBoxLoadFromSettings(QSettings &s, QComboBox *combobox, QString setting, QString default_value) {
|
||||
void SettingsDialog::ComboBoxLoadFromSettings(const QSettings &s, QComboBox *combobox, const QString &setting, const QString &default_value) {
|
||||
|
||||
QString value = s.value(setting, default_value).toString();
|
||||
int i = combobox->findData(value);
|
||||
if (i == -1) i = combobox->findData(default_value);
|
||||
combobox->setCurrentIndex(i);
|
||||
|
||||
}
|
||||
|
||||
void SettingsDialog::ComboBoxLoadFromSettings(const QSettings &s, QComboBox *combobox, const QString &setting, const int default_value) {
|
||||
|
||||
int value = s.value(setting, default_value).toInt();
|
||||
int i = combobox->findData(value);
|
||||
if (i == -1) i = combobox->findData(default_value);
|
||||
combobox->setCurrentIndex(i);
|
||||
|
||||
}
|
||||
|
@ -82,9 +82,10 @@ class SettingsDialog : public QDialog {
|
||||
Page_Transcoding,
|
||||
Page_Proxy,
|
||||
Page_Scrobbler,
|
||||
Page_Moodbar,
|
||||
Page_Tidal,
|
||||
Page_Subsonic,
|
||||
Page_Moodbar,
|
||||
Page_Qobuz,
|
||||
};
|
||||
|
||||
enum Role {
|
||||
@ -111,7 +112,8 @@ class SettingsDialog : public QDialog {
|
||||
// QWidget
|
||||
void showEvent(QShowEvent *e);
|
||||
|
||||
void ComboBoxLoadFromSettings(QSettings &s, QComboBox *combobox, QString setting, QString default_value);
|
||||
void ComboBoxLoadFromSettings(const QSettings &s, QComboBox *combobox, const QString &setting, const QString &default_value);
|
||||
void ComboBoxLoadFromSettings(const QSettings &s, QComboBox *combobox, const QString &setting, const int default_value);
|
||||
|
||||
signals:
|
||||
void ReloadSettings();
|
||||
|
Loading…
x
Reference in New Issue
Block a user