1 /* This file is part of Clementine.
2    Copyright 2011-2013, Alan Briolat <alan.briolat@gmail.com>
3    Copyright 2013, Ross Wolfson <ross.wolfson@gmail.com>
4    Copyright 2013, David Sansome <me@davidsansome.com>
5    Copyright 2013-2014, John Maguire <john.maguire@gmail.com>
6    Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
7 
8    Clementine is free software: you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation, either version 3 of the License, or
11    (at your option) any later version.
12 
13    Clementine is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17 
18    You should have received a copy of the GNU General Public License
19    along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #ifndef INTERNET_SUBSONIC_SUBSONICSERVICE_H_
23 #define INTERNET_SUBSONIC_SUBSONICSERVICE_H_
24 
25 #include <QQueue>
26 
27 #include "internet/core/internetmodel.h"
28 #include "internet/core/internetservice.h"
29 #include "internet/subsonic/subsonicdynamicplaylist.h"
30 
31 class QNetworkAccessManager;
32 class QNetworkReply;
33 class QSortFilterProxyModel;
34 class QXmlStreamReader;
35 
36 class SubsonicUrlHandler;
37 class SubsonicLibraryScanner;
38 
39 class SubsonicService : public InternetService {
40   Q_OBJECT
41   Q_ENUMS(LoginState)
42   Q_ENUMS(ApiError)
43 
44  public:
45   SubsonicService(Application* app, InternetModel* parent);
46   ~SubsonicService();
47 
48   enum LoginState {
49     LoginState_Loggedin,
50     LoginState_BadServer,
51     LoginState_OutdatedClient,
52     LoginState_OutdatedServer,
53     LoginState_BadCredentials,
54     LoginState_Unlicensed,
55     LoginState_OtherError,
56     LoginState_Unknown,
57     LoginState_ConnectionRefused,
58     LoginState_HostNotFound,
59     LoginState_Timeout,
60     LoginState_SslError,
61     LoginState_IncompleteCredentials,
62     LoginState_RedirectLimitExceeded,
63     LoginState_RedirectNoUrl,
64   };
65 
66   enum ApiError {
67     ApiError_Generic = 0,
68     ApiError_ParameterMissing = 10,
69     ApiError_OutdatedClient = 20,
70     ApiError_OutdatedServer = 30,
71     ApiError_BadCredentials = 40,
72     ApiError_Unauthorized = 50,
73     ApiError_Unlicensed = 60,
74     ApiError_NotFound = 70,
75   };
76 
77   enum Type {
78     Type_Artist = InternetModel::TypeCount,
79     Type_Album,
80     Type_Track,
81   };
82 
83   enum Role {
84     Role_Id = InternetModel::RoleCount,
85   };
86 
87   typedef QMap<QString, QString> RequestOptions;
88 
89   bool IsConfigured() const;
90   bool IsAmpache() const;
91 
92   QStandardItem* CreateRootItem();
93   void LazyPopulate(QStandardItem* item);
94   void ShowContextMenu(const QPoint& global_pos);
95   QWidget* HeaderWidget() const;
96   void ReloadSettings();
97 
98   void Login();
99   void Login(const QString& server, const QString& username,
100              const QString& password, const bool& usesslv3,
101              const bool& verifycert);
102 
login_state()103   LoginState login_state() const { return login_state_; }
104 
105   // Subsonic API methods
106   void Ping();
107 
108   QUrl BuildRequestUrl(const QString& view) const;
109   // Scrubs the part of the path that we re-add in BuildRequestUrl().
110   static QUrl ScrubUrl(const QUrl& url);
111   // Convenience function to reduce QNetworkRequest/QSslConfiguration
112   // boilerplate.
113   QNetworkReply* Send(const QUrl& url);
114 
115   friend PlaylistItemList SubsonicDynamicPlaylist::GenerateMore(int);
116 
117   static const char* kServiceName;
118   static const char* kSettingsGroup;
119   static const char* kApiVersion;
120   static const char* kApiClientName;
121 
122   static const char* kSongsTable;
123   static const char* kFtsTable;
124 
125   static const int kMaxRedirects;
126   static const int kCoverArtSize;
127 
128 
129 signals:
130   void LoginStateChanged(SubsonicService::LoginState newstate);
131 
132  private:
133   void EnsureMenuCreated();
134   // Update configured and working server state
135   void UpdateServer(const QString& server);
136 
137   QNetworkAccessManager* network_;
138   SubsonicUrlHandler* url_handler_;
139 
140   SubsonicLibraryScanner* scanner_;
141   int load_database_task_id_;
142 
143   QMenu* context_menu_;
144   QStandardItem* root_;
145 
146   LibraryBackend* library_backend_;
147   LibraryModel* library_model_;
148   LibraryFilterWidget* library_filter_;
149   QSortFilterProxyModel* library_sort_model_;
150   int total_song_count_;
151 
152   // Configuration
153   // The server that shows up in the GUI (use UpdateServer() to update)
154   QString configured_server_;
155   QString username_;
156   QString password_;
157   bool usesslv3_;
158   bool verifycert_;
159 
160   LoginState login_state_;
161   QString working_server_;  // The actual server, possibly post-redirect
162   int redirect_count_;
163   bool is_ampache_;
164 
165  private slots:
166   void UpdateTotalSongCount(int count);
167   void ReloadDatabase();
168   void ReloadDatabaseFinished();
169   void OnLoginStateChanged(SubsonicService::LoginState newstate);
170   void OnPingFinished(QNetworkReply* reply);
171 
172   void ShowConfig();
173 };
174 
175 class SubsonicLibraryScanner : public QObject {
176   Q_OBJECT
177 
178  public:
179   explicit SubsonicLibraryScanner(SubsonicService* service,
180                                   QObject* parent = nullptr);
181   ~SubsonicLibraryScanner();
182 
183   void Scan();
GetSongs()184   const SongList& GetSongs() const { return songs_; }
185 
186   static const int kAlbumChunkSize;
187   static const int kConcurrentRequests;
188   static const int kCoverArtSize;
189 
190 signals:
191   void ScanFinished();
192 
193  private slots:
194   // Step 1: use getAlbumList2 type=alphabeticalByName to list all albums
195   void OnGetAlbumListFinished(QNetworkReply* reply, int offset);
196   // Step 2: use getAlbum id=? to list all songs for each album
197   void OnGetAlbumFinished(QNetworkReply* reply);
198 
199  private:
200   void GetAlbumList(int offset);
201   void GetAlbum(const QString& id);
202   void ParsingError(const QString& message);
203 
204   SubsonicService* service_;
205   bool scanning_;
206   QQueue<QString> album_queue_;
207   QSet<QNetworkReply*> pending_requests_;
208   SongList songs_;
209 };
210 
211 #endif  // INTERNET_SUBSONIC_SUBSONICSERVICE_H_
212