1 /**
2  * \file serverimportdialog.cpp
3  * Generic dialog to import from a server.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 09 Oct 2006
8  *
9  * Copyright (C) 2006-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "serverimportdialog.h"
28 #include <QLayout>
29 #include <QPushButton>
30 #include <QLineEdit>
31 #include <QComboBox>
32 #include <QCheckBox>
33 #include <QLabel>
34 #include <QStatusBar>
35 #include <QVBoxLayout>
36 #include <QHBoxLayout>
37 #include <QListView>
38 #include <QCoreApplication>
39 #include "serverimporter.h"
40 #include "serverimporterconfig.h"
41 #include "contexthelp.h"
42 #include "trackdata.h"
43 
44 /**
45  * Constructor.
46  *
47  * @param parent  parent widget
48  */
ServerImportDialog(QWidget * parent)49 ServerImportDialog::ServerImportDialog(QWidget* parent) : QDialog(parent),
50     m_serverComboBox(nullptr), m_cgiLineEdit(nullptr), m_tokenLineEdit(nullptr),
51     m_standardTagsCheckBox(nullptr), m_additionalTagsCheckBox(nullptr),
52     m_coverArtCheckBox(nullptr), m_source(nullptr)
53 {
54   setObjectName(QLatin1String("ServerImportDialog"));
55 
56   auto vlayout = new QVBoxLayout(this);
57 
58   auto findLayout = new QHBoxLayout;
59   m_artistLineEdit = new QComboBox(this);
60   m_albumLineEdit = new QComboBox(this);
61   m_findButton = new QPushButton(tr("&Find"), this);
62   m_findButton->setAutoDefault(false);
63   m_artistLineEdit->setEditable(true);
64   m_artistLineEdit->setDuplicatesEnabled(false);
65   m_albumLineEdit->setEditable(true);
66   m_albumLineEdit->setDuplicatesEnabled(false);
67   m_artistLineEdit->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
68   m_albumLineEdit->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
69   findLayout->addWidget(m_artistLineEdit);
70   findLayout->addWidget(m_albumLineEdit);
71   findLayout->addWidget(m_findButton);
72   connect(m_findButton, &QAbstractButton::clicked, this, &ServerImportDialog::slotFind);
73   vlayout->addLayout(findLayout);
74 
75   auto serverLayout = new QHBoxLayout;
76   m_serverLabel = new QLabel(tr("&Server:"), this);
77   m_serverComboBox = new QComboBox(this);
78   m_serverComboBox->setEditable(true);
79   m_cgiLabel = new QLabel(tr("C&GI Path:"), this);
80   m_cgiLineEdit = new QLineEdit(this);
81   serverLayout->addWidget(m_serverLabel);
82   serverLayout->addWidget(m_serverComboBox);
83   m_serverLabel->setBuddy(m_serverComboBox);
84   serverLayout->addWidget(m_cgiLabel);
85   serverLayout->addWidget(m_cgiLineEdit);
86   m_cgiLabel->setBuddy(m_cgiLineEdit);
87   vlayout->addLayout(serverLayout);
88 
89   auto tokenLayout = new QHBoxLayout;
90   m_tokenLabel = new QLabel(tr("&Token:"), this);
91   m_tokenLineEdit = new QLineEdit(this);
92   tokenLayout->addWidget(m_tokenLabel);
93   tokenLayout->addWidget(m_tokenLineEdit);
94   m_tokenLabel->setBuddy(m_tokenLineEdit);
95   vlayout->addLayout(tokenLayout);
96 
97   auto hlayout = new QHBoxLayout;
98   m_standardTagsCheckBox = new QCheckBox(tr("&Standard Tags"), this);
99   m_additionalTagsCheckBox = new QCheckBox(tr("&Additional Tags"), this);
100   m_coverArtCheckBox = new QCheckBox(tr("C&over Art"), this);
101   hlayout->addWidget(m_standardTagsCheckBox);
102   hlayout->addWidget(m_additionalTagsCheckBox);
103   hlayout->addWidget(m_coverArtCheckBox);
104   vlayout->addLayout(hlayout);
105 
106   m_albumListBox = new QListView(this);
107   m_albumListBox->setEditTriggers(QAbstractItemView::NoEditTriggers);
108   vlayout->addWidget(m_albumListBox);
109   connect(m_albumListBox, &QAbstractItemView::activated,
110       this, static_cast<void (ServerImportDialog::*)(const QModelIndex&)>(
111             &ServerImportDialog::requestTrackList));
112 
113   auto buttonLayout = new QHBoxLayout;
114   m_helpButton = new QPushButton(tr("&Help"), this);
115   m_helpButton->setAutoDefault(false);
116   m_saveButton = new QPushButton(tr("&Save Settings"), this);
117   m_saveButton->setAutoDefault(false);
118   QPushButton* closeButton = new QPushButton(tr("&Close"), this);
119   closeButton->setAutoDefault(false);
120   buttonLayout->addWidget(m_helpButton);
121   connect(m_helpButton, &QAbstractButton::clicked, this, &ServerImportDialog::showHelp);
122   buttonLayout->addWidget(m_saveButton);
123   connect(m_saveButton, &QAbstractButton::clicked, this, &ServerImportDialog::saveConfig);
124   auto hspacer = new QSpacerItem(16, 0, QSizePolicy::Expanding,
125                                          QSizePolicy::Minimum);
126   buttonLayout->addItem(hspacer);
127   buttonLayout->addWidget(closeButton);
128   connect(closeButton, &QAbstractButton::clicked, this, &QDialog::accept);
129   vlayout->addLayout(buttonLayout);
130 
131   m_statusBar = new QStatusBar(this);
132   vlayout->addWidget(m_statusBar);
133   showStatusMessage(tr("Ready."));
134 }
135 
136 /**
137  * Set importer to be used.
138  *
139  * @param source  import source to use
140  */
setImportSource(ServerImporter * source)141 void ServerImportDialog::setImportSource(ServerImporter* source)
142 {
143   if (m_source) {
144     disconnect(m_source, &HttpClient::progress,
145         this, &ServerImportDialog::showStatusMessage);
146     disconnect(m_source, &ImportClient::findFinished,
147         this, &ServerImportDialog::slotFindFinished);
148     disconnect(m_source, &ImportClient::albumFinished,
149         this, &ServerImportDialog::slotAlbumFinished);
150   }
151   m_source = source;
152 
153   if (m_source) {
154     connect(m_source, &HttpClient::progress,
155         this, &ServerImportDialog::showStatusMessage);
156     connect(m_source, &ImportClient::findFinished,
157         this, &ServerImportDialog::slotFindFinished);
158     connect(m_source, &ImportClient::albumFinished,
159         this, &ServerImportDialog::slotAlbumFinished);
160 
161     setWindowTitle(QCoreApplication::translate("@default", m_source->name()));
162     if (m_source->defaultServer()) {
163       m_serverLabel->show();
164       m_serverComboBox->show();
165       if (m_source->defaultCgiPath()) {
166         m_cgiLabel->show();
167         m_cgiLineEdit->show();
168       } else {
169         m_cgiLabel->hide();
170         m_cgiLineEdit->hide();
171       }
172       if (m_source->serverList()) {
173         QStringList strList;
174         for (const char** sl = m_source->serverList(); *sl != nullptr; ++sl) {
175           strList += QString::fromLatin1(*sl); // clazy:exclude=reserve-candidates
176         }
177         m_serverComboBox->clear();
178         m_serverComboBox->addItems(strList);
179       }
180     } else {
181       m_serverLabel->hide();
182       m_serverComboBox->hide();
183       m_cgiLabel->hide();
184       m_cgiLineEdit->hide();
185     }
186     if (qstrcmp(m_source->name(), "Discogs") == 0) {
187       m_tokenLabel->show();
188       m_tokenLineEdit->show();
189     } else {
190       m_tokenLabel->hide();
191       m_tokenLineEdit->hide();
192     }
193     if (m_source->additionalTags()) {
194       m_standardTagsCheckBox->show();
195       m_additionalTagsCheckBox->show();
196       m_coverArtCheckBox->show();
197     } else {
198       m_standardTagsCheckBox->hide();
199       m_additionalTagsCheckBox->hide();
200       m_coverArtCheckBox->hide();
201     }
202 
203     m_albumListBox->setModel(m_source->getAlbumListModel());
204 
205     if (m_source->helpAnchor()) {
206       m_helpButton->show();
207     } else {
208       m_helpButton->hide();
209     }
210     if (m_source->config()) {
211       m_saveButton->show();
212     } else {
213       m_saveButton->hide();
214     }
215   }
216 }
217 
218 /**
219  * Display message in status bar.
220  *
221  * @param msg status message
222  */
showStatusMessage(const QString & msg)223 void ServerImportDialog::showStatusMessage(const QString& msg)
224 {
225   m_statusBar->showMessage(msg);
226 }
227 
228 /**
229  * Get string with server and port.
230  *
231  * @return "servername:port".
232  */
getServer() const233 QString ServerImportDialog::getServer() const
234 {
235   if (m_serverComboBox) {
236     QString server(m_serverComboBox->currentText());
237     if (server.isEmpty() && m_source) {
238       server = QString::fromLatin1(m_source->defaultServer());
239     }
240     return server;
241   } else {
242     return QString();
243   }
244 }
245 
246 /**
247  * Set string with server and port.
248  *
249  * @param srv "servername:port"
250  */
setServer(const QString & srv)251 void ServerImportDialog::setServer(const QString& srv)
252 {
253   if (m_serverComboBox) {
254     int idx = m_serverComboBox->findText(srv);
255     if (idx >= 0) {
256       m_serverComboBox->setCurrentIndex(idx);
257     } else {
258       m_serverComboBox->addItem(srv);
259       m_serverComboBox->setCurrentIndex(m_serverComboBox->count() - 1);
260     }
261   }
262 }
263 
264 /**
265  * Get string with CGI path.
266  *
267  * @return CGI path, e.g. "/~cddb/cddb.cgi".
268  */
getCgiPath() const269 QString ServerImportDialog::getCgiPath() const
270 {
271   if (m_cgiLineEdit) {
272     QString cgi(m_cgiLineEdit->text());
273     if (cgi.isEmpty() && m_source) {
274       cgi = QString::fromLatin1(m_source->defaultCgiPath());
275     }
276     return cgi;
277   } else {
278     return QString();
279   }
280 }
281 
282 /**
283  * Set string with CGI path.
284  *
285  * @param cgi CGI path, e.g. "/~cddb/cddb.cgi".
286  */
setCgiPath(const QString & cgi)287 void ServerImportDialog::setCgiPath(const QString& cgi)
288 {
289   if (m_cgiLineEdit) {
290     m_cgiLineEdit->setText(cgi);
291   }
292 }
293 
294 /**
295  * Get token to access API server.
296  * @return token.
297  */
getToken() const298 QString ServerImportDialog::getToken() const
299 {
300   return m_tokenLineEdit ? m_tokenLineEdit->text() : QString();
301 }
302 
303 /**
304  * Set token to access API server.
305  * @param token access token
306  */
setToken(const QString & token)307 void ServerImportDialog::setToken(const QString& token)
308 {
309   if (m_tokenLineEdit) {
310     m_tokenLineEdit->setText(token);
311   }
312 }
313 
314 /**
315  * Get standard tags option.
316  *
317  * @return true if standard tags are enabled.
318  */
getStandardTags() const319 bool ServerImportDialog::getStandardTags() const
320 {
321   return m_standardTagsCheckBox ?
322     m_standardTagsCheckBox->checkState() == Qt::Checked
323     : false;
324 }
325 
326 /**
327  * Set standard tags option.
328  *
329  * @param enable true if standard tags are enabled
330  */
setStandardTags(bool enable)331 void ServerImportDialog::setStandardTags(bool enable)
332 {
333   if (m_standardTagsCheckBox) {
334     m_standardTagsCheckBox->setCheckState(
335       enable ? Qt::Checked : Qt::Unchecked);
336   }
337 }
338 
339 /**
340  * Get additional tags option.
341  *
342  * @return true if additional tags are enabled.
343  */
getAdditionalTags() const344 bool ServerImportDialog::getAdditionalTags() const
345 {
346   return m_additionalTagsCheckBox ?
347     m_additionalTagsCheckBox->checkState() == Qt::Checked
348     : false;
349 }
350 
351 /**
352  * Set additional tags option.
353  *
354  * @param enable true if additional tags are enabled
355  */
setAdditionalTags(bool enable)356 void ServerImportDialog::setAdditionalTags(bool enable)
357 {
358   if (m_additionalTagsCheckBox) {
359     m_additionalTagsCheckBox->setCheckState(
360       enable ? Qt::Checked : Qt::Unchecked);
361   }
362 }
363 
364 /**
365  * Get cover art option.
366  *
367  * @return true if cover art are enabled.
368  */
getCoverArt() const369 bool ServerImportDialog::getCoverArt() const
370 {
371   return m_coverArtCheckBox ?
372     m_coverArtCheckBox->checkState() == Qt::Checked
373     : false;
374 }
375 
376 /**
377  * Set cover art option.
378  *
379  * @param enable true if cover art are enabled
380  */
setCoverArt(bool enable)381 void ServerImportDialog::setCoverArt(bool enable)
382 {
383   if (m_coverArtCheckBox) {
384     m_coverArtCheckBox->setCheckState(
385       enable ? Qt::Checked : Qt::Unchecked);
386   }
387 }
388 
389 /**
390  * Get the local configuration.
391  *
392  * @param cfg configuration
393  */
getImportSourceConfig(ServerImporterConfig * cfg) const394 void ServerImportDialog::getImportSourceConfig(ServerImporterConfig* cfg) const
395 {
396   cfg->setServer(getServer());
397   cfg->setCgiPath(getCgiPath());
398   cfg->setStandardTags(getStandardTags());
399   cfg->setAdditionalTags(getAdditionalTags());
400   cfg->setCoverArt(getCoverArt());
401   cfg->setWindowGeometry(saveGeometry());
402 
403   QString token = getToken();
404   if (!token.isEmpty() || cfg->property("token").isValid()) {
405     cfg->setProperty("token", token);
406   }
407 }
408 
409 /**
410  * Save the local settings to the configuration.
411  */
saveConfig()412 void ServerImportDialog::saveConfig()
413 {
414   if (m_source && m_source->config()) {
415     getImportSourceConfig(m_source->config());
416   }
417 }
418 
419 /**
420  * Set a find string from artist and album information.
421  *
422  * @param artist artist
423  * @param album  album
424  */
setArtistAlbum(const QString & artist,const QString & album)425 void ServerImportDialog::setArtistAlbum(const QString& artist, const QString& album)
426 {
427   if (m_source && m_source->config()) {
428     ServerImporterConfig* cf = m_source->config();
429     setServer(cf->server());
430     setCgiPath(cf->cgiPath());
431     setStandardTags(cf->standardTags());
432     setAdditionalTags(cf->additionalTags());
433     setCoverArt(cf->coverArt());
434     if (!cf->windowGeometry().isEmpty()) {
435       restoreGeometry(cf->windowGeometry());
436     }
437 
438     setToken(cf->property("token").toString());
439   }
440 
441   if (!(artist.isEmpty() && album.isEmpty())) {
442     int idx = m_artistLineEdit->findText(artist);
443     if (idx >= 0) {
444       m_artistLineEdit->setCurrentIndex(idx);
445     } else {
446       m_artistLineEdit->addItem(artist);
447       m_artistLineEdit->setCurrentIndex(m_artistLineEdit->count() - 1);
448     }
449     idx = m_albumLineEdit->findText(album);
450     if (idx >= 0) {
451       m_albumLineEdit->setCurrentIndex(idx);
452     } else {
453       m_albumLineEdit->addItem(album);
454       m_albumLineEdit->setCurrentIndex(m_albumLineEdit->count() - 1);
455     }
456     QLineEdit* lineEdit = m_artistLineEdit->lineEdit();
457     if (lineEdit) {
458       lineEdit->selectAll();
459     }
460     m_artistLineEdit->setFocus();
461   }
462 }
463 
464 /**
465  * Query a search for a keyword from the server.
466  */
slotFind()467 void ServerImportDialog::slotFind()
468 {
469   ServerImporterConfig cfg;
470   getImportSourceConfig(&cfg);
471   if (m_source)
472     m_source->find(&cfg, m_artistLineEdit->currentText(),
473                    m_albumLineEdit->currentText());
474 }
475 
476 /**
477  * Process finished find request.
478  *
479  * @param searchStr search data received
480  */
slotFindFinished(const QByteArray & searchStr)481 void ServerImportDialog::slotFindFinished(const QByteArray& searchStr)
482 {
483   if (m_source)
484     m_source->parseFindResults(searchStr);
485   m_albumListBox->setFocus();
486   if (QItemSelectionModel* selModel = m_albumListBox->selectionModel()) {
487     if (QAbstractItemModel* model = m_albumListBox->model()) {
488       if (model->rowCount() > 0) {
489         selModel->select(model->index(0, 0),
490             QItemSelectionModel::Select | QItemSelectionModel::Rows);
491       }
492     }
493   }
494 }
495 
496 /**
497  * Process finished album data.
498  *
499  * @param albumStr album track data received
500  */
slotAlbumFinished(const QByteArray & albumStr)501 void ServerImportDialog::slotAlbumFinished(const QByteArray& albumStr)
502 {
503   if (m_source) {
504     m_source->setStandardTags(getStandardTags());
505     m_source->setAdditionalTags(getAdditionalTags());
506     m_source->setCoverArt(getCoverArt());
507     m_source->parseAlbumResults(albumStr);
508   }
509   emit trackDataUpdated();
510 }
511 
512 /**
513  * Request track list from server.
514  *
515  * @param category category, e.g. "release"
516  * @param id internal ID
517  */
requestTrackList(const QString & category,const QString & id)518 void ServerImportDialog::requestTrackList(const QString& category,
519                                           const QString& id)
520 {
521   ServerImporterConfig cfg;
522   getImportSourceConfig(&cfg);
523   if (m_source)
524     m_source->getTrackList(&cfg, category, id);
525 }
526 
527 /**
528  * Request track list from server.
529  *
530  * @param index model index of list containing an AlbumListItem
531  */
requestTrackList(const QModelIndex & index)532 void ServerImportDialog::requestTrackList(const QModelIndex& index)
533 {
534   if (m_source) {
535     QString text, category, id;
536     m_source->getAlbumListModel()->getItem(index.row(), text, category, id);
537     if (!id.isEmpty()) {
538       requestTrackList(category, id);
539     }
540   }
541 }
542 
543 /**
544  * Show help.
545  */
showHelp()546 void ServerImportDialog::showHelp()
547 {
548   if (m_source && m_source->helpAnchor()) {
549     ContextHelp::displayHelp(QString::fromLatin1(m_source->helpAnchor()));
550   }
551 }
552